Skip to content

Instantly share code, notes, and snippets.

@manics
Created June 8, 2022 20:48
Show Gist options
  • Save manics/ce5ce8d05492ee3d1cf8217da14829c7 to your computer and use it in GitHub Desktop.
Save manics/ce5ce8d05492ee3d1cf8217da14829c7 to your computer and use it in GitHub Desktop.
Example of generating a partial JSON schema from a JupyterHub related traitlets class

python traitlets2schema.py kubespawner KubeSpawner
Skipping get_pod_url
Skipping modify_pod_hook
Skipping options_from_form
{
  "allow_privilege_escalation": {
    "description": "\n        Controls whether a process can gain more privileges than its parent process.\n\n        When set to False (the default), the primary user visible effect is that\n        setuid binaries (like sudo) will no longer work.\n\n        When set to None, the defaults for the cluster are respected.\n\n        This bool directly controls whether the no_new_privs flag gets set on the container\n\n        AllowPrivilegeEscalation is true always when the container is:\n        1) run as Privileged OR 2) has CAP_SYS_ADMIN.\n        ",
    "type": [
      "bool",
      "null"
    ]
  },
  "args": {
    "description": "\n        Extra arguments to be passed to the single-user server.\n\n        Some spawners allow shell-style expansion here, allowing you to use environment variables here.\n        Most, including the default, do not. Consult the documentation for your spawner to verify!\n        ",
    "type": [
      "array"
    ]
  },
  "auth_state_hook": {
    "description": "\n        An optional hook function that you can implement to pass `auth_state`\n        to the spawner after it has been initialized but before it starts.\n        The `auth_state` dictionary may be set by the `.authenticate()`\n        method of the authenticator.  This hook enables you to pass some\n        or all of that information to your spawner.\n\n        Example::\n\n            def userdata_hook(spawner, auth_state):\n                spawner.userdata = auth_state[\"userdata\"]\n\n            c.Spawner.auth_state_hook = userdata_hook\n\n        ",
    "type": [
      "object",
      "null"
    ]
  },
  "automount_service_account_token": {
    "description": "\n        Whether to mount the service account token in the spawned user pod.\n\n        The default value is None, which mounts the token if the service account is explicitly set,\n        but doesn't mount it if not.\n\n        WARNING: Be careful with this configuration! Make sure the service account being mounted\n        has the minimal permissions needed, and nothing more. When misconfigured, this can easily\n        give arbitrary users root over your entire cluster.\n        ",
    "type": [
      "bool",
      "null"
    ]
  },
  "cmd": {
    "description": "\n        The command used to start the single-user server.\n\n        Either\n          - a string containing a single command or path to a startup script\n          - a list of the command and arguments\n          - `None` (default) to use the Docker image's `CMD`\n\n        If `cmd` is set, it will be augmented with `spawner.get_args(). This will override the `CMD` specified in the Docker image.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "common_labels": {
    "description": "\n        Kubernetes labels that both spawned singleuser server pods and created\n        user PVCs will get.\n\n        Note that these are only set when the Pods and PVCs are created, not\n        later when this setting is updated.\n        ",
    "type": [
      "object"
    ]
  },
  "component_label": {
    "description": "\n        The component label used to tag the user pods. This can be used to override\n        the spawner behavior when dealing with multiple hub instances in the same\n        namespace. Usually helpful for CI workflows.\n        ",
    "type": [
      "string"
    ]
  },
  "consecutive_failure_limit": {
    "description": "\n        Maximum number of consecutive failures to allow before\n        shutting down JupyterHub.\n\n        This helps JupyterHub recover from a certain class of problem preventing launch\n        in contexts where the Hub is automatically restarted (e.g. systemd, docker, kubernetes).\n\n        A limit of 0 means no limit and consecutive failures will not be tracked.\n        ",
    "type": [
      "number"
    ]
  },
  "container_security_context": {
    "description": "\n        A Kubernetes security context for the container. Note that all\n        configuration options within here should be camelCased.\n\n        What is configured here has the highest priority, so the alternative\n        configuration `uid`, `gid`, `privileged`, and\n        `allow_privilege_escalation` will be overridden by this.\n\n        Rely on `the Kubernetes reference\n        <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#securitycontext-v1-core>`__\n        for details on allowed configuration.\n        ",
    "type": [
      "object"
    ]
  },
  "cpu_guarantee": {
    "description": "\n        Minimum number of cpu-cores a single-user notebook server is guaranteed to have available.\n\n        If this value is set to 0.5, allows use of 50% of one CPU.\n        If this value is set to 2, allows use of up to 2 CPUs.\n\n        **This is a configuration setting. Your spawner must implement support\n        for the limit to work.** The default spawner, `LocalProcessSpawner`,\n        does **not** implement this support. A custom spawner **must** add\n        support for this setting for it to be enforced.\n        ",
    "type": [
      "number",
      "null"
    ]
  },
  "cpu_limit": {
    "description": "\n        Maximum number of cpu-cores a single-user notebook server is allowed to use.\n\n        If this value is set to 0.5, allows use of 50% of one CPU.\n        If this value is set to 2, allows use of up to 2 CPUs.\n\n        The single-user notebook server will never be scheduled by the kernel to\n        use more cpu-cores than this. There is no guarantee that it can\n        access this many cpu-cores.\n\n        **This is a configuration setting. Your spawner must implement support\n        for the limit to work.** The default spawner, `LocalProcessSpawner`,\n        does **not** implement this support. A custom spawner **must** add\n        support for this setting for it to be enforced.\n        ",
    "type": [
      "number",
      "null"
    ]
  },
  "debug": {
    "description": "Enable debug-logging of the single-user server",
    "type": [
      "bool"
    ]
  },
  "default_url": {
    "description": "\n        The URL the single-user server should start in.\n\n        `{username}` will be expanded to the user's username\n\n        Example uses:\n\n        - You can set `notebook_dir` to `/` and `default_url` to `/tree/home/{username}` to allow people to\n          navigate the whole filesystem from their notebook server, but still start in their home directory.\n        - Start with `/notebooks` instead of `/tree` if `default_url` points to a notebook instead of a directory.\n        - You can set this to `/lab` to have JupyterLab start by default, rather than Jupyter Notebook.\n        ",
    "type": [
      "string"
    ]
  },
  "delete_grace_period": {
    "description": "\n        Time in seconds for the pod to be in `terminating` state before is forcefully killed.\n\n        Increase this if you need more time to execute a `preStop` lifecycle hook.\n\n        See https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods for\n        more information on how pod termination works.\n\n        Defaults to `1`.\n        ",
    "type": [
      "number"
    ]
  },
  "delete_pvc": {
    "description": "Delete PVCs when deleting Spawners.\n\n        When a Spawner is deleted (not just stopped),\n        delete its associated PVC.\n\n        This occurs when a named server is deleted,\n        or when the user itself is deleted for the default Spawner.\n\n        Requires JupyterHub 1.4.1 for Spawner.delete_forever support.\n\n        .. versionadded: 0.17\n        ",
    "type": [
      "bool"
    ]
  },
  "delete_stopped_pods": {
    "description": "\n        Whether to delete pods that have stopped themselves.\n        Set to False to leave stopped pods in the completed state,\n        allowing for easier debugging of why they may have stopped.\n        ",
    "type": [
      "bool"
    ]
  },
  "disable_user_config": {
    "description": "\n        Disable per-user configuration of single-user servers.\n\n        When starting the user's single-user server, any config file found in the user's $HOME directory\n        will be ignored.\n\n        Note: a user could circumvent this if the user modifies their Python environment, such as when\n        they have their own conda environments / virtualenvs / containers.\n        ",
    "type": [
      "bool"
    ]
  },
  "dns_name_template": {
    "description": "\n        Template to use to form the dns name for the pod.\n        ",
    "type": [
      "string"
    ]
  },
  "enable_user_namespaces": {
    "description": "\n        Cause each user to be spawned into an individual namespace.\n\n        This comes with some caveats.  The Hub must run with significantly\n        more privilege (must have ClusterRoles analogous to its usual Roles)\n        and can therefore do heinous things to the entire cluster.\n\n        It will also make the Reflectors aware of pods and events across\n        all namespaces.  This will have performance implications, although\n        using labels to restrict resource selection helps somewhat.\n\n        If you use this, consider cleaning up the user namespace in your\n        post_stop_hook.\n        ",
    "type": [
      "bool"
    ]
  },
  "env_keep": {
    "description": "\n        List of environment variables for the single-user server to inherit from the JupyterHub process.\n\n        This list is used to ensure that sensitive information in the JupyterHub process's environment\n        (such as `CONFIGPROXY_AUTH_TOKEN`) is not passed to the single-user server's process.\n        ",
    "type": [
      "array"
    ]
  },
  "environment": {
    "description": "\n        Extra environment variables to set for the single-user server's process.\n\n        Environment variables that end up in the single-user server's process come from 3 sources:\n          - This `environment` configurable\n          - The JupyterHub process' environment variables that are listed in `env_keep`\n          - Variables to establish contact between the single-user notebook and the hub (such as JUPYTERHUB_API_TOKEN)\n\n        The `environment` configurable should be set by JupyterHub administrators to add\n        installation specific environment variables. It is a dict where the key is the name of the environment\n        variable, and the value can be a string or a callable. If it is a callable, it will be called\n        with one parameter (the spawner instance), and should return a string fairly quickly (no blocking\n        operations please!).\n\n        Note that the spawner class' interface is not guaranteed to be exactly same across upgrades,\n        so if you are using the callable take care to verify it continues to work after upgrades!\n\n        .. versionchanged:: 1.2\n            environment from this configuration has highest priority,\n            allowing override of 'default' env variables,\n            such as JUPYTERHUB_API_URL.\n        ",
    "type": [
      "object"
    ]
  },
  "events_enabled": {
    "description": "\n        Enable event-watching for progress-reports to the user spawn page.\n\n        Disable if these events are not desirable\n        or to save some performance cost.\n        ",
    "type": [
      "bool"
    ]
  },
  "extra_annotations": {
    "description": "\n        Extra Kubernetes annotations to set on the spawned single-user pods, as\n        well as on the pods' associated k8s Service and k8s Secret if\n        internal_ssl is enabled.\n\n        The keys and values specified here are added as annotations on the spawned single-user\n        kubernetes pods. The keys and values must both be strings.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/>`__\n        for more info on what annotations are and why you might want to use them!\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "object"
    ]
  },
  "extra_container_config": {
    "description": "\n        Extra configuration (e.g. ``envFrom``) for notebook container which is not covered by other attributes.\n\n        This dict will be directly merge into `container` of notebook server,\n        so you should use the same structure. Each item in the dict must a field\n        of the `V1Container specification <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#container-v1-core>`__.\n\n\n        One usage is set ``envFrom`` on notebook container with configuration below::\n\n            c.KubeSpawner.extra_container_config = {\n                \"envFrom\": [{\n                    \"configMapRef\": {\n                        \"name\": \"special-config\"\n                    }\n                }]\n            }\n\n        The key could be either a camelCase word (used by Kubernetes yaml, e.g.\n        ``envFrom``) or a snake_case word (used by Kubernetes Python client,\n        e.g. ``env_from``).\n        ",
    "type": [
      "object"
    ]
  },
  "extra_containers": {
    "description": "\n        List of containers belonging to the pod which besides to the container generated for notebook server.\n\n        This list will be directly appended under `containers` in the kubernetes pod spec,\n        so you should use the same structure. Each item in the list is container configuration\n        which follows spec at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#container-v1-core\n\n\n        One usage is setting crontab in a container to clean sensitive data with configuration below::\n\n            c.KubeSpawner.extra_containers = [{\n                \"name\": \"crontab\",\n                \"image\": \"supercronic\",\n                \"command\": [\"/usr/local/bin/supercronic\", \"/etc/crontab\"]\n            }]\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "array"
    ]
  },
  "extra_labels": {
    "description": "\n        Extra kubernetes labels to set on the spawned single-user pods, as well\n        as on the pods' associated k8s Service and k8s Secret if internal_ssl is\n        enabled.\n\n        The keys and values specified here would be set as labels on the spawned single-user\n        kubernetes pods. The keys and values must both be strings that match the kubernetes\n        label key / value constraints.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/>`__\n        for more info on what labels are and why you might want to use them!\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "object"
    ]
  },
  "extra_pod_config": {
    "description": "\n        Extra configuration for the pod which is not covered by other attributes.\n\n        This dict will be directly merge into pod,so you should use the same structure.\n        Each item in the dict is field of pod configuration\n        which follows spec at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podspec-v1-core\n\n\n        One usage is set restartPolicy and dnsPolicy with configuration below::\n\n            c.KubeSpawner.extra_pod_config = {\n                \"restartPolicy\": \"OnFailure\",\n                \"dns_policy\": \"ClusterFirstWithHostNet\"\n            }\n\n        The `key` could be either a camelCase word (used by Kubernetes yaml,\n        e.g. `restartPolicy`) or a snake_case word (used by Kubernetes Python\n        client, e.g. `dns_policy`).\n        ",
    "type": [
      "object"
    ]
  },
  "extra_resource_guarantees": {
    "description": "\n        The dictionary used to request arbitrary resources.\n        Default is None and means no additional resources are requested.\n        For example, to request 1 Nvidia GPUs::\n\n            c.KubeSpawner.extra_resource_guarantees = {\"nvidia.com/gpu\": \"1\"}\n        ",
    "type": [
      "object"
    ]
  },
  "extra_resource_limits": {
    "description": "\n        The dictionary used to limit arbitrary resources.\n        Default is None and means no additional resources are limited.\n        For example, to add a limit of 3 Nvidia GPUs::\n\n            c.KubeSpawner.extra_resource_limits = {\"nvidia.com/gpu\": \"3\"}\n        ",
    "type": [
      "object"
    ]
  },
  "fs_gid": {
    "description": "\n        The GID of the group that should own any volumes that are created & mounted.\n\n        A special supplemental group that applies primarily to the volumes mounted\n        in the single-user server. In volumes from supported providers, the following\n        things happen:\n\n          1. The owning GID will be the this GID\n          2. The setgid bit is set (new files created in the volume will be owned by\n             this GID)\n          3. The permission bits are OR\u2019d with rw-rw\n\n        The single-user server will also be run with this gid as part of its supplemental\n        groups.\n\n        Instead of an integer, this could also be a callable that takes as one\n        parameter the current spawner instance and returns an integer. The callable will\n        be called asynchronously if it returns a future, rather than an int. Note that\n        the interface of the spawner class is not deemed stable across versions,\n        so using this functionality might cause your JupyterHub or kubespawner\n        upgrades to break.\n\n        You'll *have* to set this if you are using auto-provisioned volumes with most\n        cloud providers. See `fsGroup <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podsecuritycontext-v1-core>`__\n        for more details.\n        ",
    "type": [
      "number",
      "null"
    ]
  },
  "gid": {
    "description": "\n        The GID to run the single-user server containers as.\n\n        This GID should ideally map to a group that already exists in the container\n        image being used. Running as root is discouraged.\n\n        Instead of an integer, this could also be a callable that takes as one\n        parameter the current spawner instance and returns an integer. The callable\n        will be called asynchronously if it returns a future. Note that\n        the interface of the spawner class is not deemed stable across versions,\n        so using this functionality might cause your JupyterHub or kubespawner\n        upgrades to break.\n\n        If set to `None`, the group of the user specified with the `USER` directive\n        in the container metadata is used.\n        ",
    "type": [
      "number",
      "null"
    ]
  },
  "http_timeout": {
    "description": "\n        Timeout (in seconds) before giving up on a spawned HTTP server\n\n        Once a server has successfully been spawned, this is the amount of time\n        we wait before assuming that the server is unable to accept\n        connections.\n        ",
    "type": [
      "number"
    ]
  },
  "hub_connect_ip": {
    "description": "DEPRECATED. Use c.JupyterHub.hub_connect_ip",
    "type": [
      "string",
      "null"
    ]
  },
  "hub_connect_port": {
    "description": "DEPRECATED. Use c.JupyterHub.hub_connect_url",
    "type": [
      "number"
    ]
  },
  "hub_connect_url": {
    "description": "\n        The URL the single-user server should connect to the Hub.\n\n        If the Hub URL set in your JupyterHub config is not reachable\n        from spawned notebooks, you can set differnt URL by this config.\n\n        Is None if you don't need to change the URL.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "image": {
    "description": "\n        Docker image to use for spawning user's containers.\n\n        Defaults to `jupyterhub/singleuser:latest`\n\n        Name of the container + a tag, same as would be used with\n        a `docker pull` command. If tag is set to `latest`, kubernetes will\n        check the registry each time a new user is spawned to see if there\n        is a newer image available. If available, new image will be pulled.\n        Note that this could cause long delays when spawning, especially\n        if the image is large. If you do not specify a tag, whatever version\n        of the image is first pulled on the node will be used, thus possibly\n        leading to inconsistent images on different nodes. For all these\n        reasons, it is recommended to specify a specific immutable tag\n        for the image.\n\n        If your image is very large, you might need to increase the timeout\n        for starting the single user container from the default. You can\n        set this with::\n\n           c.KubeSpawner.start_timeout = 60 * 5  # Up to 5 minutes\n\n        ",
    "type": [
      "string"
    ]
  },
  "image_pull_policy": {
    "description": "\n        The image pull policy of the docker container specified in\n        `image`.\n\n        Defaults to `IfNotPresent` which causes the Kubelet to NOT pull the image\n        specified in KubeSpawner.image if it already exists, except if the tag\n        is `:latest`. For more information on image pull policy,\n        refer to `the Kubernetes documentation <https://kubernetes.io/docs/concepts/containers/images/>`__.\n\n\n        This configuration is primarily used in development if you are\n        actively changing the `image_spec` and would like to pull the image\n        whenever a user container is spawned.\n        ",
    "type": [
      "string"
    ]
  },
  "image_pull_secrets": {
    "description": "\n        A list of references to Kubernetes Secret resources with credentials to\n        pull images from image registries. This list can either have strings in\n        it or objects with the string value nested under a name field.\n\n        Passing a single string is still supported, but deprecated as of\n        KubeSpawner 0.14.0.\n\n        See `the Kubernetes documentation\n        <https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod>`__\n        for more information on when and why this might need to be set, and what\n        it should be set to.\n        ",
    "type": [
      "array",
      "string"
    ]
  },
  "init_containers": {
    "description": "\n        List of initialization containers belonging to the pod.\n\n        This list will be directly added under `initContainers` in the kubernetes pod spec,\n        so you should use the same structure. Each item in the dict must a field\n        of the `V1Container specification <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#container-v1-core>`__\n\n        One usage is disabling access to metadata service from single-user\n        notebook server with configuration below::\n\n            c.KubeSpawner.init_containers = [{\n                \"name\": \"init-iptables\",\n                \"image\": \"<image with iptables installed>\",\n                \"command\": [\"iptables\", \"-A\", \"OUTPUT\", \"-p\", \"tcp\", \"--dport\", \"80\", \"-d\", \"169.254.169.254\", \"-j\", \"DROP\"],\n                \"securityContext\": {\n                    \"capabilities\": {\n                        \"add\": [\"NET_ADMIN\"]\n                    }\n                }\n            }]\n\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/workloads/pods/init-containers/>`__\n        for more info on what init containers are and why you might want to use them!\n\n        To user this feature, Kubernetes version must greater than 1.6.\n        ",
    "type": [
      "array"
    ]
  },
  "ip": {
    "description": "\n        The IP address (or hostname) the single-user server should listen on.\n        We override this from the parent so we can set a more sane default for\n        the Kubernetes setup.\n        ",
    "type": [
      "string"
    ]
  },
  "k8s_api_host": {
    "description": "\n        Full host name of the k8s API server (\"https://hostname:port\").\n\n        Typically this is unnecessary, the hostname is picked up by\n        config.load_incluster_config() or config.load_kube_config.\n        ",
    "type": [
      "string"
    ]
  },
  "k8s_api_request_retry_timeout": {
    "description": "\n        Total timeout, including retry timeout, for kubernetes API calls\n\n        When a k8s API request connection times out, we retry it while backing\n        off exponentially. This lets you configure the total amount of time\n        we will spend trying an API request - including retries - before\n        giving up.\n        ",
    "type": [
      "number"
    ]
  },
  "k8s_api_request_timeout": {
    "description": "\n        API request timeout (in seconds) for all k8s API calls.\n\n        This is the total amount of time a request might take before the connection\n        is killed. This includes connection time and reading the response.\n\n        NOTE: This is currently only implemented for creation and deletion of pods,\n        and creation of PVCs.\n        ",
    "type": [
      "number"
    ]
  },
  "k8s_api_ssl_ca_cert": {
    "description": "\n        Location (absolute filepath) for CA certs of the k8s API server.\n\n        Typically this is unnecessary, CA certs are picked up by\n        config.load_incluster_config() or config.load_kube_config.\n\n        In rare non-standard cases, such as using custom intermediate CA\n        for your cluster, you may need to mount root CA's elsewhere in\n        your Pod/Container and point this variable to that filepath\n        ",
    "type": [
      "string"
    ]
  },
  "k8s_api_threadpool_workers": {
    "description": "\n        DEPRECATED in KubeSpawner 3.0.0.\n\n        No longer has any effect, as there is no threadpool anymore.\n        ",
    "type": [
      "number"
    ]
  },
  "lifecycle_hooks": {
    "description": "\n        Kubernetes lifecycle hooks to set on the spawned single-user pods.\n\n        The keys is name of hooks and there are only two hooks, postStart and preStop.\n        The values are handler of hook which executes by Kubernetes management system when hook is called.\n\n        Below is an sample copied from\n        `the Kubernetes documentation <https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/>`__::\n\n\n            c.KubeSpawner.lifecycle_hooks = {\n                \"postStart\": {\n                    \"exec\": {\n                        \"command\": [\"/bin/sh\", \"-c\", \"echo Hello from the postStart handler > /usr/share/message\"]\n                    }\n                },\n                \"preStop\": {\n                    \"exec\": {\n                        \"command\": [\"/usr/sbin/nginx\", \"-s\", \"quit\"]\n                    }\n                }\n            }\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/>`__\n        for more info on what lifecycle hooks are and why you might want to use them!\n        ",
    "type": [
      "object"
    ]
  },
  "mem_guarantee": {
    "description": "\n        Minimum number of bytes a single-user notebook server is guaranteed to have available.\n\n        Allows the following suffixes:\n          - K -> Kilobytes\n          - M -> Megabytes\n          - G -> Gigabytes\n          - T -> Terabytes\n\n        **This is a configuration setting. Your spawner must implement support\n        for the limit to work.** The default spawner, `LocalProcessSpawner`,\n        does **not** implement this support. A custom spawner **must** add\n        support for this setting for it to be enforced.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "mem_limit": {
    "description": "\n        Maximum number of bytes a single-user notebook server is allowed to use.\n\n        Allows the following suffixes:\n          - K -> Kilobytes\n          - M -> Megabytes\n          - G -> Gigabytes\n          - T -> Terabytes\n\n        If the single user server tries to allocate more memory than this,\n        it will fail. There is no guarantee that the single-user notebook server\n        will be able to allocate this much memory - only that it can not\n        allocate more than this.\n\n        **This is a configuration setting. Your spawner must implement support\n        for the limit to work.** The default spawner, `LocalProcessSpawner`,\n        does **not** implement this support. A custom spawner **must** add\n        support for this setting for it to be enforced.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "namespace": {
    "description": "\n        Kubernetes namespace to spawn user pods in.\n\n        Assuming that you are not running with enable_user_namespaces\n        turned on, if running inside a kubernetes cluster with service\n        accounts enabled, defaults to the current namespace, and if not,\n        defaults to `default`.\n\n        If you are running with enable_user_namespaces, this parameter\n        is ignored in favor of the `user_namespace_template` template\n        resolved with the hub namespace and the user name, with the\n        caveat that if the hub namespace is `default` the user\n        namespace will have the prefix `user` rather than `default`.\n        ",
    "type": [
      "string"
    ]
  },
  "node_affinity_preferred": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"PreferredSchedulingTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#preferredschedulingterm-v1-core\n\n        ",
    "type": [
      "array"
    ]
  },
  "node_affinity_required": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"NodeSelectorTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#nodeselectorterm-v1-core\n\n        ",
    "type": [
      "array"
    ]
  },
  "node_selector": {
    "description": "\n        The dictionary Selector labels used to match the Nodes where Pods will be launched.\n\n        Default is None and means it will be launched in any available Node.\n\n        For example to match the Nodes that have a label of `disktype: ssd` use::\n\n           c.KubeSpawner.node_selector = {'disktype': 'ssd'}\n        ",
    "type": [
      "object"
    ]
  },
  "notebook_dir": {
    "description": "\n        Path to the notebook directory for the single-user server.\n\n        The user sees a file listing of this directory when the notebook interface is started. The\n        current interface does not easily allow browsing beyond the subdirectories in this directory's\n        tree.\n\n        `~` will be expanded to the home directory of the user, and {username} will be replaced\n        with the name of the user.\n\n        Note that this does *not* prevent users from accessing files outside of this path! They\n        can do so with many other means.\n        ",
    "type": [
      "string"
    ]
  },
  "oauth_roles": {
    "description": "Allowed roles for oauth tokens.\n\n        This sets the maximum and default roles\n        assigned to oauth tokens issued by a single-user server's\n        oauth client (i.e. tokens stored in browsers after authenticating with the server),\n        defining what actions the server can take on behalf of logged-in users.\n\n        Default is an empty list, meaning minimal permissions to identify users,\n        no actions can be taken on their behalf.\n    ",
    "type": [
      "array"
    ]
  },
  "options_form": {
    "description": "\n        An HTML form for options a user can specify on launching their server.\n\n        The surrounding `<form>` element and the submit button are already provided.\n\n        For example:\n\n        .. code:: html\n\n            Set your key:\n            <input name=\"key\" val=\"default_key\"></input>\n            <br>\n            Choose a letter:\n            <select name=\"letter\" multiple=\"true\">\n              <option value=\"A\">The letter A</option>\n              <option value=\"B\">The letter B</option>\n            </select>\n\n        The data from this form submission will be passed on to your spawner in `self.user_options`\n\n        Instead of a form snippet string, this could also be a callable that takes as one\n        parameter the current spawner instance and returns a string. The callable will\n        be called asynchronously if it returns a future, rather than a str. Note that\n        the interface of the spawner class is not deemed stable across versions,\n        so using this functionality might cause your JupyterHub upgrades to break.\n    ",
    "type": [
      "string"
    ]
  },
  "pod_affinity_preferred": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"WeightedPodAffinityTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#weightedpodaffinityterm-v1-core\n\n        ",
    "type": [
      "array"
    ]
  },
  "pod_affinity_required": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"PodAffinityTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podaffinityterm-v1-core\n\n        ",
    "type": [
      "array"
    ]
  },
  "pod_anti_affinity_preferred": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"WeightedPodAffinityTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#weightedpodaffinityterm-v1-core\n        ",
    "type": [
      "array"
    ]
  },
  "pod_anti_affinity_required": {
    "description": "\n        Affinities describe where pods prefer or require to be scheduled, they\n        may prefer or require a node to have a certain label or be in proximity\n        / remoteness to another pod. To learn more visit\n        https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n\n        Pass this field an array of \"PodAffinityTerm\" objects.*\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podaffinityterm-v1-core\n        ",
    "type": [
      "array"
    ]
  },
  "pod_connect_ip": {
    "description": "\n        The IP address (or hostname) of user's pods which KubeSpawner connects to.\n        If you do not specify the value, KubeSpawner will use the pod IP.\n\n        e.g. 'jupyter-{username}--{servername}.notebooks.jupyterhub.svc.cluster.local',\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n\n        Trailing `-` characters in each domain level are stripped for safe handling of empty server names (user default servers).\n\n        This must be unique within the namespace the pods are being spawned\n        in, so if you are running multiple jupyterhubs spawning in the\n        same namespace, consider setting this to be something more unique.\n        ",
    "type": [
      "string"
    ]
  },
  "pod_name_template": {
    "description": "\n        Template to use to form the name of user's pods.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n\n        Trailing `-` characters are stripped for safe handling of empty server names (user default servers).\n\n        This must be unique within the namespace the pods are being spawned\n        in, so if you are running multiple jupyterhubs spawning in the\n        same namespace, consider setting this to be something more unique.\n\n        .. versionchanged:: 0.12\n            `--` delimiter added to the template,\n            where it was implicitly added to the `servername` field before.\n            Additionally, `username--servername` delimiter was `-` instead of `--`,\n            allowing collisions in certain circumstances.\n        ",
    "type": [
      "string"
    ]
  },
  "pod_security_context": {
    "description": "\n        A Kubernetes security context for the pod. Note that all configuration\n        options within here should be camelCased.\n\n        What is configured here has higher priority than `fs_gid` and\n        `supplemental_gids`, but lower priority than what is set in the\n        `container_security_context`.\n\n        Note that anything configured on the Pod level will influence all\n        containers, including init containers and sidecar containers.\n\n        Rely on `the Kubernetes reference\n        <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podsecuritycontext-v1-core>`__\n        for details on allowed configuration.\n        ",
    "type": [
      "object"
    ]
  },
  "poll_interval": {
    "description": "\n        Interval (in seconds) on which to poll the spawner for single-user server's status.\n\n        At every poll interval, each spawner's `.poll` method is called, which checks\n        if the single-user server is still running. If it isn't running, then JupyterHub modifies\n        its own state accordingly and removes appropriate routes from the configurable proxy.\n        ",
    "type": [
      "number"
    ]
  },
  "port": {
    "description": "\n        The port for single-user servers to listen on.\n\n        Defaults to `0`, which uses a randomly allocated port number each time.\n\n        If set to a non-zero value, all Spawners will use the same port,\n        which only makes sense if each server is on a different address,\n        e.g. in containers.\n\n        New in version 0.7.\n        ",
    "type": [
      "number"
    ]
  },
  "post_stop_hook": {
    "description": "\n        An optional hook function that you can implement to do work after\n        the spawner stops.\n\n        This can be set independent of any concrete spawner implementation.\n        ",
    "type": [
      "object",
      "null"
    ]
  },
  "pre_spawn_hook": {
    "description": "\n        An optional hook function that you can implement to do some\n        bootstrapping work before the spawner starts. For example, create a\n        directory for your user or load initial content.\n\n        This can be set independent of any concrete spawner implementation.\n\n        This maybe a coroutine.\n\n        Example::\n\n            from subprocess import check_call\n            def my_hook(spawner):\n                username = spawner.user.name\n                check_call(['./examples/bootstrap-script/bootstrap.sh', username])\n\n            c.Spawner.pre_spawn_hook = my_hook\n\n        ",
    "type": [
      "object",
      "null"
    ]
  },
  "priority_class_name": {
    "description": "\n        The priority class that the pods will use.\n\n        See https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption for\n        more information on how pod priority works.\n        ",
    "type": [
      "string"
    ]
  },
  "privileged": {
    "description": "\n        Whether to run the pod with a privileged security context.\n        ",
    "type": [
      "bool"
    ]
  },
  "profile_form_template": {
    "description": "\n        Jinja2 template for constructing profile list shown to user.\n\n        Used when `profile_list` is set.\n\n        The contents of `profile_list` are passed in to the template.\n        This should be used to construct the contents of a HTML form. When\n        posted, this form is expected to have an item with name `profile` and\n        the value the index of the profile in `profile_list`.\n        ",
    "type": [
      "string"
    ]
  },
  "profile_list": {
    "description": "\n        List of profiles to offer for selection by the user.\n\n        Signature is: `List(Dict())`, where each item is a dictionary that has two keys:\n\n        - `display_name`: the human readable display name (should be HTML safe)\n        - `slug`: the machine readable slug to identify the profile\n          (missing slugs are generated from display_name)\n        - `description`: Optional description of this profile displayed to the user.\n        - `kubespawner_override`: a dictionary with overrides to apply to the KubeSpawner\n          settings. Each value can be either the final value to change or a callable that\n          take the `KubeSpawner` instance as parameter and return the final value. This can\n          be further overridden by 'profile_options'\n        - 'profile_options': A dictionary of sub-options that allow users to further customize the\n          selected profile. By default, these are rendered as a dropdown with the label\n          provided by `display_name`. Items should have a unique key representing the customization,\n          and the value is a dictionary with the following keys:\n          - 'display_name': Name used to identify this particular option\n          - 'choices': A dictionary containing list of choices for the user to choose from\n            to set the value for this particular option. The key is an identifier for this\n            choice, and the value is a dictionary with the following possible keys:\n            - 'display_name': Human readable display name for this choice.\n            - 'default': (optional Bool) True if this is the default selected choice\n            - 'kubespawner_override': A dictionary with overrides to apply to the KubeSpawner\n              settings, on top of whatever was applied with the 'kubespawner_override' key\n              for the profile itself. The key should be the name of the kubespawner setting,\n              and value can be either the final value or a callable that returns the final\n              value when called with the spawner instance as the only parameter.\n        - `default`: (optional Bool) True if this is the default selected option\n\n        kubespawner setting overrides work in the following manner, with items further in the\n        list *replacing* (not merging with) items earlier in the list:\n\n        1. Settings directly set on KubeSpawner, via c.KubeSpawner.<traitlet_name>\n        2. `kubespawner_override` in the profile the user has chosen\n        3. `kubespawner_override` in the specific choices the user has made within the\n           profile, applied linearly based on the ordering of the option in the profile\n           definition configuration\n\n        Example::\n\n            c.KubeSpawner.profile_list = [\n                {\n                    'display_name': 'Training Env',\n                    'slug': 'training-python',\n                    'default': True,\n                    'profile_options': {\n                        'image': {\n                            'display_name': 'Image',\n                            'choices': {\n                                'pytorch': {\n                                    'display_name': 'Python 3 Training Notebook',\n                                    'kubespawner_override': {\n                                        'image': 'training/python:2022.01.01'\n                                    }\n                                },\n                                'tf': {\n                                    'display_name': 'R 4.2 Training Notebook',\n                                    'kubespawner_override': {\n                                        'image': 'training/r:2021.12.03'\n                                    }\n                                }\n                            }\n                        }\n                    },\n                    'kubespawner_override': {\n                        'cpu_limit': 1,\n                        'mem_limit': '512M',\n                    }\n                }, {\n                    'display_name': 'Python DataScience',\n                    'slug': 'datascience-small',\n                    'profile_options': {\n                        'memory': {\n                            'display_name': 'CPUs',\n                            'choices': {\n                                '2': {\n                                    'display_name': '2 CPUs',\n                                    'kubespawner_override': {\n                                        'cpu_limit': 2,\n                                        'cpu_guarantee': 1.8,\n                                        'node_selectors': {\n                                            'node.kubernetes.io/instance-type': 'n1-standard-2'\n                                        }\n                                    }\n                                },\n                                '4': {\n                                    'display_name': '4 CPUs',\n                                    'kubespawner_override': {\n                                        'cpu_limit': 4,\n                                        'cpu_guarantee': 3.5,\n                                        'node_selectors': {\n                                            'node.kubernetes.io/instance-type': 'n1-standard-4'\n                                        }\n                                    }\n                                }\n                            }\n                        },\n                    },\n                    'kubespawner_override': {\n                        'image': 'datascience/small:label',\n                    }\n                }, {\n                    'display_name': 'DataScience - Medium instance (GPUx2)',\n                    'slug': 'datascience-gpu2x',\n                    'kubespawner_override': {\n                        'image': 'datascience/medium:label',\n                        'cpu_limit': 48,\n                        'mem_limit': '96G',\n                        'extra_resource_guarantees': {\"nvidia.com/gpu\": \"2\"},\n                    }\n                }\n            ]\n\n        Instead of a list of dictionaries, this could also be a callable that takes as one\n        parameter the current spawner instance and returns a list of dictionaries. The\n        callable will be called asynchronously if it returns a future, rather than\n        a list. Note that the interface of the spawner class is not deemed stable\n        across versions, so using this functionality might cause your JupyterHub\n        or kubespawner upgrades to break.\n        ",
    "type": [
      "array"
    ]
  },
  "pvc_name_template": {
    "description": "\n        Template to use to form the name of user's pvc.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n\n        Trailing `-` characters are stripped for safe handling of empty server names (user default servers).\n\n        This must be unique within the namespace the pvc are being spawned\n        in, so if you are running multiple jupyterhubs spawning in the\n        same namespace, consider setting this to be something more unique.\n\n        .. versionchanged:: 0.12\n            `--` delimiter added to the template,\n            where it was implicitly added to the `servername` field before.\n            Additionally, `username--servername` delimiter was `-` instead of `--`,\n            allowing collisions in certain circumstances.\n        ",
    "type": [
      "string"
    ]
  },
  "scheduler_name": {
    "description": "\n        Set the pod's scheduler explicitly by name. See `the Kubernetes documentation <https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podspec-v1-core>`__\n        for more information.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "secret_mount_path": {
    "description": "\n        Location to mount the spawned pod's certificates needed for internal_ssl functionality.\n        ",
    "type": [
      "string"
    ]
  },
  "secret_name_template": {
    "description": "\n        Template to use to form the name of user's secret.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n\n        This must be unique within the namespace the pvc are being spawned\n        in, so if you are running multiple jupyterhubs spawning in the\n        same namespace, consider setting this to be something more unique.\n        ",
    "type": [
      "string"
    ]
  },
  "service_account": {
    "description": "\n        The service account to be mounted in the spawned user pod.\n\n        The token of the service account is NOT mounted by default.\n        This makes sure that we don't accidentally give access to the whole\n        kubernetes API to the users in the spawned pods.\n        Set automount_service_account_token True to mount it.\n\n        This `serviceaccount` must already exist in the namespace the user pod is being spawned in.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "services_enabled": {
    "description": "\n        Enable fronting the user pods with a kubernetes service.\n\n        This is useful in cases when network rules don't allow direct traffic\n        routing to pods in a cluster. Should be enabled when using jupyterhub\n        with a service mesh like istio with mTLS enabled.\n        ",
    "type": [
      "bool"
    ]
  },
  "ssl_alt_names": {
    "description": "List of SSL alt names\n\n        May be set in config if all spawners should have the same value(s),\n        or set at runtime by Spawner that know their names.\n        ",
    "type": [
      "array"
    ]
  },
  "ssl_alt_names_include_local": {
    "description": "Whether to include DNS:localhost, IP:127.0.0.1 in alt names",
    "type": [
      "bool"
    ]
  },
  "start_timeout": {
    "description": "\n        Timeout (in seconds) before giving up on starting of single-user server.\n\n        This is the timeout for start to return, not the timeout for the server to respond.\n        Callers of spawner.start will assume that startup has failed if it takes longer than this.\n        start should return when the server process is started and its location is known.\n        ",
    "type": [
      "number"
    ]
  },
  "storage_access_modes": {
    "description": "\n        List of access modes the user has for the pvc.\n\n        The access modes are:\n\n            - `ReadWriteOnce` : the volume can be mounted as read-write by a single node\n            - `ReadOnlyMany` : the volume can be mounted read-only by many nodes\n            - `ReadWriteMany` : the volume can be mounted as read-write by many nodes\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes>`__\n        for more information on how access modes work.\n        ",
    "type": [
      "array"
    ]
  },
  "storage_capacity": {
    "description": "\n        The amount of storage space to request from the volume that the pvc will\n        mount to. This amount will be the amount of storage space the user has\n        to work with on their notebook. If left blank, the kubespawner will not\n        create a pvc for the pod.\n\n        This will be added to the `resources: requests: storage:` in the k8s pod spec.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims>`__\n\n        for more information on how storage works.\n\n        Quantities can be represented externally as unadorned integers, or as fixed-point\n        integers with one of these SI suffices (`E, P, T, G, M, K, m`) or their power-of-two\n        equivalents (`Ei, Pi, Ti, Gi, Mi, Ki`). For example, the following represent roughly\n        the same value: `128974848`, `129e6`, `129M`, `123Mi`.\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "storage_class": {
    "description": "\n        The storage class that the pvc will use.\n\n        This will be added to the `annotations: volume.beta.kubernetes.io/storage-class:`\n        in the pvc metadata.\n\n        This will determine what type of volume the pvc will request to use. If one exists\n        that matches the criteria of the StorageClass, the pvc will mount to that. Otherwise,\n        b/c it has a storage class, k8s will dynamically spawn a pv for the pvc to bind to\n        and a machine in the cluster for the pv to bind to.\n\n        Note that an empty string is a valid value and is always interpreted to be\n        requesting a pv with no class.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/storage/storage-classes/>`__\n        for more information on how StorageClasses work.\n\n        ",
    "type": [
      "string",
      "null"
    ]
  },
  "storage_extra_labels": {
    "description": "\n        Extra kubernetes labels to set on the user PVCs.\n\n        The keys and values specified here would be set as labels on the PVCs\n        created by kubespawner for the user. Note that these are only set\n        when the PVC is created, not later when this setting is updated.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/>`__\n        for more info on what labels are and why you might want to use them!\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "object"
    ]
  },
  "storage_pvc_ensure": {
    "description": "\n        Ensure that a PVC exists for each user before spawning.\n\n        Set to true to create a PVC named with `pvc_name_template` if it does\n        not exist for the user when their pod is spawning.\n        ",
    "type": [
      "bool"
    ]
  },
  "storage_selector": {
    "description": "\n        The dictionary Selector labels used to match a PersistentVolumeClaim to\n        a PersistentVolume.\n\n        Default is None and means it will match based only on other storage criteria.\n\n        For example to match the Nodes that have a label of `content: jupyter` use::\n\n           c.KubeSpawner.storage_selector = {'matchLabels':{'content': 'jupyter'}}\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "object"
    ]
  },
  "supplemental_gids": {
    "description": "\n        A list of GIDs that should be set as additional supplemental groups to the\n        user that the container runs as.\n\n        Instead of a list of integers, this could also be a callable that takes as one\n        parameter the current spawner instance and returns a list of integers. The\n        callable will be called asynchronously if it returns a future, rather than\n        a list. Note that the interface of the spawner class is not deemed stable\n        across versions, so using this functionality might cause your JupyterHub\n        or kubespawner upgrades to break.\n\n        You may have to set this if you are deploying to an environment with RBAC/SCC\n        enforced and pods run with a 'restricted' SCC which results in the image being\n        run as an assigned user ID. The supplemental group IDs would need to include\n        the corresponding group ID of the user ID the image normally would run as. The\n        image must setup all directories/files any application needs access to, as group\n        writable.\n        ",
    "type": [
      "array"
    ]
  },
  "tolerations": {
    "description": "\n        List of tolerations that are to be assigned to the pod in order to be able to schedule the pod\n        on a node with the corresponding taints. See the official Kubernetes documentation for additional details\n        https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/\n\n        Pass this field an array of \"Toleration\" objects\n        * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#toleration-v1-core\n\n        Example::\n\n            [\n                {\n                    'key': 'key',\n                    'operator': 'Equal',\n                    'value': 'value',\n                    'effect': 'NoSchedule'\n                },\n                {\n                    'key': 'key',\n                    'operator': 'Exists',\n                    'effect': 'NoSchedule'\n                }\n            ]\n\n        ",
    "type": [
      "array"
    ]
  },
  "uid": {
    "description": "\n        The UID to run the single-user server containers as.\n\n        This UID should ideally map to a user that already exists in the container\n        image being used. Running as root is discouraged.\n\n        Instead of an integer, this could also be a callable that takes as one\n        parameter the current spawner instance and returns an integer. The callable\n        will be called asynchronously if it returns a future. Note that\n        the interface of the spawner class is not deemed stable across versions,\n        so using this functionality might cause your JupyterHub or kubespawner\n        upgrades to break.\n\n        If set to `None`, the user specified with the `USER` directive in the\n        container metadata is used.\n        ",
    "type": [
      "number",
      "null"
    ]
  },
  "user_namespace_template": {
    "description": "\n        Template to use to form the namespace of user's pods (only if\n        enable_user_namespaces is True).\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "string"
    ]
  },
  "volume_mounts": {
    "description": "\n        List of paths on which to mount volumes in the user notebook's pod.\n\n        This list will be added to the values of the `volumeMounts` key under the user's\n        container in the kubernetes pod spec, so you should use the same structure as that.\n        Each item in the list should be a dictionary with at least these two keys:\n\n           - `mountPath` The path on the container in which we want to mount the volume.\n           - `name` The name of the volume we want to mount, as specified in the `volumes` config.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/storage/volumes>`__\n        for more information on how the `volumeMount` item works.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "array"
    ]
  },
  "volumes": {
    "description": "\n        List of Kubernetes Volume specifications that will be mounted in the user pod.\n\n        This list will be directly added under `volumes` in the kubernetes pod spec,\n        so you should use the same structure. Each item in the list must have the\n        following two keys:\n\n          - `name`\n            Name that'll be later used in the `volume_mounts` config to mount this\n            volume at a specific path.\n          - `<name-of-a-supported-volume-type>` (such as `hostPath`, `persistentVolumeClaim`,\n            etc)\n            The key name determines the type of volume to mount, and the value should\n            be an object specifying the various options available for that kind of\n            volume.\n\n        See `the Kubernetes documentation <https://kubernetes.io/docs/concepts/storage/volumes>`__\n        for more information on the various kinds of volumes available and their options.\n        Your kubernetes cluster must already be configured to support the volume types you want to use.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "array"
    ]
  },
  "working_dir": {
    "description": "\n        The working directory where the Notebook server will be started inside the container.\n        Defaults to `None` so the working directory will be the one defined in the Dockerfile.\n\n        `{username}`, `{userid}`, `{servername}`, `{hubnamespace}`,\n        `{unescaped_username}`, and `{unescaped_servername}` will be expanded if\n        found within strings of this configuration. The username and servername\n        come escaped to follow the [DNS label\n        standard](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names).\n        ",
    "type": [
      "string",
      "null"
    ]
  }
}
from argparse import ArgumentParser
from importlib import import_module
from inspect import getmembers
import json
import sys
from traitlets.traitlets import Any, Bool, Callable, Dict, Float, Int, List, Set, Unicode, Union
import jupyterhub.traitlets
def lookup_schema_type(t):
traitlets_schema_map = {
Any: "object",
Bool: "bool",
Callable: None,
Dict: "object",
Float: "number",
Int: "number",
List: "array",
Set: "array",
Unicode: "string",
Union: None,
jupyterhub.traitlets.ByteSpecification: "string",
jupyterhub.traitlets.Callable: None,
jupyterhub.traitlets.Command: "string",
}
return traitlets_schema_map[t]
# https://json-schema.org/understanding-json-schema/reference/type.html
def get_schema_types(traitlet):
# ignore_type_strs = [f"<class '{t}'>" for t in (
# "traitlets.traitlets.Callable",
# "jupyterhub.traitlets.ByteSpecification",
# "jupyterhub.traitlets.Callable",
# "jupyterhub.traitlets.Command",
# )]
t = lookup_schema_type(traitlet.__class__)
if t:
return [t]
if traitlet.__class__ == Union:
ts = []
for t in traitlet.trait_types:
ts.extend(get_schema_types(t))
return ts
return []
def get_traitlets_schema(module_name, class_name):
mod = import_module(module_name)
cls = getattr(mod, class_name)
members = getmembers(cls)
configurables = {}
for (k, v) in members:
m = getattr(v, "metadata", None)
if m and m.get("config"):
configurables[k] = v
schema = {}
for k, v in configurables.items():
ts = get_schema_types(v)
if not ts:
print(f"Skipping {k}", file=sys.stderr)
continue
s = {"type": ts}
if v.allow_none:
s["type"].append("null")
s["description"] = v.help
schema[k] = s
return schema
def main():
parser = ArgumentParser()
parser.add_argument("module_name", help="Module name containing the class, e.g. 'kubespawner'")
parser.add_argument("class_name", help="Class name, e.g. 'KubeSpawner'")
args = parser.parse_args()
schema = get_traitlets_schema(args.module_name, args.class_name)
print(json.dumps(schema, indent=2, sort_keys=True))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment