IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
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.

191 lines
6.1KB

  1. #
  2. # Copyright (C) Stanislaw Adaszewski, 2020
  3. # License: GNU General Public License v3.0
  4. # URL: https://github.com/sadaszewski/focker
  5. # URL: https://adared.ch/focker
  6. #
  7. import os
  8. import yaml
  9. from .zfs import AmbiguousValueError, \
  10. zfs_find, \
  11. zfs_tag, \
  12. zfs_untag, \
  13. zfs_mountpoint, \
  14. zfs_poolname, \
  15. zfs_set_props
  16. from .jail import jail_fs_create, \
  17. jail_create, \
  18. jail_remove, \
  19. jail_stop, \
  20. backup_file, \
  21. quote
  22. from .misc import random_sha256_hexdigest, \
  23. find_prefix
  24. import subprocess
  25. import jailconf
  26. import os
  27. from .misc import focker_lock, \
  28. focker_unlock
  29. import pdb
  30. def exec_hook(spec, path, hook_name='exec.prebuild'):
  31. if isinstance(spec, str):
  32. spec = [ spec ]
  33. if not isinstance(spec, list):
  34. raise ValueError('%s should be a string or a list of strings' % hook_name)
  35. spec = ' && '.join(spec)
  36. print('Running %s command:' % hook_name, spec)
  37. spec = [ '/bin/sh', '-c', spec ]
  38. oldwd = os.getcwd()
  39. os.chdir(path)
  40. focker_unlock()
  41. res = subprocess.run(spec)
  42. focker_lock()
  43. if res.returncode != 0:
  44. raise RuntimeError('%s failed' % hook_name)
  45. os.chdir(oldwd)
  46. def exec_prebuild(spec, path):
  47. return exec_hook(spec, path, 'exec.prebuild')
  48. def exec_postbuild(spec, path):
  49. return exec_hook(spec, path, 'exec.postbuild')
  50. def build_volumes(spec):
  51. poolname = zfs_poolname()
  52. for tag, params in spec.items():
  53. name = None
  54. try:
  55. name, _ = zfs_find(tag, focker_type='volume')
  56. except ValueError:
  57. pass
  58. if name is None:
  59. sha256 = random_sha256_hexdigest()
  60. name = find_prefix(poolname + '/focker/volumes/', sha256)
  61. subprocess.check_output(['zfs', 'create', '-o', 'focker:sha256=' + sha256, name])
  62. zfs_untag([ tag ], focker_type='volume')
  63. zfs_tag(name, [ tag ])
  64. mountpoint = zfs_mountpoint(name)
  65. print('params:', params)
  66. if 'chown' in params:
  67. os.chown(mountpoint, *map(int, params['chown'].split(':')))
  68. if 'chmod' in params:
  69. os.chmod(mountpoint, params['chmod'])
  70. if 'zfs' in params:
  71. zfs_set_props(name, params['zfs'])
  72. if 'protect' in params:
  73. if params['protect']:
  74. zfs_set_props(name, { 'focker:protect': 'on' })
  75. else:
  76. zfs_run(['zfs', 'inherit', '-r', 'focker:protect', name])
  77. def build_images(spec, path, args):
  78. # print('build_images(): NotImplementedError')
  79. for (tag, focker_dir) in spec.items():
  80. cmd = ['focker', 'image', 'build',
  81. os.path.join(path, focker_dir), '-t', tag]
  82. if args.squeeze:
  83. cmd.append('--squeeze')
  84. focker_unlock()
  85. res = subprocess.run(cmd)
  86. focker_lock()
  87. if res.returncode != 0:
  88. raise RuntimeError('Image build failed: ' + str(res.returncode))
  89. def setup_dependencies(spec, generated_names):
  90. if os.path.exists('/etc/jail.conf'):
  91. conf = jailconf.load('/etc/jail.conf')
  92. else:
  93. conf = jailconf.JailConf()
  94. for (jailname, jailspec) in spec.items():
  95. if 'depend' not in jailspec:
  96. continue
  97. depend = jailspec.get('depend', [])
  98. if isinstance(depend, str):
  99. depend = [ depend ]
  100. if not isinstance(depend, list):
  101. raise ValueError('depend must be a string or a list of strings')
  102. # pdb.set_trace()
  103. depend = list(map(lambda a: generated_names[a], depend))
  104. if len(depend) == 1:
  105. depend = depend[0]
  106. conf[generated_names[jailname]]['depend'] = \
  107. depend
  108. conf.write('/etc/jail.conf')
  109. def build_jails(spec):
  110. backup_file('/etc/jail.conf')
  111. generated_names = {}
  112. for (jailname, jailspec) in spec.items():
  113. try:
  114. name, _ = zfs_find(jailname, focker_type='jail')
  115. jail_remove(zfs_mountpoint(name))
  116. except AmbiguousValueError:
  117. raise
  118. except ValueError:
  119. pass
  120. name = jail_fs_create(jailspec['image'])
  121. zfs_untag([ jailname ], focker_type='jail')
  122. zfs_tag(name, [ jailname ])
  123. path = zfs_mountpoint(name)
  124. overrides={
  125. 'exec.stop': jailspec.get('exec.stop', '/bin/sh /etc/rc.shutdown'),
  126. 'ip4.addr': jailspec.get('ip4.addr', '127.0.1.0'),
  127. 'interface': jailspec.get('interface', 'lo1'),
  128. 'host.hostname': jailspec.get('host.hostname', jailname)
  129. }
  130. if 'jail.conf' in jailspec:
  131. overrides.update(jailspec['jail.conf'])
  132. generated_names[jailname] = jail_create(path,
  133. jailspec.get('exec.start', '/bin/sh /etc/rc'),
  134. jailspec.get('env', {}),
  135. [ [from_, on] \
  136. for (from_, on) in jailspec.get('mounts', {}).items() ],
  137. hostname=jailname,
  138. overrides=overrides)
  139. setup_dependencies(spec, generated_names)
  140. def stop_jails(spec):
  141. for jailname, _ in spec.items():
  142. try:
  143. name, _ = zfs_find(jailname, focker_type='jail')
  144. except ValueError:
  145. continue
  146. print('Stopping:', jailname)
  147. jail_stop(zfs_mountpoint(name))
  148. def command_compose_build(args):
  149. if not os.path.exists(args.filename):
  150. raise ValueError('File not found: ' + args.filename)
  151. path, _ = os.path.split(args.filename)
  152. print('path:', path)
  153. with open(args.filename, 'r') as f:
  154. spec = yaml.safe_load(f)
  155. if 'jails' in spec:
  156. stop_jails(spec['jails'])
  157. if 'exec.prebuild' in spec:
  158. exec_prebuild(spec['exec.prebuild'], path)
  159. if 'volumes' in spec:
  160. build_volumes(spec['volumes'])
  161. if 'images' in spec:
  162. build_images(spec['images'], path, args)
  163. if 'jails' in spec:
  164. build_jails(spec['jails'])
  165. if 'exec.postbuild' in spec:
  166. exec_postbuild(spec['exec.postbuild'], path)
  167. def command_compose_run(args):
  168. raise NotImplementedError