From ac9ac7410b1e49d082aba72ad229e858f1b52e3f Mon Sep 17 00:00:00 2001 From: Mingzhe Zou Date: Wed, 4 Sep 2024 21:09:41 +0800 Subject: [PATCH] add dockhub --- .github/workflows/python-package.yml | 4 ++ dockloader/attribute.py | 2 +- dockloader/dockhub/__init__.py | 33 +++++++++ dockloader/dockhub/tags.py | 36 ++++++++++ dockloader/utils/__init__.py | 2 + dockloader/utils/docker_hub.py | 100 +++++++++++++++++++++++++++ setup.cfg | 1 + 7 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 dockloader/dockhub/__init__.py create mode 100644 dockloader/dockhub/tags.py create mode 100644 dockloader/utils/docker_hub.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9438470..5f5dd81 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -77,3 +77,7 @@ jobs: - name: List all images run: docker image ls + + - name: List docker hub image all tags + run: | + dockhub tags list --stdout --debug library/nginx diff --git a/dockloader/attribute.py b/dockloader/attribute.py index 23ecbeb..005fd5d 100644 --- a/dockloader/attribute.py +++ b/dockloader/attribute.py @@ -3,7 +3,7 @@ from urllib.parse import urljoin __project__ = "dockloader" -__version__ = "0.2.alpha.1" +__version__ = "0.2.alpha.2" __description__ = "Docker Image Downloader." __url_home__ = "https://github.com/podboy/dockloader/" __url_code__ = __url_home__ diff --git a/dockloader/dockhub/__init__.py b/dockloader/dockhub/__init__.py new file mode 100644 index 0000000..bf24a52 --- /dev/null +++ b/dockloader/dockhub/__init__.py @@ -0,0 +1,33 @@ +# coding:utf-8 + +from typing import Optional +from typing import Sequence + +from xarg import add_command +from xarg import argp +from xarg import commands +from xarg import run_command + +from ..attribute import __url_home__ +from ..attribute import __version__ +from .tags import add_cmd_tags + + +@add_command("dockhub") +def add_cmd(_arg: argp): + pass + + +@run_command(add_cmd, add_cmd_tags) +def run_cmd(cmds: commands) -> int: + return 0 + + +def main(argv: Optional[Sequence[str]] = None) -> int: + cmds = commands() + cmds.version = __version__ + return cmds.run( + root=add_cmd, + argv=argv, + description="docker hub toolkit", + epilog=f"For more, please visit {__url_home__}.") diff --git a/dockloader/dockhub/tags.py b/dockloader/dockhub/tags.py new file mode 100644 index 0000000..5e453cc --- /dev/null +++ b/dockloader/dockhub/tags.py @@ -0,0 +1,36 @@ +# coding:utf-8 + +from xarg import add_command +from xarg import argp +from xarg import commands +from xarg import run_command + +from ..utils import DockerHubTags + + +def add_pos_images(_arg: argp, help: str): + _arg.add_argument(type=str, dest="images", help=help, + nargs="*", default=[], metavar="IMAGE") + + +@add_command("list", help="list docker hub image all tags") +def add_cmd_list(_arg: argp): + add_pos_images(_arg, "image name") + + +@run_command(add_cmd_list) +def run_cmd_list(cmds: commands) -> int: + for image in cmds.args.images: + for tag in DockerHubTags.fetch(image).values(): + cmds.stdout(tag.name) + return 0 + + +@add_command("tags", help="docker hub image additional name") +def add_cmd_tags(_arg: argp): + pass + + +@run_command(add_cmd_tags, add_cmd_list) +def run_cmd_tags(cmds: commands) -> int: + return 0 diff --git a/dockloader/utils/__init__.py b/dockloader/utils/__init__.py index 2101f4d..b2d6307 100644 --- a/dockloader/utils/__init__.py +++ b/dockloader/utils/__init__.py @@ -1,6 +1,8 @@ # coding:utf-8 from .docker import DockerClient +from .docker_hub import DockerHubTag +from .docker_hub import DockerHubTags from .tags import Tag from .tags import TagConfigFile from .tags import Tags diff --git a/dockloader/utils/docker_hub.py b/dockloader/utils/docker_hub.py new file mode 100644 index 0000000..2934580 --- /dev/null +++ b/dockloader/utils/docker_hub.py @@ -0,0 +1,100 @@ +# coding:utf-8 + +from typing import Any +from typing import Dict +from typing import Tuple + +import requests + + +class DockerHubTag: + class ImageTag: + def __init__(self, data: Dict[str, Any]): + self.__data: Dict[str, Any] = data + self.__architecture: str = data["architecture"] + self.__os: str = data["os"] + self.__digest: str = data["digest"] + self.__size: int = data["size"] + + @property + def architecture(self) -> str: + return self.__architecture + + @property + def os(self) -> str: + return self.__os + + @property + def digest(self) -> str: + return self.__digest + + @property + def size(self) -> int: + return self.__size + + def __init__(self, data: Dict[str, Any]): + self.__data: Dict[str, Any] = data + self.__name: str = data["name"] + self.__digest: str = data["digest"] + self.__full_size: int = data["full_size"] + self.__image_tags: Tuple[DockerHubTag.ImageTag, ...] = tuple( + DockerHubTag.ImageTag(image) for image in data["images"]) + + @property + def name(self) -> str: + return self.__name + + @property + def digest(self) -> str: + return self.__digest + + @property + def full_size(self) -> int: + return self.__full_size + + @property + def images(self) -> Tuple["DockerHubTag.ImageTag", ...]: + return self.__image_tags + + +class DockerHubTags(Dict[str, DockerHubTag]): + + def __init__(self, image: str): + self.__image: str = image + super().__init__() + + @property + def image(self) -> str: + return self.__image + + @classmethod + def fetch(cls, image: str) -> "DockerHubTags": + """fetch all tags of an image + """ + count: int = 0 + tags: DockerHubTags = DockerHubTags(image) + headers: Dict[str, str] = {"Accept": "application/json"} + url: str = f"https://hub.docker.com/v2/repositories/{tags.image}/tags/" + + while url: + response = requests.get(url, headers=headers) + if response.status_code != 200: + raise IOError(f"{url} {response.status_code} {response.reason}") # noqa: E501 + + data = response.json() + for tag_data in data["results"]: + tag = DockerHubTag(tag_data) + tags[tag.name] = tag + + _count: int = data["count"] + if count == 0: + count = _count + elif count != _count: + raise ValueError(f"count {_count} != {count}") + + url = data["next"] + + if count != len(tags): + raise ValueError(f"actual count {len(tags)} != {count}") + + return tags diff --git a/setup.cfg b/setup.cfg index e846bff..d551de7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,3 +17,4 @@ python_requires = >=3.10 [options.entry_points] console_scripts = dockloader = dockloader.cmds:main + dockhub = dockloader.dockhub:main