-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Store provided environment of key/value kind into a k8s Secret owned by the spawned pod #471
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
import asyncio | ||
import json | ||
import base64 | ||
import multiprocessing | ||
import os | ||
import string | ||
|
@@ -27,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 | ||
|
@@ -1624,7 +1627,8 @@ 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=self.get_env()[1], | ||
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 +1669,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()[0], | ||
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, | ||
|
@@ -1768,11 +1771,51 @@ 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 | ||
|
||
return env | ||
|
||
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() | ||
|
||
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: | ||
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, prepared_env | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I consider introducing get_env_from alongside get_env here, I dislike the complexity we embed in this by returning a tuple. I think prepared_env also should be named more appropriately now that it means specifically env_from. The Container object has I'd love for this source code to be simpler to understand rather than more complicated, of course adding this feature will make it more complicated no matter what, but how to minimize the complexity of understanding this code in general in the future is a focus of mine. I have no concrete suggested action =/ but thats what goes in my mind while looking at this code base which I have struggled to understand in the past and think is quite hard to grasp still. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, this is a good point. I thought about this, creating a new |
||
|
||
def load_state(self, state): | ||
""" | ||
|
@@ -2182,34 +2225,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 +2268,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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let this be named string_data to not create a deviation from the naming convention of k8s secret resources.