Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.py 13KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. import base64
  2. import collections
  3. import json
  4. import os
  5. import os.path
  6. import shlex
  7. import string
  8. from datetime import datetime
  9. from packaging.version import Version
  10. from .. import errors
  11. from ..constants import DEFAULT_HTTP_HOST
  12. from ..constants import DEFAULT_UNIX_SOCKET
  13. from ..constants import DEFAULT_NPIPE
  14. from ..constants import BYTE_UNITS
  15. from ..tls import TLSConfig
  16. from urllib.parse import urlparse, urlunparse
  17. URLComponents = collections.namedtuple(
  18. 'URLComponents',
  19. 'scheme netloc url params query fragment',
  20. )
  21. def create_ipam_pool(*args, **kwargs):
  22. raise errors.DeprecatedMethod(
  23. 'utils.create_ipam_pool has been removed. Please use a '
  24. 'docker.types.IPAMPool object instead.'
  25. )
  26. def create_ipam_config(*args, **kwargs):
  27. raise errors.DeprecatedMethod(
  28. 'utils.create_ipam_config has been removed. Please use a '
  29. 'docker.types.IPAMConfig object instead.'
  30. )
  31. def decode_json_header(header):
  32. data = base64.b64decode(header)
  33. data = data.decode('utf-8')
  34. return json.loads(data)
  35. def compare_version(v1, v2):
  36. """Compare docker versions
  37. >>> v1 = '1.9'
  38. >>> v2 = '1.10'
  39. >>> compare_version(v1, v2)
  40. 1
  41. >>> compare_version(v2, v1)
  42. -1
  43. >>> compare_version(v2, v2)
  44. 0
  45. """
  46. s1 = Version(v1)
  47. s2 = Version(v2)
  48. if s1 == s2:
  49. return 0
  50. elif s1 > s2:
  51. return -1
  52. else:
  53. return 1
  54. def version_lt(v1, v2):
  55. return compare_version(v1, v2) > 0
  56. def version_gte(v1, v2):
  57. return not version_lt(v1, v2)
  58. def _convert_port_binding(binding):
  59. result = {'HostIp': '', 'HostPort': ''}
  60. if isinstance(binding, tuple):
  61. if len(binding) == 2:
  62. result['HostPort'] = binding[1]
  63. result['HostIp'] = binding[0]
  64. elif isinstance(binding[0], str):
  65. result['HostIp'] = binding[0]
  66. else:
  67. result['HostPort'] = binding[0]
  68. elif isinstance(binding, dict):
  69. if 'HostPort' in binding:
  70. result['HostPort'] = binding['HostPort']
  71. if 'HostIp' in binding:
  72. result['HostIp'] = binding['HostIp']
  73. else:
  74. raise ValueError(binding)
  75. else:
  76. result['HostPort'] = binding
  77. if result['HostPort'] is None:
  78. result['HostPort'] = ''
  79. else:
  80. result['HostPort'] = str(result['HostPort'])
  81. return result
  82. def convert_port_bindings(port_bindings):
  83. result = {}
  84. for k, v in iter(port_bindings.items()):
  85. key = str(k)
  86. if '/' not in key:
  87. key += '/tcp'
  88. if isinstance(v, list):
  89. result[key] = [_convert_port_binding(binding) for binding in v]
  90. else:
  91. result[key] = [_convert_port_binding(v)]
  92. return result
  93. def convert_volume_binds(binds):
  94. if isinstance(binds, list):
  95. return binds
  96. result = []
  97. for k, v in binds.items():
  98. if isinstance(k, bytes):
  99. k = k.decode('utf-8')
  100. if isinstance(v, dict):
  101. if 'ro' in v and 'mode' in v:
  102. raise ValueError(
  103. 'Binding cannot contain both "ro" and "mode": {}'
  104. .format(repr(v))
  105. )
  106. bind = v['bind']
  107. if isinstance(bind, bytes):
  108. bind = bind.decode('utf-8')
  109. if 'ro' in v:
  110. mode = 'ro' if v['ro'] else 'rw'
  111. elif 'mode' in v:
  112. mode = v['mode']
  113. else:
  114. mode = 'rw'
  115. result.append(
  116. f'{k}:{bind}:{mode}'
  117. )
  118. else:
  119. if isinstance(v, bytes):
  120. v = v.decode('utf-8')
  121. result.append(
  122. f'{k}:{v}:rw'
  123. )
  124. return result
  125. def convert_tmpfs_mounts(tmpfs):
  126. if isinstance(tmpfs, dict):
  127. return tmpfs
  128. if not isinstance(tmpfs, list):
  129. raise ValueError(
  130. 'Expected tmpfs value to be either a list or a dict, found: {}'
  131. .format(type(tmpfs).__name__)
  132. )
  133. result = {}
  134. for mount in tmpfs:
  135. if isinstance(mount, str):
  136. if ":" in mount:
  137. name, options = mount.split(":", 1)
  138. else:
  139. name = mount
  140. options = ""
  141. else:
  142. raise ValueError(
  143. "Expected item in tmpfs list to be a string, found: {}"
  144. .format(type(mount).__name__)
  145. )
  146. result[name] = options
  147. return result
  148. def convert_service_networks(networks):
  149. if not networks:
  150. return networks
  151. if not isinstance(networks, list):
  152. raise TypeError('networks parameter must be a list.')
  153. result = []
  154. for n in networks:
  155. if isinstance(n, str):
  156. n = {'Target': n}
  157. result.append(n)
  158. return result
  159. def parse_repository_tag(repo_name):
  160. parts = repo_name.rsplit('@', 1)
  161. if len(parts) == 2:
  162. return tuple(parts)
  163. parts = repo_name.rsplit(':', 1)
  164. if len(parts) == 2 and '/' not in parts[1]:
  165. return tuple(parts)
  166. return repo_name, None
  167. def parse_host(addr, is_win32=False, tls=False):
  168. # Sensible defaults
  169. if not addr and is_win32:
  170. return DEFAULT_NPIPE
  171. if not addr or addr.strip() == 'unix://':
  172. return DEFAULT_UNIX_SOCKET
  173. addr = addr.strip()
  174. parsed_url = urlparse(addr)
  175. proto = parsed_url.scheme
  176. if not proto or any([x not in string.ascii_letters + '+' for x in proto]):
  177. # https://bugs.python.org/issue754016
  178. parsed_url = urlparse('//' + addr, 'tcp')
  179. proto = 'tcp'
  180. if proto == 'fd':
  181. raise errors.DockerException('fd protocol is not implemented')
  182. # These protos are valid aliases for our library but not for the
  183. # official spec
  184. if proto == 'http' or proto == 'https':
  185. tls = proto == 'https'
  186. proto = 'tcp'
  187. elif proto == 'http+unix':
  188. proto = 'unix'
  189. if proto not in ('tcp', 'unix', 'npipe', 'ssh'):
  190. raise errors.DockerException(
  191. f"Invalid bind address protocol: {addr}"
  192. )
  193. if proto == 'tcp' and not parsed_url.netloc:
  194. # "tcp://" is exceptionally disallowed by convention;
  195. # omitting a hostname for other protocols is fine
  196. raise errors.DockerException(
  197. f'Invalid bind address format: {addr}'
  198. )
  199. if any([
  200. parsed_url.params, parsed_url.query, parsed_url.fragment,
  201. parsed_url.password
  202. ]):
  203. raise errors.DockerException(
  204. f'Invalid bind address format: {addr}'
  205. )
  206. if parsed_url.path and proto == 'ssh':
  207. raise errors.DockerException(
  208. 'Invalid bind address format: no path allowed for this protocol:'
  209. ' {}'.format(addr)
  210. )
  211. else:
  212. path = parsed_url.path
  213. if proto == 'unix' and parsed_url.hostname is not None:
  214. # For legacy reasons, we consider unix://path
  215. # to be valid and equivalent to unix:///path
  216. path = '/'.join((parsed_url.hostname, path))
  217. netloc = parsed_url.netloc
  218. if proto in ('tcp', 'ssh'):
  219. port = parsed_url.port or 0
  220. if port <= 0:
  221. if proto != 'ssh':
  222. raise errors.DockerException(
  223. 'Invalid bind address format: port is required:'
  224. ' {}'.format(addr)
  225. )
  226. port = 22
  227. netloc = f'{parsed_url.netloc}:{port}'
  228. if not parsed_url.hostname:
  229. netloc = f'{DEFAULT_HTTP_HOST}:{port}'
  230. # Rewrite schemes to fit library internals (requests adapters)
  231. if proto == 'tcp':
  232. proto = 'http{}'.format('s' if tls else '')
  233. elif proto == 'unix':
  234. proto = 'http+unix'
  235. if proto in ('http+unix', 'npipe'):
  236. return f"{proto}://{path}".rstrip('/')
  237. return urlunparse(URLComponents(
  238. scheme=proto,
  239. netloc=netloc,
  240. url=path,
  241. params='',
  242. query='',
  243. fragment='',
  244. )).rstrip('/')
  245. def parse_devices(devices):
  246. device_list = []
  247. for device in devices:
  248. if isinstance(device, dict):
  249. device_list.append(device)
  250. continue
  251. if not isinstance(device, str):
  252. raise errors.DockerException(
  253. f'Invalid device type {type(device)}'
  254. )
  255. device_mapping = device.split(':')
  256. if device_mapping:
  257. path_on_host = device_mapping[0]
  258. if len(device_mapping) > 1:
  259. path_in_container = device_mapping[1]
  260. else:
  261. path_in_container = path_on_host
  262. if len(device_mapping) > 2:
  263. permissions = device_mapping[2]
  264. else:
  265. permissions = 'rwm'
  266. device_list.append({
  267. 'PathOnHost': path_on_host,
  268. 'PathInContainer': path_in_container,
  269. 'CgroupPermissions': permissions
  270. })
  271. return device_list
  272. def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None):
  273. if not environment:
  274. environment = os.environ
  275. host = environment.get('DOCKER_HOST')
  276. # empty string for cert path is the same as unset.
  277. cert_path = environment.get('DOCKER_CERT_PATH') or None
  278. # empty string for tls verify counts as "false".
  279. # Any value or 'unset' counts as true.
  280. tls_verify = environment.get('DOCKER_TLS_VERIFY')
  281. if tls_verify == '':
  282. tls_verify = False
  283. else:
  284. tls_verify = tls_verify is not None
  285. enable_tls = cert_path or tls_verify
  286. params = {}
  287. if host:
  288. params['base_url'] = host
  289. if not enable_tls:
  290. return params
  291. if not cert_path:
  292. cert_path = os.path.join(os.path.expanduser('~'), '.docker')
  293. if not tls_verify and assert_hostname is None:
  294. # assert_hostname is a subset of TLS verification,
  295. # so if it's not set already then set it to false.
  296. assert_hostname = False
  297. params['tls'] = TLSConfig(
  298. client_cert=(os.path.join(cert_path, 'cert.pem'),
  299. os.path.join(cert_path, 'key.pem')),
  300. ca_cert=os.path.join(cert_path, 'ca.pem'),
  301. verify=tls_verify,
  302. ssl_version=ssl_version,
  303. assert_hostname=assert_hostname,
  304. )
  305. return params
  306. def convert_filters(filters):
  307. result = {}
  308. for k, v in iter(filters.items()):
  309. if isinstance(v, bool):
  310. v = 'true' if v else 'false'
  311. if not isinstance(v, list):
  312. v = [v, ]
  313. result[k] = [
  314. str(item) if not isinstance(item, str) else item
  315. for item in v
  316. ]
  317. return json.dumps(result)
  318. def datetime_to_timestamp(dt):
  319. """Convert a UTC datetime to a Unix timestamp"""
  320. delta = dt - datetime.utcfromtimestamp(0)
  321. return delta.seconds + delta.days * 24 * 3600
  322. def parse_bytes(s):
  323. if isinstance(s, (int, float,)):
  324. return s
  325. if len(s) == 0:
  326. return 0
  327. if s[-2:-1].isalpha() and s[-1].isalpha():
  328. if s[-1] == "b" or s[-1] == "B":
  329. s = s[:-1]
  330. units = BYTE_UNITS
  331. suffix = s[-1].lower()
  332. # Check if the variable is a string representation of an int
  333. # without a units part. Assuming that the units are bytes.
  334. if suffix.isdigit():
  335. digits_part = s
  336. suffix = 'b'
  337. else:
  338. digits_part = s[:-1]
  339. if suffix in units.keys() or suffix.isdigit():
  340. try:
  341. digits = float(digits_part)
  342. except ValueError:
  343. raise errors.DockerException(
  344. 'Failed converting the string value for memory ({}) to'
  345. ' an integer.'.format(digits_part)
  346. )
  347. # Reconvert to long for the final result
  348. s = int(digits * units[suffix])
  349. else:
  350. raise errors.DockerException(
  351. 'The specified value for memory ({}) should specify the'
  352. ' units. The postfix should be one of the `b` `k` `m` `g`'
  353. ' characters'.format(s)
  354. )
  355. return s
  356. def normalize_links(links):
  357. if isinstance(links, dict):
  358. links = iter(links.items())
  359. return [f'{k}:{v}' if v else k for k, v in sorted(links)]
  360. def parse_env_file(env_file):
  361. """
  362. Reads a line-separated environment file.
  363. The format of each line should be "key=value".
  364. """
  365. environment = {}
  366. with open(env_file) as f:
  367. for line in f:
  368. if line[0] == '#':
  369. continue
  370. line = line.strip()
  371. if not line:
  372. continue
  373. parse_line = line.split('=', 1)
  374. if len(parse_line) == 2:
  375. k, v = parse_line
  376. environment[k] = v
  377. else:
  378. raise errors.DockerException(
  379. 'Invalid line in environment file {}:\n{}'.format(
  380. env_file, line))
  381. return environment
  382. def split_command(command):
  383. return shlex.split(command)
  384. def format_environment(environment):
  385. def format_env(key, value):
  386. if value is None:
  387. return key
  388. if isinstance(value, bytes):
  389. value = value.decode('utf-8')
  390. return f'{key}={value}'
  391. return [format_env(*var) for var in iter(environment.items())]
  392. def format_extra_hosts(extra_hosts, task=False):
  393. # Use format dictated by Swarm API if container is part of a task
  394. if task:
  395. return [
  396. f'{v} {k}' for k, v in sorted(iter(extra_hosts.items()))
  397. ]
  398. return [
  399. f'{k}:{v}' for k, v in sorted(iter(extra_hosts.items()))
  400. ]
  401. def create_host_config(self, *args, **kwargs):
  402. raise errors.DeprecatedMethod(
  403. 'utils.create_host_config has been removed. Please use a '
  404. 'docker.types.HostConfig object instead.'
  405. )