Onyxia extension

Onyxia has his own extension of json schema

[x-onyxia] overwriteDefaultWith

Let's consider a sample of the values.schema.json of the InseeFrLab/helm-charts-interactive-services' Jupyter chart:

values.schema.json
"git": {
    "description": "Git user configuration",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "Add git config inside your environment",
            "default": true
        },
        "name": {
            "type": "string",
            "description": "user name for git",
            "default": "",
            "x-onyxia": {
                "overwriteDefaultWith": "git.name"
            },
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "email": {
            "type": "string",
            "description": "user email for git",
            "default": "",
            "x-onyxia": {
                "overwriteDefaultWith": "git.email"
            },
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "cache": {
            "type": "string",
            "description": "duration in seconds of the credentials cache duration",
            "default": "",
            "x-onyxia": {
                "overwriteDefaultWith": "git.credentials_cache_duration"
            },
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "token": {
            "type": "string",
            "description": "personal access token",
            "default": "",
            "x-onyxia": {
                "overwriteDefaultWith": "git.token"
            },
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "repository": {
            "type": "string",
            "description": "Repository url",
            "default": "",
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "branch": {
            "type": "string",
            "description": "Brach automatically checkout",
            "default": "",
            "hidden": {
                "value": "",
                "path": "git/repository"
            }
        }
    }
},

And it translates into this:

Note the "git.name", "git.email" and "git.token", this enables onyxia-web to pre fill the fields.

If the user took the time to fill its profile information, onyxia-web knows what is the Git username, email and personal access token of the user.

Here is defined the structure of the context that you can use in the overwriteDefaultWith field:

export type XOnyxiaParams = {
    /**
     * This is where you can reference values from the onyxia context so that they
     * are dynamically injected by the Onyxia launcher.
     *
     * Examples:
     * "overwriteDefaultWith": "user.email" ( You can also write "{{user.email}}" it's equivalent )
     * "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
     * "overwriteDefaultWith": [ "a hardcoded value", "some other hardcoded value", "{{region.oauth2.clientId}}" ]
     * "overwriteDefaultWith": { "foo": "bar", "bar": "{{region.oauth2.clientId}}" }
     *
     */
    overwriteDefaultWith?:
        | string
        | number
        | boolean
        | unknown[]
        | Record<string, unknown>;
    overwriteListEnumWith?: unknown[] | string;
    hidden?: boolean;
    readonly?: boolean;
    useRegionSliderConfig?: string;
};

export type XOnyxiaContext = {
    user: {
        idep: string;
        name: string;
        email: string;
        password: string;
        ip: string;
        darkMode: boolean;
        lang: "en" | "fr" | "zh-CN" | "no" | "fi" | "nl" | "it" | "es" | "de";
        /**
         * Decoded JWT OIDC ID token of the user launching the service.
         *
         * Sample value:
         * {
         *   "sub": "9000ffa3-5fb8-45b5-88e4-e2e869ba3cfa",
         *   "name": "Joseph Garrone",
         *   "aud": ["onyxia", "minio-datanode"],
         *   "groups": [
         *       "USER_ONYXIA",
         *       "codegouv",
         *       "onyxia",
         *       "sspcloud-admin",
         *   ],
         *   "preferred_username": "jgarrone",
         *   "given_name": "Joseph",
         *   "locale": "en",
         *   "family_name": "Garrone",
         *   "email": "joseph.garrone@insee.fr",
         *   "policy": "stsonly",
         *   "typ": "ID",
         *   "azp": "onyxia",
         *   "email_verified": true,
         *   "realm_access": {
         *       "roles": ["offline_access", "uma_authorization", "default-roles-sspcloud"]
         *   }
         * }
         */
        decodedIdToken: Record<string, unknown>;
        accessToken: string;
        refreshToken: string;
    };
    service: {
        oneTimePassword: string;
    };
    project: {
        id: string;
        password: string;
        basic: string;
    };
    git: {
        name: string;
        email: string;
        credentials_cache_duration: number;
        token: string | undefined;
    };
    vault: {
        VAULT_ADDR: string;
        VAULT_TOKEN: string;
        VAULT_MOUNT: string;
        VAULT_TOP_DIR: string;
    };
    s3: {
        AWS_ACCESS_KEY_ID: string;
        AWS_SECRET_ACCESS_KEY: string;
        AWS_SESSION_TOKEN: string;
        AWS_DEFAULT_REGION: string;
        AWS_S3_ENDPOINT: string;
        AWS_BUCKET_NAME: string;
        port: number;
        pathStyleAccess: boolean;
        /**
         * The user is assumed to have read/write access on every
         * object starting with this prefix on the bucket
         **/
        objectNamePrefix: string;
        /**
         * Only for making it easier for charts editors.
         * <AWS_BUCKET_NAME>/<objectNamePrefix>
         * */
        workingDirectoryPath: string;
        /**
         * If true the bucket's (directory) should be accessible without any credentials.
         * In this case s3.AWS_ACCESS_KEY_ID, s3.AWS_SECRET_ACCESS_KEY and s3.AWS_SESSION_TOKEN
         * will be empty strings.
         */
        isAnonymous: boolean;
    };
    region: {
        defaultIpProtection: boolean | undefined;
        defaultNetworkPolicy: boolean | undefined;
        allowedURIPattern: string;
        customValues: Record<string, unknown> | undefined;
        kafka:
            | {
                  url: string;
                  topicName: string;
              }
            | undefined;
        tolerations: unknown[] | undefined;
        from: unknown[] | undefined;
        nodeSelector: Record<string, unknown> | undefined;
        startupProbe: Record<string, unknown> | undefined;
        sliders: Record<
            string,
            {
                sliderMin: number;
                sliderMax: number;
                sliderStep: number;
                sliderUnit: string;
            }
        >;
        resources:
            | {
                  cpuRequest?: `${number}${string}`;
                  cpuLimit?: `${number}${string}`;
                  memoryRequest?: `${number}${string}`;
                  memoryLimit?: `${number}${string}`;
                  disk?: `${number}${string}`;
                  gpu?: `${number}`;
              }
            | undefined;
    };
    k8s: {
        domain: string;
        ingressClassName: string | undefined;
        ingress: boolean | undefined;
        route: boolean | undefined;
        istio:
            | {
                  enabled: boolean;
                  gateways: string[];
              }
            | undefined;
        randomSubdomain: string;
        initScriptUrl: string;
        useCertManager: boolean;
        certManagerClusterIssuer: string | undefined;
    };
    proxyInjection:
        | {
              enabled: string | undefined;
              httpProxyUrl: string | undefined;
              httpsProxyUrl: string | undefined;
              noProxy: string | undefined;
          }
        | undefined;
    packageRepositoryInjection:
        | {
              cranProxyUrl: string | undefined;
              condaProxyUrl: string | undefined;
              packageManagerUrl: string | undefined;
              pypiProxyUrl: string | undefined;
          }
        | undefined;
    certificateAuthorityInjection:
        | {
              cacerts: string | undefined;
              pathToCaBundle: string | undefined;
          }
        | undefined;
};

You can also concatenate string values using by wrapping the XOnyxia targeted values in {{}}.

values.shema.json
"hostname": {
  "type": "string",
  "form": true,
  "title": "Hostname",
  "x-onyxia": {
    "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
  }
}

This is an option for customizing the options of the forms fields rendered as select.

In your values shema such a field would be defined like:

values.shema.json
"pullPolicy": {
    "type": "string",
    "default": "IfNotPresent",
    "listEnum": [
        "IfNotPresent",
        "Always",
        "Never"
    ]
}

But what if you want to dynamically generate the option? For this you can use the overwriteListEnumWith x-onyxia option. For example if you need to let the user select one of the groups he belongs to you can write:

values.schema.json
"group": {
  "type": "string",
  "default": "",
  "listEnum": [""],
  "x-onyxia": {
    "overwriteDefaultWith": "user.decodedIdToken.groups[0]",
    "overwriteListEnumWith": "user.decodedIdToken.groups"
  }
}

[x-onyxia] overwriteSchemaWith

Certain elements of a Helm chart should be customized for each instance of Onyxia, such as resource requests and limits, node selectors and tolerations. For this purpose, chart developers can use x-onyxia.overwriteSchemaWith to allow administrators to override specific parts of the schema. Our default charts use this specification.

values.shema.json
"nodeSelector": {
    "type": "object",
    "description": "NodeSelector",
    "default": {},
    "x-onyxia": {
        "overwriteSchemaWith": "nodeSelector.json"
    }
}

You can see here the list of default schemas included in the Onyxia API. We also provide examples demonstrating how you can customize your services using our interactive services charts with the provided schemas.

The following node selector schema provided by Onyxia API is a generic definition, which may not provide the best experience for a specific Kubernetes cluster in Onyxia.

nodeSelector.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Node Selector",
    "type": "object",
    "description": "Node selector constraints for the pod",
    "additionalProperties": {
      "type": "string",
      "description": "Key-value pairs to select nodes"
    }
}

As an administrator of Onyxia, you can provide your own schemas to refine and restrict the initial schemas provided in the Helm chart.

node selectors

You can provide this schema to allow your users to choose between SSD or HDD disk types, and A2 or H100 NVIDIA GPUs. Any other values or labels are disallowed, and Onyxia will reject starting a service that does not comply with the provided schema.

nodeSelector.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Node Selector",
  "type": "object",
  "properties": {
    "disktype": {
      "description": "The type of disk",
      "type": "string",
      "enum": ["ssd", "hdd"],
      "default": "ssd"
    },
    "gpu": {
      "description": "The type of GPU",
      "type": "string",
      "enum": ["A2", "H100"],
      "default": "A2"
    }
  },
  "additionalProperties": false //any other label is disallowed
}

rolebindings for IDE pods

This is the default role for IDE pods in our charts. It is very permissive, and you may want to restrict it to view-only access.

ide/role.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Role",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "allow your service to access your namespace ressources",
            "default": true
        },
        "role": {
            "type": "string",
            "description": "bind your service account to this kubernetes default role",
            "default": "view",
            "enum": [
                "view",
                "edit",
                "admin"
            ]
        }
    }
}

Here is the refined version

ide/role.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Role",
  "type": "object",
  "properties": {
    "enabled": {
      "type": "boolean",
      "const": true,
      "description": "This value must always be true, allowing your service to access your namespace resources."
    },
    "role": {
      "type": "string",
      "const": "view",
      "description": "This value must always be 'view', binding your service account to this Kubernetes default role.",
    }
  }
}

resources for IDE

You may want to modify the slide bar for resources

ide/resources.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Resources",
    "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.",
    "type": "object",
    "properties": {
        "requests": {
            "description": "Guaranteed resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The amount of cpu guaranteed",
                    "title": "CPU",
                    "type": "string",
                    "default": "100m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 40000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The amount of memory guaranteed",
                    "title": "memory",
                    "type": "string",
                    "default": "2Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "memory"
                }
            }
        },
        "limits": {
            "description": "max resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The maximum amount of cpu",
                    "title": "CPU",
                    "type": "string",
                    "default": "30000m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 40000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The maximum amount of memory",
                    "title": "Memory",
                    "type": "string",
                    "default": "50Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "memory"
                }
            }
        }
    }
}
ide/resources.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Resources",
    "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.",
    "type": "object",
    "properties": {
        "requests": {
            "description": "Guaranteed resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The amount of cpu guaranteed",
                    "title": "CPU",
                    "type": "string",
                    "default": "100m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 10000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The amount of memory guaranteed",
                    "title": "memory",
                    "type": "string",
                    "default": "2Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "memory"
                }
            }
        },
        "limits": {
            "description": "max resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The maximum amount of cpu",
                    "title": "CPU",
                    "type": "string",
                    "default": "5000m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 10000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The maximum amount of memory",
                    "title": "Memory",
                    "type": "string",
                    "default": "50Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "memory"
                }
            }
        }
    }
}

Last updated