diff --git a/binderhub/build.py b/binderhub/build.py index d881e0518..019052449 100644 --- a/binderhub/build.py +++ b/binderhub/build.py @@ -87,7 +87,21 @@ class BuildExecutor(LoggingConfigurable): push_secret = Unicode( "", - help="Implementation dependent secret for pushing image to a registry.", + help="Implementation dependent static secret for pushing image to a registry.", + config=True, + ) + + registry_credentials = Dict( + {}, + help=( + "Implementation dependent credentials for pushing image to a registry. " + "For example, if push tokens are temporary this could be used to pass " + "dynamically created credentials " + '`{"registry": "docker.io", "username":"user", "password":"password"}`. ' + "This will be JSON encoded and passed in the environment variable " + "CONTAINER_ENGINE_REGISTRY_CREDENTIALS` to repo2docker. " + "If provided this will be used instead of push_secret." + ), config=True, ) @@ -231,7 +245,26 @@ def _default_api(self): # Overrides the default for BuildExecutor push_secret = Unicode( "binder-build-docker-config", - help="Implementation dependent secret for pushing image to a registry.", + help=( + "Name of a Kubernetes secret containing static credentials for pushing " + "an image to a registry." + ), + config=True, + ) + + registry_credentials = Dict( + {}, + help=( + "Implementation dependent credentials for pushing image to a registry. " + "For example, if push tokens are temporary this could be used to pass " + "dynamically created credentials " + '`{"registry": "docker.io", "username":"user", "password":"password"}`. ' + "This will be JSON encoded and passed in the environment variable " + "CONTAINER_ENGINE_REGISTRY_CREDENTIALS` to repo2docker. " + "If provided this will be used instead of push_secret. " + "Currently this is passed to the build pod as a plain text environment " + "variable, though future implementations may use a Kubernetes secret." + ), config=True, ) @@ -394,7 +427,23 @@ def submit(self): ) ] - if self.push_secret: + env = [ + client.V1EnvVar(name=key, value=value) + for key, value in self.extra_envs.items() + ] + if self.git_credentials: + env.append( + client.V1EnvVar(name="GIT_CREDENTIAL_ENV", value=self.git_credentials) + ) + + if self.registry_credentials: + env.append( + client.V1EnvVar( + name="CONTAINER_ENGINE_REGISTRY_CREDENTIALS", + value=json.dumps(self.registry_credentials), + ) + ) + elif self.push_secret: volume_mounts.append( client.V1VolumeMount(mount_path="/root/.docker", name="docker-config") ) @@ -405,15 +454,6 @@ def submit(self): ) ) - env = [ - client.V1EnvVar(name=key, value=value) - for key, value in self.extra_envs.items() - ] - if self.git_credentials: - env.append( - client.V1EnvVar(name="GIT_CREDENTIAL_ENV", value=self.git_credentials) - ) - self.pod = client.V1Pod( metadata=client.V1ObjectMeta( name=self.name, diff --git a/binderhub/builder.py b/binderhub/builder.py index 153d24332..4fd34579f 100644 --- a/binderhub/builder.py +++ b/binderhub/builder.py @@ -381,11 +381,12 @@ async def get(self, provider_prefix, _unescaped_spec): .lower() ) + image_without_tag, image_tag = _get_image_basename_and_tag(image_name) if self.settings["use_registry"]: for _ in range(3): try: image_manifest = await self.registry.get_image_manifest( - *_get_image_basename_and_tag(image_name) + image_without_tag, image_tag ) image_found = bool(image_manifest) break @@ -457,7 +458,13 @@ async def get(self, provider_prefix, _unescaped_spec): image_name=image_name, git_credentials=provider.git_credentials, ) - if not self.settings["use_registry"]: + if self.settings["use_registry"]: + push_token = await self.registry.get_credentials( + image_without_tag, image_tag + ) + if push_token: + build.registry_credentials = push_token + else: build.push_secret = "" self.build = build diff --git a/binderhub/registry.py b/binderhub/registry.py index 61721c8f6..0a369334a 100644 --- a/binderhub/registry.py +++ b/binderhub/registry.py @@ -329,6 +329,14 @@ async def get_image_manifest(self, image, tag): raise return json.loads(resp.body.decode("utf-8")) + async def get_credentials(self, image, tag): + """ + If a dynamic token is required for pushing an image to the registry + return a dictionary of login credentials, otherwise return None + (caller should get credentials from some other source) + """ + return None + class FakeRegistry(DockerRegistry): """