From 69e7fcce3e4b3c0afb53a22d9057fdf3b20ab55f Mon Sep 17 00:00:00 2001 From: tirumerla Date: Tue, 22 Dec 2020 22:31:26 -0800 Subject: [PATCH 1/3] read env from secrets --- kubespawner/objects.py | 50 ++---------- kubespawner/spawner.py | 88 +++++++++++++-------- tests/test_objects.py | 173 ++++++++++++++++++++--------------------- 3 files changed, 146 insertions(+), 165 deletions(-) diff --git a/kubespawner/objects.py b/kubespawner/objects.py index 19a9c58b..46830fe7 100644 --- a/kubespawner/objects.py +++ b/kubespawner/objects.py @@ -6,7 +6,6 @@ import os import re from urllib.parse import urlparse - from kubernetes.client.models import ( V1Affinity, V1Container, @@ -36,6 +35,8 @@ V1PreferredSchedulingTerm, V1ResourceRequirements, V1Secret, + V1EnvFromSource, + V1SecretEnvSource, V1SecurityContext, V1Service, V1ServicePort, @@ -62,7 +63,7 @@ def make_pod( supplemental_gids=None, run_privileged=False, allow_privilege_escalation=True, - env=None, + env_from=None, working_dir=None, volumes=None, volume_mounts=None, @@ -266,6 +267,7 @@ def make_pod( pod.spec = V1PodSpec(containers=[]) pod.spec.restart_policy = 'OnFailure' + if image_pull_secrets is not None: # image_pull_secrets as received by the make_pod function should always @@ -288,12 +290,6 @@ def make_pod( } ) - env['JUPYTERHUB_SSL_KEYFILE'] = ssl_secret_mount_path + "ssl.key" - env['JUPYTERHUB_SSL_CERTFILE'] = ssl_secret_mount_path + "ssl.crt" - env['JUPYTERHUB_SSL_CLIENT_CA'] = ( - ssl_secret_mount_path + "notebooks-ca_trust.crt" - ) - if not volume_mounts: volume_mounts = [] volume_mounts.append( @@ -340,24 +336,12 @@ def make_pod( if all([e is None for e in container_security_context.to_dict().values()]): container_security_context = None - # Transform a dict into valid Kubernetes EnvVar Python representations. This - # representation shall always have a "name" field as well as either a - # "value" field or "value_from" field. For examples see the - # test_make_pod_with_env function. - prepared_env = [] - for k, v in (env or {}).items(): - if type(v) == dict: - if not "name" in v: - v["name"] = k - prepared_env.append(get_k8s_model(V1EnvVar, v)) - else: - prepared_env.append(V1EnvVar(name=k, value=v)) notebook_container = V1Container( name='notebook', image=image, working_dir=working_dir, ports=[V1ContainerPort(name='notebook-port', container_port=port)], - env=prepared_env, + env_from=[V1EnvFromSource(secret_ref=V1SecretEnvSource(env_from))], args=cmd, image_pull_policy=image_pull_policy, lifecycle=lifecycle_hooks, @@ -671,9 +655,8 @@ def make_owner_reference(name, uid): def make_secret( name, + str_data, username, - cert_paths, - hub_ca, owner_references, labels=None, annotations=None, @@ -706,26 +689,7 @@ def make_secret( secret.metadata.annotations = (annotations or {}).copy() secret.metadata.labels = (labels or {}).copy() secret.metadata.owner_references = owner_references - - secret.data = {} - - with open(cert_paths['keyfile'], 'r') as file: - encoded = base64.b64encode(file.read().encode("utf-8")) - secret.data['ssl.key'] = encoded.decode("utf-8") - - with open(cert_paths['certfile'], 'r') as file: - encoded = base64.b64encode(file.read().encode("utf-8")) - secret.data['ssl.crt'] = encoded.decode("utf-8") - - with open(cert_paths['cafile'], 'r') as file: - encoded = base64.b64encode(file.read().encode("utf-8")) - secret.data["notebooks-ca_trust.crt"] = encoded.decode("utf-8") - - with open(hub_ca, 'r') as file: - encoded = base64.b64encode(file.read().encode("utf-8")) - secret.data["notebooks-ca_trust.crt"] = secret.data[ - "notebooks-ca_trust.crt" - ] + encoded.decode("utf-8") + secret.string_data = str_data return secret diff --git a/kubespawner/spawner.py b/kubespawner/spawner.py index d4fceb35..6f6e123c 100644 --- a/kubespawner/spawner.py +++ b/kubespawner/spawner.py @@ -7,6 +7,7 @@ import asyncio import json +import base64 import multiprocessing import os import string @@ -1624,7 +1625,7 @@ async def get_pod_manifest(self): supplemental_gids=supplemental_gids, run_privileged=self.privileged, allow_privilege_escalation=self.allow_privilege_escalation, - env=self.get_env(), + env_from=self.secret_name, volumes=self._expand_all(self.volumes), volume_mounts=self._expand_all(self.volume_mounts), working_dir=self.working_dir, @@ -1665,12 +1666,11 @@ def get_secret_manifest(self, owner_reference): annotations = self._build_common_annotations( self._expand_all(self.extra_annotations) ) - + return make_secret( name=self.secret_name, + str_data=self.get_env(), username=self.user.name, - cert_paths=self.cert_paths, - hub_ca=self.internal_trust_bundles['hub-ca'], owner_references=[owner_reference], labels=labels, annotations=annotations, @@ -1771,6 +1771,24 @@ def get_env(self): # deprecate image env['JUPYTER_IMAGE_SPEC'] = self.image env['JUPYTER_IMAGE'] = self.image + if self.internal_ssl: + with open(self.cert_paths['keyfile'], 'r') as file: + env['ssl.key'] = file.read() + + with open(self.cert_paths['certfile'], 'r') as file: + env['ssl.crt'] = file.read() + + with open(self.cert_paths['cafile'], 'r') as file: + env["notebooks-ca_trust.crt"] = file.read() + + with open(self.internal_trust_bundles['hub-ca'], 'r') as file: + encoded = base64.b64encode(file.read().encode("utf-8")) + env["notebooks-ca_trust.crt"] = env[ + "notebooks-ca_trust.crt" + ] + file.read() + env['JUPYTERHUB_SSL_KEYFILE'] = self.secret_mount_path + "ssl.key" + env['JUPYTERHUB_SSL_CERTFILE'] = self.secret_mount_path + "ssl.crt" + env['JUPYTERHUB_SSL_CLIENT_CA'] = (self.secret_mount_path + "notebooks-ca_trust.crt") return env @@ -2182,34 +2200,35 @@ async def _start(self): timeout=self.k8s_api_request_retry_timeout ) - if self.internal_ssl: - try: - # wait for pod to have uid, - # required for creating owner reference - await exponential_backoff( - lambda: self.pod_has_uid( - self.pod_reflector.pods.get(self.pod_name, None) - ), - "pod/%s does not have a uid!" % (self.pod_name), - ) + try: + # wait for pod to have uid, + # required for creating owner reference + await exponential_backoff( + lambda: self.pod_has_uid( + self.pod_reflector.pods.get(self.pod_name, None) + ), + "pod/%s does not have a uid!" % (self.pod_name), + ) - pod = self.pod_reflector.pods[self.pod_name] - owner_reference = make_owner_reference( - self.pod_name, pod["metadata"]["uid"] - ) + pod = self.pod_reflector.pods[self.pod_name] + owner_reference = make_owner_reference( + self.pod_name, pod["metadata"]["uid"] + ) - # internal ssl, create secret object - secret_manifest = self.get_secret_manifest(owner_reference) - await exponential_backoff( - partial( - self._ensure_not_exists, "secret", secret_manifest.metadata.name - ), - f"Failed to delete secret {secret_manifest.metadata.name}", - ) - await exponential_backoff( - partial(self._make_create_resource_request, "secret", secret_manifest), - f"Failed to create secret {secret_manifest.metadata.name}", - ) + # create secret object + secret_manifest = self.get_secret_manifest(owner_reference) + await exponential_backoff( + partial( + self._ensure_not_exists, "secret", secret_manifest.metadata.name + ), + f"Failed to delete secret {secret_manifest.metadata.name}", + ) + await exponential_backoff( + partial(self._make_create_resource_request, "secret", secret_manifest), + f"Failed to create secret {secret_manifest.metadata.name}", + ) + + if self.internal_ssl: service_manifest = self.get_service_manifest(owner_reference) await exponential_backoff( @@ -2224,10 +2243,11 @@ async def _start(self): ), f"Failed to create service {service_manifest.metadata.name}", ) - except Exception: - # cleanup on failure and re-raise - await self.stop(True) - raise + + except Exception: + # cleanup on failure and re-raise + await self.stop(True) + raise # we need a timeout here even though start itself has a timeout # in order for this coroutine to finish at some point. diff --git a/tests/test_objects.py b/tests/test_objects.py index e403e372..f5c625bd 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -2,7 +2,7 @@ Test functions used to create k8s objects """ from kubernetes.client import ApiClient -from kubespawner.objects import make_ingress, make_pod, make_pvc +from kubespawner.objects import make_ingress, make_pod, make_pvc, make_secret api_client = ApiClient() @@ -26,7 +26,7 @@ def test_make_simplest_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -70,7 +70,7 @@ def test_make_labeled_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -114,7 +114,7 @@ def test_make_annotated_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -162,7 +162,7 @@ def test_make_pod_with_image_pull_secrets_simplified_format(): ], "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -211,7 +211,7 @@ def test_make_pod_with_image_pull_secrets_k8s_native_format(): ], "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -261,7 +261,7 @@ def test_set_container_uid_and_gid(): "runAsUser": 0, "runAsGroup": 0 }, - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -309,7 +309,7 @@ def test_set_container_uid_and_pod_fs_gid(): "securityContext": { "runAsUser": 1000, }, - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -360,7 +360,7 @@ def test_set_pod_supplemental_gids(): "securityContext": { "runAsUser": 1000, }, - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -407,7 +407,7 @@ def test_run_privileged_container(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -454,7 +454,7 @@ def test_allow_privilege_escalation_container(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -507,7 +507,7 @@ def test_make_pod_resources_all(): "nodeSelector": {"disk": "ssd"}, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -537,33 +537,14 @@ def test_make_pod_resources_all(): } -def test_make_pod_with_env(): +def test_make_pod_with_env_from(): """ Test specification of a pod with custom environment variables. """ assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env={ - 'TEST_KEY_1': 'TEST_VALUE', - 'TEST_KEY_2': { - 'valueFrom': { - 'secretKeyRef': { - 'name': 'my-k8s-secret', - 'key': 'password', - }, - }, - }, - 'TEST_KEY_NAME_IGNORED': { - 'name': 'TEST_KEY_3', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'my-k8s-secret', - 'key': 'password', - }, - }, - }, - }, + env_from='my-k8s-secret', cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent' @@ -577,29 +558,12 @@ def test_make_pod_with_env(): 'automountServiceAccountToken': False, "containers": [ { - "env": [ - { - 'name': 'TEST_KEY_1', - 'value': 'TEST_VALUE', - }, - { - 'name': 'TEST_KEY_2', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'my-k8s-secret', - 'key': 'password', - }, - }, - }, + "envFrom": [ { - 'name': 'TEST_KEY_3', - 'valueFrom': { - 'secretKeyRef': { - 'name': 'my-k8s-secret', - 'key': 'password', - }, - }, - }, + 'secretRef': { + 'name': 'my-k8s-secret' + } + } ], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -652,7 +616,7 @@ def test_make_pod_with_lifecycle(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -717,7 +681,7 @@ def test_make_pod_with_init_containers(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -785,7 +749,7 @@ def test_make_pod_with_extra_container_config(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -851,7 +815,7 @@ def test_make_pod_with_extra_pod_config(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -912,7 +876,7 @@ def test_make_pod_with_extra_containers(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -970,7 +934,7 @@ def test_make_pod_with_extra_resources(): "nodeSelector": {"disk": "ssd"}, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1002,6 +966,51 @@ def test_make_pod_with_extra_resources(): "apiVersion": "v1" } +def test_make_secret_simple(): + """ + Test specification of the simplest possible secret specification + """ + assert api_client.sanitize_for_serialization(make_secret( + name='my-k8s-secret', + str_data={}, + username='test', + owner_references=[], + labels={} + )) == { + 'kind': 'Secret', + 'apiVersion': 'v1', + 'metadata': { + 'name': 'my-k8s-secret', + 'annotations': {}, + 'labels': {}, + 'ownerReferences': [] + }, + 'stringData': {} + } + +def test_make_secret_with_data(): + """ + Test specification of the simplest possible secret specification + """ + assert api_client.sanitize_for_serialization(make_secret( + name='my-k8s-secret', + str_data={"TEST1": "VALUE1"}, + username='test', + owner_references=[], + labels={} + )) == { + 'kind': 'Secret', + 'apiVersion': 'v1', + 'metadata': { + 'name': 'my-k8s-secret', + 'ownerReferences': [], + 'annotations': {}, + 'labels': {} + }, + 'stringData': {"TEST1": "VALUE1"} + } + + def test_make_pvc_simple(): """ Test specification of the simplest possible pvc specification @@ -1125,7 +1134,7 @@ def test_make_pod_with_service_account(): "spec": { "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1171,7 +1180,7 @@ def test_make_pod_with_scheduler_name(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1230,7 +1239,7 @@ def test_make_pod_with_tolerations(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1286,7 +1295,7 @@ def test_make_pod_with_node_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1343,7 +1352,7 @@ def test_make_pod_with_node_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1408,7 +1417,7 @@ def test_make_pod_with_pod_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1468,7 +1477,7 @@ def test_make_pod_with_pod_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1531,7 +1540,7 @@ def test_make_pod_with_pod_anti_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1591,7 +1600,7 @@ def test_make_pod_with_pod_anti_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1641,7 +1650,7 @@ def test_make_pod_with_priority_class_name(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], + "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", "imagePullPolicy": "IfNotPresent", @@ -1755,11 +1764,7 @@ def test_make_pod_with_ssl(): make_pod( name='ssl', image='jupyter/singleuser:latest', - env={ - 'JUPYTERHUB_SSL_KEYFILE': 'TEST_VALUE', - 'JUPYTERHUB_SSL_CERTFILE': 'TEST', - 'JUPYTERHUB_USER': 'TEST', - }, + env_from='ssl', working_dir='/', cmd=['jupyterhub-singleuser'], port=8888, @@ -1777,19 +1782,11 @@ def test_make_pod_with_ssl(): 'automountServiceAccountToken': False, "containers": [ { - "env": [ + "envFrom": [ { - 'name': 'JUPYTERHUB_SSL_KEYFILE', - 'value': '/etc/jupyterhub/ssl/ssl.key', - }, - { - 'name': 'JUPYTERHUB_SSL_CERTFILE', - 'value': '/etc/jupyterhub/ssl/ssl.crt', - }, - {'name': 'JUPYTERHUB_USER', 'value': 'TEST'}, - { - 'name': 'JUPYTERHUB_SSL_CLIENT_CA', - 'value': '/etc/jupyterhub/ssl/notebooks-ca_trust.crt', + 'secretRef': { + 'name': 'ssl' + } }, ], "name": "notebook", From ce9eb63bac8031adf44bd218b83c055c61fe7a0f Mon Sep 17 00:00:00 2001 From: tirumerla Date: Tue, 5 Jan 2021 23:16:40 -0800 Subject: [PATCH 2/3] fixes to allow valueFrom for env. variables --- kubespawner/objects.py | 6 +-- kubespawner/spawner.py | 31 ++++++++++++-- tests/test_objects.py | 96 ++++++++++++++++++++++++++++++++++++++++++ tests/test_spawner.py | 18 ++++++++ 4 files changed, 144 insertions(+), 7 deletions(-) diff --git a/kubespawner/objects.py b/kubespawner/objects.py index 46830fe7..0e0d675e 100644 --- a/kubespawner/objects.py +++ b/kubespawner/objects.py @@ -63,6 +63,7 @@ def make_pod( supplemental_gids=None, run_privileged=False, allow_privilege_escalation=True, + env=None, env_from=None, working_dir=None, volumes=None, @@ -341,6 +342,7 @@ def make_pod( image=image, working_dir=working_dir, ports=[V1ContainerPort(name='notebook-port', container_port=port)], + env=env, env_from=[V1EnvFromSource(secret_ref=V1SecretEnvSource(env_from))], args=cmd, image_pull_policy=image_pull_policy, @@ -671,10 +673,6 @@ def make_secret( going to be created in. username: The name of the user notebook. - cert_paths: - JupyterHub spawners cert_paths dictionary container certificate path references - hub_ca: - Path to the hub certificate authority labels: Labels to add to the secret. annotations: diff --git a/kubespawner/spawner.py b/kubespawner/spawner.py index 6f6e123c..85f3f861 100644 --- a/kubespawner/spawner.py +++ b/kubespawner/spawner.py @@ -28,6 +28,8 @@ from jupyterhub.spawner import Spawner from jupyterhub.traitlets import Command from jupyterhub.utils import exponential_backoff +from kubespawner.utils import get_k8s_model +from kubernetes.client.models import V1EnvVar from kubernetes import client from kubernetes.client.rest import ApiException from slugify import slugify @@ -1625,6 +1627,7 @@ async def get_pod_manifest(self): supplemental_gids=supplemental_gids, run_privileged=self.privileged, allow_privilege_escalation=self.allow_privilege_escalation, + env=self.get_env()[1], env_from=self.secret_name, volumes=self._expand_all(self.volumes), volume_mounts=self._expand_all(self.volume_mounts), @@ -1669,7 +1672,7 @@ def get_secret_manifest(self, owner_reference): return make_secret( name=self.secret_name, - str_data=self.get_env(), + str_data=self.get_env()[0], username=self.user.name, owner_references=[owner_reference], labels=labels, @@ -1768,10 +1771,33 @@ def get_env(self): """ env = super(KubeSpawner, self).get_env() + + """Separate env. variables into two dicts + - Dict containing only "valueFrom" env. variables, these are passed as-is. + - replace existing env dict with only "value" env. varaibles, these are passed into secret. + """ + prepared_env = [] + # create a separate dict for all "valueFrom" environment variables + extra_env = {k: v for k, v in (env or {}).items() if type(v) == dict} + # Replace existing env dict without "valueFrom" env. variables and pass it to secret + env = {k: v for k, v in (env or {}).items() if type(v) != dict} + for k, v in (extra_env or {}).items(): + if not "name" in v: + v["name"] = k + extra_env[k] = v + prepared_env.append(get_k8s_model(V1EnvVar, v)) + # deprecate image env['JUPYTER_IMAGE_SPEC'] = self.image env['JUPYTER_IMAGE'] = self.image + if self.internal_ssl: + """ + cert_paths: + certificate path references + hub_ca: + Path to the hub certificate authority + """ with open(self.cert_paths['keyfile'], 'r') as file: env['ssl.key'] = file.read() @@ -1782,7 +1808,6 @@ def get_env(self): env["notebooks-ca_trust.crt"] = file.read() with open(self.internal_trust_bundles['hub-ca'], 'r') as file: - encoded = base64.b64encode(file.read().encode("utf-8")) env["notebooks-ca_trust.crt"] = env[ "notebooks-ca_trust.crt" ] + file.read() @@ -1790,7 +1815,7 @@ def get_env(self): env['JUPYTERHUB_SSL_CERTFILE'] = self.secret_mount_path + "ssl.crt" env['JUPYTERHUB_SSL_CLIENT_CA'] = (self.secret_mount_path + "notebooks-ca_trust.crt") - return env + return env, prepared_env def load_state(self, state): """ diff --git a/tests/test_objects.py b/tests/test_objects.py index f5c625bd..95a56233 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -13,6 +13,7 @@ def test_make_simplest_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent' @@ -26,6 +27,7 @@ def test_make_simplest_pod(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -56,6 +58,7 @@ def test_make_labeled_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -70,6 +73,7 @@ def test_make_labeled_pod(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -100,6 +104,7 @@ def test_make_annotated_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -114,6 +119,7 @@ def test_make_annotated_pod(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -144,6 +150,7 @@ def test_make_pod_with_image_pull_secrets_simplified_format(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -162,6 +169,7 @@ def test_make_pod_with_image_pull_secrets_simplified_format(): ], "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -193,6 +201,7 @@ def test_make_pod_with_image_pull_secrets_k8s_native_format(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -211,6 +220,7 @@ def test_make_pod_with_image_pull_secrets_k8s_native_format(): ], "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -242,6 +252,7 @@ def test_set_container_uid_and_gid(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=0, @@ -261,6 +272,7 @@ def test_set_container_uid_and_gid(): "runAsUser": 0, "runAsGroup": 0 }, + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -291,6 +303,7 @@ def test_set_container_uid_and_pod_fs_gid(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=1000, @@ -309,6 +322,7 @@ def test_set_container_uid_and_pod_fs_gid(): "securityContext": { "runAsUser": 1000, }, + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -342,6 +356,7 @@ def test_set_pod_supplemental_gids(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=1000, @@ -360,6 +375,7 @@ def test_set_pod_supplemental_gids(): "securityContext": { "runAsUser": 1000, }, + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -393,6 +409,7 @@ def test_run_privileged_container(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, run_privileged=True, @@ -407,6 +424,7 @@ def test_run_privileged_container(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -440,6 +458,7 @@ def test_allow_privilege_escalation_container(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, allow_privilege_escalation=False, @@ -454,6 +473,7 @@ def test_allow_privilege_escalation_container(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -491,6 +511,7 @@ def test_make_pod_resources_all(): cpu_limit=2, cpu_guarantee=1, cmd=['jupyterhub-singleuser'], + env=[], port=8888, mem_limit='1Gi', mem_guarantee='512Mi', @@ -507,6 +528,7 @@ def test_make_pod_resources_all(): "nodeSelector": {"disk": "ssd"}, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -544,6 +566,26 @@ def test_make_pod_with_env_from(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[ + { + 'name': 'TEST_KEY_1', + 'valueFrom': { + 'secretKeyRef': { + 'name': 'my-test-secret', + 'key': 'password', + }, + }, + }, + { + 'name': 'TEST_KEY_2', + 'valueFrom': { + 'secretKeyRef': { + 'name': 'my-test-secret', + 'key': 'password', + }, + }, + }, + ], env_from='my-k8s-secret', cmd=['jupyterhub-singleuser'], port=8888, @@ -558,6 +600,26 @@ def test_make_pod_with_env_from(): 'automountServiceAccountToken': False, "containers": [ { + "env": [ + { + 'name': 'TEST_KEY_1', + 'valueFrom': { + 'secretKeyRef': { + 'name': 'my-test-secret', + 'key': 'password', + }, + }, + }, + { + 'name': 'TEST_KEY_2', + 'valueFrom': { + 'secretKeyRef': { + 'name': 'my-test-secret', + 'key': 'password', + }, + }, + }, + ], "envFrom": [ { 'secretRef': { @@ -596,6 +658,7 @@ def test_make_pod_with_lifecycle(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -616,6 +679,7 @@ def test_make_pod_with_lifecycle(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -656,6 +720,7 @@ def test_make_pod_with_init_containers(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -681,6 +746,7 @@ def test_make_pod_with_init_containers(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -727,6 +793,7 @@ def test_make_pod_with_extra_container_config(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -749,6 +816,7 @@ def test_make_pod_with_extra_container_config(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -788,6 +856,7 @@ def test_make_pod_with_extra_pod_config(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -815,6 +884,7 @@ def test_make_pod_with_extra_pod_config(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -856,6 +926,7 @@ def test_make_pod_with_extra_containers(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -876,6 +947,7 @@ def test_make_pod_with_extra_containers(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -917,6 +989,7 @@ def test_make_pod_with_extra_resources(): cpu_guarantee=1, extra_resource_limits={"nvidia.com/gpu": "5", "k8s.io/new-resource": "1"}, extra_resource_guarantees={"nvidia.com/gpu": "3"}, + env=[], cmd=['jupyterhub-singleuser'], port=8888, mem_limit='1Gi', @@ -934,6 +1007,7 @@ def test_make_pod_with_extra_resources(): "nodeSelector": {"disk": "ssd"}, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1121,6 +1195,7 @@ def test_make_pod_with_service_account(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1134,6 +1209,7 @@ def test_make_pod_with_service_account(): "spec": { "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1166,6 +1242,7 @@ def test_make_pod_with_scheduler_name(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1180,6 +1257,7 @@ def test_make_pod_with_scheduler_name(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1225,6 +1303,7 @@ def test_make_pod_with_tolerations(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1239,6 +1318,7 @@ def test_make_pod_with_tolerations(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1281,6 +1361,7 @@ def test_make_pod_with_node_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1295,6 +1376,7 @@ def test_make_pod_with_node_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1338,6 +1420,7 @@ def test_make_pod_with_node_affinity_required(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1352,6 +1435,7 @@ def test_make_pod_with_node_affinity_required(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1403,6 +1487,7 @@ def test_make_pod_with_pod_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1417,6 +1502,7 @@ def test_make_pod_with_pod_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1463,6 +1549,7 @@ def test_make_pod_with_pod_affinity_required(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1477,6 +1564,7 @@ def test_make_pod_with_pod_affinity_required(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1526,6 +1614,7 @@ def test_make_pod_with_pod_anti_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', + env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1540,6 +1629,7 @@ def test_make_pod_with_pod_anti_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1587,6 +1677,7 @@ def test_make_pod_with_pod_anti_affinity_required(): name='test', image='jupyter/singleuser:latest', cmd=['jupyterhub-singleuser'], + env=[], port=8888, image_pull_policy='IfNotPresent', pod_anti_affinity_required=pod_anti_affinity_required @@ -1600,6 +1691,7 @@ def test_make_pod_with_pod_anti_affinity_required(): "automountServiceAccountToken": False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1637,6 +1729,7 @@ def test_make_pod_with_priority_class_name(): name='test', image='jupyter/singleuser:latest', cmd=['jupyterhub-singleuser'], + env=[], port=8888, image_pull_policy='IfNotPresent', priority_class_name='my-custom-priority-class' @@ -1650,6 +1743,7 @@ def test_make_pod_with_priority_class_name(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1764,6 +1858,7 @@ def test_make_pod_with_ssl(): make_pod( name='ssl', image='jupyter/singleuser:latest', + env=[], env_from='ssl', working_dir='/', cmd=['jupyterhub-singleuser'], @@ -1782,6 +1877,7 @@ def test_make_pod_with_ssl(): 'automountServiceAccountToken': False, "containers": [ { + "env": [], "envFrom": [ { 'secretRef': { diff --git a/tests/test_spawner.py b/tests/test_spawner.py index ffed3673..2860bea8 100644 --- a/tests/test_spawner.py +++ b/tests/test_spawner.py @@ -581,3 +581,21 @@ def test_get_pvc_manifest(): "heritage": "jupyterhub", } assert manifest.spec.selector == {"matchLabels": {"user": "mock-5fname"}} + + +def test_env(): + c = Config() + + c.KubeSpawner.environment = { + "TEST_KEY_1": "VALUE_1", + "TEST_KEY_2": {'valueFrom': {'secretKeyRef': {'name': 'my-test-secret', 'key': 'password'}}}, + 'TEST_KEY_NAME_IGNORED': {'name': 'TEST_KEY_3', 'valueFrom': { 'secretKeyRef': {'name': 'my-test-secret', 'key': 'password'}}} + } + + spawner = KubeSpawner(config=c, _mock=True) + env = spawner.get_env() + assert env[0].get("TEST_KEY_1") == "VALUE_1" + assert env[1][0].name == "TEST_KEY_2" + assert env[1][0].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret'}} + assert env[1][1].name == "TEST_KEY_3" + assert env[1][1].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret'}} From 76eb430dc10b1bf91d066853ff46b7326bb460e7 Mon Sep 17 00:00:00 2001 From: tirumerla Date: Fri, 8 Jan 2021 02:45:27 -0800 Subject: [PATCH 3/3] fix name, empty array & remove tuple --- kubespawner/objects.py | 5 ++-- kubespawner/spawner.py | 48 ++++++++++++++++++-------------- tests/test_objects.py | 62 ++---------------------------------------- tests/test_spawner.py | 21 +++++++------- 4 files changed, 43 insertions(+), 93 deletions(-) diff --git a/kubespawner/objects.py b/kubespawner/objects.py index 0e0d675e..a97ccc40 100644 --- a/kubespawner/objects.py +++ b/kubespawner/objects.py @@ -657,8 +657,7 @@ def make_owner_reference(name, uid): def make_secret( name, - str_data, - username, + string_data, owner_references, labels=None, annotations=None, @@ -687,7 +686,7 @@ def make_secret( secret.metadata.annotations = (annotations or {}).copy() secret.metadata.labels = (labels or {}).copy() secret.metadata.owner_references = owner_references - secret.string_data = str_data + secret.string_data = string_data return secret diff --git a/kubespawner/spawner.py b/kubespawner/spawner.py index 85f3f861..c4234b51 100644 --- a/kubespawner/spawner.py +++ b/kubespawner/spawner.py @@ -1627,7 +1627,7 @@ async def get_pod_manifest(self): supplemental_gids=supplemental_gids, run_privileged=self.privileged, allow_privilege_escalation=self.allow_privilege_escalation, - env=self.get_env()[1], + env=self.get_env_value_from() if self.get_env_value_from() else None, env_from=self.secret_name, volumes=self._expand_all(self.volumes), volume_mounts=self._expand_all(self.volume_mounts), @@ -1672,8 +1672,7 @@ def get_secret_manifest(self, owner_reference): return make_secret( name=self.secret_name, - str_data=self.get_env()[0], - username=self.user.name, + string_data=self.get_env(), owner_references=[owner_reference], labels=labels, annotations=annotations, @@ -1771,22 +1770,9 @@ def get_env(self): """ env = super(KubeSpawner, self).get_env() - - """Separate env. variables into two dicts - - Dict containing only "valueFrom" env. variables, these are passed as-is. - - replace existing env dict with only "value" env. varaibles, these are passed into secret. - """ - prepared_env = [] - # create a separate dict for all "valueFrom" environment variables - extra_env = {k: v for k, v in (env or {}).items() if type(v) == dict} - # Replace existing env dict without "valueFrom" env. variables and pass it to secret + + # Filter env. variables with only values and pass it to secret env = {k: v for k, v in (env or {}).items() if type(v) != dict} - for k, v in (extra_env or {}).items(): - if not "name" in v: - v["name"] = k - extra_env[k] = v - prepared_env.append(get_k8s_model(V1EnvVar, v)) - # deprecate image env['JUPYTER_IMAGE_SPEC'] = self.image env['JUPYTER_IMAGE'] = self.image @@ -1815,7 +1801,26 @@ def get_env(self): env['JUPYTERHUB_SSL_CERTFILE'] = self.secret_mount_path + "ssl.crt" env['JUPYTERHUB_SSL_CLIENT_CA'] = (self.secret_mount_path + "notebooks-ca_trust.crt") - return env, prepared_env + return env + + def get_env_value_from(self): + + """Return the environment dict to use for the Spawner. + + See also: jupyterhub.Spawner.get_env + """ + + env = super(KubeSpawner, self).get_env() + env_value_from = [] + # Filter env. variables with only "valueFrom" environment variables. + extra_env = {k: v for k, v in (env or {}).items() if type(v) == dict} + for k, v in (extra_env or {}).items(): + if not "name" in v: + v["name"] = k + env_value_from.append(get_k8s_model(V1EnvVar, v)) + + return env_value_from + def load_state(self, state): """ @@ -2239,8 +2244,11 @@ async def _start(self): owner_reference = make_owner_reference( self.pod_name, pod["metadata"]["uid"] ) + + """Create secret object - # create secret object + Assuming there will be atleast one env. variable that will be passed + """ secret_manifest = self.get_secret_manifest(owner_reference) await exponential_backoff( partial( diff --git a/tests/test_objects.py b/tests/test_objects.py index 95a56233..d2684131 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -13,7 +13,6 @@ def test_make_simplest_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent' @@ -27,7 +26,6 @@ def test_make_simplest_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -58,7 +56,6 @@ def test_make_labeled_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -73,7 +70,6 @@ def test_make_labeled_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -104,7 +100,6 @@ def test_make_annotated_pod(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -119,7 +114,6 @@ def test_make_annotated_pod(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -150,7 +144,6 @@ def test_make_pod_with_image_pull_secrets_simplified_format(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -169,7 +162,6 @@ def test_make_pod_with_image_pull_secrets_simplified_format(): ], "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -201,7 +193,6 @@ def test_make_pod_with_image_pull_secrets_k8s_native_format(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -220,7 +211,6 @@ def test_make_pod_with_image_pull_secrets_k8s_native_format(): ], "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -252,7 +242,6 @@ def test_set_container_uid_and_gid(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=0, @@ -272,7 +261,6 @@ def test_set_container_uid_and_gid(): "runAsUser": 0, "runAsGroup": 0 }, - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -303,7 +291,6 @@ def test_set_container_uid_and_pod_fs_gid(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=1000, @@ -322,7 +309,6 @@ def test_set_container_uid_and_pod_fs_gid(): "securityContext": { "runAsUser": 1000, }, - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -356,7 +342,6 @@ def test_set_pod_supplemental_gids(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, run_as_uid=1000, @@ -375,7 +360,6 @@ def test_set_pod_supplemental_gids(): "securityContext": { "runAsUser": 1000, }, - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -409,7 +393,6 @@ def test_run_privileged_container(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, run_privileged=True, @@ -424,7 +407,6 @@ def test_run_privileged_container(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -458,7 +440,6 @@ def test_allow_privilege_escalation_container(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, allow_privilege_escalation=False, @@ -473,7 +454,6 @@ def test_allow_privilege_escalation_container(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -511,7 +491,6 @@ def test_make_pod_resources_all(): cpu_limit=2, cpu_guarantee=1, cmd=['jupyterhub-singleuser'], - env=[], port=8888, mem_limit='1Gi', mem_guarantee='512Mi', @@ -528,7 +507,6 @@ def test_make_pod_resources_all(): "nodeSelector": {"disk": "ssd"}, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -658,7 +636,6 @@ def test_make_pod_with_lifecycle(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -679,7 +656,6 @@ def test_make_pod_with_lifecycle(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -720,7 +696,6 @@ def test_make_pod_with_init_containers(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -746,7 +721,6 @@ def test_make_pod_with_init_containers(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -793,7 +767,6 @@ def test_make_pod_with_extra_container_config(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -816,7 +789,6 @@ def test_make_pod_with_extra_container_config(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -856,7 +828,6 @@ def test_make_pod_with_extra_pod_config(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -884,7 +855,6 @@ def test_make_pod_with_extra_pod_config(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -926,7 +896,6 @@ def test_make_pod_with_extra_containers(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -947,7 +916,6 @@ def test_make_pod_with_extra_containers(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -989,7 +957,6 @@ def test_make_pod_with_extra_resources(): cpu_guarantee=1, extra_resource_limits={"nvidia.com/gpu": "5", "k8s.io/new-resource": "1"}, extra_resource_guarantees={"nvidia.com/gpu": "3"}, - env=[], cmd=['jupyterhub-singleuser'], port=8888, mem_limit='1Gi', @@ -1007,7 +974,6 @@ def test_make_pod_with_extra_resources(): "nodeSelector": {"disk": "ssd"}, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1046,8 +1012,7 @@ def test_make_secret_simple(): """ assert api_client.sanitize_for_serialization(make_secret( name='my-k8s-secret', - str_data={}, - username='test', + string_data={}, owner_references=[], labels={} )) == { @@ -1068,8 +1033,7 @@ def test_make_secret_with_data(): """ assert api_client.sanitize_for_serialization(make_secret( name='my-k8s-secret', - str_data={"TEST1": "VALUE1"}, - username='test', + string_data={"TEST1": "VALUE1"}, owner_references=[], labels={} )) == { @@ -1195,7 +1159,6 @@ def test_make_pod_with_service_account(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1209,7 +1172,6 @@ def test_make_pod_with_service_account(): "spec": { "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1242,7 +1204,6 @@ def test_make_pod_with_scheduler_name(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1257,7 +1218,6 @@ def test_make_pod_with_scheduler_name(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1303,7 +1263,6 @@ def test_make_pod_with_tolerations(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1318,7 +1277,6 @@ def test_make_pod_with_tolerations(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1361,7 +1319,6 @@ def test_make_pod_with_node_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1376,7 +1333,6 @@ def test_make_pod_with_node_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1420,7 +1376,6 @@ def test_make_pod_with_node_affinity_required(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1435,7 +1390,6 @@ def test_make_pod_with_node_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1487,7 +1441,6 @@ def test_make_pod_with_pod_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1502,7 +1455,6 @@ def test_make_pod_with_pod_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1549,7 +1501,6 @@ def test_make_pod_with_pod_affinity_required(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1564,7 +1515,6 @@ def test_make_pod_with_pod_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1614,7 +1564,6 @@ def test_make_pod_with_pod_anti_affinity_preferred(): assert api_client.sanitize_for_serialization(make_pod( name='test', image='jupyter/singleuser:latest', - env=[], cmd=['jupyterhub-singleuser'], port=8888, image_pull_policy='IfNotPresent', @@ -1629,7 +1578,6 @@ def test_make_pod_with_pod_anti_affinity_preferred(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1677,7 +1625,6 @@ def test_make_pod_with_pod_anti_affinity_required(): name='test', image='jupyter/singleuser:latest', cmd=['jupyterhub-singleuser'], - env=[], port=8888, image_pull_policy='IfNotPresent', pod_anti_affinity_required=pod_anti_affinity_required @@ -1691,7 +1638,6 @@ def test_make_pod_with_pod_anti_affinity_required(): "automountServiceAccountToken": False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1729,7 +1675,6 @@ def test_make_pod_with_priority_class_name(): name='test', image='jupyter/singleuser:latest', cmd=['jupyterhub-singleuser'], - env=[], port=8888, image_pull_policy='IfNotPresent', priority_class_name='my-custom-priority-class' @@ -1743,7 +1688,6 @@ def test_make_pod_with_priority_class_name(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [{'secretRef': {}}], "name": "notebook", "image": "jupyter/singleuser:latest", @@ -1858,7 +1802,6 @@ def test_make_pod_with_ssl(): make_pod( name='ssl', image='jupyter/singleuser:latest', - env=[], env_from='ssl', working_dir='/', cmd=['jupyterhub-singleuser'], @@ -1877,7 +1820,6 @@ def test_make_pod_with_ssl(): 'automountServiceAccountToken': False, "containers": [ { - "env": [], "envFrom": [ { 'secretRef': { diff --git a/tests/test_spawner.py b/tests/test_spawner.py index 2860bea8..eabe083e 100644 --- a/tests/test_spawner.py +++ b/tests/test_spawner.py @@ -583,19 +583,20 @@ def test_get_pvc_manifest(): assert manifest.spec.selector == {"matchLabels": {"user": "mock-5fname"}} -def test_env(): +def test_env_value_from(): c = Config() - c.KubeSpawner.environment = { - "TEST_KEY_1": "VALUE_1", + c.KubeSpawner.environment = { "TEST_KEY_2": {'valueFrom': {'secretKeyRef': {'name': 'my-test-secret', 'key': 'password'}}}, - 'TEST_KEY_NAME_IGNORED': {'name': 'TEST_KEY_3', 'valueFrom': { 'secretKeyRef': {'name': 'my-test-secret', 'key': 'password'}}} + "TEST_KEY_3": {'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, + 'TEST_KEY_NAME_IGNORED': {'name': 'TEST_KEY_4', 'valueFrom': { 'secretKeyRef': {'name': 'my-test-secret-2', 'key': 'password'}}} } spawner = KubeSpawner(config=c, _mock=True) - env = spawner.get_env() - assert env[0].get("TEST_KEY_1") == "VALUE_1" - assert env[1][0].name == "TEST_KEY_2" - assert env[1][0].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret'}} - assert env[1][1].name == "TEST_KEY_3" - assert env[1][1].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret'}} + env_value_from = spawner.get_env_value_from() + assert env_value_from[0].name == "TEST_KEY_2" + assert env_value_from[0].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret'}} + assert env_value_from[1].name == "TEST_KEY_3" + assert env_value_from[1].value_from == {'fieldRef': {'fieldPath': 'metadata.namespace'}} + assert env_value_from[2].name == "TEST_KEY_4" + assert env_value_from[2].value_from == {'secretKeyRef': {'key': 'password', 'name': 'my-test-secret-2'}}