qvm_template.py 143 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487
  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. # Print str(list) directly so that the output is consistent no matter
  16. # which implementation of `column` we use
  17. self.mock_table = mock.patch('qubesadmin.tools.print_table')
  18. mock_table = self.mock_table.start()
  19. def print_table(table, *args):
  20. print(str(table))
  21. mock_table.side_effect = print_table
  22. super().setUp()
  23. def tearDown(self):
  24. self.mock_table.stop()
  25. super().tearDown()
  26. @mock.patch('rpm.TransactionSet')
  27. @mock.patch('subprocess.check_call')
  28. @mock.patch('subprocess.check_output')
  29. def test_000_verify_rpm_success(self, mock_proc, mock_call, mock_ts):
  30. # Just return a dict instead of rpm.hdr
  31. hdr = {
  32. rpm.RPMTAG_SIGPGP: 'xxx', # non-empty
  33. rpm.RPMTAG_SIGGPG: 'xxx', # non-empty
  34. }
  35. mock_ts.return_value.hdrFromFdno.return_value = hdr
  36. mock_proc.return_value = b'dummy.rpm: digests signatures OK\n'
  37. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  38. ['/path/to/key'])
  39. mock_call.assert_called_once()
  40. mock_proc.assert_called_once()
  41. self.assertEqual(hdr, ret)
  42. self.assertAllCalled()
  43. @mock.patch('rpm.TransactionSet')
  44. @mock.patch('subprocess.check_call')
  45. @mock.patch('subprocess.check_output')
  46. def test_001_verify_rpm_nosig_fail(self, mock_proc, mock_call, mock_ts):
  47. # Just return a dict instead of rpm.hdr
  48. hdr = {
  49. rpm.RPMTAG_SIGPGP: None, # empty
  50. rpm.RPMTAG_SIGGPG: None, # empty
  51. }
  52. mock_ts.return_value.hdrFromFdno.return_value = hdr
  53. mock_proc.return_value = b'dummy.rpm: digests OK\n'
  54. with self.assertRaises(Exception) as e:
  55. qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  56. ['/path/to/key'])
  57. mock_call.assert_called_once()
  58. mock_proc.assert_called_once()
  59. self.assertIn('Signature verification failed', e.exception.args[0])
  60. mock_ts.assert_not_called()
  61. self.assertAllCalled()
  62. @mock.patch('rpm.TransactionSet')
  63. @mock.patch('subprocess.check_call')
  64. @mock.patch('subprocess.check_output')
  65. def test_002_verify_rpm_nosig_success(self, mock_proc, mock_call, mock_ts):
  66. # Just return a dict instead of rpm.hdr
  67. hdr = {
  68. rpm.RPMTAG_SIGPGP: None, # empty
  69. rpm.RPMTAG_SIGGPG: None, # empty
  70. }
  71. mock_ts.return_value.hdrFromFdno.return_value = hdr
  72. mock_proc.return_value = b'dummy.rpm: digests OK\n'
  73. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  74. ['/path/to/key'], True)
  75. mock_proc.assert_not_called()
  76. mock_call.assert_not_called()
  77. self.assertEqual(ret, hdr)
  78. self.assertAllCalled()
  79. @mock.patch('rpm.TransactionSet')
  80. @mock.patch('subprocess.check_call')
  81. @mock.patch('subprocess.check_output')
  82. def test_003_verify_rpm_badsig_fail(self, mock_proc, mock_call, mock_ts):
  83. mock_proc.side_effect = subprocess.CalledProcessError(1,
  84. ['rpmkeys', '--checksig'], b'/dev/null: digests SIGNATURES NOT OK\n')
  85. with self.assertRaises(Exception) as e:
  86. qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  87. ['/path/to/key'])
  88. mock_call.assert_called_once()
  89. mock_proc.assert_called_once()
  90. self.assertIn('Signature verification failed', e.exception.args[0])
  91. mock_ts.assert_not_called()
  92. self.assertAllCalled()
  93. @mock.patch('subprocess.Popen')
  94. def test_010_extract_rpm_success(self, mock_popen):
  95. pipe = mock.Mock()
  96. mock_popen.return_value.stdout = pipe
  97. mock_popen.return_value.wait.return_value = 0
  98. with tempfile.NamedTemporaryFile() as fd, \
  99. tempfile.TemporaryDirectory() as dir:
  100. path = fd.name
  101. dirpath = dir
  102. ret = qubesadmin.tools.qvm_template.extract_rpm(
  103. 'test-vm', path, dirpath)
  104. self.assertEqual(ret, True)
  105. self.assertEqual(mock_popen.mock_calls, [
  106. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  107. mock.call([
  108. 'cpio',
  109. '-idm',
  110. '-D',
  111. dirpath,
  112. './var/lib/qubes/vm-templates/test-vm/*'
  113. ], stdin=pipe, stdout=subprocess.DEVNULL),
  114. mock.call().wait(),
  115. mock.call().wait()
  116. ])
  117. self.assertAllCalled()
  118. @mock.patch('subprocess.Popen')
  119. def test_011_extract_rpm_fail(self, mock_popen):
  120. pipe = mock.Mock()
  121. mock_popen.return_value.stdout = pipe
  122. mock_popen.return_value.wait.return_value = 1
  123. with tempfile.NamedTemporaryFile() as fd, \
  124. tempfile.TemporaryDirectory() as dir:
  125. path = fd.name
  126. dirpath = dir
  127. ret = qubesadmin.tools.qvm_template.extract_rpm(
  128. 'test-vm', path, dirpath)
  129. self.assertEqual(ret, False)
  130. self.assertEqual(mock_popen.mock_calls, [
  131. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  132. mock.call([
  133. 'cpio',
  134. '-idm',
  135. '-D',
  136. dirpath,
  137. './var/lib/qubes/vm-templates/test-vm/*'
  138. ], stdin=pipe, stdout=subprocess.DEVNULL),
  139. mock.call().wait()
  140. ])
  141. self.assertAllCalled()
  142. def add_new_vm_side_effect(self, *args, **kwargs):
  143. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  144. b'0\0test-vm class=TemplateVM state=Halted\n'
  145. self.app.domains.clear_cache()
  146. return self.app.domains['test-vm']
  147. @mock.patch('os.remove')
  148. @mock.patch('os.rename')
  149. @mock.patch('os.makedirs')
  150. @mock.patch('subprocess.check_call')
  151. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  152. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  153. @mock.patch('qubesadmin.tools.qvm_template.download')
  154. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  155. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  156. def test_100_install_local_success(
  157. self,
  158. mock_verify,
  159. mock_dl_list,
  160. mock_dl,
  161. mock_extract,
  162. mock_confirm,
  163. mock_call,
  164. mock_mkdirs,
  165. mock_rename,
  166. mock_remove):
  167. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  168. build_time = '2020-09-01 14:30:00' # 1598970600
  169. install_time = '2020-09-01 15:30:00'
  170. for key, val in [
  171. ('name', 'test-vm'),
  172. ('epoch', '2'),
  173. ('version', '4.1'),
  174. ('release', '2020'),
  175. ('reponame', '@commandline'),
  176. ('buildtime', build_time),
  177. ('installtime', install_time),
  178. ('license', 'GPL'),
  179. ('url', 'https://qubes-os.org'),
  180. ('summary', 'Summary'),
  181. ('description', 'Desc|desc')]:
  182. self.app.expected_calls[(
  183. 'test-vm',
  184. 'admin.vm.feature.Set',
  185. f'template-{key}',
  186. val.encode())] = b'0\0'
  187. mock_verify.return_value = {
  188. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  189. rpm.RPMTAG_BUILDTIME : 1598970600,
  190. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  191. rpm.RPMTAG_EPOCHNUM : 2,
  192. rpm.RPMTAG_LICENSE : 'GPL',
  193. rpm.RPMTAG_RELEASE : '2020',
  194. rpm.RPMTAG_SUMMARY : 'Summary',
  195. rpm.RPMTAG_URL : 'https://qubes-os.org',
  196. rpm.RPMTAG_VERSION : '4.1'
  197. }
  198. mock_dl_list.return_value = {}
  199. mock_call.side_effect = self.add_new_vm_side_effect
  200. mock_time = mock.Mock(wraps=datetime.datetime)
  201. mock_time.now.return_value = \
  202. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  203. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  204. mock.patch('datetime.datetime', new=mock_time), \
  205. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  206. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  207. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  208. path = template_file.name
  209. args = argparse.Namespace(
  210. templates=[path],
  211. keyring='/tmp',
  212. nogpgcheck=False,
  213. cachedir='/var/cache/qvm-template',
  214. yes=False,
  215. allow_pv=False,
  216. pool=None
  217. )
  218. mock_tmpdir.return_value.__enter__.return_value = \
  219. '/var/tmp/qvm-template-tmpdir'
  220. qubesadmin.tools.qvm_template.install(args, self.app)
  221. # Lock file created
  222. self.assertEqual(mock_open.mock_calls, [
  223. mock.call('/var/tmp/qvm-template.lck', 'x'),
  224. mock.call().__enter__(),
  225. mock.call().__exit__(None, None, None)
  226. ])
  227. # Attempt to get download list
  228. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  229. self.assertEqual(mock_dl_list.mock_calls, [
  230. mock.call(args, self.app, version_selector=selector)
  231. ])
  232. # Nothing downloaded
  233. mock_dl.assert_called_with(args, self.app,
  234. path_override='/var/tmp/qvm-template-tmpdir',
  235. dl_list={}, suffix='.unverified', version_selector=selector)
  236. # Package is extracted
  237. mock_extract.assert_called_with('test-vm', path,
  238. '/var/tmp/qvm-template-tmpdir')
  239. # No packages overwritten, so no confirm needed
  240. self.assertEqual(mock_confirm.mock_calls, [])
  241. # qvm-template-postprocess is called
  242. self.assertEqual(mock_call.mock_calls, [
  243. mock.call([
  244. 'qvm-template-postprocess',
  245. '--really',
  246. '--no-installed-by-rpm',
  247. 'post-install',
  248. 'test-vm',
  249. '/var/tmp/qvm-template-tmpdir'
  250. '/var/lib/qubes/vm-templates/test-vm'
  251. ])
  252. ])
  253. # Cache directory created
  254. self.assertEqual(mock_mkdirs.mock_calls, [
  255. mock.call(args.cachedir, exist_ok=True)
  256. ])
  257. # No templates downloaded, thus no renames needed
  258. self.assertEqual(mock_rename.mock_calls, [])
  259. # Lock file removed
  260. self.assertEqual(mock_remove.mock_calls, [
  261. mock.call('/var/tmp/qvm-template.lck')
  262. ])
  263. self.assertAllCalled()
  264. @mock.patch('os.remove')
  265. @mock.patch('os.rename')
  266. @mock.patch('os.makedirs')
  267. @mock.patch('subprocess.check_call')
  268. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  269. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  270. @mock.patch('qubesadmin.tools.qvm_template.download')
  271. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  272. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  273. def test_101_install_local_postprocargs_success(
  274. self,
  275. mock_verify,
  276. mock_dl_list,
  277. mock_dl,
  278. mock_extract,
  279. mock_confirm,
  280. mock_call,
  281. mock_mkdirs,
  282. mock_rename,
  283. mock_remove):
  284. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  285. build_time = '2020-09-01 14:30:00' # 1598970600
  286. install_time = '2020-09-01 15:30:00'
  287. for key, val in [
  288. ('name', 'test-vm'),
  289. ('epoch', '2'),
  290. ('version', '4.1'),
  291. ('release', '2020'),
  292. ('reponame', '@commandline'),
  293. ('buildtime', build_time),
  294. ('installtime', install_time),
  295. ('license', 'GPL'),
  296. ('url', 'https://qubes-os.org'),
  297. ('summary', 'Summary'),
  298. ('description', 'Desc|desc')]:
  299. self.app.expected_calls[(
  300. 'test-vm',
  301. 'admin.vm.feature.Set',
  302. f'template-{key}',
  303. val.encode())] = b'0\0'
  304. mock_verify.return_value = {
  305. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  306. rpm.RPMTAG_BUILDTIME : 1598970600,
  307. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  308. rpm.RPMTAG_EPOCHNUM : 2,
  309. rpm.RPMTAG_LICENSE : 'GPL',
  310. rpm.RPMTAG_RELEASE : '2020',
  311. rpm.RPMTAG_SUMMARY : 'Summary',
  312. rpm.RPMTAG_URL : 'https://qubes-os.org',
  313. rpm.RPMTAG_VERSION : '4.1'
  314. }
  315. mock_dl_list.return_value = {}
  316. mock_call.side_effect = self.add_new_vm_side_effect
  317. mock_time = mock.Mock(wraps=datetime.datetime)
  318. mock_time.now.return_value = \
  319. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  320. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  321. mock.patch('datetime.datetime', new=mock_time), \
  322. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  323. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  324. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  325. path = template_file.name
  326. args = argparse.Namespace(
  327. templates=[path],
  328. keyring='/tmp',
  329. nogpgcheck=False,
  330. cachedir='/var/cache/qvm-template',
  331. yes=False,
  332. allow_pv=True,
  333. pool='my-pool'
  334. )
  335. mock_tmpdir.return_value.__enter__.return_value = \
  336. '/var/tmp/qvm-template-tmpdir'
  337. qubesadmin.tools.qvm_template.install(args, self.app)
  338. # Lock file created
  339. self.assertEqual(mock_open.mock_calls, [
  340. mock.call('/var/tmp/qvm-template.lck', 'x'),
  341. mock.call().__enter__(),
  342. mock.call().__exit__(None, None, None)
  343. ])
  344. # Attempt to get download list
  345. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  346. self.assertEqual(mock_dl_list.mock_calls, [
  347. mock.call(args, self.app, version_selector=selector)
  348. ])
  349. # Nothing downloaded
  350. mock_dl.assert_called_with(args, self.app,
  351. path_override='/var/tmp/qvm-template-tmpdir',
  352. dl_list={}, suffix='.unverified', version_selector=selector)
  353. # Package is extracted
  354. mock_extract.assert_called_with('test-vm', path,
  355. '/var/tmp/qvm-template-tmpdir')
  356. # No packages overwritten, so no confirm needed
  357. self.assertEqual(mock_confirm.mock_calls, [])
  358. # qvm-template-postprocess is called
  359. self.assertEqual(mock_call.mock_calls, [
  360. mock.call([
  361. 'qvm-template-postprocess',
  362. '--really',
  363. '--no-installed-by-rpm',
  364. '--allow-pv',
  365. '--pool',
  366. 'my-pool',
  367. 'post-install',
  368. 'test-vm',
  369. '/var/tmp/qvm-template-tmpdir'
  370. '/var/lib/qubes/vm-templates/test-vm'
  371. ])
  372. ])
  373. # Cache directory created
  374. self.assertEqual(mock_mkdirs.mock_calls, [
  375. mock.call(args.cachedir, exist_ok=True)
  376. ])
  377. # No templates downloaded, thus no renames needed
  378. self.assertEqual(mock_rename.mock_calls, [])
  379. # Lock file removed
  380. self.assertEqual(mock_remove.mock_calls, [
  381. mock.call('/var/tmp/qvm-template.lck')
  382. ])
  383. self.assertAllCalled()
  384. @mock.patch('os.remove')
  385. @mock.patch('os.rename')
  386. @mock.patch('os.makedirs')
  387. @mock.patch('subprocess.check_call')
  388. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  389. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  390. @mock.patch('qubesadmin.tools.qvm_template.download')
  391. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  392. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  393. def test_102_install_local_badsig_fail(
  394. self,
  395. mock_verify,
  396. mock_dl_list,
  397. mock_dl,
  398. mock_extract,
  399. mock_confirm,
  400. mock_call,
  401. mock_mkdirs,
  402. mock_rename,
  403. mock_remove):
  404. mock_verify.return_value = None
  405. mock_time = mock.Mock(wraps=datetime.datetime)
  406. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  407. mock.patch('datetime.datetime', new=mock_time), \
  408. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  409. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  410. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  411. path = template_file.name
  412. args = argparse.Namespace(
  413. templates=[path],
  414. keyring='/tmp',
  415. nogpgcheck=False,
  416. cachedir='/var/cache/qvm-template',
  417. yes=False,
  418. allow_pv=False,
  419. pool=None
  420. )
  421. mock_tmpdir.return_value.__enter__.return_value = \
  422. '/var/tmp/qvm-template-tmpdir'
  423. # Should raise parser.error
  424. with self.assertRaises(SystemExit):
  425. qubesadmin.tools.qvm_template.install(args, self.app)
  426. # Lock file created
  427. self.assertEqual(mock_open.mock_calls, [
  428. mock.call('/var/tmp/qvm-template.lck', 'x'),
  429. mock.call().__enter__(),
  430. mock.call().__exit__(None, None, None)
  431. ])
  432. # Check error message
  433. self.assertTrue('verification failed' in mock_err.getvalue())
  434. # Should not be executed:
  435. self.assertEqual(mock_dl_list.mock_calls, [])
  436. self.assertEqual(mock_dl.mock_calls, [])
  437. self.assertEqual(mock_extract.mock_calls, [])
  438. self.assertEqual(mock_confirm.mock_calls, [])
  439. self.assertEqual(mock_call.mock_calls, [])
  440. self.assertEqual(mock_rename.mock_calls, [])
  441. # Lock file removed
  442. self.assertEqual(mock_remove.mock_calls, [
  443. mock.call('/var/tmp/qvm-template.lck')
  444. ])
  445. self.assertAllCalled()
  446. @mock.patch('os.remove')
  447. @mock.patch('os.rename')
  448. @mock.patch('os.makedirs')
  449. @mock.patch('subprocess.check_call')
  450. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  451. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  452. @mock.patch('qubesadmin.tools.qvm_template.download')
  453. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  454. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  455. def test_103_install_local_exists_fail(
  456. self,
  457. mock_verify,
  458. mock_dl_list,
  459. mock_dl,
  460. mock_extract,
  461. mock_confirm,
  462. mock_call,
  463. mock_mkdirs,
  464. mock_rename,
  465. mock_remove):
  466. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  467. b'0\0test-vm class=TemplateVM state=Halted\n'
  468. mock_verify.return_value = {
  469. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  470. rpm.RPMTAG_BUILDTIME : 1598970600,
  471. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  472. rpm.RPMTAG_EPOCHNUM : 2,
  473. rpm.RPMTAG_LICENSE : 'GPL',
  474. rpm.RPMTAG_RELEASE : '2020',
  475. rpm.RPMTAG_SUMMARY : 'Summary',
  476. rpm.RPMTAG_URL : 'https://qubes-os.org',
  477. rpm.RPMTAG_VERSION : '4.1'
  478. }
  479. mock_dl_list.return_value = {}
  480. mock_time = mock.Mock(wraps=datetime.datetime)
  481. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  482. mock.patch('datetime.datetime', new=mock_time), \
  483. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  484. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  485. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  486. path = template_file.name
  487. args = argparse.Namespace(
  488. templates=[path],
  489. keyring='/tmp',
  490. nogpgcheck=False,
  491. cachedir='/var/cache/qvm-template',
  492. yes=False,
  493. allow_pv=False,
  494. pool=None
  495. )
  496. mock_tmpdir.return_value.__enter__.return_value = \
  497. '/var/tmp/qvm-template-tmpdir'
  498. qubesadmin.tools.qvm_template.install(args, self.app)
  499. # Lock file created
  500. self.assertEqual(mock_open.mock_calls, [
  501. mock.call('/var/tmp/qvm-template.lck', 'x'),
  502. mock.call().__enter__(),
  503. mock.call().__exit__(None, None, None)
  504. ])
  505. # Check warning message
  506. self.assertTrue('already installed' in mock_err.getvalue())
  507. # Attempt to get download list
  508. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  509. self.assertEqual(mock_dl_list.mock_calls, [
  510. mock.call(args, self.app, version_selector=selector)
  511. ])
  512. # Nothing downloaded
  513. self.assertEqual(mock_dl.mock_calls, [
  514. mock.call(args, self.app, path_override='/var/tmp/qvm-template-tmpdir',
  515. dl_list={}, suffix='.unverified', version_selector=selector)
  516. ])
  517. # Should not be executed:
  518. self.assertEqual(mock_extract.mock_calls, [])
  519. self.assertEqual(mock_confirm.mock_calls, [])
  520. self.assertEqual(mock_call.mock_calls, [])
  521. self.assertEqual(mock_rename.mock_calls, [])
  522. # Lock file removed
  523. self.assertEqual(mock_remove.mock_calls, [
  524. mock.call('/var/tmp/qvm-template.lck')
  525. ])
  526. self.assertAllCalled()
  527. @mock.patch('os.remove')
  528. @mock.patch('os.rename')
  529. @mock.patch('os.makedirs')
  530. @mock.patch('subprocess.check_call')
  531. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  532. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  533. @mock.patch('qubesadmin.tools.qvm_template.download')
  534. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  535. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  536. def test_104_install_local_badpkgname_fail(
  537. self,
  538. mock_verify,
  539. mock_dl_list,
  540. mock_dl,
  541. mock_extract,
  542. mock_confirm,
  543. mock_call,
  544. mock_mkdirs,
  545. mock_rename,
  546. mock_remove):
  547. mock_verify.return_value = {
  548. rpm.RPMTAG_NAME : 'Xqubes-template-test-vm',
  549. rpm.RPMTAG_BUILDTIME : 1598970600,
  550. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  551. rpm.RPMTAG_EPOCHNUM : 2,
  552. rpm.RPMTAG_LICENSE : 'GPL',
  553. rpm.RPMTAG_RELEASE : '2020',
  554. rpm.RPMTAG_SUMMARY : 'Summary',
  555. rpm.RPMTAG_URL : 'https://qubes-os.org',
  556. rpm.RPMTAG_VERSION : '4.1'
  557. }
  558. mock_time = mock.Mock(wraps=datetime.datetime)
  559. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  560. mock.patch('datetime.datetime', new=mock_time), \
  561. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  562. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  563. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  564. path = template_file.name
  565. args = argparse.Namespace(
  566. templates=[path],
  567. keyring='/tmp',
  568. nogpgcheck=False,
  569. cachedir='/var/cache/qvm-template',
  570. yes=False,
  571. allow_pv=False,
  572. pool=None
  573. )
  574. mock_tmpdir.return_value.__enter__.return_value = \
  575. '/var/tmp/qvm-template-tmpdir'
  576. with self.assertRaises(SystemExit):
  577. qubesadmin.tools.qvm_template.install(args, self.app)
  578. # Lock file created
  579. self.assertEqual(mock_open.mock_calls, [
  580. mock.call('/var/tmp/qvm-template.lck', 'x'),
  581. mock.call().__enter__(),
  582. mock.call().__exit__(None, None, None)
  583. ])
  584. # Check error message
  585. self.assertTrue('Illegal package name' in mock_err.getvalue())
  586. # Should not be executed:
  587. self.assertEqual(mock_dl_list.mock_calls, [])
  588. self.assertEqual(mock_dl.mock_calls, [])
  589. self.assertEqual(mock_extract.mock_calls, [])
  590. self.assertEqual(mock_confirm.mock_calls, [])
  591. self.assertEqual(mock_call.mock_calls, [])
  592. self.assertEqual(mock_rename.mock_calls, [])
  593. # Lock file removed
  594. self.assertEqual(mock_remove.mock_calls, [
  595. mock.call('/var/tmp/qvm-template.lck')
  596. ])
  597. self.assertAllCalled()
  598. @mock.patch('os.rename')
  599. @mock.patch('os.makedirs')
  600. @mock.patch('subprocess.check_call')
  601. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  602. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  603. @mock.patch('qubesadmin.tools.qvm_template.download')
  604. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  605. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  606. @mock.patch('qubesadmin.tools.qvm_template.rpm_transactionset')
  607. def test_105_install_local_existinginstance_fail(
  608. self,
  609. mock_ts,
  610. mock_verify,
  611. mock_dl_list,
  612. mock_dl,
  613. mock_extract,
  614. mock_confirm,
  615. mock_call,
  616. mock_mkdirs,
  617. mock_rename):
  618. mock_time = mock.Mock(wraps=datetime.datetime)
  619. with mock.patch('datetime.datetime', new=mock_time), \
  620. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  621. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  622. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  623. path = template_file.name
  624. args = argparse.Namespace(
  625. templates=[path],
  626. keyring='/usr/share/qubes/repo-templates/keys',
  627. nogpgcheck=False,
  628. cachedir='/var/cache/qvm-template',
  629. yes=False,
  630. allow_pv=False,
  631. pool=None
  632. )
  633. mock_tmpdir.return_value.__enter__.return_value = \
  634. '/var/tmp/qvm-template-tmpdir'
  635. pathlib.Path('/var/tmp/qvm-template.lck').touch()
  636. try:
  637. with mock.patch('os.remove') as mock_remove:
  638. with self.assertRaises(SystemExit):
  639. qubesadmin.tools.qvm_template.install(args, self.app)
  640. self.assertEqual(mock_remove.mock_calls, [])
  641. finally:
  642. # Lock file not removed
  643. self.assertTrue(os.path.exists('/var/tmp/qvm-template.lck'))
  644. os.remove('/var/tmp/qvm-template.lck')
  645. # Check error message
  646. self.assertTrue('another instance of qvm-template is running' \
  647. in mock_err.getvalue())
  648. # Should not be executed:
  649. self.assertEqual(mock_ts.mock_calls, [])
  650. self.assertEqual(mock_verify.mock_calls, [])
  651. self.assertEqual(mock_dl_list.mock_calls, [])
  652. self.assertEqual(mock_dl.mock_calls, [])
  653. self.assertEqual(mock_extract.mock_calls, [])
  654. self.assertEqual(mock_confirm.mock_calls, [])
  655. self.assertEqual(mock_call.mock_calls, [])
  656. self.assertEqual(mock_rename.mock_calls, [])
  657. self.assertAllCalled()
  658. @mock.patch('os.remove')
  659. @mock.patch('os.rename')
  660. @mock.patch('os.makedirs')
  661. @mock.patch('subprocess.check_call')
  662. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  663. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  664. @mock.patch('qubesadmin.tools.qvm_template.download')
  665. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  666. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  667. def test_106_install_local_badpath_fail(
  668. self,
  669. mock_verify,
  670. mock_dl_list,
  671. mock_dl,
  672. mock_extract,
  673. mock_confirm,
  674. mock_call,
  675. mock_mkdirs,
  676. mock_rename,
  677. mock_remove):
  678. mock_time = mock.Mock(wraps=datetime.datetime)
  679. with mock.patch('builtins.open', mock.mock_open()) as mock_open, \
  680. mock.patch('datetime.datetime', new=mock_time), \
  681. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  682. mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  683. path = '/var/tmp/ShOulD-NoT-ExIsT.rpm'
  684. args = argparse.Namespace(
  685. templates=[path],
  686. keyring='/tmp',
  687. nogpgcheck=False,
  688. cachedir='/var/cache/qvm-template',
  689. yes=False,
  690. allow_pv=False,
  691. pool=None
  692. )
  693. mock_tmpdir.return_value.__enter__.return_value = \
  694. '/var/tmp/qvm-template-tmpdir'
  695. with self.assertRaises(SystemExit):
  696. qubesadmin.tools.qvm_template.install(args, self.app)
  697. # Lock file created
  698. self.assertEqual(mock_open.mock_calls, [
  699. mock.call('/var/tmp/qvm-template.lck', 'x'),
  700. mock.call().__enter__(),
  701. mock.call().__exit__(None, None, None)
  702. ])
  703. # Check error message
  704. self.assertTrue(f"RPM file '{path}' not found" \
  705. in mock_err.getvalue())
  706. # Should not be executed:
  707. self.assertEqual(mock_verify.mock_calls, [])
  708. self.assertEqual(mock_dl_list.mock_calls, [])
  709. self.assertEqual(mock_dl.mock_calls, [])
  710. self.assertEqual(mock_extract.mock_calls, [])
  711. self.assertEqual(mock_confirm.mock_calls, [])
  712. self.assertEqual(mock_call.mock_calls, [])
  713. self.assertEqual(mock_rename.mock_calls, [])
  714. # Lock file removed
  715. self.assertEqual(mock_remove.mock_calls, [
  716. mock.call('/var/tmp/qvm-template.lck')
  717. ])
  718. self.assertAllCalled()
  719. def test_110_qrexec_payload_refresh_success(self):
  720. with tempfile.NamedTemporaryFile() as repo_conf1, \
  721. tempfile.NamedTemporaryFile() as repo_conf2:
  722. repo_str1 = \
  723. '''[qubes-templates-itl]
  724. name = Qubes Templates repository
  725. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  726. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  727. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  728. enabled = 1
  729. fastestmirror = 1
  730. metadata_expire = 7d
  731. gpgcheck = 1
  732. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  733. '''
  734. repo_str2 = \
  735. '''[qubes-templates-itl-testing]
  736. name = Qubes Templates repository
  737. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl-testing
  738. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl-testing
  739. metalink = https://yum.qubes-os.org/r$releasever/templates-itl-testing/repodata/repomd.xml.metalink
  740. enabled = 0
  741. fastestmirror = 1
  742. gpgcheck = 1
  743. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  744. '''
  745. repo_conf1.write(repo_str1.encode())
  746. repo_conf1.flush()
  747. repo_conf2.write(repo_str2.encode())
  748. repo_conf2.flush()
  749. args = argparse.Namespace(
  750. enablerepo=['repo1', 'repo2'],
  751. disablerepo=['repo3', 'repo4', 'repo5'],
  752. repoid=[],
  753. releasever='4.1',
  754. repo_files=[repo_conf1.name, repo_conf2.name]
  755. )
  756. res = qubesadmin.tools.qvm_template.qrexec_payload(args, self.app,
  757. 'qubes-template-fedora-32', True)
  758. self.assertEqual(res,
  759. '''--enablerepo=repo1
  760. --enablerepo=repo2
  761. --disablerepo=repo3
  762. --disablerepo=repo4
  763. --disablerepo=repo5
  764. --refresh
  765. --releasever=4.1
  766. qubes-template-fedora-32
  767. ---
  768. ''' + repo_str1 + '\n' + repo_str2 + '\n')
  769. self.assertAllCalled()
  770. def test_111_qrexec_payload_norefresh_success(self):
  771. with tempfile.NamedTemporaryFile() as repo_conf1:
  772. repo_str1 = \
  773. '''[qubes-templates-itl]
  774. name = Qubes Templates repository
  775. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  776. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  777. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  778. enabled = 1
  779. fastestmirror = 1
  780. metadata_expire = 7d
  781. gpgcheck = 1
  782. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  783. '''
  784. repo_conf1.write(repo_str1.encode())
  785. repo_conf1.flush()
  786. args = argparse.Namespace(
  787. enablerepo=[],
  788. disablerepo=[],
  789. repoid=['repo1', 'repo2'],
  790. releasever='4.1',
  791. repo_files=[repo_conf1.name]
  792. )
  793. res = qubesadmin.tools.qvm_template.qrexec_payload(args, self.app,
  794. 'qubes-template-fedora-32', False)
  795. self.assertEqual(res,
  796. '''--repoid=repo1
  797. --repoid=repo2
  798. --releasever=4.1
  799. qubes-template-fedora-32
  800. ---
  801. ''' + repo_str1 + '\n')
  802. self.assertAllCalled()
  803. def test_112_qrexec_payload_specnewline_fail(self):
  804. with tempfile.NamedTemporaryFile() as repo_conf1:
  805. repo_str1 = \
  806. '''[qubes-templates-itl]
  807. name = Qubes Templates repository
  808. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  809. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  810. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  811. enabled = 1
  812. fastestmirror = 1
  813. metadata_expire = 7d
  814. gpgcheck = 1
  815. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  816. '''
  817. repo_conf1.write(repo_str1.encode())
  818. repo_conf1.flush()
  819. args = argparse.Namespace(
  820. enablerepo=[],
  821. disablerepo=[],
  822. repoid=['repo1', 'repo2'],
  823. releasever='4.1',
  824. repo_files=[repo_conf1.name]
  825. )
  826. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  827. with self.assertRaises(SystemExit):
  828. qubesadmin.tools.qvm_template.qrexec_payload(args,
  829. self.app, 'qubes-template-fedora\n-32', False)
  830. # Check error message
  831. self.assertTrue('Malformed template name'
  832. in mock_err.getvalue())
  833. self.assertTrue("argument should not contain '\\n'"
  834. in mock_err.getvalue())
  835. self.assertAllCalled()
  836. def test_113_qrexec_payload_enablereponewline_fail(self):
  837. with tempfile.NamedTemporaryFile() as repo_conf1:
  838. repo_str1 = \
  839. '''[qubes-templates-itl]
  840. name = Qubes Templates repository
  841. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  842. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  843. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  844. enabled = 1
  845. fastestmirror = 1
  846. metadata_expire = 7d
  847. gpgcheck = 1
  848. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  849. '''
  850. repo_conf1.write(repo_str1.encode())
  851. repo_conf1.flush()
  852. args = argparse.Namespace(
  853. enablerepo=['repo\n0'],
  854. disablerepo=[],
  855. repoid=['repo1', 'repo2'],
  856. releasever='4.1',
  857. repo_files=[repo_conf1.name]
  858. )
  859. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  860. with self.assertRaises(SystemExit):
  861. qubesadmin.tools.qvm_template.qrexec_payload(args,
  862. self.app, 'qubes-template-fedora-32', False)
  863. # Check error message
  864. self.assertTrue('Malformed --enablerepo'
  865. in mock_err.getvalue())
  866. self.assertTrue("argument should not contain '\\n'"
  867. in mock_err.getvalue())
  868. self.assertAllCalled()
  869. def test_114_qrexec_payload_disablereponewline_fail(self):
  870. with tempfile.NamedTemporaryFile() as repo_conf1:
  871. repo_str1 = \
  872. '''[qubes-templates-itl]
  873. name = Qubes Templates repository
  874. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  875. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  876. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  877. enabled = 1
  878. fastestmirror = 1
  879. metadata_expire = 7d
  880. gpgcheck = 1
  881. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  882. '''
  883. repo_conf1.write(repo_str1.encode())
  884. repo_conf1.flush()
  885. args = argparse.Namespace(
  886. enablerepo=[],
  887. disablerepo=['repo\n0'],
  888. repoid=['repo1', 'repo2'],
  889. releasever='4.1',
  890. repo_files=[repo_conf1.name]
  891. )
  892. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  893. with self.assertRaises(SystemExit):
  894. qubesadmin.tools.qvm_template.qrexec_payload(args,
  895. self.app, 'qubes-template-fedora-32', False)
  896. # Check error message
  897. self.assertTrue('Malformed --disablerepo'
  898. in mock_err.getvalue())
  899. self.assertTrue("argument should not contain '\\n'"
  900. in mock_err.getvalue())
  901. self.assertAllCalled()
  902. def test_115_qrexec_payload_repoidnewline_fail(self):
  903. with tempfile.NamedTemporaryFile() as repo_conf1:
  904. repo_str1 = \
  905. '''[qubes-templates-itl]
  906. name = Qubes Templates repository
  907. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  908. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  909. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  910. enabled = 1
  911. fastestmirror = 1
  912. metadata_expire = 7d
  913. gpgcheck = 1
  914. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  915. '''
  916. repo_conf1.write(repo_str1.encode())
  917. repo_conf1.flush()
  918. args = argparse.Namespace(
  919. enablerepo=[],
  920. disablerepo=[],
  921. repoid=['repo\n1', 'repo2'],
  922. releasever='4.1',
  923. repo_files=[repo_conf1.name]
  924. )
  925. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  926. with self.assertRaises(SystemExit):
  927. qubesadmin.tools.qvm_template.qrexec_payload(args,
  928. self.app, 'qubes-template-fedora-32', False)
  929. # Check error message
  930. self.assertTrue('Malformed --repoid'
  931. in mock_err.getvalue())
  932. self.assertTrue("argument should not contain '\\n'"
  933. in mock_err.getvalue())
  934. self.assertAllCalled()
  935. def test_116_qrexec_payload_releasevernewline_fail(self):
  936. with tempfile.NamedTemporaryFile() as repo_conf1:
  937. repo_str1 = \
  938. '''[qubes-templates-itl]
  939. name = Qubes Templates repository
  940. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  941. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  942. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  943. enabled = 1
  944. fastestmirror = 1
  945. metadata_expire = 7d
  946. gpgcheck = 1
  947. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  948. '''
  949. repo_conf1.write(repo_str1.encode())
  950. repo_conf1.flush()
  951. args = argparse.Namespace(
  952. enablerepo=[],
  953. disablerepo=[],
  954. repoid=['repo1', 'repo2'],
  955. releasever='4\n.1',
  956. repo_files=[repo_conf1.name]
  957. )
  958. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  959. with self.assertRaises(SystemExit):
  960. qubesadmin.tools.qvm_template.qrexec_payload(args,
  961. self.app, 'qubes-template-fedora-32', False)
  962. # Check error message
  963. self.assertTrue('Malformed --releasever'
  964. in mock_err.getvalue())
  965. self.assertTrue("argument should not contain '\\n'"
  966. in mock_err.getvalue())
  967. self.assertAllCalled()
  968. def test_117_qrexec_payload_specdash_fail(self):
  969. with tempfile.NamedTemporaryFile() as repo_conf1:
  970. repo_str1 = \
  971. '''[qubes-templates-itl]
  972. name = Qubes Templates repository
  973. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  974. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  975. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  976. enabled = 1
  977. fastestmirror = 1
  978. metadata_expire = 7d
  979. gpgcheck = 1
  980. gpgkey = file:///usr/share/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  981. '''
  982. repo_conf1.write(repo_str1.encode())
  983. repo_conf1.flush()
  984. args = argparse.Namespace(
  985. enablerepo=[],
  986. disablerepo=[],
  987. repoid=['repo1', 'repo2'],
  988. releasever='4.1',
  989. repo_files=[repo_conf1.name]
  990. )
  991. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  992. with self.assertRaises(SystemExit):
  993. qubesadmin.tools.qvm_template.qrexec_payload(args,
  994. self.app, '---', False)
  995. # Check error message
  996. self.assertTrue('Malformed template name'
  997. in mock_err.getvalue())
  998. self.assertTrue("argument should not be '---'"
  999. in mock_err.getvalue())
  1000. self.assertAllCalled()
  1001. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1002. def test_120_qrexec_repoquery_success(self, mock_payload):
  1003. args = argparse.Namespace(updatevm='test-vm')
  1004. mock_payload.return_value = 'str1\nstr2'
  1005. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1006. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1007. self.app.expected_service_calls[
  1008. ('test-vm', 'qubes.TemplateSearch')] = \
  1009. b'''qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template\n for fedora-32\n|
  1010. qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1011. '''
  1012. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1013. 'qubes-template-fedora-32')
  1014. self.assertEqual(res, [
  1015. qubesadmin.tools.qvm_template.Template(
  1016. 'fedora-32',
  1017. '0',
  1018. '4.1',
  1019. '20200101',
  1020. 'qubes-templates-itl',
  1021. 1048576,
  1022. datetime.datetime(2020, 1, 23, 4, 56),
  1023. 'GPL',
  1024. 'https://qubes-os.org',
  1025. 'Qubes template for fedora-32',
  1026. 'Qubes template\n for fedora-32\n'
  1027. ),
  1028. qubesadmin.tools.qvm_template.Template(
  1029. 'fedora-32',
  1030. '1',
  1031. '4.2',
  1032. '20200201',
  1033. 'qubes-templates-itl-testing',
  1034. 2048576,
  1035. datetime.datetime(2020, 2, 23, 4, 56),
  1036. 'GPLv2',
  1037. 'https://qubes-os.org/?',
  1038. 'Qubes template for fedora-32 v2',
  1039. 'Qubes template\n for fedora-32 v2\n'
  1040. )
  1041. ])
  1042. self.assertEqual(self.app.service_calls, [
  1043. ('test-vm', 'qubes.TemplateSearch',
  1044. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1045. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1046. ])
  1047. self.assertEqual(mock_payload.mock_calls, [
  1048. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1049. ])
  1050. self.assertAllCalled()
  1051. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1052. def test_121_qrexec_repoquery_refresh_success(self, mock_payload):
  1053. args = argparse.Namespace(updatevm='test-vm')
  1054. mock_payload.return_value = 'str1\nstr2'
  1055. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1056. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1057. self.app.expected_service_calls[
  1058. ('test-vm', 'qubes.TemplateSearch')] = \
  1059. b'''qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template\n for fedora-32\n|
  1060. qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1061. '''
  1062. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1063. 'qubes-template-fedora-32', True)
  1064. self.assertEqual(res, [
  1065. qubesadmin.tools.qvm_template.Template(
  1066. 'fedora-32',
  1067. '0',
  1068. '4.1',
  1069. '20200101',
  1070. 'qubes-templates-itl',
  1071. 1048576,
  1072. datetime.datetime(2020, 1, 23, 4, 56),
  1073. 'GPL',
  1074. 'https://qubes-os.org',
  1075. 'Qubes template for fedora-32',
  1076. 'Qubes template\n for fedora-32\n'
  1077. ),
  1078. qubesadmin.tools.qvm_template.Template(
  1079. 'fedora-32',
  1080. '1',
  1081. '4.2',
  1082. '20200201',
  1083. 'qubes-templates-itl-testing',
  1084. 2048576,
  1085. datetime.datetime(2020, 2, 23, 4, 56),
  1086. 'GPLv2',
  1087. 'https://qubes-os.org/?',
  1088. 'Qubes template for fedora-32 v2',
  1089. 'Qubes template\n for fedora-32 v2\n'
  1090. )
  1091. ])
  1092. self.assertEqual(self.app.service_calls, [
  1093. ('test-vm', 'qubes.TemplateSearch',
  1094. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1095. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1096. ])
  1097. self.assertEqual(mock_payload.mock_calls, [
  1098. mock.call(args, self.app, 'qubes-template-fedora-32', True)
  1099. ])
  1100. self.assertAllCalled()
  1101. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1102. def test_122_qrexec_repoquery_ignorenonspec_success(self, mock_payload):
  1103. args = argparse.Namespace(updatevm='test-vm')
  1104. mock_payload.return_value = 'str1\nstr2'
  1105. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1106. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1107. self.app.expected_service_calls[
  1108. ('test-vm', 'qubes.TemplateSearch')] = \
  1109. b'''qubes-template-debian-10|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for debian-10|Qubes template for debian-10\n|
  1110. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1111. '''
  1112. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1113. 'qubes-template-fedora-32')
  1114. self.assertEqual(res, [
  1115. qubesadmin.tools.qvm_template.Template(
  1116. 'fedora-32',
  1117. '0',
  1118. '4.1',
  1119. '20200101',
  1120. 'qubes-templates-itl',
  1121. 1048576,
  1122. datetime.datetime(2020, 1, 23, 4, 56),
  1123. 'GPL',
  1124. 'https://qubes-os.org',
  1125. 'Qubes template for fedora-32',
  1126. 'Qubes template for fedora-32\n'
  1127. )
  1128. ])
  1129. self.assertEqual(self.app.service_calls, [
  1130. ('test-vm', 'qubes.TemplateSearch',
  1131. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1132. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1133. ])
  1134. self.assertEqual(mock_payload.mock_calls, [
  1135. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1136. ])
  1137. self.assertAllCalled()
  1138. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1139. def test_123_qrexec_repoquery_ignorebadname_success(self, mock_payload):
  1140. args = argparse.Namespace(updatevm='test-vm')
  1141. mock_payload.return_value = 'str1\nstr2'
  1142. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1143. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1144. self.app.expected_service_calls[
  1145. ('test-vm', 'qubes.TemplateSearch')] = \
  1146. b'''template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1147. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1148. '''
  1149. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1150. 'qubes-template-fedora-32')
  1151. self.assertEqual(res, [
  1152. qubesadmin.tools.qvm_template.Template(
  1153. 'fedora-32',
  1154. '0',
  1155. '4.1',
  1156. '20200101',
  1157. 'qubes-templates-itl',
  1158. 1048576,
  1159. datetime.datetime(2020, 1, 23, 4, 56),
  1160. 'GPL',
  1161. 'https://qubes-os.org',
  1162. 'Qubes template for fedora-32',
  1163. 'Qubes template for fedora-32\n'
  1164. )
  1165. ])
  1166. self.assertEqual(self.app.service_calls, [
  1167. ('test-vm', 'qubes.TemplateSearch',
  1168. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1169. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1170. ])
  1171. self.assertEqual(mock_payload.mock_calls, [
  1172. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1173. ])
  1174. self.assertAllCalled()
  1175. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1176. def test_124_qrexec_repoquery_searchfail_fail(self, mock_payload):
  1177. args = argparse.Namespace(updatevm='test-vm')
  1178. mock_payload.return_value = 'str1\nstr2'
  1179. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1180. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1181. with mock.patch('qubesadmin.tests.TestProcess.wait') \
  1182. as mock_wait:
  1183. mock_wait.return_value = 1
  1184. with self.assertRaises(ConnectionError):
  1185. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1186. 'qubes-template-fedora-32')
  1187. self.assertEqual(self.app.service_calls, [
  1188. ('test-vm', 'qubes.TemplateSearch',
  1189. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1190. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1191. ])
  1192. self.assertEqual(mock_payload.mock_calls, [
  1193. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1194. ])
  1195. self.assertAllCalled()
  1196. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1197. def test_125_qrexec_repoquery_extrafield_fail(self, mock_payload):
  1198. args = argparse.Namespace(updatevm='test-vm')
  1199. mock_payload.return_value = 'str1\nstr2'
  1200. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1201. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1202. self.app.expected_service_calls[
  1203. ('test-vm', 'qubes.TemplateSearch')] = \
  1204. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Extra field|Qubes template\n for fedora-32 v2\n|
  1205. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1206. '''
  1207. with self.assertRaisesRegex(ConnectionError,
  1208. "unexpected data format"):
  1209. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1210. 'qubes-template-fedora-32')
  1211. self.assertEqual(self.app.service_calls, [
  1212. ('test-vm', 'qubes.TemplateSearch',
  1213. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1214. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1215. ])
  1216. self.assertEqual(mock_payload.mock_calls, [
  1217. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1218. ])
  1219. self.assertAllCalled()
  1220. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1221. def test_125_qrexec_repoquery_missingfield_fail(self, mock_payload):
  1222. args = argparse.Namespace(updatevm='test-vm')
  1223. mock_payload.return_value = 'str1\nstr2'
  1224. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1225. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1226. self.app.expected_service_calls[
  1227. ('test-vm', 'qubes.TemplateSearch')] = \
  1228. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1229. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1230. '''
  1231. with self.assertRaisesRegex(ConnectionError,
  1232. "unexpected data format"):
  1233. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1234. 'qubes-template-fedora-32')
  1235. self.assertEqual(self.app.service_calls, [
  1236. ('test-vm', 'qubes.TemplateSearch',
  1237. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1238. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1239. ])
  1240. self.assertEqual(mock_payload.mock_calls, [
  1241. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1242. ])
  1243. self.assertAllCalled()
  1244. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1245. def test_126_qrexec_repoquery_badfieldname_fail(self, mock_payload):
  1246. args = argparse.Namespace(updatevm='test-vm')
  1247. mock_payload.return_value = 'str1\nstr2'
  1248. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1249. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1250. self.app.expected_service_calls[
  1251. ('test-vm', 'qubes.TemplateSearch')] = \
  1252. b'''qubes-template-fedora-(32)|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1253. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1254. '''
  1255. with self.assertRaisesRegex(ConnectionError,
  1256. "unexpected data format"):
  1257. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1258. 'qubes-template-fedora-32')
  1259. self.assertEqual(self.app.service_calls, [
  1260. ('test-vm', 'qubes.TemplateSearch',
  1261. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1262. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1263. ])
  1264. self.assertEqual(mock_payload.mock_calls, [
  1265. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1266. ])
  1267. self.assertAllCalled()
  1268. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1269. def test_126_qrexec_repoquery_badfieldepoch_fail(self, mock_payload):
  1270. args = argparse.Namespace(updatevm='test-vm')
  1271. mock_payload.return_value = 'str1\nstr2'
  1272. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1273. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1274. self.app.expected_service_calls[
  1275. ('test-vm', 'qubes.TemplateSearch')] = \
  1276. b'''qubes-template-fedora-32|!1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1277. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1278. '''
  1279. with self.assertRaisesRegex(ConnectionError,
  1280. "unexpected data format"):
  1281. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1282. 'qubes-template-fedora-32')
  1283. self.assertEqual(self.app.service_calls, [
  1284. ('test-vm', 'qubes.TemplateSearch',
  1285. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1286. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1287. ])
  1288. self.assertEqual(mock_payload.mock_calls, [
  1289. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1290. ])
  1291. self.assertAllCalled()
  1292. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1293. def test_126_qrexec_repoquery_badfieldreponame_fail(self, mock_payload):
  1294. args = argparse.Namespace(updatevm='test-vm')
  1295. mock_payload.return_value = 'str1\nstr2'
  1296. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1297. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1298. self.app.expected_service_calls[
  1299. ('test-vm', 'qubes.TemplateSearch')] = \
  1300. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-<testing>|2048576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1301. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1302. '''
  1303. with self.assertRaisesRegex(ConnectionError,
  1304. "unexpected data format"):
  1305. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1306. 'qubes-template-fedora-32')
  1307. self.assertEqual(self.app.service_calls, [
  1308. ('test-vm', 'qubes.TemplateSearch',
  1309. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1310. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1311. ])
  1312. self.assertEqual(mock_payload.mock_calls, [
  1313. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1314. ])
  1315. self.assertAllCalled()
  1316. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1317. def test_126_qrexec_repoquery_badfielddlsize_fail(self, mock_payload):
  1318. args = argparse.Namespace(updatevm='test-vm')
  1319. mock_payload.return_value = 'str1\nstr2'
  1320. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1321. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1322. self.app.expected_service_calls[
  1323. ('test-vm', 'qubes.TemplateSearch')] = \
  1324. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048a576|2020-02-23 04:56|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1325. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1326. '''
  1327. with self.assertRaisesRegex(ConnectionError,
  1328. "unexpected data format"):
  1329. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1330. 'qubes-template-fedora-32')
  1331. self.assertEqual(self.app.service_calls, [
  1332. ('test-vm', 'qubes.TemplateSearch',
  1333. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1334. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1335. ])
  1336. self.assertEqual(mock_payload.mock_calls, [
  1337. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1338. ])
  1339. self.assertAllCalled()
  1340. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1341. def test_126_qrexec_repoquery_badfielddate_fail(self, mock_payload):
  1342. args = argparse.Namespace(updatevm='test-vm')
  1343. mock_payload.return_value = 'str1\nstr2'
  1344. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1345. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1346. self.app.expected_service_calls[
  1347. ('test-vm', 'qubes.TemplateSearch')] = \
  1348. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23|GPLv2|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1349. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1350. '''
  1351. with self.assertRaisesRegex(ConnectionError,
  1352. "unexpected data format"):
  1353. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1354. 'qubes-template-fedora-32')
  1355. self.assertEqual(self.app.service_calls, [
  1356. ('test-vm', 'qubes.TemplateSearch',
  1357. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1358. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1359. ])
  1360. self.assertEqual(mock_payload.mock_calls, [
  1361. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1362. ])
  1363. self.assertAllCalled()
  1364. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1365. def test_126_qrexec_repoquery_license_fail(self, mock_payload):
  1366. args = argparse.Namespace(updatevm='test-vm')
  1367. mock_payload.return_value = 'str1\nstr2'
  1368. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1369. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1370. self.app.expected_service_calls[
  1371. ('test-vm', 'qubes.TemplateSearch')] = \
  1372. b'''qubes-template-fedora-32|1|4.2|20200201|qubes-templates-itl-testing|2048576|2020-02-23 04:56|GPLv2:)|https://qubes-os.org/?|Qubes template for fedora-32 v2|Qubes template\n for fedora-32 v2\n|
  1373. qubes-template-fedora-32|0|4.1|20200101|qubes-templates-itl|1048576|2020-01-23 04:56|GPL|https://qubes-os.org|Qubes template for fedora-32|Qubes template for fedora-32\n|
  1374. '''
  1375. with self.assertRaisesRegex(ConnectionError,
  1376. "unexpected data format"):
  1377. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1378. 'qubes-template-fedora-32')
  1379. self.assertEqual(self.app.service_calls, [
  1380. ('test-vm', 'qubes.TemplateSearch',
  1381. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1382. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1383. ])
  1384. self.assertEqual(mock_payload.mock_calls, [
  1385. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1386. ])
  1387. self.assertAllCalled()
  1388. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1389. def test_130_get_dl_list_latest_success(self, mock_query):
  1390. mock_query.return_value = [
  1391. qubesadmin.tools.qvm_template.Template(
  1392. 'fedora-32',
  1393. '1',
  1394. '4.1',
  1395. '20200101',
  1396. 'qubes-templates-itl',
  1397. 1048576,
  1398. datetime.datetime(2020, 1, 23, 4, 56),
  1399. 'GPL',
  1400. 'https://qubes-os.org',
  1401. 'Qubes template for fedora-32',
  1402. 'Qubes template\n for fedora-32\n'
  1403. ),
  1404. qubesadmin.tools.qvm_template.Template(
  1405. 'fedora-32',
  1406. '0',
  1407. '4.2',
  1408. '20200201',
  1409. 'qubes-templates-itl-testing',
  1410. 2048576,
  1411. datetime.datetime(2020, 2, 23, 4, 56),
  1412. 'GPLv2',
  1413. 'https://qubes-os.org/?',
  1414. 'Qubes template for fedora-32 v2',
  1415. 'Qubes template\n for fedora-32 v2\n'
  1416. )
  1417. ]
  1418. args = argparse.Namespace(
  1419. templates=['some.local.file.rpm', 'fedora-32']
  1420. )
  1421. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1422. self.assertEqual(ret, {
  1423. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1424. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  1425. })
  1426. self.assertEqual(mock_query.mock_calls, [
  1427. mock.call(args, self.app, 'qubes-template-fedora-32')
  1428. ])
  1429. self.assertAllCalled()
  1430. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1431. def test_131_get_dl_list_latest_notfound_fail(self, mock_query):
  1432. mock_query.return_value = []
  1433. args = argparse.Namespace(
  1434. templates=['some.local.file.rpm', 'fedora-31']
  1435. )
  1436. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1437. with self.assertRaises(SystemExit):
  1438. qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1439. self.assertTrue('not found' in mock_err.getvalue())
  1440. self.assertEqual(mock_query.mock_calls, [
  1441. mock.call(args, self.app, 'qubes-template-fedora-31')
  1442. ])
  1443. self.assertAllCalled()
  1444. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1445. def test_132_get_dl_list_multimerge0_success(self, mock_query):
  1446. counter = 0
  1447. def f(*args):
  1448. nonlocal counter
  1449. counter += 1
  1450. if counter == 1:
  1451. return [
  1452. qubesadmin.tools.qvm_template.Template(
  1453. 'fedora-32',
  1454. '0',
  1455. '4.2',
  1456. '20200201',
  1457. 'qubes-templates-itl-testing',
  1458. 2048576,
  1459. datetime.datetime(2020, 2, 23, 4, 56),
  1460. 'GPLv2',
  1461. 'https://qubes-os.org/?',
  1462. 'Qubes template for fedora-32 v2',
  1463. 'Qubes template\n for fedora-32 v2\n'
  1464. )
  1465. ]
  1466. return [
  1467. qubesadmin.tools.qvm_template.Template(
  1468. 'fedora-32',
  1469. '1',
  1470. '4.1',
  1471. '20200101',
  1472. 'qubes-templates-itl',
  1473. 1048576,
  1474. datetime.datetime(2020, 1, 23, 4, 56),
  1475. 'GPL',
  1476. 'https://qubes-os.org',
  1477. 'Qubes template for fedora-32',
  1478. 'Qubes template\n for fedora-32\n'
  1479. )
  1480. ]
  1481. mock_query.side_effect = f
  1482. args = argparse.Namespace(
  1483. templates=['some.local.file.rpm', 'fedora-32:0', 'fedora-32:1']
  1484. )
  1485. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1486. self.assertEqual(ret, {
  1487. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1488. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  1489. })
  1490. self.assertEqual(mock_query.mock_calls, [
  1491. mock.call(args, self.app, 'qubes-template-fedora-32:0'),
  1492. mock.call(args, self.app, 'qubes-template-fedora-32:1')
  1493. ])
  1494. self.assertAllCalled()
  1495. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1496. def test_132_get_dl_list_multimerge1_success(self, mock_query):
  1497. counter = 0
  1498. def f(*args):
  1499. nonlocal counter
  1500. counter += 1
  1501. if counter == 1:
  1502. return [
  1503. qubesadmin.tools.qvm_template.Template(
  1504. 'fedora-32',
  1505. '2',
  1506. '4.2',
  1507. '20200201',
  1508. 'qubes-templates-itl-testing',
  1509. 2048576,
  1510. datetime.datetime(2020, 2, 23, 4, 56),
  1511. 'GPLv2',
  1512. 'https://qubes-os.org/?',
  1513. 'Qubes template for fedora-32 v2',
  1514. 'Qubes template\n for fedora-32 v2\n'
  1515. )
  1516. ]
  1517. return [
  1518. qubesadmin.tools.qvm_template.Template(
  1519. 'fedora-32',
  1520. '1',
  1521. '4.1',
  1522. '20200101',
  1523. 'qubes-templates-itl',
  1524. 1048576,
  1525. datetime.datetime(2020, 1, 23, 4, 56),
  1526. 'GPL',
  1527. 'https://qubes-os.org',
  1528. 'Qubes template for fedora-32',
  1529. 'Qubes template\n for fedora-32\n'
  1530. )
  1531. ]
  1532. mock_query.side_effect = f
  1533. args = argparse.Namespace(
  1534. templates=['some.local.file.rpm', 'fedora-32:2', 'fedora-32:1']
  1535. )
  1536. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1537. self.assertEqual(ret, {
  1538. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1539. ('2', '4.2', '20200201'),
  1540. 'qubes-templates-itl-testing',
  1541. 2048576)
  1542. })
  1543. self.assertEqual(mock_query.mock_calls, [
  1544. mock.call(args, self.app, 'qubes-template-fedora-32:2'),
  1545. mock.call(args, self.app, 'qubes-template-fedora-32:1')
  1546. ])
  1547. self.assertAllCalled()
  1548. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1549. def test_133_get_dl_list_reinstall_success(self, mock_query):
  1550. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1551. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1552. self.app.expected_calls[(
  1553. 'test-vm',
  1554. 'admin.vm.feature.Get',
  1555. f'template-name',
  1556. None)] = b'0\0test-vm'
  1557. self.app.expected_calls[(
  1558. 'test-vm',
  1559. 'admin.vm.feature.Get',
  1560. f'template-epoch',
  1561. None)] = b'0\x000'
  1562. self.app.expected_calls[(
  1563. 'test-vm',
  1564. 'admin.vm.feature.Get',
  1565. f'template-version',
  1566. None)] = b'0\x004.2'
  1567. self.app.expected_calls[(
  1568. 'test-vm',
  1569. 'admin.vm.feature.Get',
  1570. f'template-release',
  1571. None)] = b'0\x0020200201'
  1572. mock_query.return_value = [
  1573. qubesadmin.tools.qvm_template.Template(
  1574. 'test-vm',
  1575. '1',
  1576. '4.1',
  1577. '20200101',
  1578. 'qubes-templates-itl',
  1579. 1048576,
  1580. datetime.datetime(2020, 1, 23, 4, 56),
  1581. 'GPL',
  1582. 'https://qubes-os.org',
  1583. 'Qubes template for test-vm',
  1584. 'Qubes template\n for test-vm\n'
  1585. ),
  1586. qubesadmin.tools.qvm_template.Template(
  1587. 'test-vm',
  1588. '0',
  1589. '4.2',
  1590. '20200201',
  1591. 'qubes-templates-itl-testing',
  1592. 2048576,
  1593. datetime.datetime(2020, 2, 23, 4, 56),
  1594. 'GPLv2',
  1595. 'https://qubes-os.org/?',
  1596. 'Qubes template for test-vm v2',
  1597. 'Qubes template\n for test-vm v2\n'
  1598. )
  1599. ]
  1600. args = argparse.Namespace(
  1601. templates=['some.local.file.rpm', 'test-vm']
  1602. )
  1603. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1604. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1605. self.assertEqual(ret, {
  1606. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  1607. ('0', '4.2', '20200201'),
  1608. 'qubes-templates-itl-testing',
  1609. 2048576
  1610. )
  1611. })
  1612. self.assertEqual(mock_query.mock_calls, [
  1613. mock.call(args, self.app, 'qubes-template-test-vm')
  1614. ])
  1615. self.assertAllCalled()
  1616. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1617. def test_134_get_dl_list_reinstall_nolocal_fail(self, mock_query):
  1618. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1619. b'0\x00'
  1620. mock_query.return_value = [
  1621. qubesadmin.tools.qvm_template.Template(
  1622. 'test-vm',
  1623. '1',
  1624. '4.1',
  1625. '20200101',
  1626. 'qubes-templates-itl',
  1627. 1048576,
  1628. datetime.datetime(2020, 1, 23, 4, 56),
  1629. 'GPL',
  1630. 'https://qubes-os.org',
  1631. 'Qubes template for test-vm',
  1632. 'Qubes template\n for test-vm\n'
  1633. ),
  1634. qubesadmin.tools.qvm_template.Template(
  1635. 'test-vm',
  1636. '0',
  1637. '4.2',
  1638. '20200201',
  1639. 'qubes-templates-itl-testing',
  1640. 2048576,
  1641. datetime.datetime(2020, 2, 23, 4, 56),
  1642. 'GPLv2',
  1643. 'https://qubes-os.org/?',
  1644. 'Qubes template for test-vm v2',
  1645. 'Qubes template\n for test-vm v2\n'
  1646. )
  1647. ]
  1648. args = argparse.Namespace(templates=['test-vm'])
  1649. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1650. with self.assertRaises(SystemExit):
  1651. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1652. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1653. self.assertTrue('not already installed' in mock_err.getvalue())
  1654. self.assertEqual(mock_query.mock_calls, [
  1655. mock.call(args, self.app, 'qubes-template-test-vm')
  1656. ])
  1657. self.assertAllCalled()
  1658. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1659. def test_135_get_dl_list_reinstall_nonmanaged_fail(self, mock_query):
  1660. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1661. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1662. mock_query.return_value = [
  1663. qubesadmin.tools.qvm_template.Template(
  1664. 'test-vm',
  1665. '1',
  1666. '4.1',
  1667. '20200101',
  1668. 'qubes-templates-itl',
  1669. 1048576,
  1670. datetime.datetime(2020, 1, 23, 4, 56),
  1671. 'GPL',
  1672. 'https://qubes-os.org',
  1673. 'Qubes template for test-vm',
  1674. 'Qubes template\n for test-vm\n'
  1675. ),
  1676. qubesadmin.tools.qvm_template.Template(
  1677. 'test-vm',
  1678. '0',
  1679. '4.2',
  1680. '20200201',
  1681. 'qubes-templates-itl-testing',
  1682. 2048576,
  1683. datetime.datetime(2020, 2, 23, 4, 56),
  1684. 'GPLv2',
  1685. 'https://qubes-os.org/?',
  1686. 'Qubes template for test-vm v2',
  1687. 'Qubes template\n for test-vm v2\n'
  1688. )
  1689. ]
  1690. args = argparse.Namespace(templates=['test-vm'])
  1691. def qubesd_call(dest, method,
  1692. arg=None, payload=None, payload_stream=None,
  1693. orig_func=self.app.qubesd_call):
  1694. if method == 'admin.vm.feature.Get':
  1695. raise KeyError
  1696. return orig_func(dest, method, arg, payload, payload_stream)
  1697. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  1698. mock.patch.object(self.app, 'qubesd_call') as mock_call:
  1699. mock_call.side_effect = qubesd_call
  1700. with self.assertRaises(SystemExit):
  1701. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1702. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1703. self.assertTrue('not managed' in mock_err.getvalue())
  1704. self.assertEqual(mock_query.mock_calls, [
  1705. mock.call(args, self.app, 'qubes-template-test-vm')
  1706. ])
  1707. self.assertAllCalled()
  1708. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1709. def test_135_get_dl_list_reinstall_nonmanagednoname_fail(self, mock_query):
  1710. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1711. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1712. self.app.expected_calls[(
  1713. 'test-vm',
  1714. 'admin.vm.feature.Get',
  1715. f'template-name',
  1716. None)] = b'0\0test-vm-2'
  1717. mock_query.return_value = [
  1718. qubesadmin.tools.qvm_template.Template(
  1719. 'test-vm',
  1720. '1',
  1721. '4.1',
  1722. '20200101',
  1723. 'qubes-templates-itl',
  1724. 1048576,
  1725. datetime.datetime(2020, 1, 23, 4, 56),
  1726. 'GPL',
  1727. 'https://qubes-os.org',
  1728. 'Qubes template for test-vm',
  1729. 'Qubes template\n for test-vm\n'
  1730. ),
  1731. qubesadmin.tools.qvm_template.Template(
  1732. 'test-vm',
  1733. '0',
  1734. '4.2',
  1735. '20200201',
  1736. 'qubes-templates-itl-testing',
  1737. 2048576,
  1738. datetime.datetime(2020, 2, 23, 4, 56),
  1739. 'GPLv2',
  1740. 'https://qubes-os.org/?',
  1741. 'Qubes template for test-vm v2',
  1742. 'Qubes template\n for test-vm v2\n'
  1743. )
  1744. ]
  1745. args = argparse.Namespace(templates=['test-vm'])
  1746. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1747. with self.assertRaises(SystemExit):
  1748. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1749. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1750. self.assertTrue('not managed' in mock_err.getvalue())
  1751. self.assertEqual(mock_query.mock_calls, [
  1752. mock.call(args, self.app, 'qubes-template-test-vm')
  1753. ])
  1754. self.assertAllCalled()
  1755. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1756. def test_136_get_dl_list_downgrade_success(self, mock_query):
  1757. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1758. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1759. self.app.expected_calls[(
  1760. 'test-vm',
  1761. 'admin.vm.feature.Get',
  1762. f'template-name',
  1763. None)] = b'0\0test-vm'
  1764. self.app.expected_calls[(
  1765. 'test-vm',
  1766. 'admin.vm.feature.Get',
  1767. f'template-epoch',
  1768. None)] = b'0\x000'
  1769. self.app.expected_calls[(
  1770. 'test-vm',
  1771. 'admin.vm.feature.Get',
  1772. f'template-version',
  1773. None)] = b'0\x004.3'
  1774. self.app.expected_calls[(
  1775. 'test-vm',
  1776. 'admin.vm.feature.Get',
  1777. f'template-release',
  1778. None)] = b'0\x0020200201'
  1779. mock_query.return_value = [
  1780. qubesadmin.tools.qvm_template.Template(
  1781. 'test-vm',
  1782. '0',
  1783. '4.2',
  1784. '20200201',
  1785. 'qubes-templates-itl-testing',
  1786. 2048576,
  1787. datetime.datetime(2020, 2, 23, 4, 56),
  1788. 'GPLv2',
  1789. 'https://qubes-os.org/?',
  1790. 'Qubes template for test-vm v2',
  1791. 'Qubes template\n for test-vm v2\n'
  1792. ),
  1793. qubesadmin.tools.qvm_template.Template(
  1794. 'test-vm',
  1795. '0',
  1796. '4.1',
  1797. '20200101',
  1798. 'qubes-templates-itl',
  1799. 1048576,
  1800. datetime.datetime(2020, 1, 23, 4, 56),
  1801. 'GPL',
  1802. 'https://qubes-os.org',
  1803. 'Qubes template for test-vm',
  1804. 'Qubes template\n for test-vm\n'
  1805. ),
  1806. qubesadmin.tools.qvm_template.Template(
  1807. 'test-vm',
  1808. '1',
  1809. '4.1',
  1810. '20200101',
  1811. 'qubes-templates-itl',
  1812. 1048576,
  1813. datetime.datetime(2020, 1, 23, 4, 56),
  1814. 'GPL',
  1815. 'https://qubes-os.org',
  1816. 'Qubes template for test-vm',
  1817. 'Qubes template\n for test-vm\n'
  1818. )
  1819. ]
  1820. args = argparse.Namespace(templates=['test-vm'])
  1821. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1822. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  1823. self.assertEqual(ret, {
  1824. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  1825. ('0', '4.2', '20200201'),
  1826. 'qubes-templates-itl-testing',
  1827. 2048576
  1828. )
  1829. })
  1830. self.assertEqual(mock_query.mock_calls, [
  1831. mock.call(args, self.app, 'qubes-template-test-vm')
  1832. ])
  1833. self.assertAllCalled()
  1834. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1835. def test_137_get_dl_list_downgrade_nonmanaged_fail(self, mock_query):
  1836. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1837. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1838. self.app.expected_calls[(
  1839. 'test-vm',
  1840. 'admin.vm.feature.Get',
  1841. f'template-name',
  1842. None)] = b'0\0test-vm-2'
  1843. mock_query.return_value = [
  1844. qubesadmin.tools.qvm_template.Template(
  1845. 'test-vm',
  1846. '0',
  1847. '4.2',
  1848. '20200201',
  1849. 'qubes-templates-itl-testing',
  1850. 2048576,
  1851. datetime.datetime(2020, 2, 23, 4, 56),
  1852. 'GPLv2',
  1853. 'https://qubes-os.org/?',
  1854. 'Qubes template for test-vm v2',
  1855. 'Qubes template\n for test-vm v2\n'
  1856. ),
  1857. qubesadmin.tools.qvm_template.Template(
  1858. 'test-vm',
  1859. '0',
  1860. '4.1',
  1861. '20200101',
  1862. 'qubes-templates-itl',
  1863. 1048576,
  1864. datetime.datetime(2020, 1, 23, 4, 56),
  1865. 'GPL',
  1866. 'https://qubes-os.org',
  1867. 'Qubes template for test-vm',
  1868. 'Qubes template\n for test-vm\n'
  1869. ),
  1870. qubesadmin.tools.qvm_template.Template(
  1871. 'test-vm',
  1872. '1',
  1873. '4.1',
  1874. '20200101',
  1875. 'qubes-templates-itl',
  1876. 1048576,
  1877. datetime.datetime(2020, 1, 23, 4, 56),
  1878. 'GPL',
  1879. 'https://qubes-os.org',
  1880. 'Qubes template for test-vm',
  1881. 'Qubes template\n for test-vm\n'
  1882. )
  1883. ]
  1884. args = argparse.Namespace(templates=['test-vm'])
  1885. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1886. with self.assertRaises(SystemExit):
  1887. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1888. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1889. self.assertTrue('not managed' in mock_err.getvalue())
  1890. self.assertEqual(mock_query.mock_calls, [
  1891. mock.call(args, self.app, 'qubes-template-test-vm')
  1892. ])
  1893. self.assertAllCalled()
  1894. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1895. def test_138_get_dl_list_downgrade_notfound_skip(self, mock_query):
  1896. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1897. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1898. self.app.expected_calls[(
  1899. 'test-vm',
  1900. 'admin.vm.feature.Get',
  1901. f'template-name',
  1902. None)] = b'0\0test-vm'
  1903. self.app.expected_calls[(
  1904. 'test-vm',
  1905. 'admin.vm.feature.Get',
  1906. f'template-epoch',
  1907. None)] = b'0\x000'
  1908. self.app.expected_calls[(
  1909. 'test-vm',
  1910. 'admin.vm.feature.Get',
  1911. f'template-version',
  1912. None)] = b'0\x004.3'
  1913. self.app.expected_calls[(
  1914. 'test-vm',
  1915. 'admin.vm.feature.Get',
  1916. f'template-release',
  1917. None)] = b'0\x0020200201'
  1918. mock_query.return_value = [
  1919. qubesadmin.tools.qvm_template.Template(
  1920. 'test-vm',
  1921. '1',
  1922. '4.1',
  1923. '20200101',
  1924. 'qubes-templates-itl',
  1925. 1048576,
  1926. datetime.datetime(2020, 1, 23, 4, 56),
  1927. 'GPL',
  1928. 'https://qubes-os.org',
  1929. 'Qubes template for test-vm',
  1930. 'Qubes template\n for test-vm\n'
  1931. )
  1932. ]
  1933. args = argparse.Namespace(templates=['test-vm'])
  1934. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1935. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1936. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  1937. self.assertTrue('lowest version' in mock_err.getvalue())
  1938. self.assertEqual(ret, {})
  1939. self.assertEqual(mock_query.mock_calls, [
  1940. mock.call(args, self.app, 'qubes-template-test-vm')
  1941. ])
  1942. self.assertAllCalled()
  1943. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1944. def test_139_get_dl_list_upgrade_success(self, mock_query):
  1945. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1946. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1947. self.app.expected_calls[(
  1948. 'test-vm',
  1949. 'admin.vm.feature.Get',
  1950. f'template-name',
  1951. None)] = b'0\0test-vm'
  1952. self.app.expected_calls[(
  1953. 'test-vm',
  1954. 'admin.vm.feature.Get',
  1955. f'template-epoch',
  1956. None)] = b'0\x000'
  1957. self.app.expected_calls[(
  1958. 'test-vm',
  1959. 'admin.vm.feature.Get',
  1960. f'template-version',
  1961. None)] = b'0\x004.3'
  1962. self.app.expected_calls[(
  1963. 'test-vm',
  1964. 'admin.vm.feature.Get',
  1965. f'template-release',
  1966. None)] = b'0\x0020200201'
  1967. mock_query.return_value = [
  1968. qubesadmin.tools.qvm_template.Template(
  1969. 'test-vm',
  1970. '1',
  1971. '4.1',
  1972. '20200101',
  1973. 'qubes-templates-itl',
  1974. 1048576,
  1975. datetime.datetime(2020, 1, 23, 4, 56),
  1976. 'GPL',
  1977. 'https://qubes-os.org',
  1978. 'Qubes template for test-vm',
  1979. 'Qubes template\n for test-vm\n'
  1980. )
  1981. ]
  1982. args = argparse.Namespace(templates=['test-vm'])
  1983. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1984. qubesadmin.tools.qvm_template.VersionSelector.LATEST_HIGHER)
  1985. self.assertEqual(ret, {
  1986. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  1987. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576
  1988. )
  1989. })
  1990. self.assertEqual(mock_query.mock_calls, [
  1991. mock.call(args, self.app, 'qubes-template-test-vm')
  1992. ])
  1993. self.assertAllCalled()
  1994. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1995. def test_140_get_dl_list_downgrade_notfound_skip(self, mock_query):
  1996. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1997. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1998. self.app.expected_calls[(
  1999. 'test-vm',
  2000. 'admin.vm.feature.Get',
  2001. f'template-name',
  2002. None)] = b'0\0test-vm'
  2003. self.app.expected_calls[(
  2004. 'test-vm',
  2005. 'admin.vm.feature.Get',
  2006. f'template-epoch',
  2007. None)] = b'0\x000'
  2008. self.app.expected_calls[(
  2009. 'test-vm',
  2010. 'admin.vm.feature.Get',
  2011. f'template-version',
  2012. None)] = b'0\x004.3'
  2013. self.app.expected_calls[(
  2014. 'test-vm',
  2015. 'admin.vm.feature.Get',
  2016. f'template-release',
  2017. None)] = b'0\x0020200201'
  2018. mock_query.return_value = [
  2019. qubesadmin.tools.qvm_template.Template(
  2020. 'test-vm',
  2021. '0',
  2022. '4.1',
  2023. '20200101',
  2024. 'qubes-templates-itl',
  2025. 1048576,
  2026. datetime.datetime(2020, 1, 23, 4, 56),
  2027. 'GPL',
  2028. 'https://qubes-os.org',
  2029. 'Qubes template for test-vm',
  2030. 'Qubes template\n for test-vm\n'
  2031. )
  2032. ]
  2033. args = argparse.Namespace(templates=['test-vm'])
  2034. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2035. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2036. qubesadmin.tools.qvm_template.VersionSelector.LATEST_HIGHER)
  2037. self.assertTrue('highest version' in mock_err.getvalue())
  2038. self.assertEqual(ret, {})
  2039. self.assertEqual(mock_query.mock_calls, [
  2040. mock.call(args, self.app, 'qubes-template-test-vm')
  2041. ])
  2042. self.assertAllCalled()
  2043. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2044. def test_141_get_dl_list_reinstall_notfound_fail(self, mock_query):
  2045. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2046. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2047. self.app.expected_calls[(
  2048. 'test-vm',
  2049. 'admin.vm.feature.Get',
  2050. f'template-name',
  2051. None)] = b'0\0test-vm'
  2052. self.app.expected_calls[(
  2053. 'test-vm',
  2054. 'admin.vm.feature.Get',
  2055. f'template-epoch',
  2056. None)] = b'0\x000'
  2057. self.app.expected_calls[(
  2058. 'test-vm',
  2059. 'admin.vm.feature.Get',
  2060. f'template-version',
  2061. None)] = b'0\x004.3'
  2062. self.app.expected_calls[(
  2063. 'test-vm',
  2064. 'admin.vm.feature.Get',
  2065. f'template-release',
  2066. None)] = b'0\x0020200201'
  2067. mock_query.return_value = [
  2068. qubesadmin.tools.qvm_template.Template(
  2069. 'test-vm',
  2070. '0',
  2071. '4.1',
  2072. '20200101',
  2073. 'qubes-templates-itl',
  2074. 1048576,
  2075. datetime.datetime(2020, 1, 23, 4, 56),
  2076. 'GPL',
  2077. 'https://qubes-os.org',
  2078. 'Qubes template for test-vm',
  2079. 'Qubes template\n for test-vm\n'
  2080. )
  2081. ]
  2082. args = argparse.Namespace(templates=['test-vm'])
  2083. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2084. with self.assertRaises(SystemExit):
  2085. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2086. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  2087. self.assertTrue('Same version' in mock_err.getvalue())
  2088. self.assertTrue('not found' in mock_err.getvalue())
  2089. self.assertEqual(mock_query.mock_calls, [
  2090. mock.call(args, self.app, 'qubes-template-test-vm')
  2091. ])
  2092. self.assertAllCalled()
  2093. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2094. def test_150_list_templates_installed_success(self, mock_query):
  2095. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2096. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2097. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2098. b'non-spec class=TemplateVM state=Halted\n'
  2099. build_time = '2020-09-01 14:30:00' # 1598970600
  2100. install_time = '2020-09-01 15:30:00'
  2101. for key, val in [
  2102. ('name', 'test-vm'),
  2103. ('epoch', '2'),
  2104. ('version', '4.1'),
  2105. ('release', '2020'),
  2106. ('reponame', '@commandline'),
  2107. ('buildtime', build_time),
  2108. ('installtime', install_time),
  2109. ('license', 'GPL'),
  2110. ('url', 'https://qubes-os.org'),
  2111. ('summary', 'Summary'),
  2112. ('description', 'Desc|desc')]:
  2113. self.app.expected_calls[(
  2114. 'test-vm',
  2115. 'admin.vm.feature.Get',
  2116. f'template-{key}',
  2117. None)] = b'0\0' + val.encode()
  2118. for key, val in [('name', 'test-vm-2-not-managed')]:
  2119. self.app.expected_calls[(
  2120. 'test-vm-2',
  2121. 'admin.vm.feature.Get',
  2122. f'template-{key}',
  2123. None)] = b'0\0' + val.encode()
  2124. for key, val in [
  2125. ('name', 'non-spec'),
  2126. ('epoch', '0'),
  2127. ('version', '4.3'),
  2128. ('release', '20200201')]:
  2129. self.app.expected_calls[(
  2130. 'non-spec',
  2131. 'admin.vm.feature.Get',
  2132. f'template-{key}',
  2133. None)] = b'0\0' + val.encode()
  2134. args = argparse.Namespace(
  2135. all=False,
  2136. installed=True,
  2137. available=False,
  2138. extras=False,
  2139. upgrades=False,
  2140. machine_readable=False,
  2141. machine_readable_json=False,
  2142. templates=['test-vm*']
  2143. )
  2144. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2145. mock.patch.object(self.app.domains['test-vm'],
  2146. 'get_disk_utilization') as mock_disk:
  2147. mock_disk.return_value = 1234321
  2148. qubesadmin.tools.qvm_template.list_templates(
  2149. args, self.app, 'list')
  2150. self.assertEqual(mock_out.getvalue(),
  2151. '''Installed Templates
  2152. [('test-vm', '2:4.1-2020', '@commandline')]
  2153. ''')
  2154. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2155. self.assertEqual(mock_query.mock_calls, [])
  2156. self.assertAllCalled()
  2157. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2158. def test_151_list_templates_available_success(self, mock_query):
  2159. counter = 0
  2160. def f(*args):
  2161. nonlocal counter
  2162. counter += 1
  2163. if counter == 1:
  2164. return [
  2165. qubesadmin.tools.qvm_template.Template(
  2166. 'fedora-32',
  2167. '0',
  2168. '4.2',
  2169. '20200201',
  2170. 'qubes-templates-itl-testing',
  2171. 2048576,
  2172. datetime.datetime(2020, 2, 23, 4, 56),
  2173. 'GPLv2',
  2174. 'https://qubes-os.org/?',
  2175. 'Qubes template for fedora-32 v2',
  2176. 'Qubes template\n for fedora-32 v2\n'
  2177. )
  2178. ]
  2179. return [
  2180. qubesadmin.tools.qvm_template.Template(
  2181. 'fedora-31',
  2182. '1',
  2183. '4.1',
  2184. '20200101',
  2185. 'qubes-templates-itl',
  2186. 1048576,
  2187. datetime.datetime(2020, 1, 23, 4, 56),
  2188. 'GPL',
  2189. 'https://qubes-os.org',
  2190. 'Qubes template for fedora-31',
  2191. 'Qubes template\n for fedora-31\n'
  2192. )
  2193. ]
  2194. mock_query.side_effect = f
  2195. args = argparse.Namespace(
  2196. all=False,
  2197. installed=False,
  2198. available=True,
  2199. extras=False,
  2200. upgrades=False,
  2201. machine_readable=False,
  2202. machine_readable_json=False,
  2203. templates=['fedora-32', 'fedora-31']
  2204. )
  2205. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2206. qubesadmin.tools.qvm_template.list_templates(
  2207. args, self.app, 'list')
  2208. # Order not determinstic because of sets
  2209. expected = [
  2210. ('fedora-31', '1:4.1-20200101', 'qubes-templates-itl'),
  2211. ('fedora-32', '0:4.2-20200201', 'qubes-templates-itl-testing')
  2212. ]
  2213. self.assertTrue(mock_out.getvalue() == \
  2214. f'''Available Templates
  2215. {str([expected[1], expected[0]])}
  2216. ''' \
  2217. or mock_out.getvalue() == \
  2218. f'''Available Templates
  2219. {str([expected[0], expected[1]])}
  2220. ''')
  2221. self.assertEqual(mock_query.mock_calls, [
  2222. mock.call(args, self.app, 'fedora-32'),
  2223. mock.call(args, self.app, 'fedora-31')
  2224. ])
  2225. self.assertAllCalled()
  2226. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2227. def test_151_list_templates_available_all_success(self, mock_query):
  2228. mock_query.return_value = [
  2229. qubesadmin.tools.qvm_template.Template(
  2230. 'fedora-31',
  2231. '1',
  2232. '4.1',
  2233. '20200101',
  2234. 'qubes-templates-itl',
  2235. 1048576,
  2236. datetime.datetime(2020, 1, 23, 4, 56),
  2237. 'GPL',
  2238. 'https://qubes-os.org',
  2239. 'Qubes template for fedora-31',
  2240. 'Qubes template\n for fedora-31\n'
  2241. )
  2242. ]
  2243. args = argparse.Namespace(
  2244. all=False,
  2245. installed=False,
  2246. available=True,
  2247. extras=False,
  2248. upgrades=False,
  2249. machine_readable=False,
  2250. machine_readable_json=False,
  2251. templates=[]
  2252. )
  2253. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2254. qubesadmin.tools.qvm_template.list_templates(
  2255. args, self.app, 'list')
  2256. self.assertEqual(mock_out.getvalue(),
  2257. '''Available Templates
  2258. [('fedora-31', '1:4.1-20200101', 'qubes-templates-itl')]
  2259. ''')
  2260. self.assertEqual(mock_query.mock_calls, [
  2261. mock.call(args, self.app)
  2262. ])
  2263. self.assertAllCalled()
  2264. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2265. def test_152_list_templates_extras_success(self, mock_query):
  2266. mock_query.return_value = [
  2267. qubesadmin.tools.qvm_template.Template(
  2268. 'test-vm',
  2269. '2',
  2270. '4.1',
  2271. '2020',
  2272. 'qubes-templates-itl',
  2273. 1048576,
  2274. datetime.datetime(2020, 9, 1, 14, 30,
  2275. tzinfo=datetime.timezone.utc),
  2276. 'GPL',
  2277. 'https://qubes-os.org',
  2278. 'Qubes template for fedora-31',
  2279. 'Qubes template\n for fedora-31\n'
  2280. )
  2281. ]
  2282. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2283. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2284. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2285. b'test-vm-3 class=TemplateVM state=Halted\n' \
  2286. b'non-spec class=TemplateVM state=Halted\n'
  2287. for key, val in [('name', 'test-vm')]:
  2288. self.app.expected_calls[(
  2289. 'test-vm',
  2290. 'admin.vm.feature.Get',
  2291. f'template-{key}',
  2292. None)] = b'0\0' + val.encode()
  2293. for key, val in [
  2294. ('name', 'test-vm-2'),
  2295. ('epoch', '1'),
  2296. ('version', '4.0'),
  2297. ('release', '2019'),
  2298. ('reponame', 'qubes-template-itl'),
  2299. ('buildtime', '2020-09-02 14:30:00'),
  2300. ('installtime', '2020-09-02 15:30:00'),
  2301. ('license', 'GPLv2'),
  2302. ('url', 'https://qubes-os.org/?'),
  2303. ('summary', 'Summary2'),
  2304. ('description', 'Desc|desc|2')]:
  2305. self.app.expected_calls[(
  2306. 'test-vm-2',
  2307. 'admin.vm.feature.Get',
  2308. f'template-{key}',
  2309. None)] = b'0\0' + val.encode()
  2310. for key, val in [('name', 'test-vm-3-non-managed')]:
  2311. self.app.expected_calls[(
  2312. 'test-vm-3',
  2313. 'admin.vm.feature.Get',
  2314. f'template-{key}',
  2315. None)] = b'0\0' + val.encode()
  2316. for key, val in [
  2317. ('name', 'non-spec'),
  2318. ('epoch', '1'),
  2319. ('version', '4.0'),
  2320. ('release', '2019')]:
  2321. self.app.expected_calls[(
  2322. 'non-spec',
  2323. 'admin.vm.feature.Get',
  2324. f'template-{key}',
  2325. None)] = b'0\0' + val.encode()
  2326. args = argparse.Namespace(
  2327. all=False,
  2328. installed=False,
  2329. available=False,
  2330. extras=True,
  2331. upgrades=False,
  2332. machine_readable=False,
  2333. machine_readable_json=False,
  2334. templates=['test-vm*']
  2335. )
  2336. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2337. mock.patch.object(self.app.domains['test-vm-2'],
  2338. 'get_disk_utilization') as mock_disk:
  2339. mock_disk.return_value = 1234321
  2340. qubesadmin.tools.qvm_template.list_templates(
  2341. args, self.app, 'list')
  2342. self.assertEqual(mock_out.getvalue(),
  2343. '''Extra Templates
  2344. [('test-vm-2', '1:4.0-2019', 'qubes-template-itl')]
  2345. ''')
  2346. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2347. self.assertEqual(mock_query.mock_calls, [
  2348. mock.call(args, self.app, 'test-vm*')
  2349. ])
  2350. self.assertAllCalled()
  2351. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2352. def test_153_list_templates_upgrades_success(self, mock_query):
  2353. mock_query.return_value = [
  2354. qubesadmin.tools.qvm_template.Template(
  2355. 'test-vm',
  2356. '2',
  2357. '4.1',
  2358. '2020',
  2359. 'qubes-templates-itl',
  2360. 1048576,
  2361. datetime.datetime(2020, 9, 1, 14, 30,
  2362. tzinfo=datetime.timezone.utc),
  2363. 'GPL',
  2364. 'https://qubes-os.org',
  2365. 'Qubes template for fedora-31',
  2366. 'Qubes template\n for fedora-31\n'
  2367. ),
  2368. qubesadmin.tools.qvm_template.Template(
  2369. 'test-vm',
  2370. '0',
  2371. '4.1',
  2372. '2020',
  2373. 'qubes-templates-itl',
  2374. 1048576,
  2375. datetime.datetime(2020, 9, 1, 14, 30,
  2376. tzinfo=datetime.timezone.utc),
  2377. 'GPL',
  2378. 'https://qubes-os.org',
  2379. 'Qubes template for fedora-31',
  2380. 'Qubes template\n for fedora-31\n'
  2381. ),
  2382. qubesadmin.tools.qvm_template.Template(
  2383. 'test-vm-3',
  2384. '0',
  2385. '4.1',
  2386. '2020',
  2387. 'qubes-templates-itl',
  2388. 1048576,
  2389. datetime.datetime(2020, 9, 1, 14, 30,
  2390. tzinfo=datetime.timezone.utc),
  2391. 'GPL',
  2392. 'https://qubes-os.org',
  2393. 'Qubes template for fedora-31',
  2394. 'Qubes template\n for fedora-31\n'
  2395. )
  2396. ]
  2397. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2398. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2399. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2400. b'test-vm-3 class=TemplateVM state=Halted\n'
  2401. for key, val in [
  2402. ('name', 'test-vm'),
  2403. ('epoch', '1'),
  2404. ('version', '4.0'),
  2405. ('release', '2019')]:
  2406. self.app.expected_calls[(
  2407. 'test-vm',
  2408. 'admin.vm.feature.Get',
  2409. f'template-{key}',
  2410. None)] = b'0\0' + val.encode()
  2411. for key, val in [
  2412. ('name', 'test-vm-2'),
  2413. ('epoch', '1'),
  2414. ('version', '4.0'),
  2415. ('release', '2019')]:
  2416. self.app.expected_calls[(
  2417. 'test-vm-2',
  2418. 'admin.vm.feature.Get',
  2419. f'template-{key}',
  2420. None)] = b'0\0' + val.encode()
  2421. for key, val in [('name', 'test-vm-3-non-managed')]:
  2422. self.app.expected_calls[(
  2423. 'test-vm-3',
  2424. 'admin.vm.feature.Get',
  2425. f'template-{key}',
  2426. None)] = b'0\0' + val.encode()
  2427. args = argparse.Namespace(
  2428. all=False,
  2429. installed=False,
  2430. available=False,
  2431. extras=False,
  2432. upgrades=True,
  2433. machine_readable=False,
  2434. machine_readable_json=False,
  2435. templates=['test-vm*']
  2436. )
  2437. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2438. mock.patch.object(self.app.domains['test-vm-2'],
  2439. 'get_disk_utilization') as mock_disk:
  2440. mock_disk.return_value = 1234321
  2441. qubesadmin.tools.qvm_template.list_templates(
  2442. args, self.app, 'list')
  2443. self.assertEqual(mock_out.getvalue(),
  2444. '''Available Upgrades
  2445. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2446. ''')
  2447. self.assertEqual(mock_disk.mock_calls, [])
  2448. self.assertEqual(mock_query.mock_calls, [
  2449. mock.call(args, self.app, 'test-vm*')
  2450. ])
  2451. self.assertAllCalled()
  2452. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2453. def __test_list_templates_all_success(self, operation,
  2454. args, expected, mock_query):
  2455. mock_query.return_value = [
  2456. qubesadmin.tools.qvm_template.Template(
  2457. 'test-vm',
  2458. '2',
  2459. '4.1',
  2460. '2020',
  2461. 'qubes-templates-itl',
  2462. 1048576,
  2463. datetime.datetime(2020, 9, 1, 14, 30,
  2464. tzinfo=datetime.timezone.utc),
  2465. 'GPL',
  2466. 'https://qubes-os.org',
  2467. 'Qubes template for fedora-31',
  2468. 'Qubes template\n for fedora-31\n'
  2469. )
  2470. ]
  2471. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2472. b'0\x00test-vm-2 class=TemplateVM state=Halted\n'
  2473. for key, val in [
  2474. ('name', 'test-vm-2'),
  2475. ('epoch', '1'),
  2476. ('version', '4.0'),
  2477. ('release', '2019'),
  2478. ('reponame', '@commandline'),
  2479. ('buildtime', '2020-09-02 14:30:00'),
  2480. ('installtime', '2020-09-02 15:30:00'),
  2481. ('license', 'GPL'),
  2482. ('url', 'https://qubes-os.org'),
  2483. ('summary', 'Summary'),
  2484. ('description', 'Desc|desc')]:
  2485. self.app.expected_calls[(
  2486. 'test-vm-2',
  2487. 'admin.vm.feature.Get',
  2488. f'template-{key}',
  2489. None)] = b'0\0' + val.encode()
  2490. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2491. mock.patch.object(self.app.domains['test-vm-2'],
  2492. 'get_disk_utilization') as mock_disk:
  2493. mock_disk.return_value = 1234321
  2494. qubesadmin.tools.qvm_template.list_templates(
  2495. args, self.app, operation)
  2496. self.assertEqual(mock_out.getvalue(), expected)
  2497. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2498. self.assertEqual(mock_query.mock_calls, [
  2499. mock.call(args, self.app, 'test-vm*')
  2500. ])
  2501. self.assertAllCalled()
  2502. def test_154_list_templates_all_success(self):
  2503. args = argparse.Namespace(
  2504. all=True,
  2505. installed=False,
  2506. available=False,
  2507. extras=False,
  2508. upgrades=False,
  2509. machine_readable=False,
  2510. machine_readable_json=False,
  2511. templates=['test-vm*']
  2512. )
  2513. expected = \
  2514. '''Installed Templates
  2515. [('test-vm-2', '1:4.0-2019', '@commandline')]
  2516. Available Templates
  2517. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2518. '''
  2519. self.__test_list_templates_all_success('list', args, expected)
  2520. def test_155_list_templates_all_implicit_success(self):
  2521. args = argparse.Namespace(
  2522. all=False,
  2523. installed=False,
  2524. available=False,
  2525. extras=False,
  2526. upgrades=False,
  2527. machine_readable=False,
  2528. machine_readable_json=False,
  2529. templates=['test-vm*']
  2530. )
  2531. expected = \
  2532. '''Installed Templates
  2533. [('test-vm-2', '1:4.0-2019', '@commandline')]
  2534. Available Templates
  2535. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2536. '''
  2537. self.__test_list_templates_all_success('list', args, expected)
  2538. def test_156_list_templates_info_all_success(self):
  2539. args = argparse.Namespace(
  2540. all=False,
  2541. installed=False,
  2542. available=False,
  2543. extras=False,
  2544. upgrades=False,
  2545. machine_readable=False,
  2546. machine_readable_json=False,
  2547. templates=['test-vm*']
  2548. )
  2549. expected = \
  2550. '''Installed Templates
  2551. [('Name', ':', 'test-vm-2'), ('Epoch', ':', '1'), ('Version', ':', '4.0'), ('Release', ':', '2019'), ('Size', ':', '1.2 MiB'), ('Repository', ':', '@commandline'), ('Buildtime', ':', '2020-09-02 14:30:00'), ('Install time', ':', '2020-09-02 15:30:00'), ('URL', ':', 'https://qubes-os.org'), ('License', ':', 'GPL'), ('Summary', ':', 'Summary'), ('Description', ':', 'Desc'), ('', ':', 'desc'), (' ', ' ', ' ')]
  2552. Available Templates
  2553. [('Name', ':', 'test-vm'), ('Epoch', ':', '2'), ('Version', ':', '4.1'), ('Release', ':', '2020'), ('Size', ':', '1.0 MiB'), ('Repository', ':', 'qubes-templates-itl'), ('Buildtime', ':', '2020-09-01 14:30:00+00:00'), ('URL', ':', 'https://qubes-os.org'), ('License', ':', 'GPL'), ('Summary', ':', 'Qubes template for fedora-31'), ('Description', ':', 'Qubes template'), ('', ':', ' for fedora-31'), (' ', ' ', ' ')]
  2554. '''
  2555. self.__test_list_templates_all_success('info', args, expected)
  2556. def test_157_list_templates_list_all_machinereadable_success(self):
  2557. args = argparse.Namespace(
  2558. all=False,
  2559. installed=False,
  2560. available=False,
  2561. extras=False,
  2562. upgrades=False,
  2563. machine_readable=True,
  2564. machine_readable_json=False,
  2565. templates=['test-vm*']
  2566. )
  2567. expected = \
  2568. '''installed|test-vm-2|1:4.0-2019|@commandline
  2569. available|test-vm|2:4.1-2020|qubes-templates-itl
  2570. '''
  2571. self.__test_list_templates_all_success('list', args, expected)
  2572. def test_158_list_templates_info_all_machinereadable_success(self):
  2573. args = argparse.Namespace(
  2574. all=False,
  2575. installed=False,
  2576. available=False,
  2577. extras=False,
  2578. upgrades=False,
  2579. machine_readable=True,
  2580. machine_readable_json=False,
  2581. templates=['test-vm*']
  2582. )
  2583. expected = \
  2584. '''installed|test-vm-2|1|4.0|2019|@commandline|1234321|2020-09-02 14:30:00|2020-09-02 15:30:00|GPL|https://qubes-os.org|Summary|Desc|desc
  2585. available|test-vm|2|4.1|2020|qubes-templates-itl|1048576|2020-09-01 14:30:00||GPL|https://qubes-os.org|Qubes template for fedora-31|Qubes template| for fedora-31|
  2586. '''
  2587. self.__test_list_templates_all_success('info', args, expected)
  2588. def test_159_list_templates_list_all_machinereadablejson_success(self):
  2589. args = argparse.Namespace(
  2590. all=False,
  2591. installed=False,
  2592. available=False,
  2593. extras=False,
  2594. upgrades=False,
  2595. machine_readable=False,
  2596. machine_readable_json=True,
  2597. templates=['test-vm*']
  2598. )
  2599. expected = \
  2600. '''{"installed": [{"name": "test-vm-2", "evr": "1:4.0-2019", "reponame": "@commandline"}], "available": [{"name": "test-vm", "evr": "2:4.1-2020", "reponame": "qubes-templates-itl"}]}
  2601. '''
  2602. self.__test_list_templates_all_success('list', args, expected)
  2603. def test_160_list_templates_info_all_machinereadablejson_success(self):
  2604. args = argparse.Namespace(
  2605. all=False,
  2606. installed=False,
  2607. available=False,
  2608. extras=False,
  2609. upgrades=False,
  2610. machine_readable=False,
  2611. machine_readable_json=True,
  2612. templates=['test-vm*']
  2613. )
  2614. expected = \
  2615. r'''{"installed": [{"name": "test-vm-2", "epoch": "1", "version": "4.0", "release": "2019", "reponame": "@commandline", "size": "1234321", "buildtime": "2020-09-02 14:30:00", "installtime": "2020-09-02 15:30:00", "license": "GPL", "url": "https://qubes-os.org", "summary": "Summary", "description": "Desc\ndesc"}], "available": [{"name": "test-vm", "epoch": "2", "version": "4.1", "release": "2020", "reponame": "qubes-templates-itl", "size": "1048576", "buildtime": "2020-09-01 14:30:00", "installtime": "", "license": "GPL", "url": "https://qubes-os.org", "summary": "Qubes template for fedora-31", "description": "Qubes template\n for fedora-31\n"}]}
  2616. '''
  2617. self.__test_list_templates_all_success('info', args, expected)
  2618. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2619. def test_161_list_templates_noresults_fail(self, mock_query):
  2620. mock_query.return_value = []
  2621. args = argparse.Namespace(
  2622. all=False,
  2623. installed=False,
  2624. available=True,
  2625. extras=False,
  2626. upgrades=False,
  2627. machine_readable=False,
  2628. machine_readable_json=False,
  2629. templates=[]
  2630. )
  2631. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2632. with self.assertRaises(SystemExit):
  2633. qubesadmin.tools.qvm_template.list_templates(
  2634. args, self.app, 'list')
  2635. self.assertTrue('No matching templates' in mock_err.getvalue())
  2636. self.assertEqual(mock_query.mock_calls, [
  2637. mock.call(args, self.app)
  2638. ])
  2639. self.assertAllCalled()
  2640. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2641. def test_170_search_success(self, mock_query):
  2642. mock_query.return_value = [
  2643. qubesadmin.tools.qvm_template.Template(
  2644. 'test-vm',
  2645. '2',
  2646. '4.1',
  2647. '2020',
  2648. 'qubes-templates-itl',
  2649. 1048576,
  2650. datetime.datetime(2020, 9, 1, 14, 30,
  2651. tzinfo=datetime.timezone.utc),
  2652. 'GPL',
  2653. 'https://qubes-os.org',
  2654. 'Qubes template for fedora-31',
  2655. 'Qubes template\n for fedora-31\n'
  2656. ),
  2657. qubesadmin.tools.qvm_template.Template(
  2658. 'test-vm',
  2659. '0',
  2660. '4.1',
  2661. '2020',
  2662. 'qubes-templates-itl',
  2663. 1048576,
  2664. datetime.datetime(2020, 9, 1, 14, 30,
  2665. tzinfo=datetime.timezone.utc),
  2666. 'GPL',
  2667. 'https://qubes-os.org',
  2668. 'Older Qubes template for fedora-31',
  2669. 'Older Qubes template\n for fedora-31\n'
  2670. ),
  2671. qubesadmin.tools.qvm_template.Template(
  2672. 'should-not-match-3',
  2673. '0',
  2674. '4.1',
  2675. '2020',
  2676. 'qubes-templates-itl',
  2677. 1048576,
  2678. datetime.datetime(2020, 9, 1, 14, 30,
  2679. tzinfo=datetime.timezone.utc),
  2680. 'GPL',
  2681. 'https://qubes-os.org/test-vm',
  2682. 'Qubes template for fedora-31',
  2683. 'test-vm Qubes template\n for fedora-31\n'
  2684. )
  2685. ]
  2686. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2687. b'0\x00test-vm-2 class=TemplateVM state=Halted\n'
  2688. for key, val in [
  2689. ('name', 'test-vm-2'),
  2690. ('epoch', '1'),
  2691. ('version', '4.0'),
  2692. ('release', '2019'),
  2693. ('reponame', '@commandline'),
  2694. ('buildtime', '2020-09-02 14:30:00'),
  2695. ('license', 'GPL'),
  2696. ('url', 'https://qubes-os.org'),
  2697. ('summary', 'Summary'),
  2698. ('description', 'Desc|desc')]:
  2699. self.app.expected_calls[(
  2700. 'test-vm-2',
  2701. 'admin.vm.feature.Get',
  2702. f'template-{key}',
  2703. None)] = b'0\0' + val.encode()
  2704. args = argparse.Namespace(
  2705. all=False,
  2706. templates=['test-vm']
  2707. )
  2708. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2709. mock.patch.object(self.app.domains['test-vm-2'],
  2710. 'get_disk_utilization') as mock_disk:
  2711. mock_disk.return_value = 1234321
  2712. qubesadmin.tools.qvm_template.search(args, self.app)
  2713. self.assertEqual(mock_out.getvalue(),
  2714. '''=== Name Exactly Matched: test-vm ===
  2715. test-vm : Qubes template for fedora-31
  2716. === Name Matched: test-vm ===
  2717. test-vm-2 : Summary
  2718. ''')
  2719. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2720. self.assertEqual(mock_query.mock_calls, [
  2721. mock.call(args, self.app)
  2722. ])
  2723. self.assertAllCalled()
  2724. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2725. def test_171_search_summary_success(self, mock_query):
  2726. mock_query.return_value = [
  2727. qubesadmin.tools.qvm_template.Template(
  2728. 'test-template',
  2729. '2',
  2730. '4.1',
  2731. '2020',
  2732. 'qubes-templates-itl',
  2733. 1048576,
  2734. datetime.datetime(2020, 9, 1, 14, 30,
  2735. tzinfo=datetime.timezone.utc),
  2736. 'GPL',
  2737. 'https://qubes-os.org',
  2738. 'Qubes template for test-vm :)',
  2739. 'Qubes template\n for fedora-31\n'
  2740. ),
  2741. qubesadmin.tools.qvm_template.Template(
  2742. 'test-template-exact',
  2743. '2',
  2744. '4.1',
  2745. '2020',
  2746. 'qubes-templates-itl',
  2747. 1048576,
  2748. datetime.datetime(2020, 9, 1, 14, 30,
  2749. tzinfo=datetime.timezone.utc),
  2750. 'GPL',
  2751. 'https://qubes-os.org',
  2752. 'test-vm',
  2753. 'Qubes template\n for fedora-31\n'
  2754. ),
  2755. qubesadmin.tools.qvm_template.Template(
  2756. 'test-vm',
  2757. '2',
  2758. '4.1',
  2759. '2020',
  2760. 'qubes-templates-itl',
  2761. 1048576,
  2762. datetime.datetime(2020, 9, 1, 14, 30,
  2763. tzinfo=datetime.timezone.utc),
  2764. 'GPL',
  2765. 'https://qubes-os.org',
  2766. 'Qubes template for test-vm',
  2767. 'Qubes template\n for fedora-31\n'
  2768. ),
  2769. qubesadmin.tools.qvm_template.Template(
  2770. 'test-vm-2',
  2771. '2',
  2772. '4.1',
  2773. '2020',
  2774. 'qubes-templates-itl',
  2775. 1048576,
  2776. datetime.datetime(2020, 9, 1, 14, 30,
  2777. tzinfo=datetime.timezone.utc),
  2778. 'GPL',
  2779. 'https://qubes-os.org',
  2780. 'Qubes template for test-vm-2',
  2781. 'Qubes template\n for fedora-31\n'
  2782. ),
  2783. ]
  2784. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2785. b'0\x00'
  2786. args = argparse.Namespace(
  2787. all=False,
  2788. templates=['test-vm']
  2789. )
  2790. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2791. qubesadmin.tools.qvm_template.search(args, self.app)
  2792. self.assertEqual(mock_out.getvalue(),
  2793. '''=== Name & Summary Matched: test-vm ===
  2794. test-vm : Qubes template for test-vm
  2795. test-vm-2 : Qubes template for test-vm-2
  2796. === Summary Matched: test-vm ===
  2797. test-template : Qubes template for test-vm :)
  2798. === Summary Exactly Matched: test-vm ===
  2799. test-template-exact : test-vm
  2800. ''')
  2801. self.assertEqual(mock_query.mock_calls, [
  2802. mock.call(args, self.app)
  2803. ])
  2804. self.assertAllCalled()
  2805. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2806. def test_172_search_namesummaryexact_success(self, mock_query):
  2807. mock_query.return_value = [
  2808. qubesadmin.tools.qvm_template.Template(
  2809. 'test-template-exact',
  2810. '2',
  2811. '4.1',
  2812. '2020',
  2813. 'qubes-templates-itl',
  2814. 1048576,
  2815. datetime.datetime(2020, 9, 1, 14, 30,
  2816. tzinfo=datetime.timezone.utc),
  2817. 'GPL',
  2818. 'https://qubes-os.org',
  2819. 'test-vm',
  2820. 'Qubes template\n for fedora-31\n'
  2821. ),
  2822. qubesadmin.tools.qvm_template.Template(
  2823. 'test-vm',
  2824. '2',
  2825. '4.1',
  2826. '2020',
  2827. 'qubes-templates-itl',
  2828. 1048576,
  2829. datetime.datetime(2020, 9, 1, 14, 30,
  2830. tzinfo=datetime.timezone.utc),
  2831. 'GPL',
  2832. 'https://qubes-os.org',
  2833. 'test-vm',
  2834. 'Qubes template\n for fedora-31\n'
  2835. ),
  2836. qubesadmin.tools.qvm_template.Template(
  2837. 'test-vm-2',
  2838. '2',
  2839. '4.1',
  2840. '2020',
  2841. 'qubes-templates-itl',
  2842. 1048576,
  2843. datetime.datetime(2020, 9, 1, 14, 30,
  2844. tzinfo=datetime.timezone.utc),
  2845. 'GPL',
  2846. 'https://qubes-os.org',
  2847. 'test-vm',
  2848. 'Qubes template\n for fedora-31\n'
  2849. )
  2850. ]
  2851. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2852. b'0\x00'
  2853. args = argparse.Namespace(
  2854. all=False,
  2855. templates=['test-vm']
  2856. )
  2857. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2858. qubesadmin.tools.qvm_template.search(args, self.app)
  2859. self.assertEqual(mock_out.getvalue(),
  2860. '''=== Name & Summary Exactly Matched: test-vm ===
  2861. test-vm : test-vm
  2862. === Name & Summary Matched: test-vm ===
  2863. test-vm-2 : test-vm
  2864. === Summary Exactly Matched: test-vm ===
  2865. test-template-exact : test-vm
  2866. ''')
  2867. self.assertEqual(mock_query.mock_calls, [
  2868. mock.call(args, self.app)
  2869. ])
  2870. self.assertAllCalled()
  2871. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2872. def test_173_search_multiquery_success(self, mock_query):
  2873. mock_query.return_value = [
  2874. qubesadmin.tools.qvm_template.Template(
  2875. 'test-template-exact',
  2876. '2',
  2877. '4.1',
  2878. '2020',
  2879. 'qubes-templates-itl',
  2880. 1048576,
  2881. datetime.datetime(2020, 9, 1, 14, 30,
  2882. tzinfo=datetime.timezone.utc),
  2883. 'GPL',
  2884. 'https://qubes-os.org',
  2885. 'test-vm',
  2886. 'Qubes template\n for fedora-31\n'
  2887. ),
  2888. qubesadmin.tools.qvm_template.Template(
  2889. 'test-vm',
  2890. '2',
  2891. '4.1',
  2892. '2020',
  2893. 'qubes-templates-itl',
  2894. 1048576,
  2895. datetime.datetime(2020, 9, 1, 14, 30,
  2896. tzinfo=datetime.timezone.utc),
  2897. 'GPL',
  2898. 'https://qubes-os.org',
  2899. 'test-vm',
  2900. 'Qubes template\n for fedora-31\n'
  2901. ),
  2902. qubesadmin.tools.qvm_template.Template(
  2903. 'should-not-match',
  2904. '2',
  2905. '4.1',
  2906. '2020',
  2907. 'qubes-templates-itl',
  2908. 1048576,
  2909. datetime.datetime(2020, 9, 1, 14, 30,
  2910. tzinfo=datetime.timezone.utc),
  2911. 'GPL',
  2912. 'https://qubes-os.org',
  2913. 'Summary',
  2914. 'test-vm Qubes template\n for fedora-31\n'
  2915. )
  2916. ]
  2917. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2918. b'0\x00'
  2919. args = argparse.Namespace(
  2920. all=False,
  2921. templates=['test-vm', 'test-template']
  2922. )
  2923. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2924. qubesadmin.tools.qvm_template.search(args, self.app)
  2925. self.assertEqual(mock_out.getvalue(),
  2926. '''=== Name & Summary Matched: test-template, test-vm ===
  2927. test-template-exact : test-vm
  2928. ''')
  2929. self.assertEqual(mock_query.mock_calls, [
  2930. mock.call(args, self.app)
  2931. ])
  2932. self.assertAllCalled()
  2933. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2934. def test_174_search_multiquery_exact_success(self, mock_query):
  2935. mock_query.return_value = [
  2936. qubesadmin.tools.qvm_template.Template(
  2937. 'test-vm',
  2938. '2',
  2939. '4.1',
  2940. '2020',
  2941. 'qubes-templates-itl',
  2942. 1048576,
  2943. datetime.datetime(2020, 9, 1, 14, 30,
  2944. tzinfo=datetime.timezone.utc),
  2945. 'GPL',
  2946. 'https://qubes-os.org',
  2947. 'summary',
  2948. 'Qubes template\n for fedora-31\n'
  2949. ),
  2950. qubesadmin.tools.qvm_template.Template(
  2951. 'summary',
  2952. '2',
  2953. '4.1',
  2954. '2020',
  2955. 'qubes-templates-itl',
  2956. 1048576,
  2957. datetime.datetime(2020, 9, 1, 14, 30,
  2958. tzinfo=datetime.timezone.utc),
  2959. 'GPL',
  2960. 'https://qubes-os.org',
  2961. 'test-vm Summary',
  2962. 'Qubes template\n for fedora-31\n'
  2963. )
  2964. ]
  2965. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2966. b'0\x00'
  2967. args = argparse.Namespace(
  2968. all=False,
  2969. templates=['test-vm', 'summary']
  2970. )
  2971. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2972. qubesadmin.tools.qvm_template.search(args, self.app)
  2973. self.assertEqual(mock_out.getvalue(),
  2974. '''=== Name & Summary Matched: summary, test-vm ===
  2975. summary : test-vm Summary
  2976. === Name & Summary Exactly Matched: summary, test-vm ===
  2977. test-vm : summary
  2978. ''')
  2979. self.assertEqual(mock_query.mock_calls, [
  2980. mock.call(args, self.app)
  2981. ])
  2982. self.assertAllCalled()
  2983. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2984. def test_175_search_all_success(self, mock_query):
  2985. mock_query.return_value = [
  2986. qubesadmin.tools.qvm_template.Template(
  2987. 'test-vm',
  2988. '2',
  2989. '4.1',
  2990. '2020',
  2991. 'qubes-templates-itl',
  2992. 1048576,
  2993. datetime.datetime(2020, 9, 1, 14, 30,
  2994. tzinfo=datetime.timezone.utc),
  2995. 'GPL',
  2996. 'https://qubes-os.org/keyword-url',
  2997. 'summary',
  2998. 'Qubes template\n for fedora-31\n'
  2999. ),
  3000. qubesadmin.tools.qvm_template.Template(
  3001. 'test-vm-exact',
  3002. '2',
  3003. '4.1',
  3004. '2020',
  3005. 'qubes-templates-itl',
  3006. 1048576,
  3007. datetime.datetime(2020, 9, 1, 14, 30,
  3008. tzinfo=datetime.timezone.utc),
  3009. 'GPL',
  3010. 'https://qubes-os.org',
  3011. 'test-vm Summary',
  3012. 'Qubes template\n for fedora-31\n'
  3013. ),
  3014. qubesadmin.tools.qvm_template.Template(
  3015. 'test-vm-exac2',
  3016. '2',
  3017. '4.1',
  3018. '2020',
  3019. 'qubes-templates-itl',
  3020. 1048576,
  3021. datetime.datetime(2020, 9, 1, 14, 30,
  3022. tzinfo=datetime.timezone.utc),
  3023. 'GPL',
  3024. 'test-vm-exac2',
  3025. 'test-vm Summary',
  3026. 'Qubes template\n for fedora-31\n'
  3027. ),
  3028. qubesadmin.tools.qvm_template.Template(
  3029. 'test-vm-2',
  3030. '2',
  3031. '4.1',
  3032. '2020',
  3033. 'qubes-templates-itl',
  3034. 1048576,
  3035. datetime.datetime(2020, 9, 1, 14, 30,
  3036. tzinfo=datetime.timezone.utc),
  3037. 'GPL',
  3038. 'https://qubes-os.org',
  3039. 'test-vm Summary',
  3040. 'keyword-desc'
  3041. ),
  3042. qubesadmin.tools.qvm_template.Template(
  3043. 'should-not-match',
  3044. '2',
  3045. '4.1',
  3046. '2020',
  3047. 'qubes-templates-itl',
  3048. 1048576,
  3049. datetime.datetime(2020, 9, 1, 14, 30,
  3050. tzinfo=datetime.timezone.utc),
  3051. 'GPL',
  3052. 'https://qubes-os.org',
  3053. 'Summary',
  3054. 'Description'
  3055. )
  3056. ]
  3057. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3058. b'0\x00'
  3059. args = argparse.Namespace(
  3060. all=True,
  3061. templates=['test-vm-exact', 'test-vm-exac2',
  3062. 'keyword-url', 'keyword-desc']
  3063. )
  3064. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3065. qubesadmin.tools.qvm_template.search(args, self.app)
  3066. self.assertEqual(mock_out.getvalue(),
  3067. '''=== Name & URL Exactly Matched: test-vm-exac2 ===
  3068. test-vm-exac2 : test-vm Summary
  3069. === Name Exactly Matched: test-vm-exact ===
  3070. test-vm-exact : test-vm Summary
  3071. === Description Exactly Matched: keyword-desc ===
  3072. test-vm-2 : test-vm Summary
  3073. === URL Matched: keyword-url ===
  3074. test-vm : summary
  3075. ''')
  3076. self.assertEqual(mock_query.mock_calls, [
  3077. mock.call(args, self.app)
  3078. ])
  3079. self.assertAllCalled()
  3080. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3081. def test_176_search_wildcard_success(self, mock_query):
  3082. mock_query.return_value = [
  3083. qubesadmin.tools.qvm_template.Template(
  3084. 'test-vm',
  3085. '2',
  3086. '4.1',
  3087. '2020',
  3088. 'qubes-templates-itl',
  3089. 1048576,
  3090. datetime.datetime(2020, 9, 1, 14, 30,
  3091. tzinfo=datetime.timezone.utc),
  3092. 'GPL',
  3093. 'https://qubes-os.org',
  3094. 'Qubes template for fedora-31',
  3095. 'Qubes template\n for fedora-31\n'
  3096. ),
  3097. qubesadmin.tools.qvm_template.Template(
  3098. 'should-not-match-3',
  3099. '0',
  3100. '4.1',
  3101. '2020',
  3102. 'qubes-templates-itl',
  3103. 1048576,
  3104. datetime.datetime(2020, 9, 1, 14, 30,
  3105. tzinfo=datetime.timezone.utc),
  3106. 'GPL',
  3107. 'https://qubes-os.org/test-vm',
  3108. 'Qubes template for fedora-31',
  3109. 'test-vm Qubes template\n for fedora-31\n'
  3110. )
  3111. ]
  3112. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3113. b'0\x00'
  3114. args = argparse.Namespace(
  3115. all=False,
  3116. templates=['t?st-vm']
  3117. )
  3118. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3119. qubesadmin.tools.qvm_template.search(args, self.app)
  3120. self.assertEqual(mock_out.getvalue(),
  3121. '''=== Name Matched: t?st-vm ===
  3122. test-vm : Qubes template for fedora-31
  3123. ''')
  3124. self.assertEqual(mock_query.mock_calls, [
  3125. mock.call(args, self.app)
  3126. ])
  3127. self.assertAllCalled()
  3128. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3129. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3130. def test_180_download_success(self, mock_qrexec, mock_dllist):
  3131. with tempfile.TemporaryDirectory() as dir:
  3132. args = argparse.Namespace(
  3133. retries=1
  3134. )
  3135. qubesadmin.tools.qvm_template.download(args, self.app, dir, {
  3136. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3137. ('1', '2', '3'), 'qubes-templates-itl', 1048576),
  3138. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  3139. ('0', '1', '2'),
  3140. 'qubes-templates-itl-testing',
  3141. 2048576)
  3142. }, '.unverified')
  3143. self.assertEqual(mock_qrexec.mock_calls, [
  3144. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3145. dir + '/qubes-template-fedora-31-1:2-3.rpm.unverified',
  3146. 1048576),
  3147. mock.call(args, self.app, 'qubes-template-fedora-32-0:1-2',
  3148. dir + '/qubes-template-fedora-32-0:1-2.rpm.unverified',
  3149. 2048576)
  3150. ])
  3151. self.assertEqual(mock_dllist.mock_calls, [])
  3152. self.assertTrue(all(
  3153. [x.endswith('.unverified') for x in os.listdir(dir)]))
  3154. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3155. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3156. def test_181_download_success_nosuffix(self, mock_qrexec, mock_dllist):
  3157. with tempfile.TemporaryDirectory() as dir:
  3158. args = argparse.Namespace(
  3159. retries=1,
  3160. downloaddir=dir
  3161. )
  3162. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3163. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3164. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3165. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3166. })
  3167. self.assertEqual(mock_qrexec.mock_calls, [
  3168. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3169. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3170. 1048576)
  3171. ])
  3172. self.assertEqual(mock_dllist.mock_calls, [])
  3173. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3174. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3175. def test_182_download_success_getdllist(self, mock_qrexec, mock_dllist):
  3176. mock_dllist.return_value = {
  3177. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3178. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3179. }
  3180. with tempfile.TemporaryDirectory() as dir:
  3181. args = argparse.Namespace(
  3182. retries=1
  3183. )
  3184. qubesadmin.tools.qvm_template.download(args, self.app,
  3185. dir, None, '.unverified',
  3186. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  3187. self.assertEqual(mock_qrexec.mock_calls, [
  3188. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3189. dir + '/qubes-template-fedora-31-1:2-3.rpm.unverified',
  3190. 1048576)
  3191. ])
  3192. self.assertEqual(mock_dllist.mock_calls, [
  3193. mock.call(args, self.app,
  3194. version_selector=\
  3195. qubesadmin.tools.qvm_template.\
  3196. VersionSelector.LATEST_LOWER)
  3197. ])
  3198. self.assertTrue(all(
  3199. [x.endswith('.unverified') for x in os.listdir(dir)]))
  3200. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3201. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3202. def test_183_download_success_downloaddir(self, mock_qrexec, mock_dllist):
  3203. with tempfile.TemporaryDirectory() as dir:
  3204. args = argparse.Namespace(
  3205. retries=1,
  3206. downloaddir=dir
  3207. )
  3208. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3209. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3210. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3211. }, '.unverified')
  3212. self.assertEqual(mock_qrexec.mock_calls, [
  3213. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3214. dir + '/qubes-template-fedora-31-1:2-3.rpm.unverified',
  3215. 1048576)
  3216. ])
  3217. self.assertEqual(mock_dllist.mock_calls, [])
  3218. self.assertTrue(all(
  3219. [x.endswith('.unverified') for x in os.listdir(dir)]))
  3220. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3221. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3222. def test_184_download_success_exists(self, mock_qrexec, mock_dllist):
  3223. with tempfile.TemporaryDirectory() as dir:
  3224. with open(os.path.join(
  3225. dir, 'qubes-template-fedora-31-1:2-3.rpm.unverified'),
  3226. 'w') as _:
  3227. pass
  3228. args = argparse.Namespace(
  3229. retries=1,
  3230. downloaddir=dir
  3231. )
  3232. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3233. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3234. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3235. ('1', '2', '3'), 'qubes-templates-itl', 1048576),
  3236. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  3237. ('0', '1', '2'),
  3238. 'qubes-templates-itl-testing',
  3239. 2048576)
  3240. }, '.unverified')
  3241. self.assertTrue('already exists, skipping'
  3242. in mock_err.getvalue())
  3243. self.assertEqual(mock_qrexec.mock_calls, [
  3244. mock.call(args, self.app, 'qubes-template-fedora-32-0:1-2',
  3245. dir + '/qubes-template-fedora-32-0:1-2.rpm.unverified',
  3246. 2048576)
  3247. ])
  3248. self.assertEqual(mock_dllist.mock_calls, [])
  3249. self.assertTrue(all(
  3250. [x.endswith('.unverified') for x in os.listdir(dir)]))
  3251. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3252. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3253. def test_185_download_success_existsmove(self, mock_qrexec, mock_dllist):
  3254. with tempfile.TemporaryDirectory() as dir:
  3255. with open(os.path.join(
  3256. dir, 'qubes-template-fedora-31-1:2-3.rpm'),
  3257. 'w') as _:
  3258. pass
  3259. args = argparse.Namespace(
  3260. retries=1,
  3261. downloaddir=dir
  3262. )
  3263. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3264. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3265. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3266. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3267. }, '.unverified')
  3268. self.assertTrue('already exists, skipping'
  3269. in mock_err.getvalue())
  3270. self.assertEqual(mock_qrexec.mock_calls, [])
  3271. self.assertEqual(mock_dllist.mock_calls, [])
  3272. self.assertTrue(os.path.exists(
  3273. dir + '/qubes-template-fedora-31-1:2-3.rpm.unverified'))
  3274. self.assertTrue(all(
  3275. [x.endswith('.unverified') for x in os.listdir(dir)]))
  3276. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3277. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3278. def test_186_download_success_existsnosuffix(self, mock_qrexec, mock_dllist):
  3279. with tempfile.TemporaryDirectory() as dir:
  3280. with open(os.path.join(
  3281. dir, 'qubes-template-fedora-31-1:2-3.rpm'),
  3282. 'w') as _:
  3283. pass
  3284. args = argparse.Namespace(
  3285. retries=1,
  3286. downloaddir=dir
  3287. )
  3288. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3289. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3290. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3291. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3292. })
  3293. self.assertTrue('already exists, skipping'
  3294. in mock_err.getvalue())
  3295. self.assertEqual(mock_qrexec.mock_calls, [])
  3296. self.assertEqual(mock_dllist.mock_calls, [])
  3297. self.assertTrue(os.path.exists(
  3298. dir + '/qubes-template-fedora-31-1:2-3.rpm'))
  3299. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3300. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3301. def test_187_download_success_retry(self, mock_qrexec, mock_dllist):
  3302. counter = 0
  3303. def f(*args):
  3304. nonlocal counter
  3305. counter += 1
  3306. if counter == 1:
  3307. raise ConnectionError
  3308. mock_qrexec.side_effect = f
  3309. with tempfile.TemporaryDirectory() as dir:
  3310. args = argparse.Namespace(
  3311. retries=2,
  3312. downloaddir=dir
  3313. )
  3314. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3315. mock.patch('os.remove') as mock_rm:
  3316. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3317. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3318. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3319. })
  3320. self.assertTrue('retrying...' in mock_err.getvalue())
  3321. self.assertEqual(mock_rm.mock_calls, [
  3322. mock.call(dir + '/qubes-template-fedora-31-1:2-3.rpm')
  3323. ])
  3324. self.assertEqual(mock_qrexec.mock_calls, [
  3325. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3326. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3327. 1048576),
  3328. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3329. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3330. 1048576)
  3331. ])
  3332. self.assertEqual(mock_dllist.mock_calls, [])
  3333. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3334. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3335. def test_188_download_fail_retry(self, mock_qrexec, mock_dllist):
  3336. counter = 0
  3337. def f(*args):
  3338. nonlocal counter
  3339. counter += 1
  3340. if counter <= 3:
  3341. raise ConnectionError
  3342. mock_qrexec.side_effect = f
  3343. with tempfile.TemporaryDirectory() as dir:
  3344. args = argparse.Namespace(
  3345. retries=3,
  3346. downloaddir=dir
  3347. )
  3348. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3349. mock.patch('os.remove') as mock_rm:
  3350. with self.assertRaises(SystemExit):
  3351. qubesadmin.tools.qvm_template.download(
  3352. args, self.app, None, {
  3353. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3354. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3355. })
  3356. self.assertEqual(mock_err.getvalue().count('retrying...'), 2)
  3357. self.assertTrue('download failed' in mock_err.getvalue())
  3358. self.assertEqual(mock_rm.mock_calls, [
  3359. mock.call(dir + '/qubes-template-fedora-31-1:2-3.rpm'),
  3360. mock.call(dir + '/qubes-template-fedora-31-1:2-3.rpm'),
  3361. mock.call(dir + '/qubes-template-fedora-31-1:2-3.rpm')
  3362. ])
  3363. self.assertEqual(mock_qrexec.mock_calls, [
  3364. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3365. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3366. 1048576),
  3367. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3368. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3369. 1048576),
  3370. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3371. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3372. 1048576)
  3373. ])
  3374. self.assertEqual(mock_dllist.mock_calls, [])
  3375. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3376. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3377. def test_189_download_fail_interrupt(self, mock_qrexec, mock_dllist):
  3378. def f(*args):
  3379. raise RuntimeError
  3380. mock_qrexec.side_effect = f
  3381. with tempfile.TemporaryDirectory() as dir:
  3382. args = argparse.Namespace(
  3383. retries=3,
  3384. downloaddir=dir
  3385. )
  3386. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3387. mock.patch('os.remove') as mock_rm:
  3388. with self.assertRaises(RuntimeError):
  3389. qubesadmin.tools.qvm_template.download(
  3390. args, self.app, None, {
  3391. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3392. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3393. })
  3394. self.assertEqual(mock_rm.mock_calls, [
  3395. mock.call(dir + '/qubes-template-fedora-31-1:2-3.rpm')
  3396. ])
  3397. self.assertEqual(mock_qrexec.mock_calls, [
  3398. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3399. dir + '/qubes-template-fedora-31-1:2-3.rpm',
  3400. 1048576)
  3401. ])
  3402. self.assertEqual(mock_dllist.mock_calls, [])