Added a utility class for the Docker Hub (and therefore docker registry v2 API hosts)
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/223b5178 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/223b5178 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/223b5178 Branch: refs/heads/trunk Commit: 223b5178de08b46862d558c81c20d1cd6e5171a7 Parents: 6e4df6d Author: anthony-shaw <[email protected]> Authored: Mon Jan 11 10:23:17 2016 +1100 Committer: anthony-shaw <[email protected]> Committed: Mon Jan 11 10:23:17 2016 +1100 ---------------------------------------------------------------------- docs/container/index.rst | 8 +- docs/container/utilities.rst | 7 + libcloud/container/util/__init__.py | 0 libcloud/container/util/docker.py | 137 +++++++ .../fixtures/docker/list_tags_ubuntu.json | 354 +++++++++++++++++++ 5 files changed, 505 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/223b5178/docs/container/index.rst ---------------------------------------------------------------------- diff --git a/docs/container/index.rst b/docs/container/index.rst index 4f5fb66..2b33b4a 100644 --- a/docs/container/index.rst +++ b/docs/container/index.rst @@ -128,4 +128,10 @@ API Reference ------------- For a full reference of all the classes and methods exposed by the Container -API, see :doc:`this page </container/api>`. \ No newline at end of file +API, see :doc:`this page </container/api>`. + +Utility Classes +--------------- + +There are some utility classes for example, a Docker Hub API client for fetching images +and iterating through repositories see :doc:`this page </container/utilities>`. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/223b5178/docs/container/utilities.rst ---------------------------------------------------------------------- diff --git a/docs/container/utilities.rst b/docs/container/utilities.rst new file mode 100644 index 0000000..48aafc2 --- /dev/null +++ b/docs/container/utilities.rst @@ -0,0 +1,7 @@ +:orphan: + +Container Utility API +===================== + +.. autoclass:: libcloud.container.util.HubClient + :members: http://git-wip-us.apache.org/repos/asf/libcloud/blob/223b5178/libcloud/container/util/__init__.py ---------------------------------------------------------------------- diff --git a/libcloud/container/util/__init__.py b/libcloud/container/util/__init__.py new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/libcloud/blob/223b5178/libcloud/container/util/docker.py ---------------------------------------------------------------------- diff --git a/libcloud/container/util/docker.py b/libcloud/container/util/docker.py new file mode 100644 index 0000000..3edfec8 --- /dev/null +++ b/libcloud/container/util/docker.py @@ -0,0 +1,137 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import with_statement + +from requests.auth import HTTPBasicAuth +import requests + +from libcloud.container.base import ContainerImage + +__all__ = [ + 'HubClient' +] + + +class HubClient(object): + """ + A client for the Docker Hub API + + The hub is based on the v1 registry API + """ + host = 'registry.hub.docker.com' + base_url = 'https://%s/' % (host) + + def __init__(self, username=None, password=None): + """ + Construct a Docker hub client + + :param username: (optional) Your Hub account username + :type username: ``str`` + + :param password: (optional) Your hub account password + :type password: ``str`` + """ + if username is not None: + self.auth = HTTPBasicAuth(username, password) + else: + self.auth = None + + def list_tags(self, repository_name, namespace='library', max_count=100): + """ + List the tags (versions) in a repository + + :param repository_name: The name of the repository e.g. 'ubuntu' + :type repository_name: ``str`` + + :param namespace: (optional) The docker namespace + :type namespace: ``str`` + + :param max_count: The maximum number of records to return + :type max_count: ``int`` + + :return: A list of tags + :rtype: ``list`` of ``object`` + """ + path = 'v2/repositories/%s/%s/tags/?page=1&page_size=%s' \ + % (namespace, repository_name, max_count) + response = requests.get(self.base_url + path, auth=self.auth) + return response.json().results + + def list_image_ids(self, repository_name, namespace='library'): + """ + List the image IDs (versions) in a repository + + :param repository_name: The name of the repository e.g. 'ubuntu' + :type repository_name: ``str`` + + :param namespace: (optional) The docker namespace + :type namespace: ``str`` + + :return: A list of ids + :rtype: ``list`` of ``object`` + """ + path = 'v1/repositories/%s/%s/images' % (namespace, repository_name) + response = requests.get(self.base_url + path, auth=self.auth) + return response.json() + + def get_repository(self, repository_name, namespace='library'): + """ + Get the information about a specific repository + + :param repository_name: The name of the repository e.g. 'ubuntu' + :type repository_name: ``str`` + + :param namespace: (optional) The docker namespace + :type namespace: ``str`` + + :return: The details of the repository + :rtype: ``object`` + """ + path = 'v2/repositories/%s/%s' % (namespace, repository_name) + response = requests.get(self.base_url + path, auth=self.auth) + return response.json() + + def get_image(self, repository_name, tag='latest', namespace='library'): + """ + Get an image from a repository with a specific tag + + :param repository_name: The name of the repository, e.g. ubuntu + :type repository_name: ``str`` + + :param tag: (optional) The image tag (defaults to latest) + :type tag: ``str`` + + :param namespace: (optional) The docker namespace + :type namespace: ``str`` + + :return: A container image + :rtype: :class:`libcloud.container.base.ContainerImage` + """ + path = 'v2/repositories/%s/%s/tags/%s' \ + % (namespace, repository_name, tag) + response = requests.get(self.base_url + path, auth=self.auth) + obj = response.json() + path = '%s/%s:%s' % (self.host, repository_name, tag) + return ContainerImage( + id=obj['id'], + path=path, + name=path, + version=tag, + extra={ + 'full_size': obj['full_size'] + }, + driver=None + ) http://git-wip-us.apache.org/repos/asf/libcloud/blob/223b5178/libcloud/test/container/fixtures/docker/list_tags_ubuntu.json ---------------------------------------------------------------------- diff --git a/libcloud/test/container/fixtures/docker/list_tags_ubuntu.json b/libcloud/test/container/fixtures/docker/list_tags_ubuntu.json new file mode 100644 index 0000000..2786d5c --- /dev/null +++ b/libcloud/test/container/fixtures/docker/list_tags_ubuntu.json @@ -0,0 +1,354 @@ +[ + { + "layer": "af88597e", + "name": "latest" + }, + { + "layer": "3db9c44f", + "name": "10.04" + }, + { + "layer": "bad926a6", + "name": "12.04" + }, + { + "layer": "bad926a6", + "name": "12.04.5" + }, + { + "layer": "c5881f11", + "name": "12.10" + }, + { + "layer": "463ff6be", + "name": "13.04" + }, + { + "layer": "195eb90b", + "name": "13.10" + }, + { + "layer": "af88597e", + "name": "14.04" + }, + { + "layer": "5ba9dab4", + "name": "14.04.1" + }, + { + "layer": "63e3c102", + "name": "14.04.2" + }, + { + "layer": "af88597e", + "name": "14.04.3" + }, + { + "layer": "dce38fb5", + "name": "14.10" + }, + { + "layer": "3f0d2d4f", + "name": "15.04" + }, + { + "layer": "3e0c71ad", + "name": "15.10" + }, + { + "layer": "db856b41", + "name": "16.04" + }, + { + "layer": "3db9c44f", + "name": "lucid" + }, + { + "layer": "bad926a6", + "name": "precise" + }, + { + "layer": "1f80e9ca", + "name": "precise-20150212" + }, + { + "layer": "5898adab", + "name": "precise-20150228.11" + }, + { + "layer": "9610cfc6", + "name": "precise-20150320" + }, + { + "layer": "ac6b0eaa", + "name": "precise-20150427" + }, + { + "layer": "5c97af89", + "name": "precise-20150528" + }, + { + "layer": "78cef618", + "name": "precise-20150612" + }, + { + "layer": "6d021018", + "name": "precise-20150626" + }, + { + "layer": "d0e008c6", + "name": "precise-20150729" + }, + { + "layer": "57bca513", + "name": "precise-20150813" + }, + { + "layer": "61994089", + "name": "precise-20150924" + }, + { + "layer": "0ac5b09d", + "name": "precise-20151020" + }, + { + "layer": "04c3793b", + "name": "precise-20151028" + }, + { + "layer": "bad926a6", + "name": "precise-20151208" + }, + { + "layer": "c5881f11", + "name": "quantal" + }, + { + "layer": "463ff6be", + "name": "raring" + }, + { + "layer": "195eb90b", + "name": "saucy" + }, + { + "layer": "af88597e", + "name": "trusty" + }, + { + "layer": "2d24f826", + "name": "trusty-20150218.1" + }, + { + "layer": "2103b00b", + "name": "trusty-20150228.11" + }, + { + "layer": "b7cf8f0d", + "name": "trusty-20150320" + }, + { + "layer": "07f8e8c5", + "name": "trusty-20150427" + }, + { + "layer": "fa81ed08", + "name": "trusty-20150528" + }, + { + "layer": "6d494699", + "name": "trusty-20150612" + }, + { + "layer": "d2a0ecff", + "name": "trusty-20150630" + }, + { + "layer": "63e3c102", + "name": "trusty-20150730" + }, + { + "layer": "8251da35", + "name": "trusty-20150806" + }, + { + "layer": "91e54dfb", + "name": "trusty-20150814" + }, + { + "layer": "cdd47452", + "name": "trusty-20151001" + }, + { + "layer": "a005e6b7", + "name": "trusty-20151009" + }, + { + "layer": "a5a467fd", + "name": "trusty-20151021" + }, + { + "layer": "ca4d7b1b", + "name": "trusty-20151028" + }, + { + "layer": "d55e68e6", + "name": "trusty-20151208" + }, + { + "layer": "af88597e", + "name": "trusty-20151218" + }, + { + "layer": "dce38fb5", + "name": "utopic" + }, + { + "layer": "78949b1e", + "name": "utopic-20150211" + }, + { + "layer": "525b6e4a", + "name": "utopic-20150228.11" + }, + { + "layer": "59a878f2", + "name": "utopic-20150319" + }, + { + "layer": "0b79d1c2", + "name": "utopic-20150418" + }, + { + "layer": "d191563a", + "name": "utopic-20150427" + }, + { + "layer": "ce78e71c", + "name": "utopic-20150528" + }, + { + "layer": "ab57dbaf", + "name": "utopic-20150612" + }, + { + "layer": "dce38fb5", + "name": "utopic-20150625" + }, + { + "layer": "3f0d2d4f", + "name": "vivid" + }, + { + "layer": "2427658c", + "name": "vivid-20150218" + }, + { + "layer": "19c8c047", + "name": "vivid-20150309" + }, + { + "layer": "751a7482", + "name": "vivid-20150319.1" + }, + { + "layer": "ef8182f3", + "name": "vivid-20150421" + }, + { + "layer": "bd94ae58", + "name": "vivid-20150427" + }, + { + "layer": "c6a35822", + "name": "vivid-20150528" + }, + { + "layer": "6be21d1e", + "name": "vivid-20150611" + }, + { + "layer": "23635bbe", + "name": "vivid-20150802" + }, + { + "layer": "013f3d01", + "name": "vivid-20150813" + }, + { + "layer": "82554298", + "name": "vivid-20150930" + }, + { + "layer": "4e9c8310", + "name": "vivid-20151021" + }, + { + "layer": "e97f8c6b", + "name": "vivid-20151106" + }, + { + "layer": "db672630", + "name": "vivid-20151111" + }, + { + "layer": "3f0d2d4f", + "name": "vivid-20151208" + }, + { + "layer": "3e0c71ad", + "name": "wily" + }, + { + "layer": "0fe5a10d", + "name": "wily-20150528.1" + }, + { + "layer": "906f2d7d", + "name": "wily-20150611" + }, + { + "layer": "22ba5544", + "name": "wily-20150708" + }, + { + "layer": "6767f35d", + "name": "wily-20150731" + }, + { + "layer": "1832625b", + "name": "wily-20150807" + }, + { + "layer": "53816304", + "name": "wily-20150818" + }, + { + "layer": "1884caee", + "name": "wily-20150829" + }, + { + "layer": "4dc94650", + "name": "wily-20151006" + }, + { + "layer": "6171b5c3", + "name": "wily-20151009" + }, + { + "layer": "337d0fd7", + "name": "wily-20151019" + }, + { + "layer": "3e0c71ad", + "name": "wily-20151208" + }, + { + "layer": "db856b41", + "name": "xenial" + }, + { + "layer": "db856b41", + "name": "xenial-20151218.1" + } +] \ No newline at end of file
