object.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import yaml
  2. from lib.compose import SERVICE_RESTART_ON_FAILURE
  3. class Quoted(str):
  4. pass
  5. def quoted_presenter(dumper, data):
  6. return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"')
  7. yaml.add_representer(quoted, quoted_presenter)
  8. class Utils(object):
  9. @classmethod
  10. def dump_yaml(cls, data):
  11. yaml.add_representer(Quoted, quoted_presenter)
  12. output = yaml.dump(data, default_flow_style=False, sort_keys=False)
  13. return output
  14. class ComposeObject(object):
  15. @classmethod
  16. def inject_data(cls, data_dict, name, data, list_as_dict=None):
  17. if not data:
  18. return
  19. if isinstance(data, list):
  20. if list_as_dict is None:
  21. list_as_dict = name in ["services", "networks", "volumes", "configs", "secrets"]
  22. for v in data:
  23. if data_dict.get(name, None) is None:
  24. if list_as_dict:
  25. data_dict[name] = {}
  26. else:
  27. data_dict[name] = []
  28. if isinstance(v, ComposeObject):
  29. if list_as_dict:
  30. data_dict[name][v.get_name()] = v.to_output()
  31. else:
  32. data_dict[name].append(v.to_output())
  33. else:
  34. # print(f'{data_dict[name]} {name} append {v}')
  35. data_dict[name].append(v)
  36. else:
  37. if hasattr(data, '__len__'):
  38. if len(data) == 0:
  39. return
  40. data_dict[name] = data
  41. def to_output(self):
  42. raise Exception("to_output is not implemented")
  43. def to_yaml(self):
  44. return Utils.dump_yaml(self.to_output())
  45. class DependOn(ComposeObject):
  46. # specifies that a service is started
  47. CONDITION_SERVICE_STARTED = "service_started"
  48. # specifies that a dependency is expected to be “healthy” (as indicated by healthcheck) before starting a dependent service.
  49. CONDITION_SERVICE_HEALTHY = "service_healthy"
  50. # specifies that a dependency is expected to run to successful completion before starting a dependent service.
  51. CONDITION_COMPLETED_SUCCESSFULLY = "service_completed_successfully"
  52. def __init__(self, service, condition, restart=False):
  53. self.service = service
  54. self.condition = condition
  55. self.restart = restart
  56. def get_name(self):
  57. return self.service.get_name()
  58. def to_output(self):
  59. out = {
  60. "condition": self.condition,
  61. }
  62. if self.restart:
  63. out["restart"] = self.restart
  64. return out
  65. class Service(ComposeObject):
  66. def __init__(self, name, image):
  67. self.name = name
  68. self.image = image
  69. self.volumes = []
  70. self.ports = []
  71. self.environment = {}
  72. self.env_file = []
  73. self.depends_on = {}
  74. self.command = []
  75. self.healthcheck = None
  76. self.restart = None
  77. self.privileged = False
  78. self.network_mode = None
  79. def get_name(self):
  80. return self.name
  81. def add_volume(self, *vol):
  82. self.volumes = self.volumes + list(vol)
  83. return self
  84. def get_volumes(self):
  85. return self.volumes
  86. def add_port(self, *port):
  87. self.ports = self.ports + list(port)
  88. return self
  89. def enable_privileged(self):
  90. self.privileged = True
  91. def use_host_network(self):
  92. self.network_mode = "host"
  93. def is_host_network(self):
  94. return self.network_mode == "host"
  95. def add_environment(self, env):
  96. for key in env:
  97. self.environment[key] = env[key]
  98. return self
  99. def add_environment_file(self, env_file):
  100. self.env_file.append(env_file)
  101. return self
  102. def depend_on(self, service,
  103. condition=DependOn.CONDITION_SERVICE_STARTED, restart=False):
  104. depend_on = DependOn(service, condition, restart)
  105. self.depends_on[depend_on.get_name()] = depend_on.to_output()
  106. return self
  107. def depend_on_health(self, service, restart=False):
  108. return self.depend_on(service,
  109. condition=DependOn.CONDITION_SERVICE_HEALTHY,
  110. restart=restart)
  111. def depend_on_completed(self, service, restart=False):
  112. return self.depend_on(service,
  113. condition=DependOn.CONDITION_COMPLETED_SUCCESSFULLY,
  114. restart=restart)
  115. def set_command(self, *command):
  116. self.command = list(command)
  117. return self
  118. def set_healthcheck(self, test,
  119. interval=5,
  120. timeout=10,
  121. retries=10,
  122. start_period=30):
  123. self.healthcheck = {
  124. 'test': test,
  125. 'interval': f'{interval}s',
  126. 'timeout': f'{timeout}s',
  127. 'retries': retries,
  128. 'start_period': f'{start_period}s'
  129. }
  130. return self
  131. def set_restart(self, restart_policy):
  132. self.restart = restart_policy
  133. return self
  134. def set_restart_on_failure(self):
  135. return self.set_restart(SERVICE_RESTART_ON_FAILURE)
  136. def get_ports(self):
  137. return self.ports
  138. def to_output(self):
  139. data = {
  140. "image": self.image,
  141. }
  142. self.inject_data(data, "ports", self.get_ports(), list_as_dict=False)
  143. self.inject_data(data, "volumes", self.get_volumes(), list_as_dict=False)
  144. self.inject_data(data, "environment", self.environment)
  145. self.inject_data(data, "env_file", self.env_file)
  146. self.inject_data(data, "depends_on", self.depends_on)
  147. self.inject_data(data, "command", self.command)
  148. self.inject_data(data, "healthcheck", self.healthcheck)
  149. self.inject_data(data, "restart", self.restart)
  150. if self.privileged:
  151. self.inject_data(data, "privileged", self.privileged)
  152. if self.network_mode:
  153. self.inject_data(data, "network_mode", self.network_mode)
  154. return data
  155. class ServiceVolume(ComposeObject):
  156. BIND_PROPAGATION_SHARED = "shared"
  157. def __init__(self, src, target, readonly=False, bind=None):
  158. self.src = src
  159. self.target = target
  160. self.readonly = readonly
  161. self.bind = bind
  162. def __str__(self):
  163. ret = '%s:%s' % (self.src, self.target)
  164. if self.readonly:
  165. ret = f"{ret}:ro"
  166. return ret
  167. def to_output(self):
  168. if self.bind:
  169. return {
  170. "type": "bind",
  171. "source": self.src,
  172. "target": self.target,
  173. "read_only": self.readonly,
  174. "bind": {
  175. "propagation": self.bind,
  176. },
  177. }
  178. else:
  179. return self.__str__()
  180. class ServiceDataVolume(ServiceVolume):
  181. SRC_DATA_DIR = "./data"
  182. def __init__(self, target_dir, readonly=False, bind=None):
  183. super().__init__(
  184. self.SRC_DATA_DIR + target_dir,
  185. target_dir,
  186. readonly=readonly, bind=bind)
  187. class ServicePort(ComposeObject):
  188. def __init__(self, src, target, protocol='tcp'):
  189. self.src_port = src
  190. self.target_port = target
  191. self.protocol = protocol
  192. def __str__(self):
  193. return '%s:%s/%s' % (self.src_port, self.target_port, self.protocol)
  194. def to_output(self):
  195. return self.__str__()
  196. class ComposeManifest(ComposeObject):
  197. def __init__(self, version="3.9"):
  198. self.version = version
  199. self.services = []
  200. self.volumes = []
  201. def add_service(self, *svc):
  202. if svc is None:
  203. return self
  204. self.services = self.services + list(svc)
  205. return self
  206. def add_volume(self, *vols):
  207. self.volumes = self.volumes + list(vols)
  208. return self
  209. def to_output(self):
  210. result = {}
  211. self.inject_data(result, "version", self.version)
  212. self.inject_data(result, "services", self.services)
  213. self.inject_data(result, "volumes", self.volumes)
  214. return result