فهرست منبع

Warn on missing digests, don't push/pull by default

Add a --fetch-digests flag to automatically push/pull

Signed-off-by: Aanand Prasad <[email protected]>
Aanand Prasad 9 سال پیش
والد
کامیت
020d46ff21
2فایلهای تغییر یافته به همراه79 افزوده شده و 16 حذف شده
  1. 51 13
      compose/bundle.py
  2. 28 3
      compose/cli/main.py

+ 51 - 13
compose/bundle.py

@@ -40,6 +40,22 @@ SUPPORTED_KEYS = {
 VERSION = '0.1'
 
 
+class NeedsPush(Exception):
+    def __init__(self, image_name):
+        self.image_name = image_name
+
+
+class NeedsPull(Exception):
+    def __init__(self, image_name):
+        self.image_name = image_name
+
+
+class MissingDigests(Exception):
+    def __init__(self, needs_push, needs_pull):
+        self.needs_push = needs_push
+        self.needs_pull = needs_pull
+
+
 def serialize_bundle(config, image_digests):
     if config.networks:
         log.warn("Unsupported top level key 'networks' - ignoring")
@@ -54,21 +70,36 @@ def serialize_bundle(config, image_digests):
     )
 
 
-def get_image_digests(project):
-    return {
-        service.name: get_image_digest(service)
-        for service in project.services
-    }
+def get_image_digests(project, allow_fetch=False):
+    digests = {}
+    needs_push = set()
+    needs_pull = set()
+
+    for service in project.services:
+        try:
+            digests[service.name] = get_image_digest(
+                service,
+                allow_fetch=allow_fetch,
+            )
+        except NeedsPush as e:
+            needs_push.add(e.image_name)
+        except NeedsPull as e:
+            needs_pull.add(e.image_name)
+
+    if needs_push or needs_pull:
+        raise MissingDigests(needs_push, needs_pull)
+
+    return digests
 
 
-def get_image_digest(service):
+def get_image_digest(service, allow_fetch=False):
     if 'image' not in service.options:
         raise UserError(
             "Service '{s.name}' doesn't define an image tag. An image name is "
             "required to generate a proper image digest for the bundle. Specify "
             "an image repo and tag with the 'image' option.".format(s=service))
 
-    repo, tag, separator = parse_repository_tag(service.options['image'])
+    separator = parse_repository_tag(service.options['image'])[2]
     # Compose file already uses a digest, no lookup required
     if separator == '@':
         return service.options['image']
@@ -87,13 +118,17 @@ def get_image_digest(service):
         # digests
         return image['RepoDigests'][0]
 
+    if not allow_fetch:
+        if 'build' in service.options:
+            raise NeedsPush(service.image_name)
+        else:
+            raise NeedsPull(service.image_name)
+
+    return fetch_image_digest(service)
+
+
+def fetch_image_digest(service):
     if 'build' not in service.options:
-        log.warn(
-            "Compose needs to pull the image for '{s.name}' in order to create "
-            "a bundle. This may result in a more recent image being used. "
-            "It is recommended that you use an image tagged with a "
-            "specific version to minimize the potential "
-            "differences.".format(s=service))
         digest = service.pull()
     else:
         try:
@@ -108,12 +143,15 @@ def get_image_digest(service):
     if not digest:
         raise ValueError("Failed to get digest for %s" % service.name)
 
+    repo = parse_repository_tag(service.options['image'])[0]
     identifier = '{repo}@{digest}'.format(repo=repo, digest=digest)
 
     # Pull by digest so that image['RepoDigests'] is populated for next time
     # and we don't have to pull/push again
     service.client.pull(identifier)
 
+    log.info("Stored digest for {}".format(service.image_name))
+
     return identifier
 
 

+ 28 - 3
compose/cli/main.py

@@ -15,6 +15,7 @@ from . import errors
 from . import signals
 from .. import __version__
 from ..bundle import get_image_digests
+from ..bundle import MissingDigests
 from ..bundle import serialize_bundle
 from ..config import ConfigurationError
 from ..config import parse_environment
@@ -218,12 +219,17 @@ class TopLevelCommand(object):
         """
         Generate a Docker bundle from the Compose file.
 
-        Local images will be pushed to a Docker registry, and remote images
-        will be pulled to fetch an image digest.
+        Images must have digests stored, which requires interaction with a
+        Docker registry. If digests aren't stored for all images, you can pass
+        `--fetch-digests` to automatically fetch them. Images for services
+        with a `build` key will be pushed. Images for services without a
+        `build` key will be pulled.
 
         Usage: bundle [options]
 
         Options:
+            --fetch-digests            Automatically fetch image digests if missing
+
             -o, --output PATH          Path to write the bundle file to.
                                        Defaults to "<project name>.dsb".
         """
@@ -235,7 +241,26 @@ class TopLevelCommand(object):
             output = "{}.dsb".format(self.project.name)
 
         with errors.handle_connection_errors(self.project.client):
-            image_digests = get_image_digests(self.project)
+            try:
+                image_digests = get_image_digests(
+                    self.project,
+                    allow_fetch=options['--fetch-digests'],
+                )
+            except MissingDigests as e:
+                def list_images(images):
+                    return "\n".join("    {}".format(name) for name in sorted(images))
+
+                paras = ["Some images are missing digests."]
+
+                if e.needs_push:
+                    paras += ["The following images need to be pushed:", list_images(e.needs_push)]
+
+                if e.needs_pull:
+                    paras += ["The following images need to be pulled:", list_images(e.needs_pull)]
+
+                paras.append("If this is OK, run `docker-compose bundle --fetch-digests`.")
+
+                raise UserError("\n\n".join(paras))
 
         with open(output, 'w') as f:
             f.write(serialize_bundle(compose_config, image_digests))