diff --git a/docker/auth/auth.py b/docker/auth/auth.py index f0ab7447..59fe1a58 100644 --- a/docker/auth/auth.py +++ b/docker/auth/auth.py @@ -87,6 +87,10 @@ def resolve_authconfig(authconfig, registry=None): return authconfig.get(swap_protocol(registry), None) +def encode_auth(auth_info): + return base64.b64encode(auth_info.get('username', '') + b':' + + auth_info.get('password', '')) + def decode_auth(auth): if isinstance(auth, six.string_types): auth = auth.encode('ascii') @@ -100,6 +104,12 @@ def encode_header(auth): return base64.b64encode(auth_json) +def encode_full_header(auth): + """ Returns the given auth block encoded for the X-Registry-Config header. + """ + return encode_header({'configs': auth}) + + def load_config(root=None): """Loads authentication data from a Docker configuration file in the given root directory.""" diff --git a/docker/client.py b/docker/client.py index 67f4ad09..635ba19d 100644 --- a/docker/client.py +++ b/docker/client.py @@ -28,7 +28,7 @@ from .utils import utils if not six.PY3: import websocket -DEFAULT_DOCKER_API_VERSION = '1.8' +DEFAULT_DOCKER_API_VERSION = '1.9' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 @@ -361,6 +361,17 @@ class Client(requests.Session): if context is not None: headers = {'Content-Type': 'application/tar'} + if utils.compare_version('1.9', self._version) >= 0: + # If we don't have any auth data so far, try reloading the config + # file one more time in case anything showed up in there. + if not self._auth_configs: + self._auth_configs = auth.load_config() + + # Send the full auth configuration (if any exists), since the build + # could use any (or all) of the registries. + if self._auth_configs: + headers['X-Registry-Config'] = auth.encode_full_header(self._auth_configs) + response = self._post( u, data=context, @@ -618,7 +629,7 @@ class Client(requests.Session): self._auth_configs = auth.load_config() authcfg = auth.resolve_authconfig(self._auth_configs, registry) - # Do not fail here if no atuhentication exists for this specific + # Do not fail here if no authentication exists for this specific # registry as we can have a readonly pull. Just put the header if # we can. if authcfg: @@ -644,7 +655,7 @@ class Client(requests.Session): self._auth_configs = auth.load_config() authcfg = auth.resolve_authconfig(self._auth_configs, registry) - # Do not fail here if no atuhentication exists for this specific + # Do not fail here if no authentication exists for this specific # registry as we can have a readonly pull. Just put the header if # we can. if authcfg: diff --git a/tests/integration_test.py b/tests/integration_test.py index c6189ffb..2f597b15 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -32,7 +32,7 @@ class BaseTestCase(unittest.TestCase): tmp_containers = [] def setUp(self): - self.client = docker.Client() + self.client = docker.Client(base_url='http://localhost:4243') self.client.pull('busybox') self.tmp_imgs = [] self.tmp_containers = [] @@ -755,6 +755,29 @@ class TestBuildFromStringIO(BaseTestCase): self.assertNotEqual(logs, '') +class TestBuildWithAuth(BaseTestCase): + def runTest(self): + if self.client._version < 1.9: + return + + key = 'K4104GON3P4Q6ZUJFZRRC2ZQTBJ5YT0UMZD7TGT7ZVIR8Y05FAH2TJQI6Y90SMIB' + self.client.login('quay+fortesting', key, registry='https://quay.io/v1/', email='') + + script = io.BytesIO('\n'.join([ + 'FROM quay.io/quay/teststuff', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + ]).encode('ascii')) + + stream = self.client.build(fileobj=script, stream=True) + logs = '' + for chunk in stream: + logs += chunk + + self.assertNotEqual(logs, '') + self.assertEqual(logs.find('HTTP code: 403'), -1) + + ####################### # PY SPECIFIC TESTS # ####################### @@ -820,7 +843,7 @@ class TestLoadJSONConfig(BaseTestCase): class TestConnectionTimeout(unittest.TestCase): def setUp(self): self.timeout = 0.5 - self.client = docker.client.Client(base_url='http://192.168.10.2:4243', + self.client = docker.client.Client(base_url='http://localhost:4243', timeout=self.timeout) def runTest(self):