qvm_template.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. from unittest import mock
  2. import argparse
  3. import asyncio
  4. import datetime
  5. import io
  6. import os
  7. import pathlib
  8. import subprocess
  9. import tempfile
  10. import rpm
  11. import qubesadmin.tests
  12. import qubesadmin.tools.qvm_template
  13. class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
  14. def setUp(self):
  15. super().setUp()
  16. def tearDown(self):
  17. super().tearDown()
  18. def test_000_verify_rpm_success(self):
  19. ts = mock.MagicMock()
  20. # Just return a dict instead of rpm.hdr
  21. hdr = {
  22. rpm.RPMTAG_SIGPGP: 'xxx', # non-empty
  23. rpm.RPMTAG_SIGGPG: 'xxx', # non-empty
  24. }
  25. ts.hdrFromFdno.return_value = hdr
  26. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null', ts)
  27. ts.hdrFromFdno.assert_called_once()
  28. self.assertEqual(hdr, ret)
  29. self.assertAllCalled()
  30. def test_001_verify_rpm_nosig_fail(self):
  31. ts = mock.MagicMock()
  32. # Just return a dict instead of rpm.hdr
  33. hdr = {
  34. rpm.RPMTAG_SIGPGP: None, # empty
  35. rpm.RPMTAG_SIGGPG: None, # empty
  36. }
  37. ts.hdrFromFdno.return_value = hdr
  38. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null', ts)
  39. ts.hdrFromFdno.assert_called_once()
  40. self.assertEqual(ret, None)
  41. self.assertAllCalled()
  42. def test_002_verify_rpm_nosig_success(self):
  43. ts = mock.MagicMock()
  44. # Just return a dict instead of rpm.hdr
  45. hdr = {
  46. rpm.RPMTAG_SIGPGP: None, # empty
  47. rpm.RPMTAG_SIGGPG: None, # empty
  48. }
  49. ts.hdrFromFdno.return_value = hdr
  50. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null', ts, True)
  51. ts.hdrFromFdno.assert_called_once()
  52. self.assertEqual(ret, hdr)
  53. self.assertAllCalled()
  54. def test_003_verify_rpm_badsig_fail(self):
  55. ts = mock.MagicMock()
  56. def f(*args):
  57. raise rpm.error('public key not trusted')
  58. ts.hdrFromFdno.side_effect = f
  59. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null', ts)
  60. ts.hdrFromFdno.assert_called_once()
  61. self.assertEqual(ret, None)
  62. self.assertAllCalled()
  63. @mock.patch('subprocess.Popen')
  64. def test_010_extract_rpm_success(self, mock_popen):
  65. pipe = mock.Mock()
  66. mock_popen.return_value.stdout = pipe
  67. mock_popen.return_value.wait.return_value = 0
  68. with tempfile.NamedTemporaryFile() as fd, \
  69. tempfile.TemporaryDirectory() as dir:
  70. path = fd.name
  71. dirpath = dir
  72. ret = qubesadmin.tools.qvm_template.extract_rpm(
  73. 'test-vm', path, dirpath)
  74. self.assertEqual(ret, True)
  75. self.assertEqual(mock_popen.mock_calls, [
  76. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  77. mock.call([
  78. 'cpio',
  79. '-idm',
  80. '-D',
  81. dirpath,
  82. './var/lib/qubes/vm-templates/test-vm/*'
  83. ], stdin=pipe, stdout=subprocess.DEVNULL),
  84. mock.call().wait(),
  85. mock.call().wait()
  86. ])
  87. self.assertAllCalled()
  88. @mock.patch('subprocess.Popen')
  89. def test_011_extract_rpm_fail(self, mock_popen):
  90. pipe = mock.Mock()
  91. mock_popen.return_value.stdout = pipe
  92. mock_popen.return_value.wait.return_value = 1
  93. with tempfile.NamedTemporaryFile() as fd, \
  94. tempfile.TemporaryDirectory() as dir:
  95. path = fd.name
  96. dirpath = dir
  97. ret = qubesadmin.tools.qvm_template.extract_rpm(
  98. 'test-vm', path, dirpath)
  99. self.assertEqual(ret, False)
  100. self.assertEqual(mock_popen.mock_calls, [
  101. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  102. mock.call([
  103. 'cpio',
  104. '-idm',
  105. '-D',
  106. dirpath,
  107. './var/lib/qubes/vm-templates/test-vm/*'
  108. ], stdin=pipe, stdout=subprocess.DEVNULL),
  109. mock.call().wait()
  110. ])
  111. self.assertAllCalled()
  112. def add_new_vm_side_effect(self, *args, **kwargs):
  113. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  114. b'0\0test-vm class=TemplateVM state=Halted\n'
  115. self.app.domains.clear_cache()
  116. return self.app.domains['test-vm']
  117. @mock.patch('os.remove')
  118. @mock.patch('os.rename')
  119. @mock.patch('os.makedirs')
  120. @mock.patch('subprocess.check_call')
  121. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  122. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  123. @mock.patch('qubesadmin.tools.qvm_template.download')
  124. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  125. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  126. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  127. def test_100_install_local_success(
  128. self,
  129. mock_ts,
  130. mock_verify,
  131. mock_dl_list,
  132. mock_dl,
  133. mock_extract,
  134. mock_confirm,
  135. mock_call,
  136. mock_mkdirs,
  137. mock_rename,
  138. mock_remove):
  139. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  140. build_time = '2020-09-01 22:30:00' # 1598970600
  141. install_time = '2020-09-01 23:30:00.508230'
  142. for key, val in [
  143. ('name', 'test-vm'),
  144. ('epoch', '2'),
  145. ('version', '4.1'),
  146. ('release', '2020'),
  147. ('reponame', '@commandline'),
  148. ('buildtime', build_time),
  149. ('installtime', install_time),
  150. ('license', 'GPL'),
  151. ('url', 'https://qubes-os.org'),
  152. ('summary', 'Summary'),
  153. ('description', 'Desc|desc')]:
  154. self.app.expected_calls[(
  155. 'test-vm',
  156. 'admin.vm.feature.Set',
  157. f'template-{key}',
  158. val.encode())] = b'0\0'
  159. mock_verify.return_value = {
  160. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  161. rpm.RPMTAG_BUILDTIME : 1598970600,
  162. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  163. rpm.RPMTAG_EPOCHNUM : 2,
  164. rpm.RPMTAG_LICENSE : 'GPL',
  165. rpm.RPMTAG_RELEASE : '2020',
  166. rpm.RPMTAG_SUMMARY : 'Summary',
  167. rpm.RPMTAG_URL : 'https://qubes-os.org',
  168. rpm.RPMTAG_VERSION : '4.1'
  169. }
  170. mock_dl_list.return_value = {}
  171. mock_call.side_effect = self.add_new_vm_side_effect
  172. mock_time = mock.Mock(wraps=datetime.datetime)
  173. mock_time.today.return_value = \
  174. datetime.datetime.fromisoformat(install_time)
  175. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  176. mock.patch('datetime.datetime', new=mock_time), \
  177. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  178. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  179. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  180. path = template_file.name
  181. args = argparse.Namespace(
  182. templates=[path],
  183. keyring='/usr/share/qubes/repo-templates/keys',
  184. nogpgcheck=False,
  185. cachedir='/var/cache/qvm-template',
  186. yes=False,
  187. allow_pv=False,
  188. pool=None
  189. )
  190. mock_tmpdir.return_value.__enter__.return_value = \
  191. '/var/tmp/qvm-template-tmpdir'
  192. qubesadmin.tools.qvm_template.install(args, self.app)
  193. # Lock file created
  194. self.assertEqual(mock_open.mock_calls, [
  195. mock.call('/var/tmp/qvm-template.lck', 'x'),
  196. mock.call().__enter__(),
  197. mock.call().__exit__(None, None, None)
  198. ])
  199. # Keyring created
  200. self.assertEqual(mock_ts.mock_calls, [
  201. mock.call('/usr/share/qubes/repo-templates/keys')
  202. ])
  203. # Package verified
  204. self.assertEqual(mock_verify.mock_calls, [
  205. mock.call(path, mock_ts('/usr/share/qubes/repo-templates/keys'),
  206. False)
  207. ])
  208. # Attempt to get download list
  209. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  210. self.assertEqual(mock_dl_list.mock_calls, [
  211. mock.call(args, self.app, version_selector=selector)
  212. ])
  213. # Nothing downloaded
  214. self.assertEqual(mock_dl.mock_calls, [
  215. mock.call(args, self.app, path_override='/var/cache/qvm-template',
  216. dl_list={}, suffix='.unverified', version_selector=selector)
  217. ])
  218. # Package is extracted
  219. self.assertEqual(mock_extract.mock_calls, [
  220. mock.call('test-vm', path, '/var/tmp/qvm-template-tmpdir')
  221. ])
  222. # No packages overwritten, so no confirm needed
  223. self.assertEqual(mock_confirm.mock_calls, [])
  224. # qvm-template-postprocess is called
  225. self.assertEqual(mock_call.mock_calls, [
  226. mock.call([
  227. 'qvm-template-postprocess',
  228. '--really',
  229. '--no-installed-by-rpm',
  230. 'post-install',
  231. 'test-vm',
  232. '/var/tmp/qvm-template-tmpdir'
  233. '/var/lib/qubes/vm-templates/test-vm'
  234. ])
  235. ])
  236. # Cache directory created
  237. self.assertEqual(mock_mkdirs.mock_calls, [
  238. mock.call(args.cachedir, exist_ok=True)
  239. ])
  240. # No templates downloaded, thus no renames needed
  241. self.assertEqual(mock_rename.mock_calls, [])
  242. # Lock file removed
  243. self.assertEqual(mock_remove.mock_calls, [
  244. mock.call('/var/tmp/qvm-template.lck')
  245. ])
  246. self.assertAllCalled()
  247. @mock.patch('os.remove')
  248. @mock.patch('os.rename')
  249. @mock.patch('os.makedirs')
  250. @mock.patch('subprocess.check_call')
  251. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  252. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  253. @mock.patch('qubesadmin.tools.qvm_template.download')
  254. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  255. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  256. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  257. def test_101_install_local_postprocargs_success(
  258. self,
  259. mock_ts,
  260. mock_verify,
  261. mock_dl_list,
  262. mock_dl,
  263. mock_extract,
  264. mock_confirm,
  265. mock_call,
  266. mock_mkdirs,
  267. mock_rename,
  268. mock_remove):
  269. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  270. build_time = '2020-09-01 22:30:00' # 1598970600
  271. install_time = '2020-09-01 23:30:00.508230'
  272. for key, val in [
  273. ('name', 'test-vm'),
  274. ('epoch', '2'),
  275. ('version', '4.1'),
  276. ('release', '2020'),
  277. ('reponame', '@commandline'),
  278. ('buildtime', build_time),
  279. ('installtime', install_time),
  280. ('license', 'GPL'),
  281. ('url', 'https://qubes-os.org'),
  282. ('summary', 'Summary'),
  283. ('description', 'Desc|desc')]:
  284. self.app.expected_calls[(
  285. 'test-vm',
  286. 'admin.vm.feature.Set',
  287. f'template-{key}',
  288. val.encode())] = b'0\0'
  289. mock_verify.return_value = {
  290. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  291. rpm.RPMTAG_BUILDTIME : 1598970600,
  292. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  293. rpm.RPMTAG_EPOCHNUM : 2,
  294. rpm.RPMTAG_LICENSE : 'GPL',
  295. rpm.RPMTAG_RELEASE : '2020',
  296. rpm.RPMTAG_SUMMARY : 'Summary',
  297. rpm.RPMTAG_URL : 'https://qubes-os.org',
  298. rpm.RPMTAG_VERSION : '4.1'
  299. }
  300. mock_dl_list.return_value = {}
  301. mock_call.side_effect = self.add_new_vm_side_effect
  302. mock_time = mock.Mock(wraps=datetime.datetime)
  303. mock_time.today.return_value = \
  304. datetime.datetime.fromisoformat(install_time)
  305. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  306. mock.patch('datetime.datetime', new=mock_time), \
  307. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  308. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  309. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  310. path = template_file.name
  311. args = argparse.Namespace(
  312. templates=[path],
  313. keyring='/usr/share/qubes/repo-templates/keys',
  314. nogpgcheck=False,
  315. cachedir='/var/cache/qvm-template',
  316. yes=False,
  317. allow_pv=True,
  318. pool='my-pool'
  319. )
  320. mock_tmpdir.return_value.__enter__.return_value = \
  321. '/var/tmp/qvm-template-tmpdir'
  322. qubesadmin.tools.qvm_template.install(args, self.app)
  323. # Lock file created
  324. self.assertEqual(mock_open.mock_calls, [
  325. mock.call('/var/tmp/qvm-template.lck', 'x'),
  326. mock.call().__enter__(),
  327. mock.call().__exit__(None, None, None)
  328. ])
  329. # Keyring created
  330. self.assertEqual(mock_ts.mock_calls, [
  331. mock.call('/usr/share/qubes/repo-templates/keys')
  332. ])
  333. # Package verified
  334. self.assertEqual(mock_verify.mock_calls, [
  335. mock.call(path, mock_ts('/usr/share/qubes/repo-templates/keys'),
  336. False)
  337. ])
  338. # Attempt to get download list
  339. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  340. self.assertEqual(mock_dl_list.mock_calls, [
  341. mock.call(args, self.app, version_selector=selector)
  342. ])
  343. # Nothing downloaded
  344. self.assertEqual(mock_dl.mock_calls, [
  345. mock.call(args, self.app, path_override='/var/cache/qvm-template',
  346. dl_list={}, suffix='.unverified', version_selector=selector)
  347. ])
  348. # Package is extracted
  349. self.assertEqual(mock_extract.mock_calls, [
  350. mock.call('test-vm', path, '/var/tmp/qvm-template-tmpdir')
  351. ])
  352. # No packages overwritten, so no confirm needed
  353. self.assertEqual(mock_confirm.mock_calls, [])
  354. # qvm-template-postprocess is called
  355. self.assertEqual(mock_call.mock_calls, [
  356. mock.call([
  357. 'qvm-template-postprocess',
  358. '--really',
  359. '--no-installed-by-rpm',
  360. '--allow-pv',
  361. '--pool',
  362. 'my-pool',
  363. 'post-install',
  364. 'test-vm',
  365. '/var/tmp/qvm-template-tmpdir'
  366. '/var/lib/qubes/vm-templates/test-vm'
  367. ])
  368. ])
  369. # Cache directory created
  370. self.assertEqual(mock_mkdirs.mock_calls, [
  371. mock.call(args.cachedir, exist_ok=True)
  372. ])
  373. # No templates downloaded, thus no renames needed
  374. self.assertEqual(mock_rename.mock_calls, [])
  375. # Lock file removed
  376. self.assertEqual(mock_remove.mock_calls, [
  377. mock.call('/var/tmp/qvm-template.lck')
  378. ])
  379. self.assertAllCalled()
  380. @mock.patch('os.remove')
  381. @mock.patch('os.rename')
  382. @mock.patch('os.makedirs')
  383. @mock.patch('subprocess.check_call')
  384. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  385. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  386. @mock.patch('qubesadmin.tools.qvm_template.download')
  387. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  388. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  389. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  390. def test_102_install_local_badsig_fail(
  391. self,
  392. mock_ts,
  393. mock_verify,
  394. mock_dl_list,
  395. mock_dl,
  396. mock_extract,
  397. mock_confirm,
  398. mock_call,
  399. mock_mkdirs,
  400. mock_rename,
  401. mock_remove):
  402. mock_verify.return_value = None
  403. mock_time = mock.Mock(wraps=datetime.datetime)
  404. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  405. mock.patch('datetime.datetime', new=mock_time), \
  406. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  407. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  408. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  409. path = template_file.name
  410. args = argparse.Namespace(
  411. templates=[path],
  412. keyring='/usr/share/qubes/repo-templates/keys',
  413. nogpgcheck=False,
  414. cachedir='/var/cache/qvm-template',
  415. yes=False,
  416. allow_pv=False,
  417. pool=None
  418. )
  419. mock_tmpdir.return_value.__enter__.return_value = \
  420. '/var/tmp/qvm-template-tmpdir'
  421. # Should raise parser.error
  422. with self.assertRaises(SystemExit):
  423. qubesadmin.tools.qvm_template.install(args, self.app)
  424. # Lock file created
  425. self.assertEqual(mock_open.mock_calls, [
  426. mock.call('/var/tmp/qvm-template.lck', 'x'),
  427. mock.call().__enter__(),
  428. mock.call().__exit__(None, None, None)
  429. ])
  430. # Check error message
  431. self.assertTrue('verification failed' in mock_err.getvalue())
  432. # Keyring created
  433. self.assertEqual(mock_ts.mock_calls, [
  434. mock.call('/usr/share/qubes/repo-templates/keys')
  435. ])
  436. # Package verified
  437. self.assertEqual(mock_verify.mock_calls, [
  438. mock.call(path, mock_ts('/usr/share/qubes/repo-templates/keys'),
  439. False)
  440. ])
  441. # Should not be executed:
  442. self.assertEqual(mock_dl_list.mock_calls, [])
  443. self.assertEqual(mock_dl.mock_calls, [])
  444. self.assertEqual(mock_extract.mock_calls, [])
  445. self.assertEqual(mock_confirm.mock_calls, [])
  446. self.assertEqual(mock_call.mock_calls, [])
  447. self.assertEqual(mock_rename.mock_calls, [])
  448. # Lock file removed
  449. self.assertEqual(mock_remove.mock_calls, [
  450. mock.call('/var/tmp/qvm-template.lck')
  451. ])
  452. self.assertAllCalled()
  453. @mock.patch('os.remove')
  454. @mock.patch('os.rename')
  455. @mock.patch('os.makedirs')
  456. @mock.patch('subprocess.check_call')
  457. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  458. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  459. @mock.patch('qubesadmin.tools.qvm_template.download')
  460. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  461. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  462. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  463. def test_103_install_local_exists_fail(
  464. self,
  465. mock_ts,
  466. mock_verify,
  467. mock_dl_list,
  468. mock_dl,
  469. mock_extract,
  470. mock_confirm,
  471. mock_call,
  472. mock_mkdirs,
  473. mock_rename,
  474. mock_remove):
  475. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  476. b'0\0test-vm class=TemplateVM state=Halted\n'
  477. mock_verify.return_value = {
  478. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  479. rpm.RPMTAG_BUILDTIME : 1598970600,
  480. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  481. rpm.RPMTAG_EPOCHNUM : 2,
  482. rpm.RPMTAG_LICENSE : 'GPL',
  483. rpm.RPMTAG_RELEASE : '2020',
  484. rpm.RPMTAG_SUMMARY : 'Summary',
  485. rpm.RPMTAG_URL : 'https://qubes-os.org',
  486. rpm.RPMTAG_VERSION : '4.1'
  487. }
  488. mock_dl_list.return_value = {}
  489. mock_time = mock.Mock(wraps=datetime.datetime)
  490. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  491. mock.patch('datetime.datetime', new=mock_time), \
  492. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  493. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  494. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  495. path = template_file.name
  496. args = argparse.Namespace(
  497. templates=[path],
  498. keyring='/usr/share/qubes/repo-templates/keys',
  499. nogpgcheck=False,
  500. cachedir='/var/cache/qvm-template',
  501. yes=False,
  502. allow_pv=False,
  503. pool=None
  504. )
  505. mock_tmpdir.return_value.__enter__.return_value = \
  506. '/var/tmp/qvm-template-tmpdir'
  507. qubesadmin.tools.qvm_template.install(args, self.app)
  508. # Lock file created
  509. self.assertEqual(mock_open.mock_calls, [
  510. mock.call('/var/tmp/qvm-template.lck', 'x'),
  511. mock.call().__enter__(),
  512. mock.call().__exit__(None, None, None)
  513. ])
  514. # Check warning message
  515. self.assertTrue('already installed' in mock_err.getvalue())
  516. # Keyring created
  517. self.assertEqual(mock_ts.mock_calls, [
  518. mock.call('/usr/share/qubes/repo-templates/keys')
  519. ])
  520. # Package verified
  521. self.assertEqual(mock_verify.mock_calls, [
  522. mock.call(path, mock_ts('/usr/share/qubes/repo-templates/keys'),
  523. False)
  524. ])
  525. # Attempt to get download list
  526. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  527. self.assertEqual(mock_dl_list.mock_calls, [
  528. mock.call(args, self.app, version_selector=selector)
  529. ])
  530. # Nothing downloaded
  531. self.assertEqual(mock_dl.mock_calls, [
  532. mock.call(args, self.app, path_override='/var/cache/qvm-template',
  533. dl_list={}, suffix='.unverified', version_selector=selector)
  534. ])
  535. # Should not be executed:
  536. self.assertEqual(mock_extract.mock_calls, [])
  537. self.assertEqual(mock_confirm.mock_calls, [])
  538. self.assertEqual(mock_call.mock_calls, [])
  539. self.assertEqual(mock_rename.mock_calls, [])
  540. # Lock file removed
  541. self.assertEqual(mock_remove.mock_calls, [
  542. mock.call('/var/tmp/qvm-template.lck')
  543. ])
  544. self.assertAllCalled()
  545. @mock.patch('os.remove')
  546. @mock.patch('os.rename')
  547. @mock.patch('os.makedirs')
  548. @mock.patch('subprocess.check_call')
  549. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  550. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  551. @mock.patch('qubesadmin.tools.qvm_template.download')
  552. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  553. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  554. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  555. def test_104_install_local_badpkgname_fail(
  556. self,
  557. mock_ts,
  558. mock_verify,
  559. mock_dl_list,
  560. mock_dl,
  561. mock_extract,
  562. mock_confirm,
  563. mock_call,
  564. mock_mkdirs,
  565. mock_rename,
  566. mock_remove):
  567. mock_verify.return_value = {
  568. rpm.RPMTAG_NAME : 'Xqubes-template-test-vm',
  569. rpm.RPMTAG_BUILDTIME : 1598970600,
  570. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  571. rpm.RPMTAG_EPOCHNUM : 2,
  572. rpm.RPMTAG_LICENSE : 'GPL',
  573. rpm.RPMTAG_RELEASE : '2020',
  574. rpm.RPMTAG_SUMMARY : 'Summary',
  575. rpm.RPMTAG_URL : 'https://qubes-os.org',
  576. rpm.RPMTAG_VERSION : '4.1'
  577. }
  578. mock_time = mock.Mock(wraps=datetime.datetime)
  579. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  580. mock.patch('datetime.datetime', new=mock_time), \
  581. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  582. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  583. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  584. path = template_file.name
  585. args = argparse.Namespace(
  586. templates=[path],
  587. keyring='/usr/share/qubes/repo-templates/keys',
  588. nogpgcheck=False,
  589. cachedir='/var/cache/qvm-template',
  590. yes=False,
  591. allow_pv=False,
  592. pool=None
  593. )
  594. mock_tmpdir.return_value.__enter__.return_value = \
  595. '/var/tmp/qvm-template-tmpdir'
  596. with self.assertRaises(SystemExit):
  597. qubesadmin.tools.qvm_template.install(args, self.app)
  598. # Lock file created
  599. self.assertEqual(mock_open.mock_calls, [
  600. mock.call('/var/tmp/qvm-template.lck', 'x'),
  601. mock.call().__enter__(),
  602. mock.call().__exit__(None, None, None)
  603. ])
  604. # Check error message
  605. self.assertTrue('Illegal package name' in mock_err.getvalue())
  606. # Keyring created
  607. self.assertEqual(mock_ts.mock_calls, [
  608. mock.call('/usr/share/qubes/repo-templates/keys')
  609. ])
  610. # Package verified
  611. self.assertEqual(mock_verify.mock_calls, [
  612. mock.call(path, mock_ts('/usr/share/qubes/repo-templates/keys'),
  613. False)
  614. ])
  615. # Should not be executed:
  616. self.assertEqual(mock_dl_list.mock_calls, [])
  617. self.assertEqual(mock_dl.mock_calls, [])
  618. self.assertEqual(mock_extract.mock_calls, [])
  619. self.assertEqual(mock_confirm.mock_calls, [])
  620. self.assertEqual(mock_call.mock_calls, [])
  621. self.assertEqual(mock_rename.mock_calls, [])
  622. # Lock file removed
  623. self.assertEqual(mock_remove.mock_calls, [
  624. mock.call('/var/tmp/qvm-template.lck')
  625. ])
  626. self.assertAllCalled()
  627. @mock.patch('os.rename')
  628. @mock.patch('os.makedirs')
  629. @mock.patch('subprocess.check_call')
  630. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  631. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  632. @mock.patch('qubesadmin.tools.qvm_template.download')
  633. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  634. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  635. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  636. def test_105_install_local_existinginstance_fail(
  637. self,
  638. mock_ts,
  639. mock_verify,
  640. mock_dl_list,
  641. mock_dl,
  642. mock_extract,
  643. mock_confirm,
  644. mock_call,
  645. mock_mkdirs,
  646. mock_rename):
  647. mock_time = mock.Mock(wraps=datetime.datetime)
  648. with mock.patch('datetime.datetime', new=mock_time), \
  649. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  650. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  651. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  652. path = template_file.name
  653. args = argparse.Namespace(
  654. templates=[path],
  655. keyring='/usr/share/qubes/repo-templates/keys',
  656. nogpgcheck=False,
  657. cachedir='/var/cache/qvm-template',
  658. yes=False,
  659. allow_pv=False,
  660. pool=None
  661. )
  662. mock_tmpdir.return_value.__enter__.return_value = \
  663. '/var/tmp/qvm-template-tmpdir'
  664. pathlib.Path('/var/tmp/qvm-template.lck').touch()
  665. try:
  666. with self.assertRaises(SystemExit), \
  667. mock.patch('os.remove') as mock_remove:
  668. qubesadmin.tools.qvm_template.install(args, self.app)
  669. self.assertEqual(mock_remove.mock_calls, [])
  670. finally:
  671. # Lock file not removed
  672. self.assertTrue(os.path.exists('/var/tmp/qvm-template.lck'))
  673. os.remove('/var/tmp/qvm-template.lck')
  674. # Check error message
  675. self.assertTrue('another instance of qvm-template is running' \
  676. in mock_err.getvalue())
  677. # Should not be executed:
  678. self.assertEqual(mock_ts.mock_calls, [])
  679. self.assertEqual(mock_verify.mock_calls, [])
  680. self.assertEqual(mock_dl_list.mock_calls, [])
  681. self.assertEqual(mock_dl.mock_calls, [])
  682. self.assertEqual(mock_extract.mock_calls, [])
  683. self.assertEqual(mock_confirm.mock_calls, [])
  684. self.assertEqual(mock_call.mock_calls, [])
  685. self.assertEqual(mock_rename.mock_calls, [])
  686. self.assertAllCalled()
  687. @mock.patch('os.remove')
  688. @mock.patch('os.rename')
  689. @mock.patch('os.makedirs')
  690. @mock.patch('subprocess.check_call')
  691. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  692. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  693. @mock.patch('qubesadmin.tools.qvm_template.download')
  694. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  695. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  696. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  697. def test_106_install_local_badpath_fail(
  698. self,
  699. mock_ts,
  700. mock_verify,
  701. mock_dl_list,
  702. mock_dl,
  703. mock_extract,
  704. mock_confirm,
  705. mock_call,
  706. mock_mkdirs,
  707. mock_rename,
  708. mock_remove):
  709. mock_time = mock.Mock(wraps=datetime.datetime)
  710. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  711. mock.patch('datetime.datetime', new=mock_time), \
  712. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  713. mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  714. path = '/var/tmp/ShOulD-NoT-ExIsT.rpm'
  715. args = argparse.Namespace(
  716. templates=[path],
  717. keyring='/usr/share/qubes/repo-templates/keys',
  718. nogpgcheck=False,
  719. cachedir='/var/cache/qvm-template',
  720. yes=False,
  721. allow_pv=False,
  722. pool=None
  723. )
  724. mock_tmpdir.return_value.__enter__.return_value = \
  725. '/var/tmp/qvm-template-tmpdir'
  726. with self.assertRaises(SystemExit):
  727. qubesadmin.tools.qvm_template.install(args, self.app)
  728. # Lock file created
  729. self.assertEqual(mock_open.mock_calls, [
  730. mock.call('/var/tmp/qvm-template.lck', 'x'),
  731. mock.call().__enter__(),
  732. mock.call().__exit__(None, None, None)
  733. ])
  734. # Check error message
  735. self.assertTrue(f"RPM file '{path}' not found" \
  736. in mock_err.getvalue())
  737. # Keyring created
  738. self.assertEqual(mock_ts.mock_calls, [
  739. mock.call('/usr/share/qubes/repo-templates/keys')
  740. ])
  741. # Should not be executed:
  742. self.assertEqual(mock_verify.mock_calls, [])
  743. self.assertEqual(mock_dl_list.mock_calls, [])
  744. self.assertEqual(mock_dl.mock_calls, [])
  745. self.assertEqual(mock_extract.mock_calls, [])
  746. self.assertEqual(mock_confirm.mock_calls, [])
  747. self.assertEqual(mock_call.mock_calls, [])
  748. self.assertEqual(mock_rename.mock_calls, [])
  749. # Lock file removed
  750. self.assertEqual(mock_remove.mock_calls, [
  751. mock.call('/var/tmp/qvm-template.lck')
  752. ])
  753. self.assertAllCalled()