Skip to content

Commit 5e8ee1c

Browse files
committed
Add registry optional credentials to push()
1 parent 1e61d4f commit 5e8ee1c

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

repo2docker/docker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ def inspect_image(self, image):
120120
return Image(tags=image["RepoTags"], config=image["ContainerConfig"])
121121

122122
def push(self, image_spec):
123+
if self.registry_credentials:
124+
self._apiclient.login(**self.registry_credentials)
123125
return self._apiclient.push(image_spec, stream=True)
124126

125127
def run(

repo2docker/engine.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
Interface for a repo2docker container engine
33
"""
44

5+
import json
6+
import os
57
from abc import ABC, abstractmethod
68

9+
from traitlets import Dict, default
710
from traitlets.config import LoggingConfigurable
811

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

148+
registry_credentials = Dict(
149+
help="""
150+
Credentials dictionary, if set will be used to authenticate with
151+
the registry. Typically this will include the keys:
152+
153+
- `username`: The registry username
154+
- `password`: The registry password or token
155+
- `registry`: The registry URL
156+
157+
This can also be set by passing a JSON object in the
158+
CONTAINER_ENGINE_REGISTRY_CREDENTIALS environment variable.
159+
""",
160+
config=True,
161+
)
162+
163+
@default("registry_credentials")
164+
def _registry_credentials_default(self):
165+
"""
166+
Set the registry credentials from CONTAINER_ENGINE_REGISTRY_CREDENTIALS
167+
"""
168+
obj = os.getenv("CONTAINER_ENGINE_REGISTRY_CREDENTIALS")
169+
if obj:
170+
try:
171+
return json.loads(obj)
172+
except json.JSONDecodeError:
173+
self.log.error(
174+
"CONTAINER_ENGINE_REGISTRY_CREDENTIALS is not valid JSON"
175+
)
176+
raise
177+
return {}
178+
145179
string_output = True
146180
"""
147181
Whether progress events should be strings or an object.
@@ -251,6 +285,9 @@ def push(self, image_spec):
251285
"""
252286
Push image to a registry
253287
288+
If the registry_credentials traitlets is set it should be used to
289+
authenticate with the registry before pushing.
290+
254291
Parameters
255292
----------
256293
image_spec : str

tests/unit/test_docker.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import os
44
from subprocess import check_output
5+
from unittest.mock import Mock, patch
6+
7+
from repo2docker.docker import DockerEngine
58

69
repo_root = os.path.abspath(
710
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
@@ -19,3 +22,43 @@ def test_git_credential_env():
1922
.strip()
2023
)
2124
assert out == credential_env
25+
26+
27+
class MockDockerEngine(DockerEngine):
28+
def __init__(self, *args, **kwargs):
29+
self._apiclient = Mock()
30+
31+
32+
def test_docker_push_no_credentials():
33+
engine = MockDockerEngine()
34+
35+
engine.push("image")
36+
37+
assert len(engine._apiclient.method_calls) == 1
38+
engine._apiclient.push.assert_called_once_with("image", stream=True)
39+
40+
41+
def test_docker_push_dict_credentials():
42+
engine = MockDockerEngine()
43+
engine.registry_credentials = {"username": "abc", "password": "def"}
44+
45+
engine.push("image")
46+
47+
assert len(engine._apiclient.method_calls) == 2
48+
engine._apiclient.login.assert_called_once_with(username="abc", password="def")
49+
engine._apiclient.push.assert_called_once_with("image", stream=True)
50+
51+
52+
def test_docker_push_env_credentials():
53+
engine = MockDockerEngine()
54+
with patch.dict(
55+
"os.environ",
56+
{
57+
"CONTAINER_ENGINE_REGISTRY_CREDENTIALS": '{"username": "abc", "password": "def"}'
58+
},
59+
):
60+
engine.push("image")
61+
62+
assert len(engine._apiclient.method_calls) == 2
63+
engine._apiclient.login.assert_called_once_with(username="abc", password="def")
64+
engine._apiclient.push.assert_called_once_with("image", stream=True)

0 commit comments

Comments
 (0)