diff options
-rw-r--r-- | PKG-INFO | 2 | ||||
-rw-r--r-- | docker/auth/__init__.py | 1 | ||||
-rw-r--r-- | docker/auth/auth.py | 38 | ||||
-rw-r--r-- | docker/client.py | 46 | ||||
-rw-r--r-- | docker/clientbase.py | 4 | ||||
-rw-r--r-- | docker/constants.py | 4 | ||||
-rw-r--r-- | docker/errors.py | 4 | ||||
-rw-r--r-- | docker/utils/utils.py | 12 | ||||
-rw-r--r-- | docker/version.py | 2 | ||||
-rw-r--r-- | docker_py.egg-info/PKG-INFO | 2 | ||||
-rw-r--r-- | tests/integration_test.py | 11 | ||||
-rw-r--r-- | tests/test.py | 8 | ||||
-rw-r--r-- | tests/utils_test.py | 91 |
13 files changed, 172 insertions, 53 deletions
@@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: docker-py -Version: 1.3.0 +Version: 1.3.1 Summary: Python client for Docker. Home-page: https://github.com/docker/docker-py/ Author: UNKNOWN diff --git a/docker/auth/__init__.py b/docker/auth/__init__.py index d068b7f..6fc83f8 100644 --- a/docker/auth/__init__.py +++ b/docker/auth/__init__.py @@ -1,4 +1,5 @@ from .auth import ( + INDEX_NAME, INDEX_URL, encode_header, load_config, diff --git a/docker/auth/auth.py b/docker/auth/auth.py index 1c29615..4af741e 100644 --- a/docker/auth/auth.py +++ b/docker/auth/auth.py @@ -16,38 +16,34 @@ import base64 import fileinput import json import os +import warnings import six -from ..utils import utils +from .. import constants from .. import errors -INDEX_URL = 'https://index.docker.io/v1/' +INDEX_NAME = 'index.docker.io' +INDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME) DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json') LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg' -def expand_registry_url(hostname, insecure=False): - if hostname.startswith('http:') or hostname.startswith('https:'): - return hostname - if utils.ping_registry('https://' + hostname): - return 'https://' + hostname - elif insecure: - return 'http://' + hostname - else: - raise errors.DockerException( - "HTTPS endpoint unresponsive and insecure mode isn't enabled." +def resolve_repository_name(repo_name, insecure=False): + if insecure: + warnings.warn( + constants.INSECURE_REGISTRY_DEPRECATION_WARNING.format( + 'resolve_repository_name()' + ), DeprecationWarning ) - -def resolve_repository_name(repo_name, insecure=False): if '://' in repo_name: raise errors.InvalidRepository( 'Repository name cannot contain a scheme ({0})'.format(repo_name)) parts = repo_name.split('/', 1) if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost': # This is a docker index repo (ex: foo/bar or ubuntu) - return INDEX_URL, repo_name + return INDEX_NAME, repo_name if len(parts) < 2: raise errors.InvalidRepository( 'Invalid repository name ({0})'.format(repo_name)) @@ -57,7 +53,7 @@ def resolve_repository_name(repo_name, insecure=False): 'Invalid repository name, try "{0}" instead'.format(parts[1]) ) - return expand_registry_url(parts[0], insecure), parts[1] + return parts[0], parts[1] def resolve_authconfig(authconfig, registry=None): @@ -68,7 +64,7 @@ def resolve_authconfig(authconfig, registry=None): Returns None if no match was found. """ # Default to the public index server - registry = convert_to_hostname(registry) if registry else INDEX_URL + registry = convert_to_hostname(registry) if registry else INDEX_NAME if registry in authconfig: return authconfig[registry] @@ -102,12 +98,6 @@ 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 parse_auth(entries): """ Parses authentication entries @@ -185,7 +175,7 @@ def load_config(config_path=None): 'Invalid or empty configuration file!') username, password = decode_auth(data[0]) - conf[INDEX_URL] = { + conf[INDEX_NAME] = { 'username': username, 'password': password, 'email': data[1], diff --git a/docker/client.py b/docker/client.py index e52cb53..e4712c2 100644 --- a/docker/client.py +++ b/docker/client.py @@ -25,6 +25,7 @@ from . import constants from . import errors from .auth import auth from .utils import utils, check_resource +from .constants import INSECURE_REGISTRY_DEPRECATION_WARNING class Client(clientbase.ClientBase): @@ -139,9 +140,14 @@ class Client(clientbase.ClientBase): if self._auth_configs: if headers is None: headers = {} - headers['X-Registry-Config'] = auth.encode_full_header( - self._auth_configs - ) + if utils.compare_version('1.19', self._version) >= 0: + headers['X-Registry-Config'] = auth.encode_header( + self._auth_configs + ) + else: + headers['X-Registry-Config'] = auth.encode_header({ + 'configs': self._auth_configs + }) response = self._post( u, @@ -267,10 +273,12 @@ class Client(clientbase.ClientBase): 'filters': filters } - return self._stream_helper(self.get(self._url('/events'), - params=params, stream=True), - decode=decode) + return self._stream_helper( + self.get(self._url('/events'), params=params, stream=True), + decode=decode + ) + @check_resource def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False, privileged=False): if utils.compare_version('1.15', self._version) < 0: @@ -499,6 +507,12 @@ class Client(clientbase.ClientBase): def login(self, username, password=None, email=None, registry=None, reauth=False, insecure_registry=False, dockercfg_path=None): + if insecure_registry: + warnings.warn( + INSECURE_REGISTRY_DEPRECATION_WARNING.format('login()'), + DeprecationWarning + ) + # 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 dockercfg_path is passed check to see if the config file exists, @@ -584,11 +598,15 @@ class Client(clientbase.ClientBase): def pull(self, repository, tag=None, stream=False, insecure_registry=False, auth_config=None): + if insecure_registry: + warnings.warn( + INSECURE_REGISTRY_DEPRECATION_WARNING.format('pull()'), + DeprecationWarning + ) + if not tag: repository, tag = utils.parse_repository_tag(repository) - registry, repo_name = auth.resolve_repository_name( - repository, insecure=insecure_registry - ) + registry, repo_name = auth.resolve_repository_name(repository) if repo_name.count(":") == 1: repository, tag = repository.rsplit(":", 1) @@ -631,11 +649,15 @@ class Client(clientbase.ClientBase): def push(self, repository, tag=None, stream=False, insecure_registry=False): + if insecure_registry: + warnings.warn( + INSECURE_REGISTRY_DEPRECATION_WARNING.format('push()'), + DeprecationWarning + ) + if not tag: repository, tag = utils.parse_repository_tag(repository) - registry, repo_name = auth.resolve_repository_name( - repository, insecure=insecure_registry - ) + registry, repo_name = auth.resolve_repository_name(repository) u = self._url("/images/{0}/push".format(repository)) params = { 'tag': tag diff --git a/docker/clientbase.py b/docker/clientbase.py index c1ae813..ce52ffa 100644 --- a/docker/clientbase.py +++ b/docker/clientbase.py @@ -99,6 +99,8 @@ class ClientBase(requests.Session): try: response.raise_for_status() except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + raise errors.NotFound(e, response, explanation=explanation) raise errors.APIError(e, response, explanation=explanation) def _result(self, response, json=False, binary=False): @@ -215,7 +217,7 @@ class ClientBase(requests.Session): break _, length = struct.unpack('>BxxxL', header) if not length: - break + continue data = response.raw.read(length) if not data: break diff --git a/docker/constants.py b/docker/constants.py index f99f192..10a2fee 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -4,3 +4,7 @@ STREAM_HEADER_SIZE_BYTES = 8 CONTAINER_LIMITS_KEYS = [ 'memory', 'memswap', 'cpushares', 'cpusetcpus' ] + +INSECURE_REGISTRY_DEPRECATION_WARNING = \ + 'The `insecure_registry` argument to {} ' \ + 'is deprecated and non-functional. Please remove it.' diff --git a/docker/errors.py b/docker/errors.py index d15e332..066406a 100644 --- a/docker/errors.py +++ b/docker/errors.py @@ -53,6 +53,10 @@ class DockerException(Exception): pass +class NotFound(APIError): + pass + + class InvalidVersion(DockerException): pass diff --git a/docker/utils/utils.py b/docker/utils/utils.py index 175a7e0..a714c97 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -19,6 +19,7 @@ import json import shlex import tarfile import tempfile +import warnings from distutils.version import StrictVersion from fnmatch import fnmatch from datetime import datetime @@ -120,6 +121,11 @@ def compare_version(v1, v2): def ping_registry(url): + warnings.warn( + 'The `ping_registry` method is deprecated and will be removed.', + DeprecationWarning + ) + return ping(url + '/v2/', [401]) or ping(url + '/v1/_ping') @@ -333,9 +339,9 @@ def convert_filters(filters): return json.dumps(result) -def datetime_to_timestamp(dt=datetime.now()): - """Convert a datetime in local timezone to a unix timestamp""" - delta = dt - datetime.fromtimestamp(0) +def datetime_to_timestamp(dt): + """Convert a UTC datetime to a Unix timestamp""" + delta = dt - datetime.utcfromtimestamp(0) return delta.seconds + delta.days * 24 * 3600 diff --git a/docker/version.py b/docker/version.py index 42ddd98..bc778b9 100644 --- a/docker/version.py +++ b/docker/version.py @@ -1,2 +1,2 @@ -version = "1.3.0" +version = "1.3.1" version_info = tuple([int(d) for d in version.split("-")[0].split(".")]) diff --git a/docker_py.egg-info/PKG-INFO b/docker_py.egg-info/PKG-INFO index 2e130e7..eb4e26c 100644 --- a/docker_py.egg-info/PKG-INFO +++ b/docker_py.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: docker-py -Version: 1.3.0 +Version: 1.3.1 Summary: Python client for Docker. Home-page: https://github.com/docker/docker-py/ Author: UNKNOWN diff --git a/tests/integration_test.py b/tests/integration_test.py index ac4a871..226ea34 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -242,6 +242,7 @@ class TestCreateContainerWithRoBinds(BaseTestCase): self.assertFalse(inspect_data['VolumesRW'][mount_dest]) +@unittest.skipIf(NOT_ON_HOST, 'Tests running inside a container; no syslog') class TestCreateContainerWithLogConfig(BaseTestCase): def runTest(self): config = docker.utils.LogConfig( @@ -1356,8 +1357,8 @@ class TestLoadConfig(BaseTestCase): f.write('email = sakuya@scarlet.net') f.close() cfg = docker.auth.load_config(cfg_path) - self.assertNotEqual(cfg[docker.auth.INDEX_URL], None) - cfg = cfg[docker.auth.INDEX_URL] + self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) + cfg = cfg[docker.auth.INDEX_NAME] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') @@ -1386,7 +1387,7 @@ class TestLoadJSONConfig(BaseTestCase): class TestAutoDetectVersion(unittest.TestCase): def test_client_init(self): - client = docker.Client(version='auto') + client = docker.Client(base_url=DEFAULT_BASE_URL, version='auto') client_version = client._version api_version = client.version(api_version=False)['ApiVersion'] self.assertEqual(client_version, api_version) @@ -1395,7 +1396,7 @@ class TestAutoDetectVersion(unittest.TestCase): client.close() def test_auto_client(self): - client = docker.AutoVersionClient() + client = docker.AutoVersionClient(base_url=DEFAULT_BASE_URL) client_version = client._version api_version = client.version(api_version=False)['ApiVersion'] self.assertEqual(client_version, api_version) @@ -1403,7 +1404,7 @@ class TestAutoDetectVersion(unittest.TestCase): self.assertEqual(client_version, api_version_2) client.close() with self.assertRaises(docker.errors.DockerException): - docker.AutoVersionClient(version='1.11') + docker.AutoVersionClient(base_url=DEFAULT_BASE_URL, version='1.11') class TestConnectionTimeout(unittest.TestCase): diff --git a/tests/test.py b/tests/test.py index f6535b2..9e12bb8 100644 --- a/tests/test.py +++ b/tests/test.py @@ -221,7 +221,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase): def test_events_with_since_until(self): ts = 1356048000 - now = datetime.datetime.fromtimestamp(ts) + now = datetime.datetime.utcfromtimestamp(ts) since = now - datetime.timedelta(seconds=10) until = now + datetime.timedelta(seconds=10) try: @@ -2424,9 +2424,9 @@ class DockerClientTest(Cleanup, base.BaseTestCase): f.write('auth = {0}\n'.format(auth_)) f.write('email = sakuya@scarlet.net') cfg = docker.auth.load_config(dockercfg_path) - self.assertTrue(docker.auth.INDEX_URL in cfg) - self.assertNotEqual(cfg[docker.auth.INDEX_URL], None) - cfg = cfg[docker.auth.INDEX_URL] + self.assertTrue(docker.auth.INDEX_NAME in cfg) + self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) + cfg = cfg[docker.auth.INDEX_NAME] self.assertEqual(cfg['username'], 'sakuya') self.assertEqual(cfg['password'], 'izayoi') self.assertEqual(cfg['email'], 'sakuya@scarlet.net') diff --git a/tests/utils_test.py b/tests/utils_test.py index 716cde5..1c8729c 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -9,7 +9,7 @@ from docker.utils import ( create_host_config, Ulimit, LogConfig, parse_bytes ) from docker.utils.ports import build_port_bindings, split_port -from docker.auth import resolve_authconfig +from docker.auth import resolve_repository_name, resolve_authconfig import base @@ -167,6 +167,61 @@ class UtilsTest(base.BaseTestCase): type=LogConfig.types.JSON, config='helloworld' )) + def test_resolve_repository_name(self): + # docker hub library image + self.assertEqual( + resolve_repository_name('image'), + ('index.docker.io', 'image'), + ) + + # docker hub image + self.assertEqual( + resolve_repository_name('username/image'), + ('index.docker.io', 'username/image'), + ) + + # private registry + self.assertEqual( + resolve_repository_name('my.registry.net/image'), + ('my.registry.net', 'image'), + ) + + # private registry with port + self.assertEqual( + resolve_repository_name('my.registry.net:5000/image'), + ('my.registry.net:5000', 'image'), + ) + + # private registry with username + self.assertEqual( + resolve_repository_name('my.registry.net/username/image'), + ('my.registry.net', 'username/image'), + ) + + # no dots but port + self.assertEqual( + resolve_repository_name('hostname:5000/image'), + ('hostname:5000', 'image'), + ) + + # no dots but port and username + self.assertEqual( + resolve_repository_name('hostname:5000/username/image'), + ('hostname:5000', 'username/image'), + ) + + # localhost + self.assertEqual( + resolve_repository_name('localhost/image'), + ('localhost', 'image'), + ) + + # localhost with username + self.assertEqual( + resolve_repository_name('localhost/username/image'), + ('localhost', 'username/image'), + ) + def test_resolve_authconfig(self): auth_config = { 'https://index.docker.io/v1/': {'auth': 'indexuser'}, @@ -231,6 +286,40 @@ class UtilsTest(base.BaseTestCase): resolve_authconfig(auth_config, 'does.not.exist') is None ) + def test_resolve_registry_and_auth(self): + auth_config = { + 'https://index.docker.io/v1/': {'auth': 'indexuser'}, + 'my.registry.net': {'auth': 'privateuser'}, + } + + # library image + image = 'image' + self.assertEqual( + resolve_authconfig(auth_config, resolve_repository_name(image)[0]), + {'auth': 'indexuser'}, + ) + + # docker hub image + image = 'username/image' + self.assertEqual( + resolve_authconfig(auth_config, resolve_repository_name(image)[0]), + {'auth': 'indexuser'}, + ) + + # private registry + image = 'my.registry.net/image' + self.assertEqual( + resolve_authconfig(auth_config, resolve_repository_name(image)[0]), + {'auth': 'privateuser'}, + ) + + # unauthenticated registry + image = 'other.registry.net/image' + self.assertEqual( + resolve_authconfig(auth_config, resolve_repository_name(image)[0]), + None, + ) + def test_split_port_with_host_ip(self): internal_port, external_port = split_port("127.0.0.1:1000:2000") self.assertEqual(internal_port, ["2000"]) |