Skip to content

Commit

Permalink
Add registry optional credentials to push()
Browse files Browse the repository at this point in the history
  • Loading branch information
manics committed Feb 12, 2023
1 parent 1e61d4f commit 5e8ee1c
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 0 deletions.
2 changes: 2 additions & 0 deletions repo2docker/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ def inspect_image(self, image):
return Image(tags=image["RepoTags"], config=image["ContainerConfig"])

def push(self, image_spec):
if self.registry_credentials:
self._apiclient.login(**self.registry_credentials)
return self._apiclient.push(image_spec, stream=True)

def run(
Expand Down
37 changes: 37 additions & 0 deletions repo2docker/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
Interface for a repo2docker container engine
"""

import json
import os
from abc import ABC, abstractmethod

from traitlets import Dict, default
from traitlets.config import LoggingConfigurable

# Based on https://docker-py.readthedocs.io/en/4.2.0/containers.html
Expand Down Expand Up @@ -142,6 +145,37 @@ class ContainerEngine(LoggingConfigurable):
Initialised with a reference to the parent so can also be configured using traitlets.
"""

registry_credentials = Dict(
help="""
Credentials dictionary, if set will be used to authenticate with
the registry. Typically this will include the keys:
- `username`: The registry username
- `password`: The registry password or token
- `registry`: The registry URL
This can also be set by passing a JSON object in the
CONTAINER_ENGINE_REGISTRY_CREDENTIALS environment variable.
""",
config=True,
)

@default("registry_credentials")
def _registry_credentials_default(self):
"""
Set the registry credentials from CONTAINER_ENGINE_REGISTRY_CREDENTIALS
"""
obj = os.getenv("CONTAINER_ENGINE_REGISTRY_CREDENTIALS")
if obj:
try:
return json.loads(obj)
except json.JSONDecodeError:
self.log.error(
"CONTAINER_ENGINE_REGISTRY_CREDENTIALS is not valid JSON"
)
raise
return {}

string_output = True
"""
Whether progress events should be strings or an object.
Expand Down Expand Up @@ -251,6 +285,9 @@ def push(self, image_spec):
"""
Push image to a registry
If the registry_credentials traitlets is set it should be used to
authenticate with the registry before pushing.
Parameters
----------
image_spec : str
Expand Down
43 changes: 43 additions & 0 deletions tests/unit/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import os
from subprocess import check_output
from unittest.mock import Mock, patch

from repo2docker.docker import DockerEngine

repo_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
Expand All @@ -19,3 +22,43 @@ def test_git_credential_env():
.strip()
)
assert out == credential_env


class MockDockerEngine(DockerEngine):
def __init__(self, *args, **kwargs):
self._apiclient = Mock()


def test_docker_push_no_credentials():
engine = MockDockerEngine()

engine.push("image")

assert len(engine._apiclient.method_calls) == 1
engine._apiclient.push.assert_called_once_with("image", stream=True)


def test_docker_push_dict_credentials():
engine = MockDockerEngine()
engine.registry_credentials = {"username": "abc", "password": "def"}

engine.push("image")

assert len(engine._apiclient.method_calls) == 2
engine._apiclient.login.assert_called_once_with(username="abc", password="def")
engine._apiclient.push.assert_called_once_with("image", stream=True)


def test_docker_push_env_credentials():
engine = MockDockerEngine()
with patch.dict(
"os.environ",
{
"CONTAINER_ENGINE_REGISTRY_CREDENTIALS": '{"username": "abc", "password": "def"}'
},
):
engine.push("image")

assert len(engine._apiclient.method_calls) == 2
engine._apiclient.login.assert_called_once_with(username="abc", password="def")
engine._apiclient.push.assert_called_once_with("image", stream=True)

0 comments on commit 5e8ee1c

Please sign in to comment.