Skip to content
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

Support dynamic push credentials obtained from registry #1724

Merged
merged 3 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions binderhub/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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. "
consideRatio marked this conversation as resolved.
Show resolved Hide resolved
"If provided this will be used instead of push_secret."
),
config=True,
)

Expand Down Expand Up @@ -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. "
consideRatio marked this conversation as resolved.
Show resolved Hide resolved
"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,
)

Expand Down Expand Up @@ -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",
consideRatio marked this conversation as resolved.
Show resolved Hide resolved
value=json.dumps(self.registry_credentials),
)
)
elif self.push_secret:
volume_mounts.append(
client.V1VolumeMount(mount_path="/root/.docker", name="docker-config")
)
Expand All @@ -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,
Expand Down
11 changes: 9 additions & 2 deletions binderhub/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions binderhub/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down