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.

178 lines
5.7KB

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