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!
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
9.2KB

  1. import subprocess
  2. from .zfs import *
  3. import random
  4. import shutil
  5. import json
  6. from tabulate import tabulate
  7. import os
  8. import jailconf
  9. from .mount import getmntinfo
  10. def jail_fs_create(image):
  11. image, _ = zfs_find(image, focker_type='image', zfs_type='snapshot')
  12. sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex()
  13. lst = zfs_list(fields=['focker:sha256'], focker_type='image')
  14. lst = list(filter(lambda a: a[0] == sha256, lst))
  15. if lst:
  16. raise ValueError('Whew, a collision...')
  17. poolname = zfs_poolname()
  18. for pre in range(7, 32):
  19. name = poolname + '/focker/jails/' + sha256[:pre]
  20. if not zfs_exists(name):
  21. break
  22. zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, image, name])
  23. return name
  24. def gen_env_command(command, env):
  25. env = [ 'export ' + k + '=' + quote(v) \
  26. for (k, v) in env.items() ]
  27. command = ' && '.join(env + [ command ])
  28. return command
  29. def quote(s):
  30. s = s.replace('\\', '\\\\')
  31. s = s.replace('\'', '\\\'')
  32. s = '\'' + s + '\''
  33. return s
  34. def jail_create(path, command, env, mounts, hostname=None):
  35. name = os.path.split(path)[-1]
  36. if os.path.exists('/etc/jail.conf'):
  37. conf = jailconf.load('/etc/jail.conf')
  38. else:
  39. conf = jailconf.JailConf()
  40. conf[name] = blk = jailconf.JailBlock()
  41. blk['path'] = path
  42. if command:
  43. command = gen_env_command(command, env)
  44. command = quote(command)
  45. print('command:', command)
  46. blk['exec.start'] = command
  47. prestart = [ 'cp /etc/resolv.conf ' +
  48. quote(os.path.join(path, 'etc/resolv.conf')) ]
  49. poststop = []
  50. if mounts:
  51. for (from_, on) in mounts:
  52. if not from_.startswith('/'):
  53. from_, _ = zfs_find(from_, focker_type='volume')
  54. from_ = zfs_mountpoint(from_)
  55. prestart.append('mount -t nullfs ' + quote(from_) +
  56. ' ' + quote(os.path.join(path, on.strip('/'))))
  57. poststop += [ 'umount -f ' +
  58. os.path.join(path, on.strip('/')) \
  59. for (_, on) in reversed(mounts) ]
  60. if prestart:
  61. blk['exec.prestart'] = quote(' && '.join(prestart))
  62. if poststop:
  63. blk['exec.poststop'] = quote(' && '.join(poststop))
  64. blk['persist'] = True
  65. blk['interface'] = 'lo1'
  66. blk['ip4.addr'] = '127.0.1.0'
  67. blk['mount.devfs'] = True
  68. blk['exec.clean'] = True
  69. blk['host.hostname'] = hostname or name
  70. conf.write('/etc/jail.conf')
  71. return name
  72. def jail_run_v2(path, command, env, mounts):
  73. name = os.path.split(path)[-1]
  74. command = gen_env_command(command, env)
  75. jail_create(path, None, None, mounts)
  76. subprocess.check_output([ 'jail', '-c', name ])
  77. subprocess.run([ 'jexec', name, '/bin/sh', '-c', command ])
  78. subprocess.check_output([ 'jail', '-r', name ])
  79. def get_jid(path):
  80. data = json.loads(subprocess.check_output(['jls', '--libxo=json']))
  81. lst = data['jail-information']['jail']
  82. lst = list(filter(lambda a: a['path'] == path, lst))
  83. if len(lst) == 0:
  84. raise ValueError('JID not found for path: ' + path)
  85. if len(lst) > 1:
  86. raise ValueError('Ambiguous JID for path: ' + path)
  87. return str(lst[0]['jid'])
  88. def do_mounts(path, mounts):
  89. print('mounts:', mounts)
  90. for (source, target) in mounts:
  91. if source.startswith('/'):
  92. name = source
  93. else:
  94. name, _ = zfs_find(source, focker_type='volume')
  95. name = zfs_mountpoint(name)
  96. while target.startswith('/'):
  97. target = target[1:]
  98. subprocess.check_output(['mount', '-t', 'nullfs', name, os.path.join(path, target)])
  99. def undo_mounts(path, mounts):
  100. for (_, target) in reversed(mounts):
  101. while target.startswith('/'):
  102. target = target[1:]
  103. subprocess.check_output(['umount', '-f', os.path.join(path, target)])
  104. def jail_run(path, command, mounts=[]):
  105. command = ['jail', '-c', 'host.hostname=' + os.path.split(path)[1], 'persist=1', 'mount.devfs=1', 'interface=lo1', 'ip4.addr=127.0.1.0', 'path=' + path, 'command', '/bin/sh', '-c', command]
  106. print('Running:', ' '.join(command))
  107. try:
  108. do_mounts(path, mounts)
  109. shutil.copyfile('/etc/resolv.conf', os.path.join(path, 'etc/resolv.conf'))
  110. res = subprocess.run(command)
  111. finally:
  112. try:
  113. subprocess.run(['jail', '-r', get_jid(path)])
  114. except ValueError:
  115. pass
  116. subprocess.run(['umount', '-f', os.path.join(path, 'dev')])
  117. undo_mounts(path, mounts)
  118. if res.returncode != 0:
  119. # subprocess.run(['umount', os.path.join(path, 'dev')])
  120. raise RuntimeError('Command failed')
  121. def jail_stop(path):
  122. try:
  123. jid = get_jid(path)
  124. jailname = os.path.split(path)[-1]
  125. subprocess.run(['jail', '-r', jailname])
  126. except ValueError:
  127. print('JID could not be determined')
  128. # import time
  129. # time.sleep(1)
  130. mi = getmntinfo()
  131. for m in mi:
  132. mntonname = m['f_mntonname'].decode('utf-8')
  133. if mntonname.startswith(path + os.path.sep):
  134. print('Unmounting:', mntonname)
  135. subprocess.run(['umount', '-f', mntonname])
  136. def jail_remove(path):
  137. print('Removing jail:', path)
  138. jail_stop(path)
  139. subprocess.run(['zfs', 'destroy', '-r', '-f', zfs_name(path)])
  140. if os.path.exists('/etc/jail.conf'):
  141. conf = jailconf.load('/etc/jail.conf')
  142. name = os.path.split(path)[-1]
  143. if name in conf:
  144. del conf[name]
  145. conf.write('/etc/jail.conf')
  146. def command_jail_create(args):
  147. name = jail_fs_create(args.image)
  148. if args.tags:
  149. zfs_tag(name, args.tags)
  150. path = zfs_mountpoint(name)
  151. jail_create(path, args.command,
  152. { a.split(':')[0]: ':'.join(a.split(':')[1:]) \
  153. for a in args.env },
  154. [ [a.split(':')[0], ':'.join(a.split(':')[1:])] \
  155. for a in args.mounts ],
  156. args.hostname )
  157. print(sha256)
  158. print(path)
  159. def command_jail_start(args):
  160. name, _ = zfs_find(args.reference, focker_type='jail')
  161. path = zfs_mountpoint(name)
  162. jailname = os.path.split(path)[-1]
  163. subprocess.run(['jail', '-c', jailname])
  164. def command_jail_stop(args):
  165. name, _ = zfs_find(args.reference, focker_type='jail')
  166. path = zfs_mountpoint(name)
  167. jail_stop(path)
  168. def command_jail_remove(args):
  169. name, _ = zfs_find(args.reference, focker_type='jail')
  170. path = zfs_mountpoint(name)
  171. jail_remove(path)
  172. def command_jail_exec(args):
  173. name, _ = zfs_find(args.reference, focker_type='jail')
  174. path = zfs_mountpoint(name)
  175. jid = get_jid(path)
  176. subprocess.run(['jexec', str(jid)] + args.command)
  177. def command_jail_oneshot(args):
  178. name = jail_fs_create(args.image)
  179. path = zfs_mountpoint(name)
  180. env = { a.split(':')[0]: ':'.join(a.split(':')[1:]) \
  181. for a in args.env }
  182. mounts = [ [ a.split(':')[0], a.split(':')[1] ] \
  183. for a in args.mounts]
  184. jailname = jail_create(path, ' '.join(map(quote, args.command)), env, mounts)
  185. subprocess.run(['jail', '-c', jailname])
  186. jail_remove(path)
  187. # Deprecated
  188. def command_jail_oneshot_old():
  189. base, _ = zfs_snapshot_by_tag_or_sha256(args.image)
  190. # root = '/'.join(base.split('/')[:-1])
  191. for _ in range(10**6):
  192. sha256 = bytes([ random.randint(0, 255) for _ in range(32) ]).hex()
  193. name = sha256[:7]
  194. name = base.split('/')[0] + '/focker/jails/' + name
  195. if not zfs_exists(name):
  196. break
  197. zfs_run(['zfs', 'clone', '-o', 'focker:sha256=' + sha256, base, name])
  198. try:
  199. mounts = list(map(lambda a: a.split(':'), args.mounts))
  200. jail_run(zfs_mountpoint(name), args.command, mounts)
  201. # subprocess.check_output(['jail', '-c', 'interface=lo1', 'ip4.addr=127.0.1.0', 'path=' + zfs_mountpoint(name), 'command', command])
  202. finally:
  203. # subprocess.run(['umount', zfs_mountpoint(name) + '/dev'])
  204. zfs_run(['zfs', 'destroy', '-f', name])
  205. # raise
  206. def command_jail_list(args):
  207. lst = zfs_list(fields=['focker:sha256,focker:tags,mountpoint'], focker_type='jail')
  208. jails = subprocess.check_output(['jls', '--libxo=json'])
  209. jails = json.loads(jails)['jail-information']['jail']
  210. jails = { j['path']: j for j in jails }
  211. lst = list(map(lambda a: [ a[1],
  212. a[0] if args.full_sha256 else a[0][:7],
  213. a[2],
  214. jails[a[2]]['jid'] if a[2] in jails else '-' ], lst))
  215. print(tabulate(lst, headers=['Tags', 'SHA256', 'mountpoint', 'JID']))
  216. def command_jail_tag(args):
  217. name, _ = zfs_find(args.reference, focker_type='jail')
  218. zfs_untag(args.tags, focker_type='jail')
  219. zfs_tag(name, args.tags)
  220. def command_jail_untag(args):
  221. zfs_untag(args.tags, focker_type='jail')
  222. def command_jail_prune(args):
  223. jails = subprocess.check_output(['jls', '--libxo=json'])
  224. jails = json.loads(jails)['jail-information']['jail']
  225. used = set()
  226. for j in jails:
  227. used.add(j['path'])
  228. lst = zfs_list(fields=['focker:sha256,focker:tags,mountpoint,name'], focker_type='jail')
  229. for j in lst:
  230. if j[1] == '-' and (j[2] not in used or args.force):
  231. jail_remove(j[2])