Merge "[GENERAL] Add Andreas Geissler as committer."
[oom/offline-installer.git] / build / download / docker_downloader.py
index 27dde12..9897325 100755 (executable)
@@ -3,7 +3,7 @@
 
 #   COPYRIGHT NOTICE STARTS HERE
 
-#   Copyright 2019 © Samsung Electronics Co., Ltd.
+#   Copyright 2022 © Samsung Electronics Co., Ltd.
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -36,14 +36,19 @@ log = logging.getLogger(__name__)
 
 
 class DockerDownloader(ConcurrentDownloader):
-    def __init__(self, save, *list_args, workers=3):
+    def __init__(self, save, *list_args, mirror=None, mirror_exclude=[], workers=3):
+        """
+        :param mirror: private repository mirror address (ip:port)
+        """
         self._save = save
+        self._mirror = mirror
+        self._mirror_exclude = mirror_exclude
         try:
             # big timeout in case of massive images like pnda-mirror-container:5.0.0 (11.4GB)
             self._docker_client = docker.from_env(timeout=300)
         except docker.errors.DockerException as err:
             log.exception(
-                'Error creating docker client. Check if is docker installed and running'
+                'Error creating docker client. Check if docker is installed and running'
                 ' or if you have right permissions.')
             raise err
         self._pulled_images = set(itertools.chain.from_iterable((image.tags for image
@@ -153,7 +158,28 @@ class DockerDownloader(ConcurrentDownloader):
         if ':' not in image_name.rsplit('/')[-1]:
             image_name = '{}:latest'.format(image_name)
         try:
-            image = self._docker_client.images.pull(image_name)
+            if self._mirror:
+                # if docker mirroring repository is set
+                image_name_split = image_name.split('/')
+                if (len(image_name_split) > 1) \
+                   and (image_name_split[0].find(".")) >= 0 \
+                   and not (image_name.startswith('docker.io/')) \
+                   and not (image_name.startswith(self._mirror)) \
+                   and (image_name_split[0] not in self._mirror_exclude):
+                    # if image originates from private registry and its name does not start with 'docker.io'
+                    # and it does not originate from excluded registry
+                    # and docker mirror name differs from private registry name
+                    # -> download image from docker mirror and retag it to its original name
+                    mirrored_image_name = self._mirror + "/" + '/'.join(image_name_split[1:])
+                    img = self._docker_client.images.pull(mirrored_image_name)
+                    self._docker_client.images.model.tag(img, image_name)
+                    # untag the image pulled from mirror
+                    self._docker_client.images.remove(mirrored_image_name)
+                    image = self._docker_client.images.get(image_name)
+                else:
+                    image = self._docker_client.images.pull(image_name)
+            else:
+                image = self._docker_client.images.pull(image_name)
             log.info('Image {} pulled'.format(image_name))
             return image
         except docker.errors.APIError as err:
@@ -168,8 +194,7 @@ class DockerDownloader(ConcurrentDownloader):
         :param image_name: name of the image from list
         """
         dst = '{}/{}'.format(output_dir, self._image_filename(image_name))
-        if not os.path.exists(output_dir):
-            os.makedirs(output_dir)
+        os.makedirs(output_dir, exist_ok=True)
         try:
             with open(dst, 'wb') as f:
                 for chunk in image.save(named=self.image_registry_name(image_name)):
@@ -206,6 +231,13 @@ def run_cli():
                         help='Save images (without it only pull is executed)')
     parser.add_argument('--output-dir', '-o', default=os.getcwd(),
                         help='Download destination')
+    parser.add_argument('--private-registry-mirror', default=None, metavar='HOST:PORT',
+                        help='Address of docker mirroring repository that caches images'
+                             ' from private registries to get those images from')
+    parser.add_argument('--private-registry-exclude', action='append', default=[], metavar='REGISTRY_NAME',
+                        help='The name of a private registry to exclude when using --private-registry-mirror.'
+                             ' Images that originate from excluded registry will not be'
+                             ' pulled from mirroring repository. This option can be used multiple times.')
     parser.add_argument('--check', '-c', action='store_true', default=False,
                         help='Check what is missing. No download.'
                              'Use with combination with -s to check saved images as well.')
@@ -221,7 +253,7 @@ def run_cli():
     else:
         logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(message)s')
 
-    downloader = DockerDownloader(args.save, [args.file_list, args.output_dir], workers=args.workers)
+    downloader = DockerDownloader(args.save, [args.image_list, args.output_dir], mirror=args.private_registry_mirror, mirror_exclude=args.private_registry_exclude, workers=args.workers)
 
     if args.check:
         log.info('Check mode. No download will be executed.')