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.

170 lines
5.4KB

  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. def build_images(spec, path, args):
  72. # print('build_images(): NotImplementedError')
  73. for (tag, focker_dir) in spec.items():
  74. cmd = ['focker', 'image', 'build',
  75. os.path.join(path, focker_dir), '-t', tag]
  76. if args.squeeze:
  77. cmd.append('--squeeze')
  78. focker_unlock()
  79. res = subprocess.run(cmd)
  80. focker_lock()
  81. if res.returncode != 0:
  82. raise RuntimeError('Image build failed: ' + str(res.returncode))
  83. def setup_dependencies(spec, generated_names):
  84. if os.path.exists('/etc/jail.conf'):
  85. conf = jailconf.load('/etc/jail.conf')
  86. else:
  87. conf = jailconf.JailConf()
  88. for (jailname, jailspec) in spec.items():
  89. if 'depend' not in jailspec:
  90. continue
  91. depend = jailspec.get('depend', [])
  92. if isinstance(depend, str):
  93. depend = [ depend ]
  94. if not isinstance(depend, list):
  95. raise ValueError('depend must be a string or a list of strings')
  96. # pdb.set_trace()
  97. depend = list(map(lambda a: generated_names[a], depend))
  98. if len(depend) == 1:
  99. depend = depend[0]
  100. conf[generated_names[jailname]]['depend'] = \
  101. depend
  102. conf.write('/etc/jail.conf')
  103. def build_jails(spec):
  104. backup_file('/etc/jail.conf')
  105. generated_names = {}
  106. for (jailname, jailspec) in spec.items():
  107. try:
  108. name, _ = zfs_find(jailname, focker_type='jail')
  109. jail_remove(zfs_mountpoint(name))
  110. except AmbiguousValueError:
  111. raise
  112. except ValueError:
  113. pass
  114. name = jail_fs_create(jailspec['image'])
  115. zfs_untag([ jailname ], focker_type='jail')
  116. zfs_tag(name, [ jailname ])
  117. path = zfs_mountpoint(name)
  118. generated_names[jailname] = jail_create(path,
  119. jailspec.get('exec.start', '/bin/sh /etc/rc'),
  120. jailspec.get('env', {}),
  121. [ [from_, on] \
  122. for (from_, on) in jailspec.get('mounts', {}).items() ],
  123. hostname=jailname,
  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. setup_dependencies(spec, generated_names)
  131. def command_compose_build(args):
  132. if not os.path.exists(args.filename):
  133. raise ValueError('File not found: ' + args.filename)
  134. path, _ = os.path.split(args.filename)
  135. print('path:', path)
  136. with open(args.filename, 'r') as f:
  137. spec = yaml.safe_load(f)
  138. if 'exec.prebuild' in spec:
  139. exec_prebuild(spec['exec.prebuild'], path)
  140. if 'volumes' in spec:
  141. build_volumes(spec['volumes'])
  142. if 'images' in spec:
  143. build_images(spec['images'], path, args)
  144. if 'jails' in spec:
  145. build_jails(spec['jails'])
  146. if 'exec.postbuild' in spec:
  147. exec_postbuild(spec['exec.postbuild'], path)
  148. def command_compose_run(args):
  149. raise NotImplementedError