diff --git a/helm-chart/renku-notebooks/requirements.lock b/helm-chart/renku-notebooks/requirements.lock index 8fe74f90e..f2ec5939c 100644 --- a/helm-chart/renku-notebooks/requirements.lock +++ b/helm-chart/renku-notebooks/requirements.lock @@ -1,6 +1,6 @@ dependencies: - name: jupyterhub repository: https://jupyterhub.github.io/helm-chart - version: 0.9-e120fda -digest: sha256:4d545e7a3c7d7c50eae0416fffc95c0aa7a00df1528073a3b6f731077df8fff9 -generated: 2019-03-06T00:48:01.519542+01:00 + version: 0.9.0-beta.4 +digest: sha256:6b20d99bb549be425df3b96e943f08574a252034378814d993d750bf4e589b99 +generated: "2020-06-08T11:36:32.854872+02:00" diff --git a/helm-chart/renku-notebooks/templates/deployment.yaml b/helm-chart/renku-notebooks/templates/deployment.yaml index 9893278c6..b099067fd 100644 --- a/helm-chart/renku-notebooks/templates/deployment.yaml +++ b/helm-chart/renku-notebooks/templates/deployment.yaml @@ -55,10 +55,6 @@ spec: value: {{ template "notebooks.http" . }}://{{ .Values.global.renku.domain }} - name: JUPYTERHUB_CLIENT_ID value: {{ .Values.jupyterhub.hub.services.notebooks.oauth_client_id }} - {{ if and .Values.gitlab.registry.username .Values.gitlab.registry.token }} - - name: GITLAB_REGISTRY_SECRET - value: {{ template "notebooks.fullname" . }}-registry - {{ end }} - name: GITLAB_URL {{ if .Values.gitlab.url }} value: {{ .Values.gitlab.url }} diff --git a/helm-chart/renku-notebooks/templates/registry-secret.yaml b/helm-chart/renku-notebooks/templates/registry-secret.yaml deleted file mode 100644 index 46a64affe..000000000 --- a/helm-chart/renku-notebooks/templates/registry-secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if and .Values.gitlab.registry.username .Values.gitlab.registry.token }} -{{- $auth := printf "%s:%s" .Values.gitlab.registry.username .Values.gitlab.registry.token | b64enc -}} -{{- $registry := .Values.gitlab.registry.host | replace "http://" "" | replace "https://" "" | replace ":443" "" -}} -{{- $secret := printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"root@example.com\",\"auth\":\"%s\"}}}" $registry .Values.gitlab.registry.username .Values.gitlab.registry.token $auth -}} -apiVersion: v1 -kind: Secret -type: kubernetes.io/dockerconfigjson -metadata: - name: {{ template "notebooks.fullname" . }}-registry - labels: - app: {{ template "notebooks.name" . }} - chart: {{ template "notebooks.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -data: - .dockerconfigjson: {{ $secret | b64enc | quote }} -{{- end }} diff --git a/helm-chart/renku-notebooks/templates/role.yaml b/helm-chart/renku-notebooks/templates/role.yaml index 9bde5902e..1c735374b 100644 --- a/helm-chart/renku-notebooks/templates/role.yaml +++ b/helm-chart/renku-notebooks/templates/role.yaml @@ -16,6 +16,7 @@ rules: - pods/log - services - endpoints + - secrets verbs: - get - list @@ -24,6 +25,13 @@ rules: - "" resources: - pods + - secrets verbs: - delete + - apiGroups: + - "" + resources: + - secrets + verbs: + - create {{- end -}} diff --git a/helm-chart/renku-notebooks/values.yaml b/helm-chart/renku-notebooks/values.yaml index 0c55b5466..23cac97ad 100644 --- a/helm-chart/renku-notebooks/values.yaml +++ b/helm-chart/renku-notebooks/values.yaml @@ -15,10 +15,6 @@ gitlab: registry: ## Set the default image registry host: - ## Username and token are used for creating a secret for pulling private docker images. - ## This combination therefore must give access to private projects, so it should be an admin. - # username: - # token: ## For sending exceptions to Sentry, specify the DSN to use # sentryDsn: diff --git a/jupyterhub/spawners.py b/jupyterhub/spawners.py index fb0280404..12902e147 100644 --- a/jupyterhub/spawners.py +++ b/jupyterhub/spawners.py @@ -229,7 +229,7 @@ def get_pod_manifest(self): ), client.V1EnvVar(name="BRANCH", value=options.get("branch", "master")), client.V1EnvVar(name="JUPYTERHUB_USER", value=self.user.name), - client.V1EnvVar(name="GITLAB_AUTOSAVE", value=gitlab_autosave) + client.V1EnvVar(name="GITLAB_AUTOSAVE", value=gitlab_autosave), ], image=options.get("git_clone_image"), volume_mounts=[volume_mount], @@ -294,6 +294,9 @@ def get_pod_manifest(self): self.gid = 100 self.supplemental_gids = [1000] + # set the image pull policy + self.image_pull_policy = "Always" + pod = yield super().get_pod_manifest() # Because repository comes from a coroutine, we can't put it simply in `get_env()` diff --git a/renku_notebooks/api/notebooks.py b/renku_notebooks/api/notebooks.py index cd7f2ace6..1aae3411e 100644 --- a/renku_notebooks/api/notebooks.py +++ b/renku_notebooks/api/notebooks.py @@ -38,6 +38,7 @@ get_user_server, get_user_servers, delete_user_pod, + create_or_replace_registry_secret, ) from .auth import authenticated @@ -143,9 +144,11 @@ def launch_notebook(user): current_app.logger.debug(f"Creating server {server_name} with {payload}") - if os.environ.get("GITLAB_REGISTRY_SECRET"): - payload["image_pull_secrets"] = payload.get("image_pull_secrets", []) - payload["image_pull_secrets"].append(os.environ["GITLAB_REGISTRY_SECRET"]) + # only create a pull secret if the project has limited visibility and a token is available + if config.GITLAB_AUTH and gl_project.visibility in {"private", "internal"}: + secret_name = f"{user.get('name')}-registry" + create_or_replace_registry_secret(user, namespace, secret_name) + payload["image_pull_secrets"] = [secret_name] r = create_named_server(user, server_name, payload) diff --git a/renku_notebooks/util/kubernetes_.py b/renku_notebooks/util/kubernetes_.py index 3e05ca649..97e641a3f 100644 --- a/renku_notebooks/util/kubernetes_.py +++ b/renku_notebooks/util/kubernetes_.py @@ -35,6 +35,7 @@ ) from .. import config +from .gitlab_ import _get_oauth_token # adjust k8s service account paths if running inside telepresence @@ -212,3 +213,62 @@ def read_namespaced_pod_log(pod_name, max_log_lines=0): pod_name, kubernetes_namespace, tail_lines=max_log_lines ) return logs + + +def create_or_replace_registry_secret(user, namespace, secret_name): + """Read or replace a registry secret for a user.""" + import base64 + import json + + token = _get_oauth_token(user) + payload = { + "auths": { + current_app.config.get("IMAGE_REGISTRY"): { + "Username": "oauth2", + "Password": token, + "Email": user.get("email"), + } + } + } + + data = { + ".dockerconfigjson": base64.b64encode(json.dumps(payload).encode()).decode() + } + + secret = client.V1Secret( + api_version="v1", + data=data, + kind="Secret", + metadata={ + "name": secret_name, + "namespace": kubernetes_namespace, + "annotations": { + current_app.config.get("RENKU_ANNOTATION_PREFIX") + + "username": user.get("name") + }, + "labels": { + "component": "singleuser-server", + current_app.config.get("RENKU_ANNOTATION_PREFIX") + + "username": user.get("name"), + }, + }, + type="kubernetes.io/dockerconfigjson", + ) + + if _secret_exists(secret_name, kubernetes_namespace): + v1.replace_namespaced_secret(secret_name, kubernetes_namespace, secret) + else: + v1.create_namespaced_secret(kubernetes_namespace, body=secret) + + return secret + + +def _secret_exists(name, namespace): + """Check if the secret exists.""" + + try: + v1.read_namespaced_secret(name, namespace) + return True + except client.rest.ApiException: + pass + return False diff --git a/tests/conftest.py b/tests/conftest.py index 424c472e3..29901b460 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -178,6 +178,7 @@ def items(self): { f"{namespace}/{project_name}": { "id": 42, + "visibility": "public", "path_with_namespace": f"{namespace}/{project_name}", "attributes": { "permissions": List([{}, {"access_level": access_level}])