| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 | 
							- from __future__ import absolute_import
 
- from __future__ import unicode_literals
 
- import json
 
- import logging
 
- import six
 
- from docker.utils import split_command
 
- from docker.utils.ports import split_port
 
- from .cli.errors import UserError
 
- from .config.serialize import denormalize_config
 
- from .network import get_network_defs_for_service
 
- from .service import format_environment
 
- from .service import NoSuchImageError
 
- from .service import parse_repository_tag
 
- log = logging.getLogger(__name__)
 
- SERVICE_KEYS = {
 
-     'working_dir': 'WorkingDir',
 
-     'user': 'User',
 
-     'labels': 'Labels',
 
- }
 
- IGNORED_KEYS = {'build'}
 
- SUPPORTED_KEYS = {
 
-     'image',
 
-     'ports',
 
-     'expose',
 
-     'networks',
 
-     'command',
 
-     'environment',
 
-     'entrypoint',
 
- } | set(SERVICE_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, service_name):
 
-         self.image_name = image_name
 
-         self.service_name = service_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):
 
-     return json.dumps(to_bundle(config, image_digests), indent=2, sort_keys=True)
 
- def get_image_digests(project, allow_push=False):
 
-     digests = {}
 
-     needs_push = set()
 
-     needs_pull = set()
 
-     for service in project.services:
 
-         try:
 
-             digests[service.name] = get_image_digest(
 
-                 service,
 
-                 allow_push=allow_push,
 
-             )
 
-         except NeedsPush as e:
 
-             needs_push.add(e.image_name)
 
-         except NeedsPull as e:
 
-             needs_pull.add(e.service_name)
 
-     if needs_push or needs_pull:
 
-         raise MissingDigests(needs_push, needs_pull)
 
-     return digests
 
- def get_image_digest(service, allow_push=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))
 
-     _, _, separator = parse_repository_tag(service.options['image'])
 
-     # Compose file already uses a digest, no lookup required
 
-     if separator == '@':
 
-         return service.options['image']
 
-     try:
 
-         image = service.image()
 
-     except NoSuchImageError:
 
-         action = 'build' if 'build' in service.options else 'pull'
 
-         raise UserError(
 
-             "Image not found for service '{service}'. "
 
-             "You might need to run `docker-compose {action} {service}`."
 
-             .format(service=service.name, action=action))
 
-     if image['RepoDigests']:
 
-         # TODO: pick a digest based on the image tag if there are multiple
 
-         # digests
 
-         return image['RepoDigests'][0]
 
-     if 'build' not in service.options:
 
-         raise NeedsPull(service.image_name, service.name)
 
-     if not allow_push:
 
-         raise NeedsPush(service.image_name)
 
-     return push_image(service)
 
- def push_image(service):
 
-     try:
 
-         digest = service.push()
 
-     except:
 
-         log.error(
 
-             "Failed to push image for service '{s.name}'. Please use an "
 
-             "image tag that can be pushed to a Docker "
 
-             "registry.".format(s=service))
 
-         raise
 
-     if not digest:
 
-         raise ValueError("Failed to get digest for %s" % service.name)
 
-     repo, _, _ = parse_repository_tag(service.options['image'])
 
-     identifier = '{repo}@{digest}'.format(repo=repo, digest=digest)
 
-     # only do this if RepoDigests isn't already populated
 
-     image = service.image()
 
-     if not image['RepoDigests']:
 
-         # 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
 
- def to_bundle(config, image_digests):
 
-     if config.networks:
 
-         log.warn("Unsupported top level key 'networks' - ignoring")
 
-     if config.volumes:
 
-         log.warn("Unsupported top level key 'volumes' - ignoring")
 
-     config = denormalize_config(config)
 
-     return {
 
-         'Version': VERSION,
 
-         'Services': {
 
-             name: convert_service_to_bundle(
 
-                 name,
 
-                 service_dict,
 
-                 image_digests[name],
 
-             )
 
-             for name, service_dict in config['services'].items()
 
-         },
 
-     }
 
- def convert_service_to_bundle(name, service_dict, image_digest):
 
-     container_config = {'Image': image_digest}
 
-     for key, value in service_dict.items():
 
-         if key in IGNORED_KEYS:
 
-             continue
 
-         if key not in SUPPORTED_KEYS:
 
-             log.warn("Unsupported key '{}' in services.{} - ignoring".format(key, name))
 
-             continue
 
-         if key == 'environment':
 
-             container_config['Env'] = format_environment({
 
-                 envkey: envvalue for envkey, envvalue in value.items()
 
-                 if envvalue
 
-             })
 
-             continue
 
-         if key in SERVICE_KEYS:
 
-             container_config[SERVICE_KEYS[key]] = value
 
-             continue
 
-     set_command_and_args(
 
-         container_config,
 
-         service_dict.get('entrypoint', []),
 
-         service_dict.get('command', []))
 
-     container_config['Networks'] = make_service_networks(name, service_dict)
 
-     ports = make_port_specs(service_dict)
 
-     if ports:
 
-         container_config['Ports'] = ports
 
-     return container_config
 
- # See https://github.com/docker/swarmkit/blob/agent/exec/container/container.go#L95
 
- def set_command_and_args(config, entrypoint, command):
 
-     if isinstance(entrypoint, six.string_types):
 
-         entrypoint = split_command(entrypoint)
 
-     if isinstance(command, six.string_types):
 
-         command = split_command(command)
 
-     if entrypoint:
 
-         config['Command'] = entrypoint + command
 
-         return
 
-     if command:
 
-         config['Args'] = command
 
- def make_service_networks(name, service_dict):
 
-     networks = []
 
-     for network_name, network_def in get_network_defs_for_service(service_dict).items():
 
-         for key in network_def.keys():
 
-             log.warn(
 
-                 "Unsupported key '{}' in services.{}.networks.{} - ignoring"
 
-                 .format(key, name, network_name))
 
-         networks.append(network_name)
 
-     return networks
 
- def make_port_specs(service_dict):
 
-     ports = []
 
-     internal_ports = [
 
-         internal_port
 
-         for port_def in service_dict.get('ports', [])
 
-         for internal_port in split_port(port_def)[0]
 
-     ]
 
-     internal_ports += service_dict.get('expose', [])
 
-     for internal_port in internal_ports:
 
-         spec = make_port_spec(internal_port)
 
-         if spec not in ports:
 
-             ports.append(spec)
 
-     return ports
 
- def make_port_spec(value):
 
-     components = six.text_type(value).partition('/')
 
-     return {
 
-         'Protocol': components[2] or 'tcp',
 
-         'Port': int(components[0]),
 
-     }
 
 
  |