浏览代码

Merge pull request #2754 from aanand/upgrading-improvements

Improvements to upgrade guide and script
Aanand Prasad 9 年之前
父节点
当前提交
8c250e220c
共有 2 个文件被更改,包括 129 次插入34 次删除
  1. 108 34
      contrib/migration/migrate-compose-file-v1-to-v2.py
  2. 21 0
      docs/compose-file.md

+ 108 - 34
contrib/migration/migrate-compose-file-v1-to-v2.py

@@ -12,6 +12,8 @@ import sys
 
 import ruamel.yaml
 
+from compose.config.types import VolumeSpec
+
 
 log = logging.getLogger('migrate')
 
@@ -20,44 +22,116 @@ def migrate(content):
     data = ruamel.yaml.load(content, ruamel.yaml.RoundTripLoader)
 
     service_names = data.keys()
+
     for name, service in data.items():
-        # remove links and external links
-        service.pop('links', None)
-        external_links = service.pop('external_links', None)
-        if external_links:
-            log.warn(
-                "Service {name} has external_links: {ext}, which are no longer "
-                "supported. See https://docs.docker.com/compose/networking/ "
-                "for options on how to connect external containers to the "
-                "compose network.".format(name=name, ext=external_links))
-
-        # net is now networks
-        if 'net' in service:
-            service['networks'] = [service.pop('net')]
-
-        # create build section
-        if 'dockerfile' in service:
-            service['build'] = {
-                'context': service.pop('build'),
-                'dockerfile': service.pop('dockerfile'),
-            }
-
-        # create logging section
-        if 'log_driver' in service:
-            service['logging'] = {'driver': service.pop('log_driver')}
-            if 'log_opt' in service:
-                service['logging']['options'] = service.pop('log_opt')
-
-        # volumes_from prefix with 'container:'
-        for idx, volume_from in enumerate(service.get('volumes_from', [])):
-            if volume_from.split(':', 1)[0] not in service_names:
-                service['volumes_from'][idx] = 'container:%s' % volume_from
-
-    data['services'] = {name: data.pop(name) for name in data.keys()}
+        warn_for_links(name, service)
+        warn_for_external_links(name, service)
+        rewrite_net(service, service_names)
+        rewrite_build(service)
+        rewrite_logging(service)
+        rewrite_volumes_from(service, service_names)
+
+    services = {name: data.pop(name) for name in data.keys()}
+
     data['version'] = 2
+    data['services'] = services
+    create_volumes_section(data)
+
     return data
 
 
+def warn_for_links(name, service):
+    links = service.get('links')
+    if links:
+        example_service = links[0].partition(':')[0]
+        log.warn(
+            "Service {name} has links, which no longer create environment "
+            "variables such as {example_service_upper}_PORT. "
+            "If you are using those in your application code, you should "
+            "instead connect directly to the hostname, e.g. "
+            "'{example_service}'."
+            .format(name=name, example_service=example_service,
+                    example_service_upper=example_service.upper()))
+
+
+def warn_for_external_links(name, service):
+    external_links = service.get('external_links')
+    if external_links:
+        log.warn(
+            "Service {name} has external_links: {ext}, which now work "
+            "slightly differently. In particular, two containers must be "
+            "connected to at least one network in common in order to "
+            "communicate, even if explicitly linked together.\n\n"
+            "Either connect the external container to your app's default "
+            "network, or connect both the external container and your "
+            "service's containers to a pre-existing network. See "
+            "https://docs.docker.com/compose/networking/ "
+            "for more on how to do this."
+            .format(name=name, ext=external_links))
+
+
+def rewrite_net(service, service_names):
+    if 'net' in service:
+        network_mode = service.pop('net')
+
+        # "container:<service name>" is now "service:<service name>"
+        if network_mode.startswith('container:'):
+            name = network_mode.partition(':')[2]
+            if name in service_names:
+                network_mode = 'service:{}'.format(name)
+
+        service['network_mode'] = network_mode
+
+
+def rewrite_build(service):
+    if 'dockerfile' in service:
+        service['build'] = {
+            'context': service.pop('build'),
+            'dockerfile': service.pop('dockerfile'),
+        }
+
+
+def rewrite_logging(service):
+    if 'log_driver' in service:
+        service['logging'] = {'driver': service.pop('log_driver')}
+        if 'log_opt' in service:
+            service['logging']['options'] = service.pop('log_opt')
+
+
+def rewrite_volumes_from(service, service_names):
+    for idx, volume_from in enumerate(service.get('volumes_from', [])):
+        if volume_from.split(':', 1)[0] not in service_names:
+            service['volumes_from'][idx] = 'container:%s' % volume_from
+
+
+def create_volumes_section(data):
+    named_volumes = get_named_volumes(data['services'])
+    if named_volumes:
+        log.warn(
+            "Named volumes ({names}) must be explicitly declared. Creating a "
+            "'volumes' section with declarations.\n\n"
+            "For backwards-compatibility, they've been declared as external. "
+            "If you don't mind the volume names being prefixed with the "
+            "project name, you can remove the 'external' option from each one."
+            .format(names=', '.join(list(named_volumes))))
+
+        data['volumes'] = named_volumes
+
+
+def get_named_volumes(services):
+    volume_specs = [
+        VolumeSpec.parse(volume)
+        for service in services.values()
+        for volume in service.get('volumes', [])
+    ]
+    names = {
+        spec.external
+        for spec in volume_specs
+        if spec.is_named_volume
+    }
+    return {name: {'external': True} for name in names}
+
+
 def write(stream, new_format, indent, width):
     ruamel.yaml.dump(
         new_format,
@@ -81,7 +155,7 @@ def parse_opts(args):
 
 
 def main(args):
-    logging.basicConfig()
+    logging.basicConfig(format='\033[33m%(levelname)s:\033[37m %(message)s\n')
 
     opts = parse_opts(args)
 

+ 21 - 0
docs/compose-file.md

@@ -941,6 +941,27 @@ It's more complicated if you're using particular configuration features:
         net: "container:cont-name"  ->  network_mode: "container:cont-name"
         net: "container:abc12345"   ->  network_mode: "container:abc12345"
 
+-   `volumes` with named volumes: these must now be explicitly declared in a
+    top-level `volumes` section of your Compose file. If a service mounts a
+    named volume called `data`, you must declare a `data` volume in your
+    top-level `volumes` section. The whole file might look like this:
+
+        version: 2
+        services:
+          db:
+            image: postgres
+            volumes:
+              - data:/var/lib/postgresql/data
+        volumes:
+          data: {}
+
+    By default, Compose creates a volume whose name is prefixed with your
+    project name. If you want it to just be called `data`, declared it as
+    external:
+
+        volumes:
+          data:
+            external: true
 
 ## Variable substitution