types.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. """
  2. Types for objects parsed from the configuration.
  3. """
  4. from __future__ import absolute_import
  5. from __future__ import unicode_literals
  6. import os
  7. from collections import namedtuple
  8. from compose.config.errors import ConfigurationError
  9. from compose.const import IS_WINDOWS_PLATFORM
  10. class VolumeFromSpec(namedtuple('_VolumeFromSpec', 'source mode type')):
  11. # TODO: drop service_names arg when v1 is removed
  12. @classmethod
  13. def parse(cls, volume_from_config, service_names, version):
  14. func = cls.parse_v1 if version == 1 else cls.parse_v2
  15. return func(service_names, volume_from_config)
  16. @classmethod
  17. def parse_v1(cls, service_names, volume_from_config):
  18. parts = volume_from_config.split(':')
  19. if len(parts) > 2:
  20. raise ConfigurationError(
  21. "volume_from {} has incorrect format, should be "
  22. "service[:mode]".format(volume_from_config))
  23. if len(parts) == 1:
  24. source = parts[0]
  25. mode = 'rw'
  26. else:
  27. source, mode = parts
  28. type = 'service' if source in service_names else 'container'
  29. return cls(source, mode, type)
  30. @classmethod
  31. def parse_v2(cls, service_names, volume_from_config):
  32. parts = volume_from_config.split(':')
  33. if len(parts) > 3:
  34. raise ConfigurationError(
  35. "volume_from {} has incorrect format, should be one of "
  36. "'<service name>[:<mode>]' or "
  37. "'container:<container name>[:<mode>]'".format(volume_from_config))
  38. if len(parts) == 1:
  39. source = parts[0]
  40. return cls(source, 'rw', 'service')
  41. if len(parts) == 2:
  42. if parts[0] == 'container':
  43. type, source = parts
  44. return cls(source, 'rw', type)
  45. source, mode = parts
  46. return cls(source, mode, 'service')
  47. if len(parts) == 3:
  48. type, source, mode = parts
  49. if type not in ('service', 'container'):
  50. raise ConfigurationError(
  51. "Unknown volumes_from type '{}' in '{}'".format(
  52. type,
  53. volume_from_config))
  54. return cls(source, mode, type)
  55. def parse_restart_spec(restart_config):
  56. if not restart_config:
  57. return None
  58. parts = restart_config.split(':')
  59. if len(parts) > 2:
  60. raise ConfigurationError(
  61. "Restart %s has incorrect format, should be "
  62. "mode[:max_retry]" % restart_config)
  63. if len(parts) == 2:
  64. name, max_retry_count = parts
  65. else:
  66. name, = parts
  67. max_retry_count = 0
  68. return {'Name': name, 'MaximumRetryCount': int(max_retry_count)}
  69. def parse_extra_hosts(extra_hosts_config):
  70. if not extra_hosts_config:
  71. return {}
  72. if isinstance(extra_hosts_config, dict):
  73. return dict(extra_hosts_config)
  74. if isinstance(extra_hosts_config, list):
  75. extra_hosts_dict = {}
  76. for extra_hosts_line in extra_hosts_config:
  77. # TODO: validate string contains ':' ?
  78. host, ip = extra_hosts_line.split(':')
  79. extra_hosts_dict[host.strip()] = ip.strip()
  80. return extra_hosts_dict
  81. def normalize_paths_for_engine(external_path, internal_path):
  82. """Windows paths, c:\my\path\shiny, need to be changed to be compatible with
  83. the Engine. Volume paths are expected to be linux style /c/my/path/shiny/
  84. """
  85. if not IS_WINDOWS_PLATFORM:
  86. return external_path, internal_path
  87. if external_path:
  88. drive, tail = os.path.splitdrive(external_path)
  89. if drive:
  90. external_path = '/' + drive.lower().rstrip(':') + tail
  91. external_path = external_path.replace('\\', '/')
  92. return external_path, internal_path.replace('\\', '/')
  93. class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
  94. @classmethod
  95. def parse(cls, volume_config):
  96. """Parse a volume_config path and split it into external:internal[:mode]
  97. parts to be returned as a valid VolumeSpec.
  98. """
  99. if IS_WINDOWS_PLATFORM:
  100. # relative paths in windows expand to include the drive, eg C:\
  101. # so we join the first 2 parts back together to count as one
  102. drive, tail = os.path.splitdrive(volume_config)
  103. parts = tail.split(":")
  104. if drive:
  105. parts[0] = drive + parts[0]
  106. else:
  107. parts = volume_config.split(':')
  108. if len(parts) > 3:
  109. raise ConfigurationError(
  110. "Volume %s has incorrect format, should be "
  111. "external:internal[:mode]" % volume_config)
  112. if len(parts) == 1:
  113. external, internal = normalize_paths_for_engine(
  114. None,
  115. os.path.normpath(parts[0]))
  116. else:
  117. external, internal = normalize_paths_for_engine(
  118. os.path.normpath(parts[0]),
  119. os.path.normpath(parts[1]))
  120. mode = 'rw'
  121. if len(parts) == 3:
  122. mode = parts[2]
  123. return cls(external, internal, mode)