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.

226 lines
7.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 subprocess
  8. import csv
  9. import io
  10. import os
  11. class AmbiguousValueError(ValueError):
  12. def __init__(self, msg):
  13. super().__init__(msg)
  14. def zfs_run(command):
  15. # print('Running:', command)
  16. out = subprocess.check_output(command, stderr=subprocess.STDOUT)
  17. return out
  18. def zfs_parse_output(command):
  19. out = zfs_run(command)
  20. s = io.StringIO(out.decode('utf-8'))
  21. r = csv.reader(s, delimiter='\t')
  22. return [a for a in r]
  23. def zfs_get_type(name):
  24. lst = zfs_parse_output(['zfs', 'list', '-o', 'name,type', '-H', name])
  25. return lst[0][1]
  26. def zfs_snapshot_by_tag_or_sha256(s, focker_type='image'):
  27. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,focker:tags,type,name',
  28. '-H', '-t', 'snapshot', '-r', poolname + '/focker/' + focker_type + 's'])
  29. lst = list(filter(lambda a: (a[0] == s or s in a[1].split(' ')) and a[2] == 'snapshot', lst))
  30. if len(lst) == 0:
  31. raise ValueError('Reference not found: ' + s)
  32. if len(lst) > 1:
  33. raise AmbiguousValueError('Ambiguous reference: ' + s)
  34. return (lst[0][3], lst[0][0])
  35. def zfs_find(reference, focker_type='image', zfs_type='filesystem'):
  36. poolname = zfs_poolname()
  37. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,focker:tags,type,name', '-H', '-t', zfs_type, '-r', poolname + '/focker/' + focker_type + 's'])
  38. def match(sha256, tags, type, name, exact=False):
  39. if exact:
  40. predicate = lambda a: (a == reference)
  41. else:
  42. predicate = lambda a: a.startswith(reference)
  43. if predicate(sha256) or \
  44. any(map(predicate, tags.split(' '))) or \
  45. predicate(name.split('/')[-1]):
  46. return True
  47. return False
  48. lst = list(filter(lambda a: match(*a), lst))
  49. exact_lst = list(filter(lambda a: match(*a, exact=True), lst))
  50. if len(lst) == 0:
  51. raise ValueError('Reference not found: ' + reference)
  52. if len(lst) > 1:
  53. if len(exact_lst) == 1:
  54. return (exact_lst[0][3], exact_lst[0][0])
  55. raise AmbiguousValueError('Ambiguous reference: ' + reference)
  56. return (lst[0][3], lst[0][0])
  57. def zfs_list(fields=['name'], focker_type='image', zfs_type='filesystem'):
  58. poolname = zfs_poolname()
  59. fields.append('focker:sha256')
  60. lst = zfs_parse_output(['zfs', 'list', '-o', ','.join(fields),
  61. '-H', '-t', zfs_type, '-r', poolname + '/focker/' + focker_type + 's'])
  62. lst = list(filter(lambda a: a[-1] != '-', lst))
  63. return lst
  64. def zfs_prune(focker_type='image'):
  65. poolname = zfs_poolname()
  66. again = True
  67. while again:
  68. again = False
  69. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,focker:tags,origin,name,focker:protect', '-H', '-r', poolname + '/focker/' + focker_type + 's'])
  70. used = set()
  71. for r in lst:
  72. if r[2] == '-':
  73. continue
  74. used.add(r[2].split('@')[0])
  75. for r in lst:
  76. if r[0] == '-' or r[1] != '-':
  77. continue
  78. if r[3] in used:
  79. continue
  80. if r[4] != '-':
  81. print('%s is protected against removal' % r[3])
  82. continue
  83. print('Removing:', r[3])
  84. zfs_run(['zfs', 'destroy', '-r', '-f', r[3]])
  85. again = True
  86. def zfs_destroy(name):
  87. lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:protect', name])
  88. if lst[0][2] != '-':
  89. raise RuntimeError('%s is protected against removal' % name)
  90. zfs_run(['zfs', 'destroy', '-r', '-f', name])
  91. def zfs_protect(name):
  92. zfs_run(['zfs', 'set', 'focker:protect=on', name])
  93. def zfs_unprotect(name):
  94. zfs_run(['zfs', 'inherit', '-r', 'focker:protect', name])
  95. def zfs_clone(name, target_name):
  96. zfs_run(['zfs', 'clone', name, target_name])
  97. def zfs_exists(name):
  98. try:
  99. zfs_run(['zfs', 'list', name])
  100. except subprocess.CalledProcessError as e:
  101. return False
  102. return True
  103. def zfs_set_props(name, props):
  104. for (k, v) in props.items():
  105. zfs_run(['zfs', 'set', k + '=' + v, name])
  106. def zfs_mountpoint(name):
  107. lst = zfs_parse_output(['zfs', 'list', '-o', 'mountpoint', '-H', name])
  108. return lst[0][0]
  109. def zfs_exists_snapshot_sha256(sha256, focker_type='image'):
  110. poolname = zfs_poolname()
  111. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256', '-t', 'snap',
  112. '-r', poolname + '/focker/' + focker_type + 's'])
  113. lst = list(filter(lambda a: a[0] == sha256, lst))
  114. if len(lst) == 0:
  115. return False
  116. return True
  117. def zfs_snapshot_by_sha256(sha256, focker_type='image'):
  118. poolname = zfs_poolname()
  119. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,name',
  120. '-t', 'snap', '-H', '-r', poolname + '/focker/' + focker_type + 's'])
  121. lst = list(filter(lambda a: a[0] == sha256, lst))
  122. if len(lst) == 0:
  123. raise ValueError('Snapshot with given sha256 does not exist: ' + sha256)
  124. if len(lst) > 1:
  125. raise AmbiguousValueError('Ambiguous snapshot sha256: ' + sha256)
  126. return lst[0][1]
  127. def zfs_tag(name, tags, replace=False):
  128. if any(map(lambda a: ' ' in a, tags)):
  129. raise ValueError('Tags cannot contain spaces')
  130. lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:tags', '-H', name])
  131. if not replace:
  132. tags = list(tags)
  133. tags.extend(lst[0][0].split(' '))
  134. tags = list(set(tags))
  135. tags = list(filter(lambda a: a != '-', tags))
  136. if len(tags) > 0:
  137. zfs_run(['zfs', 'set', 'focker:tags=' + ' '.join(tags), name])
  138. else:
  139. zfs_run(['zfs', 'inherit', 'focker:tags', name])
  140. def zfs_untag(tags, focker_type='image'):
  141. if any(map(lambda a: ' ' in a, tags)):
  142. raise ValueError('Tags cannot contain spaces')
  143. # print('zfs_untag(), tags:', tags)
  144. poolname = zfs_poolname()
  145. lst = zfs_parse_output(['zfs', 'list', '-o', 'name,focker:tags', '-H', '-r', poolname + '/focker/' + focker_type + 's'])
  146. lst = filter(lambda a: any([b in a[1].split(' ') for b in tags]), lst)
  147. for row in lst:
  148. cur_tags = row[1].split(' ')
  149. for t in tags:
  150. if t in cur_tags:
  151. cur_tags.remove(t)
  152. zfs_tag(row[0], cur_tags, replace=True)
  153. def zfs_name(path):
  154. lst = zfs_parse_output(['zfs', 'list', '-o', 'name', '-H', path])
  155. if len(lst) == 0:
  156. raise ValueError('Not a ZFS path')
  157. if len(lst) > 1:
  158. raise AmbiguousValueError('Ambiguous ZFS path')
  159. return lst[0][0]
  160. def zfs_poolname():
  161. poolname = zfs_parse_output(['zfs', 'list', '-H', '/'])
  162. if len(poolname) == 0:
  163. raise ValueError('Not a ZFS root')
  164. poolname = poolname[0][0].split('/')[0]
  165. return poolname
  166. def zfs_init():
  167. poolname = zfs_poolname()
  168. print('poolname:', poolname)
  169. for path in ['/focker', '/focker/images', '/focker/volumes', '/focker/jails']:
  170. if not os.path.exists(path):
  171. os.mkdir(path)
  172. os.chmod('/focker', 0o600)
  173. if not zfs_exists(poolname + '/focker'):
  174. zfs_run(['zfs', 'create', '-o', 'canmount=off', '-o', 'mountpoint=/focker', poolname + '/focker'])
  175. if not zfs_exists(poolname + '/focker/images'):
  176. zfs_run(['zfs', 'create', '-o', 'canmount=off', poolname + '/focker/images'])
  177. if not zfs_exists(poolname + '/focker/volumes'):
  178. zfs_run(['zfs', 'create', '-o', 'canmount=off', poolname + '/focker/volumes'])
  179. if not zfs_exists(poolname + '/focker/jails'):
  180. zfs_run(['zfs', 'create', '-o', 'canmount=off', poolname + '/focker/jails'])