migrate-compose-file-v1-to-v2.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. #!/usr/bin/env python
  2. """
  3. Migrate a Compose file from the V1 format in Compose 1.5 to the V2 format
  4. supported by Compose 1.6+
  5. """
  6. from __future__ import absolute_import
  7. from __future__ import unicode_literals
  8. import argparse
  9. import logging
  10. import sys
  11. import ruamel.yaml
  12. log = logging.getLogger('migrate')
  13. def migrate(content):
  14. data = ruamel.yaml.load(content, ruamel.yaml.RoundTripLoader)
  15. service_names = data.keys()
  16. for name, service in data.items():
  17. links = service.get('links')
  18. if links:
  19. example_service = links[0].partition(':')[0]
  20. log.warn(
  21. "Service {name} has links, which no longer create environment "
  22. "variables such as {example_service_upper}_PORT. "
  23. "If you are using those in your application code, you should "
  24. "instead connect directly to the hostname, e.g. "
  25. "'{example_service}'."
  26. .format(name=name, example_service=example_service,
  27. example_service_upper=example_service.upper()))
  28. external_links = service.get('external_links')
  29. if external_links:
  30. log.warn(
  31. "Service {name} has external_links: {ext}, which now work "
  32. "slightly differently. In particular, two containers must be "
  33. "connected to at least one network in common in order to "
  34. "communicate, even if explicitly linked together.\n\n"
  35. "Either connect the external container to your app's default "
  36. "network, or connect both the external container and your "
  37. "service's containers to a pre-existing network. See "
  38. "https://docs.docker.com/compose/networking/ "
  39. "for more on how to do this."
  40. .format(name=name, ext=external_links))
  41. # net is now network_mode
  42. if 'net' in service:
  43. network_mode = service.pop('net')
  44. # "container:<service name>" is now "service:<service name>"
  45. if network_mode.startswith('container:'):
  46. name = network_mode.partition(':')[2]
  47. if name in service_names:
  48. network_mode = 'service:{}'.format(name)
  49. service['network_mode'] = network_mode
  50. # create build section
  51. if 'dockerfile' in service:
  52. service['build'] = {
  53. 'context': service.pop('build'),
  54. 'dockerfile': service.pop('dockerfile'),
  55. }
  56. # create logging section
  57. if 'log_driver' in service:
  58. service['logging'] = {'driver': service.pop('log_driver')}
  59. if 'log_opt' in service:
  60. service['logging']['options'] = service.pop('log_opt')
  61. # volumes_from prefix with 'container:'
  62. for idx, volume_from in enumerate(service.get('volumes_from', [])):
  63. if volume_from.split(':', 1)[0] not in service_names:
  64. service['volumes_from'][idx] = 'container:%s' % volume_from
  65. services = {name: data.pop(name) for name in data.keys()}
  66. data['version'] = 2
  67. data['services'] = services
  68. return data
  69. def write(stream, new_format, indent, width):
  70. ruamel.yaml.dump(
  71. new_format,
  72. stream,
  73. Dumper=ruamel.yaml.RoundTripDumper,
  74. indent=indent,
  75. width=width)
  76. def parse_opts(args):
  77. parser = argparse.ArgumentParser()
  78. parser.add_argument("filename", help="Compose file filename.")
  79. parser.add_argument("-i", "--in-place", action='store_true')
  80. parser.add_argument(
  81. "--indent", type=int, default=2,
  82. help="Number of spaces used to indent the output yaml.")
  83. parser.add_argument(
  84. "--width", type=int, default=80,
  85. help="Number of spaces used as the output width.")
  86. return parser.parse_args()
  87. def main(args):
  88. logging.basicConfig(format='\033[33m%(levelname)s:\033[37m %(message)s\n')
  89. opts = parse_opts(args)
  90. with open(opts.filename, 'r') as fh:
  91. new_format = migrate(fh.read())
  92. if opts.in_place:
  93. output = open(opts.filename, 'w')
  94. else:
  95. output = sys.stdout
  96. write(output, new_format, opts.indent, opts.width)
  97. if __name__ == "__main__":
  98. main(sys.argv)