qvm_template.py 180 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363
  1. import re
  2. from unittest import mock
  3. import argparse
  4. import asyncio
  5. import datetime
  6. import io
  7. import os
  8. import pathlib
  9. import subprocess
  10. import tempfile
  11. import fcntl
  12. import rpm
  13. import qubesadmin.tests
  14. import qubesadmin.tools.qvm_template
  15. class re_str(str):
  16. def __eq__(self, other):
  17. return bool(re.match(self, other))
  18. def __hash__(self):
  19. return super().__hash__()
  20. class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
  21. def setUp(self):
  22. # Print str(list) directly so that the output is consistent no matter
  23. # which implementation of `column` we use
  24. self.mock_table = mock.patch('qubesadmin.tools.print_table')
  25. mock_table = self.mock_table.start()
  26. def print_table(table, *args):
  27. print(str(table))
  28. mock_table.side_effect = print_table
  29. super().setUp()
  30. def tearDown(self):
  31. self.mock_table.stop()
  32. super().tearDown()
  33. @mock.patch('rpm.TransactionSet')
  34. @mock.patch('subprocess.check_call')
  35. @mock.patch('subprocess.check_output')
  36. def test_000_verify_rpm_success(self, mock_proc, mock_call, mock_ts):
  37. # Just return a dict instead of rpm.hdr
  38. hdr = {
  39. rpm.RPMTAG_SIGPGP: 'xxx', # non-empty
  40. rpm.RPMTAG_SIGGPG: 'xxx', # non-empty
  41. rpm.RPMTAG_NAME: 'qubes-template-test-vm',
  42. }
  43. mock_ts.return_value.hdrFromFdno.return_value = hdr
  44. mock_proc.return_value = b'dummy.rpm: digests signatures OK\n'
  45. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  46. '/path/to/key', template_name='test-vm')
  47. mock_call.assert_called_once()
  48. mock_proc.assert_called_once()
  49. self.assertEqual(hdr, ret)
  50. self.assertAllCalled()
  51. @mock.patch('rpm.TransactionSet')
  52. @mock.patch('subprocess.check_call')
  53. @mock.patch('subprocess.check_output')
  54. def test_001_verify_rpm_nosig_fail(self, mock_proc, mock_call, mock_ts):
  55. # Just return a dict instead of rpm.hdr
  56. hdr = {
  57. rpm.RPMTAG_SIGPGP: None, # empty
  58. rpm.RPMTAG_SIGGPG: None, # empty
  59. }
  60. mock_ts.return_value.hdrFromFdno.return_value = hdr
  61. mock_proc.return_value = b'dummy.rpm: digests OK\n'
  62. with self.assertRaises(Exception) as e:
  63. qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  64. '/path/to/key')
  65. mock_call.assert_called_once()
  66. mock_proc.assert_called_once()
  67. self.assertIn('Signature verification failed', e.exception.args[0])
  68. mock_ts.assert_not_called()
  69. self.assertAllCalled()
  70. @mock.patch('rpm.TransactionSet')
  71. @mock.patch('subprocess.check_call')
  72. @mock.patch('subprocess.check_output')
  73. def test_002_verify_rpm_nosig_success(self, mock_proc, mock_call, mock_ts):
  74. # Just return a dict instead of rpm.hdr
  75. hdr = {
  76. rpm.RPMTAG_SIGPGP: None, # empty
  77. rpm.RPMTAG_SIGGPG: None, # empty
  78. }
  79. mock_ts.return_value.hdrFromFdno.return_value = hdr
  80. mock_proc.return_value = b'dummy.rpm: digests OK\n'
  81. ret = qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  82. '/path/to/key', True)
  83. mock_proc.assert_not_called()
  84. mock_call.assert_not_called()
  85. self.assertEqual(ret, hdr)
  86. self.assertAllCalled()
  87. @mock.patch('rpm.TransactionSet')
  88. @mock.patch('subprocess.check_call')
  89. @mock.patch('subprocess.check_output')
  90. def test_003_verify_rpm_badsig_fail(self, mock_proc, mock_call, mock_ts):
  91. mock_proc.side_effect = subprocess.CalledProcessError(1,
  92. ['rpmkeys', '--checksig'], b'/dev/null: digests SIGNATURES NOT OK\n')
  93. with self.assertRaises(Exception) as e:
  94. qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  95. '/path/to/key')
  96. mock_call.assert_called_once()
  97. mock_proc.assert_called_once()
  98. self.assertIn('Signature verification failed', e.exception.args[0])
  99. mock_ts.assert_not_called()
  100. self.assertAllCalled()
  101. @mock.patch('rpm.TransactionSet')
  102. @mock.patch('subprocess.check_call')
  103. @mock.patch('subprocess.check_output')
  104. def test_004_verify_rpm_badname(self, mock_proc, mock_call, mock_ts):
  105. mock_proc.return_value = b'/dev/null: digests signatures OK\n'
  106. hdr = {
  107. rpm.RPMTAG_SIGPGP: 'xxx', # non-empty
  108. rpm.RPMTAG_SIGGPG: 'xxx', # non-empty
  109. rpm.RPMTAG_NAME: 'qubes-template-unexpected',
  110. }
  111. mock_ts.return_value.hdrFromFdno.return_value = hdr
  112. with self.assertRaises(
  113. qubesadmin.tools.qvm_template.SignatureVerificationError) as e:
  114. qubesadmin.tools.qvm_template.verify_rpm('/dev/null',
  115. '/path/to/key', template_name='test-vm')
  116. mock_call.assert_called_once()
  117. mock_proc.assert_called_once()
  118. self.assertIn('package does not match expected template name',
  119. e.exception.args[0])
  120. mock_ts.assert_called_once()
  121. self.assertAllCalled()
  122. @mock.patch('subprocess.Popen')
  123. def test_010_extract_rpm_success(self, mock_popen):
  124. pipe = mock.Mock()
  125. mock_popen.return_value.stdout = pipe
  126. mock_popen.return_value.wait.return_value = 0
  127. with tempfile.NamedTemporaryFile() as fd, \
  128. tempfile.TemporaryDirectory() as dir:
  129. path = fd.name
  130. dirpath = dir
  131. ret = qubesadmin.tools.qvm_template.extract_rpm(
  132. 'test-vm', path, dirpath)
  133. self.assertEqual(ret, True)
  134. self.assertEqual(mock_popen.mock_calls, [
  135. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  136. mock.call([
  137. 'cpio',
  138. '-idm',
  139. '-D',
  140. dirpath,
  141. './var/lib/qubes/vm-templates/test-vm/*'
  142. ], stdin=pipe, stdout=subprocess.DEVNULL),
  143. mock.call().wait(),
  144. mock.call().wait()
  145. ])
  146. self.assertAllCalled()
  147. @mock.patch('subprocess.Popen')
  148. def test_011_extract_rpm_fail(self, mock_popen):
  149. pipe = mock.Mock()
  150. mock_popen.return_value.stdout = pipe
  151. mock_popen.return_value.wait.return_value = 1
  152. with tempfile.NamedTemporaryFile() as fd, \
  153. tempfile.TemporaryDirectory() as dir:
  154. path = fd.name
  155. dirpath = dir
  156. ret = qubesadmin.tools.qvm_template.extract_rpm(
  157. 'test-vm', path, dirpath)
  158. self.assertEqual(ret, False)
  159. self.assertEqual(mock_popen.mock_calls, [
  160. mock.call(['rpm2cpio', path], stdout=subprocess.PIPE),
  161. mock.call([
  162. 'cpio',
  163. '-idm',
  164. '-D',
  165. dirpath,
  166. './var/lib/qubes/vm-templates/test-vm/*'
  167. ], stdin=pipe, stdout=subprocess.DEVNULL),
  168. mock.call().wait()
  169. ])
  170. self.assertAllCalled()
  171. @mock.patch('qubesadmin.tools.qvm_template.get_keys_for_repos')
  172. def test_090_install_lock(self, mock_get_keys):
  173. class SuccessError(Exception):
  174. pass
  175. mock_get_keys.side_effect = SuccessError
  176. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'):
  177. with self.subTest('not locked'):
  178. with self.assertRaises(SuccessError):
  179. # args don't matter
  180. qubesadmin.tools.qvm_template.install(mock.MagicMock(), None)
  181. self.assertFalse(os.path.exists('/tmp/test.lock'))
  182. with self.subTest('lock exists but unlocked'):
  183. with open('/tmp/test.lock', 'w') as f:
  184. with self.assertRaises(SuccessError):
  185. # args don't matter
  186. qubesadmin.tools.qvm_template.install(mock.MagicMock(), None)
  187. self.assertFalse(os.path.exists('/tmp/test.lock'))
  188. with self.subTest('locked'):
  189. with open('/tmp/test.lock', 'w') as f:
  190. fcntl.flock(f, fcntl.LOCK_EX)
  191. with self.assertRaises(
  192. qubesadmin.tools.qvm_template.AlreadyRunning):
  193. # args don't matter
  194. qubesadmin.tools.qvm_template.install(mock.MagicMock(), None)
  195. # and not cleaned up then
  196. self.assertTrue(os.path.exists('/tmp/test.lock'))
  197. def add_new_vm_side_effect(self, *args, **kwargs):
  198. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  199. b'0\0test-vm class=TemplateVM state=Halted\n'
  200. self.app.domains.clear_cache()
  201. return self.app.domains['test-vm']
  202. @mock.patch('os.rename')
  203. @mock.patch('os.makedirs')
  204. @mock.patch('subprocess.check_call')
  205. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  206. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  207. @mock.patch('qubesadmin.tools.qvm_template.download')
  208. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  209. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  210. def test_100_install_local_success(
  211. self,
  212. mock_verify,
  213. mock_dl_list,
  214. mock_dl,
  215. mock_extract,
  216. mock_confirm,
  217. mock_call,
  218. mock_mkdirs,
  219. mock_rename):
  220. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  221. build_time = '2020-09-01 14:30:00' # 1598970600
  222. install_time = '2020-09-01 15:30:00'
  223. for key, val in [
  224. ('name', 'test-vm'),
  225. ('epoch', '2'),
  226. ('version', '4.1'),
  227. ('release', '2020'),
  228. ('reponame', '@commandline'),
  229. ('buildtime', build_time),
  230. ('installtime', install_time),
  231. ('license', 'GPL'),
  232. ('url', 'https://qubes-os.org'),
  233. ('summary', 'Summary'),
  234. ('description', 'Desc|desc')]:
  235. self.app.expected_calls[(
  236. 'test-vm',
  237. 'admin.vm.feature.Set',
  238. f'template-{key}',
  239. val.encode())] = b'0\0'
  240. mock_verify.return_value = {
  241. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  242. rpm.RPMTAG_BUILDTIME : 1598970600,
  243. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  244. rpm.RPMTAG_EPOCHNUM : 2,
  245. rpm.RPMTAG_LICENSE : 'GPL',
  246. rpm.RPMTAG_RELEASE : '2020',
  247. rpm.RPMTAG_SUMMARY : 'Summary',
  248. rpm.RPMTAG_URL : 'https://qubes-os.org',
  249. rpm.RPMTAG_VERSION : '4.1'
  250. }
  251. mock_dl_list.return_value = {}
  252. mock_call.side_effect = self.add_new_vm_side_effect
  253. mock_time = mock.Mock(wraps=datetime.datetime)
  254. mock_time.now.return_value = \
  255. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  256. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  257. mock.patch('datetime.datetime', new=mock_time), \
  258. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  259. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  260. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  261. path = template_file.name
  262. args = argparse.Namespace(
  263. templates=[path],
  264. keyring='/tmp/keyring.gpg',
  265. nogpgcheck=False,
  266. cachedir='/var/cache/qvm-template',
  267. repo_files=[],
  268. releasever='4.1',
  269. yes=False,
  270. allow_pv=False,
  271. pool=None
  272. )
  273. mock_tmpdir.return_value.__enter__.return_value = \
  274. '/var/tmp/qvm-template-tmpdir'
  275. qubesadmin.tools.qvm_template.install(args, self.app)
  276. # Attempt to get download list
  277. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  278. self.assertEqual(mock_dl_list.mock_calls, [
  279. mock.call(args, self.app, version_selector=selector)
  280. ])
  281. # Nothing downloaded
  282. mock_dl.assert_called_with(args, self.app,
  283. dl_list={}, version_selector=selector)
  284. mock_verify.assert_called_once_with(template_file.name, '/tmp/keyring.gpg',
  285. nogpgcheck=False)
  286. # Package is extracted
  287. mock_extract.assert_called_with('test-vm', path,
  288. '/var/tmp/qvm-template-tmpdir')
  289. # No packages overwritten, so no confirm needed
  290. self.assertEqual(mock_confirm.mock_calls, [])
  291. # qvm-template-postprocess is called
  292. self.assertEqual(mock_call.mock_calls, [
  293. mock.call([
  294. 'qvm-template-postprocess',
  295. '--really',
  296. '--no-installed-by-rpm',
  297. 'post-install',
  298. 'test-vm',
  299. '/var/tmp/qvm-template-tmpdir'
  300. '/var/lib/qubes/vm-templates/test-vm'
  301. ])
  302. ])
  303. # Cache directory created
  304. self.assertEqual(mock_mkdirs.mock_calls, [
  305. mock.call(args.cachedir, exist_ok=True)
  306. ])
  307. # No templates downloaded, thus no renames needed
  308. self.assertEqual(mock_rename.mock_calls, [])
  309. self.assertAllCalled()
  310. @mock.patch('os.rename')
  311. @mock.patch('os.makedirs')
  312. @mock.patch('subprocess.check_call')
  313. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  314. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  315. @mock.patch('qubesadmin.tools.qvm_template.download')
  316. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  317. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  318. def test_101_install_local_postprocargs_success(
  319. self,
  320. mock_verify,
  321. mock_dl_list,
  322. mock_dl,
  323. mock_extract,
  324. mock_confirm,
  325. mock_call,
  326. mock_mkdirs,
  327. mock_rename):
  328. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  329. build_time = '2020-09-01 14:30:00' # 1598970600
  330. install_time = '2020-09-01 15:30:00'
  331. for key, val in [
  332. ('name', 'test-vm'),
  333. ('epoch', '2'),
  334. ('version', '4.1'),
  335. ('release', '2020'),
  336. ('reponame', '@commandline'),
  337. ('buildtime', build_time),
  338. ('installtime', install_time),
  339. ('license', 'GPL'),
  340. ('url', 'https://qubes-os.org'),
  341. ('summary', 'Summary'),
  342. ('description', 'Desc|desc')]:
  343. self.app.expected_calls[(
  344. 'test-vm',
  345. 'admin.vm.feature.Set',
  346. f'template-{key}',
  347. val.encode())] = b'0\0'
  348. mock_verify.return_value = {
  349. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  350. rpm.RPMTAG_BUILDTIME : 1598970600,
  351. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  352. rpm.RPMTAG_EPOCHNUM : 2,
  353. rpm.RPMTAG_LICENSE : 'GPL',
  354. rpm.RPMTAG_RELEASE : '2020',
  355. rpm.RPMTAG_SUMMARY : 'Summary',
  356. rpm.RPMTAG_URL : 'https://qubes-os.org',
  357. rpm.RPMTAG_VERSION : '4.1'
  358. }
  359. mock_dl_list.return_value = {}
  360. mock_call.side_effect = self.add_new_vm_side_effect
  361. mock_time = mock.Mock(wraps=datetime.datetime)
  362. mock_time.now.return_value = \
  363. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  364. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  365. mock.patch('datetime.datetime', new=mock_time), \
  366. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  367. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  368. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  369. path = template_file.name
  370. args = argparse.Namespace(
  371. templates=[path],
  372. keyring='/tmp',
  373. nogpgcheck=False,
  374. cachedir='/var/cache/qvm-template',
  375. repo_files=[],
  376. releasever='4.1',
  377. yes=False,
  378. allow_pv=True,
  379. pool='my-pool'
  380. )
  381. mock_tmpdir.return_value.__enter__.return_value = \
  382. '/var/tmp/qvm-template-tmpdir'
  383. qubesadmin.tools.qvm_template.install(args, self.app)
  384. # Attempt to get download list
  385. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  386. self.assertEqual(mock_dl_list.mock_calls, [
  387. mock.call(args, self.app, version_selector=selector)
  388. ])
  389. # Nothing downloaded
  390. mock_dl.assert_called_with(args, self.app,
  391. dl_list={}, version_selector=selector)
  392. # Package is extracted
  393. mock_extract.assert_called_with('test-vm', path,
  394. '/var/tmp/qvm-template-tmpdir')
  395. # No packages overwritten, so no confirm needed
  396. self.assertEqual(mock_confirm.mock_calls, [])
  397. # qvm-template-postprocess is called
  398. self.assertEqual(mock_call.mock_calls, [
  399. mock.call([
  400. 'qvm-template-postprocess',
  401. '--really',
  402. '--no-installed-by-rpm',
  403. '--allow-pv',
  404. '--pool',
  405. 'my-pool',
  406. 'post-install',
  407. 'test-vm',
  408. '/var/tmp/qvm-template-tmpdir'
  409. '/var/lib/qubes/vm-templates/test-vm'
  410. ])
  411. ])
  412. # Cache directory created
  413. self.assertEqual(mock_mkdirs.mock_calls, [
  414. mock.call(args.cachedir, exist_ok=True)
  415. ])
  416. # No templates downloaded, thus no renames needed
  417. self.assertEqual(mock_rename.mock_calls, [])
  418. self.assertAllCalled()
  419. @mock.patch('os.rename')
  420. @mock.patch('os.makedirs')
  421. @mock.patch('subprocess.check_call')
  422. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  423. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  424. @mock.patch('qubesadmin.tools.qvm_template.download')
  425. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  426. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  427. def test_102_install_local_badsig_fail(
  428. self,
  429. mock_verify,
  430. mock_dl_list,
  431. mock_dl,
  432. mock_extract,
  433. mock_confirm,
  434. mock_call,
  435. mock_mkdirs,
  436. mock_rename):
  437. mock_verify.return_value = None
  438. mock_time = mock.Mock(wraps=datetime.datetime)
  439. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  440. mock.patch('datetime.datetime', new=mock_time), \
  441. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  442. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  443. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  444. path = template_file.name
  445. args = argparse.Namespace(
  446. templates=[path],
  447. keyring='/tmp',
  448. nogpgcheck=False,
  449. cachedir='/var/cache/qvm-template',
  450. repo_files=[],
  451. releasever='4.1',
  452. yes=False,
  453. allow_pv=False,
  454. pool=None
  455. )
  456. mock_tmpdir.return_value.__enter__.return_value = \
  457. '/var/tmp/qvm-template-tmpdir'
  458. # Should raise parser.error
  459. with self.assertRaises(SystemExit):
  460. qubesadmin.tools.qvm_template.install(args, self.app)
  461. # Check error message
  462. self.assertTrue('verification failed' in mock_err.getvalue())
  463. # Should not be executed:
  464. self.assertEqual(mock_dl_list.mock_calls, [])
  465. self.assertEqual(mock_dl.mock_calls, [])
  466. self.assertEqual(mock_extract.mock_calls, [])
  467. self.assertEqual(mock_confirm.mock_calls, [])
  468. self.assertEqual(mock_call.mock_calls, [])
  469. self.assertEqual(mock_rename.mock_calls, [])
  470. self.assertAllCalled()
  471. @mock.patch('os.rename')
  472. @mock.patch('os.makedirs')
  473. @mock.patch('subprocess.check_call')
  474. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  475. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  476. @mock.patch('qubesadmin.tools.qvm_template.download')
  477. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  478. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  479. def test_103_install_local_exists_fail(
  480. self,
  481. mock_verify,
  482. mock_dl_list,
  483. mock_dl,
  484. mock_extract,
  485. mock_confirm,
  486. mock_call,
  487. mock_mkdirs,
  488. mock_rename):
  489. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  490. b'0\0test-vm class=TemplateVM state=Halted\n'
  491. mock_verify.return_value = {
  492. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  493. rpm.RPMTAG_BUILDTIME : 1598970600,
  494. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  495. rpm.RPMTAG_EPOCHNUM : 2,
  496. rpm.RPMTAG_LICENSE : 'GPL',
  497. rpm.RPMTAG_RELEASE : '2020',
  498. rpm.RPMTAG_SUMMARY : 'Summary',
  499. rpm.RPMTAG_URL : 'https://qubes-os.org',
  500. rpm.RPMTAG_VERSION : '4.1'
  501. }
  502. mock_dl_list.return_value = {}
  503. mock_time = mock.Mock(wraps=datetime.datetime)
  504. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  505. mock.patch('datetime.datetime', new=mock_time), \
  506. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  507. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  508. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  509. path = template_file.name
  510. args = argparse.Namespace(
  511. templates=[path],
  512. keyring='/tmp',
  513. nogpgcheck=False,
  514. cachedir='/var/cache/qvm-template',
  515. repo_files=[],
  516. releasever='4.1',
  517. yes=False,
  518. allow_pv=False,
  519. pool=None
  520. )
  521. mock_tmpdir.return_value.__enter__.return_value = \
  522. '/var/tmp/qvm-template-tmpdir'
  523. qubesadmin.tools.qvm_template.install(args, self.app)
  524. # Check warning message
  525. self.assertTrue('already installed' in mock_err.getvalue())
  526. # Attempt to get download list
  527. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  528. self.assertEqual(mock_dl_list.mock_calls, [
  529. mock.call(args, self.app, version_selector=selector)
  530. ])
  531. # Nothing downloaded
  532. self.assertEqual(mock_dl.mock_calls, [
  533. mock.call(args, self.app,
  534. dl_list={}, version_selector=selector)
  535. ])
  536. # Should not be executed:
  537. self.assertEqual(mock_extract.mock_calls, [])
  538. self.assertEqual(mock_confirm.mock_calls, [])
  539. self.assertEqual(mock_call.mock_calls, [])
  540. self.assertEqual(mock_rename.mock_calls, [])
  541. self.assertAllCalled()
  542. @mock.patch('os.rename')
  543. @mock.patch('os.makedirs')
  544. @mock.patch('subprocess.check_call')
  545. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  546. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  547. @mock.patch('qubesadmin.tools.qvm_template.download')
  548. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  549. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  550. def test_104_install_local_badpkgname_fail(
  551. self,
  552. mock_verify,
  553. mock_dl_list,
  554. mock_dl,
  555. mock_extract,
  556. mock_confirm,
  557. mock_call,
  558. mock_mkdirs,
  559. mock_rename):
  560. mock_verify.return_value = {
  561. rpm.RPMTAG_NAME : 'Xqubes-template-test-vm',
  562. rpm.RPMTAG_BUILDTIME : 1598970600,
  563. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  564. rpm.RPMTAG_EPOCHNUM : 2,
  565. rpm.RPMTAG_LICENSE : 'GPL',
  566. rpm.RPMTAG_RELEASE : '2020',
  567. rpm.RPMTAG_SUMMARY : 'Summary',
  568. rpm.RPMTAG_URL : 'https://qubes-os.org',
  569. rpm.RPMTAG_VERSION : '4.1'
  570. }
  571. mock_time = mock.Mock(wraps=datetime.datetime)
  572. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  573. mock.patch('datetime.datetime', new=mock_time), \
  574. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  575. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  576. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  577. path = template_file.name
  578. args = argparse.Namespace(
  579. templates=[path],
  580. keyring='/tmp',
  581. nogpgcheck=False,
  582. cachedir='/var/cache/qvm-template',
  583. repo_files=[],
  584. releasever='4.1',
  585. yes=False,
  586. allow_pv=False,
  587. pool=None
  588. )
  589. mock_tmpdir.return_value.__enter__.return_value = \
  590. '/var/tmp/qvm-template-tmpdir'
  591. with self.assertRaises(SystemExit):
  592. qubesadmin.tools.qvm_template.install(args, self.app)
  593. # Check error message
  594. self.assertTrue('Illegal package name' in mock_err.getvalue())
  595. # Should not be executed:
  596. self.assertEqual(mock_dl_list.mock_calls, [])
  597. self.assertEqual(mock_dl.mock_calls, [])
  598. self.assertEqual(mock_extract.mock_calls, [])
  599. self.assertEqual(mock_confirm.mock_calls, [])
  600. self.assertEqual(mock_call.mock_calls, [])
  601. self.assertEqual(mock_rename.mock_calls, [])
  602. self.assertAllCalled()
  603. @mock.patch('os.rename')
  604. @mock.patch('os.makedirs')
  605. @mock.patch('subprocess.check_call')
  606. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  607. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  608. @mock.patch('qubesadmin.tools.qvm_template.download')
  609. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  610. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  611. def test_106_install_local_badpath_fail(
  612. self,
  613. mock_verify,
  614. mock_dl_list,
  615. mock_dl,
  616. mock_extract,
  617. mock_confirm,
  618. mock_call,
  619. mock_mkdirs,
  620. mock_rename):
  621. mock_time = mock.Mock(wraps=datetime.datetime)
  622. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  623. mock.patch('datetime.datetime', new=mock_time), \
  624. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  625. mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  626. path = '/var/tmp/ShOulD-NoT-ExIsT.rpm'
  627. args = argparse.Namespace(
  628. templates=[path],
  629. keyring='/tmp',
  630. nogpgcheck=False,
  631. cachedir='/var/cache/qvm-template',
  632. repo_files=[],
  633. releasever='4.1',
  634. yes=False,
  635. allow_pv=False,
  636. pool=None
  637. )
  638. mock_tmpdir.return_value.__enter__.return_value = \
  639. '/var/tmp/qvm-template-tmpdir'
  640. with self.assertRaises(SystemExit):
  641. qubesadmin.tools.qvm_template.install(args, self.app)
  642. # Check error message
  643. self.assertTrue(f"RPM file '{path}' not found" \
  644. in mock_err.getvalue())
  645. # Should not be executed:
  646. self.assertEqual(mock_verify.mock_calls, [])
  647. self.assertEqual(mock_dl_list.mock_calls, [])
  648. self.assertEqual(mock_dl.mock_calls, [])
  649. self.assertEqual(mock_extract.mock_calls, [])
  650. self.assertEqual(mock_confirm.mock_calls, [])
  651. self.assertEqual(mock_call.mock_calls, [])
  652. self.assertEqual(mock_rename.mock_calls, [])
  653. self.assertAllCalled()
  654. @mock.patch('os.rename')
  655. @mock.patch('os.makedirs')
  656. @mock.patch('subprocess.check_call')
  657. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  658. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  659. @mock.patch('qubesadmin.tools.qvm_template.download')
  660. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  661. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  662. def test_107_install_download_success(
  663. self,
  664. mock_verify,
  665. mock_dl_list,
  666. mock_dl,
  667. mock_extract,
  668. mock_confirm,
  669. mock_call,
  670. mock_mkdirs,
  671. mock_rename):
  672. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  673. build_time = '2020-09-01 14:30:00' # 1598970600
  674. install_time = '2020-09-01 15:30:00'
  675. for key, val in [
  676. ('name', 'test-vm'),
  677. ('epoch', '2'),
  678. ('version', '4.1'),
  679. ('release', '2020'),
  680. ('reponame', 'qubes-templates-itl'),
  681. ('buildtime', build_time),
  682. ('installtime', install_time),
  683. ('license', 'GPL'),
  684. ('url', 'https://qubes-os.org'),
  685. ('summary', 'Summary'),
  686. ('description', 'Desc|desc')]:
  687. self.app.expected_calls[(
  688. 'test-vm',
  689. 'admin.vm.feature.Set',
  690. f'template-{key}',
  691. val.encode())] = b'0\0'
  692. mock_dl.return_value = {'test-vm': {
  693. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  694. rpm.RPMTAG_BUILDTIME : 1598970600,
  695. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  696. rpm.RPMTAG_EPOCHNUM : 2,
  697. rpm.RPMTAG_LICENSE : 'GPL',
  698. rpm.RPMTAG_RELEASE : '2020',
  699. rpm.RPMTAG_SUMMARY : 'Summary',
  700. rpm.RPMTAG_URL : 'https://qubes-os.org',
  701. rpm.RPMTAG_VERSION : '4.1'
  702. }}
  703. dl_list = {
  704. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  705. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  706. }
  707. mock_dl_list.return_value = dl_list
  708. mock_call.side_effect = self.add_new_vm_side_effect
  709. mock_time = mock.Mock(wraps=datetime.datetime)
  710. mock_time.now.return_value = \
  711. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  712. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  713. mock.patch('datetime.datetime', new=mock_time), \
  714. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  715. mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  716. args = argparse.Namespace(
  717. templates='test-vm',
  718. keyring='/tmp/keyring.gpg',
  719. nogpgcheck=False,
  720. cachedir='/var/cache/qvm-template',
  721. repo_files=[],
  722. releasever='4.1',
  723. yes=False,
  724. keep_cache=True,
  725. allow_pv=False,
  726. pool=None
  727. )
  728. mock_tmpdir.return_value.__enter__.return_value = \
  729. '/var/tmp/qvm-template-tmpdir'
  730. qubesadmin.tools.qvm_template.install(args, self.app)
  731. # Attempt to get download list
  732. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  733. self.assertEqual(mock_dl_list.mock_calls, [
  734. mock.call(args, self.app, version_selector=selector)
  735. ])
  736. mock_dl.assert_called_with(args, self.app,
  737. dl_list=dl_list, version_selector=selector)
  738. # download already verify the package internally
  739. self.assertEqual(mock_verify.mock_calls, [])
  740. # Package is extracted
  741. mock_extract.assert_called_with('test-vm',
  742. '/var/cache/qvm-template/qubes-template-test-vm-1:4.1-20200101.rpm',
  743. '/var/tmp/qvm-template-tmpdir')
  744. # No packages overwritten, so no confirm needed
  745. self.assertEqual(mock_confirm.mock_calls, [])
  746. # qvm-template-postprocess is called
  747. self.assertEqual(mock_call.mock_calls, [
  748. mock.call([
  749. 'qvm-template-postprocess',
  750. '--really',
  751. '--no-installed-by-rpm',
  752. 'post-install',
  753. 'test-vm',
  754. '/var/tmp/qvm-template-tmpdir'
  755. '/var/lib/qubes/vm-templates/test-vm'
  756. ])
  757. ])
  758. # Cache directory created
  759. self.assertEqual(mock_mkdirs.mock_calls, [
  760. mock.call(args.cachedir, exist_ok=True)
  761. ])
  762. # No templates downloaded, thus no renames needed
  763. self.assertEqual(mock_rename.mock_calls, [])
  764. self.assertAllCalled()
  765. @mock.patch('os.rename')
  766. @mock.patch('os.makedirs')
  767. @mock.patch('subprocess.check_call')
  768. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  769. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  770. @mock.patch('qubesadmin.tools.qvm_template.download')
  771. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  772. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  773. def test_108_install_download_fail_exists(
  774. self,
  775. mock_verify,
  776. mock_dl_list,
  777. mock_dl,
  778. mock_extract,
  779. mock_confirm,
  780. mock_call,
  781. mock_mkdirs,
  782. mock_rename):
  783. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  784. b'0\x00test-vm class=TemplateVM state=Halted\n'
  785. mock_dl.return_value = {'test-vm': {
  786. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  787. rpm.RPMTAG_BUILDTIME : 1598970600,
  788. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  789. rpm.RPMTAG_EPOCHNUM : 2,
  790. rpm.RPMTAG_LICENSE : 'GPL',
  791. rpm.RPMTAG_RELEASE : '2020',
  792. rpm.RPMTAG_SUMMARY : 'Summary',
  793. rpm.RPMTAG_URL : 'https://qubes-os.org',
  794. rpm.RPMTAG_VERSION : '4.1'
  795. }}
  796. dl_list = {
  797. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  798. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  799. }
  800. mock_dl_list.return_value = dl_list
  801. mock_time = mock.Mock(wraps=datetime.datetime)
  802. mock_time.now.return_value = \
  803. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  804. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  805. mock.patch('datetime.datetime', new=mock_time), \
  806. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  807. mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  808. args = argparse.Namespace(
  809. templates='test-vm',
  810. keyring='/tmp/keyring.gpg',
  811. nogpgcheck=False,
  812. cachedir='/var/cache/qvm-template',
  813. repo_files=[],
  814. releasever='4.1',
  815. yes=False,
  816. keep_cache=True,
  817. allow_pv=False,
  818. pool=None
  819. )
  820. mock_tmpdir.return_value.__enter__.return_value = \
  821. '/var/tmp/qvm-template-tmpdir'
  822. qubesadmin.tools.qvm_template.install(args, self.app)
  823. self.assertIn('already installed, skipping', mock_err.getvalue())
  824. # Attempt to get download list
  825. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  826. self.assertEqual(mock_dl_list.mock_calls, [
  827. mock.call(args, self.app, version_selector=selector)
  828. ])
  829. # Nothing downloaded nor installed
  830. mock_dl.assert_called_with(args, self.app,
  831. dl_list={}, version_selector=selector)
  832. mock_verify.assert_not_called()
  833. mock_extract.assert_not_called()
  834. mock_confirm.assert_not_called()
  835. mock_call.assert_not_called()
  836. # Cache directory created
  837. self.assertEqual(mock_mkdirs.mock_calls, [
  838. mock.call(args.cachedir, exist_ok=True)
  839. ])
  840. # No templates downloaded, thus no renames needed
  841. self.assertEqual(mock_rename.mock_calls, [])
  842. self.assertAllCalled()
  843. @mock.patch('os.rename')
  844. @mock.patch('os.makedirs')
  845. @mock.patch('subprocess.check_call')
  846. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  847. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  848. @mock.patch('qubesadmin.tools.qvm_template.download')
  849. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  850. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  851. def test_109_install_fail_extract(
  852. self,
  853. mock_verify,
  854. mock_dl_list,
  855. mock_dl,
  856. mock_extract,
  857. mock_confirm,
  858. mock_call,
  859. mock_mkdirs,
  860. mock_rename):
  861. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = b'0\0'
  862. mock_verify.return_value = {
  863. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  864. rpm.RPMTAG_BUILDTIME : 1598970600,
  865. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  866. rpm.RPMTAG_EPOCHNUM : 2,
  867. rpm.RPMTAG_LICENSE : 'GPL',
  868. rpm.RPMTAG_RELEASE : '2020',
  869. rpm.RPMTAG_SUMMARY : 'Summary',
  870. rpm.RPMTAG_URL : 'https://qubes-os.org',
  871. rpm.RPMTAG_VERSION : '4.1'
  872. }
  873. mock_dl_list.return_value = {}
  874. mock_call.side_effect = self.add_new_vm_side_effect
  875. mock_time = mock.Mock(wraps=datetime.datetime)
  876. mock_time.now.return_value = \
  877. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  878. # Extraction error
  879. mock_extract.return_value = False
  880. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  881. mock.patch('datetime.datetime', new=mock_time), \
  882. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir, \
  883. mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  884. tempfile.NamedTemporaryFile(suffix='.rpm') as template_file:
  885. path = template_file.name
  886. args = argparse.Namespace(
  887. templates=[path],
  888. keyring='/tmp/keyring.gpg',
  889. nogpgcheck=False,
  890. cachedir='/var/cache/qvm-template',
  891. repo_files=[],
  892. releasever='4.1',
  893. yes=False,
  894. allow_pv=False,
  895. pool=None
  896. )
  897. mock_tmpdir.return_value.__enter__.return_value = \
  898. '/var/tmp/qvm-template-tmpdir'
  899. with self.assertRaises(Exception) as e:
  900. qubesadmin.tools.qvm_template.install(args, self.app)
  901. self.assertIn('Failed to extract', e.exception.args[0])
  902. # Attempt to get download list
  903. selector = qubesadmin.tools.qvm_template.VersionSelector.LATEST
  904. self.assertEqual(mock_dl_list.mock_calls, [
  905. mock.call(args, self.app, version_selector=selector)
  906. ])
  907. # Nothing downloaded
  908. mock_dl.assert_called_with(args, self.app,
  909. dl_list={}, version_selector=selector)
  910. mock_verify.assert_called_once_with(template_file.name,
  911. '/tmp/keyring.gpg',
  912. nogpgcheck=False)
  913. # Package is (attempted to be) extracted
  914. mock_extract.assert_called_with('test-vm', path,
  915. '/var/tmp/qvm-template-tmpdir')
  916. # No packages overwritten, so no confirm needed
  917. self.assertEqual(mock_confirm.mock_calls, [])
  918. # No VM created
  919. mock_call.assert_not_called()
  920. # Cache directory created
  921. self.assertEqual(mock_mkdirs.mock_calls, [
  922. mock.call(args.cachedir, exist_ok=True)
  923. ])
  924. # No templates downloaded, thus no renames needed
  925. self.assertEqual(mock_rename.mock_calls, [])
  926. self.assertAllCalled()
  927. def test_110_qrexec_payload_refresh_success(self):
  928. with tempfile.NamedTemporaryFile() as repo_conf1, \
  929. tempfile.NamedTemporaryFile() as repo_conf2:
  930. repo_str1 = \
  931. '''[qubes-templates-itl]
  932. name = Qubes Templates repository
  933. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  934. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  935. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  936. enabled = 1
  937. fastestmirror = 1
  938. metadata_expire = 7d
  939. gpgcheck = 1
  940. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  941. '''
  942. repo_str2 = \
  943. '''[qubes-templates-itl-testing]
  944. name = Qubes Templates repository
  945. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl-testing
  946. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl-testing
  947. metalink = https://yum.qubes-os.org/r$releasever/templates-itl-testing/repodata/repomd.xml.metalink
  948. enabled = 0
  949. fastestmirror = 1
  950. gpgcheck = 1
  951. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  952. '''
  953. repo_conf1.write(repo_str1.encode())
  954. repo_conf1.flush()
  955. repo_conf2.write(repo_str2.encode())
  956. repo_conf2.flush()
  957. args = argparse.Namespace(
  958. enablerepo=['repo1', 'repo2'],
  959. disablerepo=['repo3', 'repo4', 'repo5'],
  960. repoid=[],
  961. releasever='4.1',
  962. repo_files=[repo_conf1.name, repo_conf2.name]
  963. )
  964. res = qubesadmin.tools.qvm_template.qrexec_payload(args, self.app,
  965. 'qubes-template-fedora-32', True)
  966. self.assertEqual(res,
  967. '''--enablerepo=repo1
  968. --enablerepo=repo2
  969. --disablerepo=repo3
  970. --disablerepo=repo4
  971. --disablerepo=repo5
  972. --refresh
  973. --releasever=4.1
  974. qubes-template-fedora-32
  975. ---
  976. ''' + repo_str1 + '\n' + repo_str2 + '\n')
  977. self.assertAllCalled()
  978. def test_111_qrexec_payload_norefresh_success(self):
  979. with tempfile.NamedTemporaryFile() as repo_conf1:
  980. repo_str1 = \
  981. '''[qubes-templates-itl]
  982. name = Qubes Templates repository
  983. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  984. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  985. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  986. enabled = 1
  987. fastestmirror = 1
  988. metadata_expire = 7d
  989. gpgcheck = 1
  990. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  991. '''
  992. repo_conf1.write(repo_str1.encode())
  993. repo_conf1.flush()
  994. args = argparse.Namespace(
  995. enablerepo=[],
  996. disablerepo=[],
  997. repoid=['repo1', 'repo2'],
  998. releasever='4.1',
  999. repo_files=[repo_conf1.name]
  1000. )
  1001. res = qubesadmin.tools.qvm_template.qrexec_payload(args, self.app,
  1002. 'qubes-template-fedora-32', False)
  1003. self.assertEqual(res,
  1004. '''--repoid=repo1
  1005. --repoid=repo2
  1006. --releasever=4.1
  1007. qubes-template-fedora-32
  1008. ---
  1009. ''' + repo_str1 + '\n')
  1010. self.assertAllCalled()
  1011. def test_112_qrexec_payload_specnewline_fail(self):
  1012. with tempfile.NamedTemporaryFile() as repo_conf1:
  1013. repo_str1 = \
  1014. '''[qubes-templates-itl]
  1015. name = Qubes Templates repository
  1016. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1017. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1018. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1019. enabled = 1
  1020. fastestmirror = 1
  1021. metadata_expire = 7d
  1022. gpgcheck = 1
  1023. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1024. '''
  1025. repo_conf1.write(repo_str1.encode())
  1026. repo_conf1.flush()
  1027. args = argparse.Namespace(
  1028. enablerepo=[],
  1029. disablerepo=[],
  1030. repoid=['repo1', 'repo2'],
  1031. releasever='4.1',
  1032. repo_files=[repo_conf1.name]
  1033. )
  1034. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1035. with self.assertRaises(SystemExit):
  1036. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1037. self.app, 'qubes-template-fedora\n-32', False)
  1038. # Check error message
  1039. self.assertTrue('Malformed template name'
  1040. in mock_err.getvalue())
  1041. self.assertTrue("argument should not contain '\\n'"
  1042. in mock_err.getvalue())
  1043. self.assertAllCalled()
  1044. def test_113_qrexec_payload_enablereponewline_fail(self):
  1045. with tempfile.NamedTemporaryFile() as repo_conf1:
  1046. repo_str1 = \
  1047. '''[qubes-templates-itl]
  1048. name = Qubes Templates repository
  1049. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1050. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1051. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1052. enabled = 1
  1053. fastestmirror = 1
  1054. metadata_expire = 7d
  1055. gpgcheck = 1
  1056. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1057. '''
  1058. repo_conf1.write(repo_str1.encode())
  1059. repo_conf1.flush()
  1060. args = argparse.Namespace(
  1061. enablerepo=['repo\n0'],
  1062. disablerepo=[],
  1063. repoid=['repo1', 'repo2'],
  1064. releasever='4.1',
  1065. repo_files=[repo_conf1.name]
  1066. )
  1067. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1068. with self.assertRaises(SystemExit):
  1069. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1070. self.app, 'qubes-template-fedora-32', False)
  1071. # Check error message
  1072. self.assertTrue('Malformed --enablerepo'
  1073. in mock_err.getvalue())
  1074. self.assertTrue("argument should not contain '\\n'"
  1075. in mock_err.getvalue())
  1076. self.assertAllCalled()
  1077. def test_114_qrexec_payload_disablereponewline_fail(self):
  1078. with tempfile.NamedTemporaryFile() as repo_conf1:
  1079. repo_str1 = \
  1080. '''[qubes-templates-itl]
  1081. name = Qubes Templates repository
  1082. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1083. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1084. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1085. enabled = 1
  1086. fastestmirror = 1
  1087. metadata_expire = 7d
  1088. gpgcheck = 1
  1089. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1090. '''
  1091. repo_conf1.write(repo_str1.encode())
  1092. repo_conf1.flush()
  1093. args = argparse.Namespace(
  1094. enablerepo=[],
  1095. disablerepo=['repo\n0'],
  1096. repoid=['repo1', 'repo2'],
  1097. releasever='4.1',
  1098. repo_files=[repo_conf1.name]
  1099. )
  1100. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1101. with self.assertRaises(SystemExit):
  1102. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1103. self.app, 'qubes-template-fedora-32', False)
  1104. # Check error message
  1105. self.assertTrue('Malformed --disablerepo'
  1106. in mock_err.getvalue())
  1107. self.assertTrue("argument should not contain '\\n'"
  1108. in mock_err.getvalue())
  1109. self.assertAllCalled()
  1110. def test_115_qrexec_payload_repoidnewline_fail(self):
  1111. with tempfile.NamedTemporaryFile() as repo_conf1:
  1112. repo_str1 = \
  1113. '''[qubes-templates-itl]
  1114. name = Qubes Templates repository
  1115. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1116. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1117. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1118. enabled = 1
  1119. fastestmirror = 1
  1120. metadata_expire = 7d
  1121. gpgcheck = 1
  1122. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1123. '''
  1124. repo_conf1.write(repo_str1.encode())
  1125. repo_conf1.flush()
  1126. args = argparse.Namespace(
  1127. enablerepo=[],
  1128. disablerepo=[],
  1129. repoid=['repo\n1', 'repo2'],
  1130. releasever='4.1',
  1131. repo_files=[repo_conf1.name]
  1132. )
  1133. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1134. with self.assertRaises(SystemExit):
  1135. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1136. self.app, 'qubes-template-fedora-32', False)
  1137. # Check error message
  1138. self.assertTrue('Malformed --repoid'
  1139. in mock_err.getvalue())
  1140. self.assertTrue("argument should not contain '\\n'"
  1141. in mock_err.getvalue())
  1142. self.assertAllCalled()
  1143. def test_116_qrexec_payload_releasevernewline_fail(self):
  1144. with tempfile.NamedTemporaryFile() as repo_conf1:
  1145. repo_str1 = \
  1146. '''[qubes-templates-itl]
  1147. name = Qubes Templates repository
  1148. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1149. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1150. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1151. enabled = 1
  1152. fastestmirror = 1
  1153. metadata_expire = 7d
  1154. gpgcheck = 1
  1155. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1156. '''
  1157. repo_conf1.write(repo_str1.encode())
  1158. repo_conf1.flush()
  1159. args = argparse.Namespace(
  1160. enablerepo=[],
  1161. disablerepo=[],
  1162. repoid=['repo1', 'repo2'],
  1163. releasever='4\n.1',
  1164. repo_files=[repo_conf1.name]
  1165. )
  1166. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1167. with self.assertRaises(SystemExit):
  1168. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1169. self.app, 'qubes-template-fedora-32', False)
  1170. # Check error message
  1171. self.assertTrue('Malformed --releasever'
  1172. in mock_err.getvalue())
  1173. self.assertTrue("argument should not contain '\\n'"
  1174. in mock_err.getvalue())
  1175. self.assertAllCalled()
  1176. def test_117_qrexec_payload_specdash_fail(self):
  1177. with tempfile.NamedTemporaryFile() as repo_conf1:
  1178. repo_str1 = \
  1179. '''[qubes-templates-itl]
  1180. name = Qubes Templates repository
  1181. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  1182. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  1183. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  1184. enabled = 1
  1185. fastestmirror = 1
  1186. metadata_expire = 7d
  1187. gpgcheck = 1
  1188. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  1189. '''
  1190. repo_conf1.write(repo_str1.encode())
  1191. repo_conf1.flush()
  1192. args = argparse.Namespace(
  1193. enablerepo=[],
  1194. disablerepo=[],
  1195. repoid=['repo1', 'repo2'],
  1196. releasever='4.1',
  1197. repo_files=[repo_conf1.name]
  1198. )
  1199. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1200. with self.assertRaises(SystemExit):
  1201. qubesadmin.tools.qvm_template.qrexec_payload(args,
  1202. self.app, '---', False)
  1203. # Check error message
  1204. self.assertTrue('Malformed template name'
  1205. in mock_err.getvalue())
  1206. self.assertTrue("argument should not be '---'"
  1207. in mock_err.getvalue())
  1208. self.assertAllCalled()
  1209. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1210. def test_120_qrexec_repoquery_success(self, mock_payload):
  1211. args = argparse.Namespace(updatevm='test-vm')
  1212. mock_payload.return_value = 'str1\nstr2'
  1213. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1214. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1215. self.app.expected_service_calls[
  1216. ('test-vm', 'qubes.TemplateSearch')] = \
  1217. 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|
  1218. 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|
  1219. '''
  1220. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1221. 'qubes-template-fedora-32')
  1222. self.assertEqual(res, [
  1223. qubesadmin.tools.qvm_template.Template(
  1224. 'fedora-32',
  1225. '0',
  1226. '4.1',
  1227. '20200101',
  1228. 'qubes-templates-itl',
  1229. 1048576,
  1230. datetime.datetime(2020, 1, 23, 4, 56),
  1231. 'GPL',
  1232. 'https://qubes-os.org',
  1233. 'Qubes template for fedora-32',
  1234. 'Qubes template\n for fedora-32\n'
  1235. ),
  1236. qubesadmin.tools.qvm_template.Template(
  1237. 'fedora-32',
  1238. '1',
  1239. '4.2',
  1240. '20200201',
  1241. 'qubes-templates-itl-testing',
  1242. 2048576,
  1243. datetime.datetime(2020, 2, 23, 4, 56),
  1244. 'GPLv2',
  1245. 'https://qubes-os.org/?',
  1246. 'Qubes template for fedora-32 v2',
  1247. 'Qubes template\n for fedora-32 v2\n'
  1248. )
  1249. ])
  1250. self.assertEqual(self.app.service_calls, [
  1251. ('test-vm', 'qubes.TemplateSearch',
  1252. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1253. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1254. ])
  1255. self.assertEqual(mock_payload.mock_calls, [
  1256. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1257. ])
  1258. self.assertAllCalled()
  1259. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1260. def test_121_qrexec_repoquery_refresh_success(self, mock_payload):
  1261. args = argparse.Namespace(updatevm='test-vm')
  1262. mock_payload.return_value = 'str1\nstr2'
  1263. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1264. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1265. self.app.expected_service_calls[
  1266. ('test-vm', 'qubes.TemplateSearch')] = \
  1267. 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|
  1268. 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|
  1269. '''
  1270. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1271. 'qubes-template-fedora-32', True)
  1272. self.assertEqual(res, [
  1273. qubesadmin.tools.qvm_template.Template(
  1274. 'fedora-32',
  1275. '0',
  1276. '4.1',
  1277. '20200101',
  1278. 'qubes-templates-itl',
  1279. 1048576,
  1280. datetime.datetime(2020, 1, 23, 4, 56),
  1281. 'GPL',
  1282. 'https://qubes-os.org',
  1283. 'Qubes template for fedora-32',
  1284. 'Qubes template\n for fedora-32\n'
  1285. ),
  1286. qubesadmin.tools.qvm_template.Template(
  1287. 'fedora-32',
  1288. '1',
  1289. '4.2',
  1290. '20200201',
  1291. 'qubes-templates-itl-testing',
  1292. 2048576,
  1293. datetime.datetime(2020, 2, 23, 4, 56),
  1294. 'GPLv2',
  1295. 'https://qubes-os.org/?',
  1296. 'Qubes template for fedora-32 v2',
  1297. 'Qubes template\n for fedora-32 v2\n'
  1298. )
  1299. ])
  1300. self.assertEqual(self.app.service_calls, [
  1301. ('test-vm', 'qubes.TemplateSearch',
  1302. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1303. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1304. ])
  1305. self.assertEqual(mock_payload.mock_calls, [
  1306. mock.call(args, self.app, 'qubes-template-fedora-32', True)
  1307. ])
  1308. self.assertAllCalled()
  1309. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1310. def test_122_qrexec_repoquery_ignorenonspec_success(self, mock_payload):
  1311. args = argparse.Namespace(updatevm='test-vm')
  1312. mock_payload.return_value = 'str1\nstr2'
  1313. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1314. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1315. self.app.expected_service_calls[
  1316. ('test-vm', 'qubes.TemplateSearch')] = \
  1317. 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|
  1318. 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|
  1319. '''
  1320. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1321. 'qubes-template-fedora-32')
  1322. self.assertEqual(res, [
  1323. qubesadmin.tools.qvm_template.Template(
  1324. 'fedora-32',
  1325. '0',
  1326. '4.1',
  1327. '20200101',
  1328. 'qubes-templates-itl',
  1329. 1048576,
  1330. datetime.datetime(2020, 1, 23, 4, 56),
  1331. 'GPL',
  1332. 'https://qubes-os.org',
  1333. 'Qubes template for fedora-32',
  1334. 'Qubes template for fedora-32\n'
  1335. )
  1336. ])
  1337. self.assertEqual(self.app.service_calls, [
  1338. ('test-vm', 'qubes.TemplateSearch',
  1339. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1340. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1341. ])
  1342. self.assertEqual(mock_payload.mock_calls, [
  1343. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1344. ])
  1345. self.assertAllCalled()
  1346. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1347. def test_123_qrexec_repoquery_ignorebadname_success(self, mock_payload):
  1348. args = argparse.Namespace(updatevm='test-vm')
  1349. mock_payload.return_value = 'str1\nstr2'
  1350. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1351. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1352. self.app.expected_service_calls[
  1353. ('test-vm', 'qubes.TemplateSearch')] = \
  1354. 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|
  1355. 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|
  1356. '''
  1357. res = qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1358. 'qubes-template-fedora-32')
  1359. self.assertEqual(res, [
  1360. qubesadmin.tools.qvm_template.Template(
  1361. 'fedora-32',
  1362. '0',
  1363. '4.1',
  1364. '20200101',
  1365. 'qubes-templates-itl',
  1366. 1048576,
  1367. datetime.datetime(2020, 1, 23, 4, 56),
  1368. 'GPL',
  1369. 'https://qubes-os.org',
  1370. 'Qubes template for fedora-32',
  1371. 'Qubes template for fedora-32\n'
  1372. )
  1373. ])
  1374. self.assertEqual(self.app.service_calls, [
  1375. ('test-vm', 'qubes.TemplateSearch',
  1376. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1377. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1378. ])
  1379. self.assertEqual(mock_payload.mock_calls, [
  1380. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1381. ])
  1382. self.assertAllCalled()
  1383. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1384. def test_124_qrexec_repoquery_searchfail_fail(self, mock_payload):
  1385. args = argparse.Namespace(updatevm='test-vm')
  1386. mock_payload.return_value = 'str1\nstr2'
  1387. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1388. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1389. with mock.patch('qubesadmin.tests.TestProcess.wait') \
  1390. as mock_wait:
  1391. mock_wait.return_value = 1
  1392. with self.assertRaises(ConnectionError):
  1393. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1394. 'qubes-template-fedora-32')
  1395. self.assertEqual(self.app.service_calls, [
  1396. ('test-vm', 'qubes.TemplateSearch',
  1397. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1398. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1399. ])
  1400. self.assertEqual(mock_payload.mock_calls, [
  1401. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1402. ])
  1403. self.assertAllCalled()
  1404. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1405. def test_125_qrexec_repoquery_extrafield_fail(self, mock_payload):
  1406. args = argparse.Namespace(updatevm='test-vm')
  1407. mock_payload.return_value = 'str1\nstr2'
  1408. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1409. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1410. self.app.expected_service_calls[
  1411. ('test-vm', 'qubes.TemplateSearch')] = \
  1412. 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|
  1413. 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|
  1414. '''
  1415. with self.assertRaisesRegex(ConnectionError,
  1416. "unexpected data format"):
  1417. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1418. 'qubes-template-fedora-32')
  1419. self.assertEqual(self.app.service_calls, [
  1420. ('test-vm', 'qubes.TemplateSearch',
  1421. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1422. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1423. ])
  1424. self.assertEqual(mock_payload.mock_calls, [
  1425. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1426. ])
  1427. self.assertAllCalled()
  1428. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1429. def test_125_qrexec_repoquery_missingfield_fail(self, mock_payload):
  1430. args = argparse.Namespace(updatevm='test-vm')
  1431. mock_payload.return_value = 'str1\nstr2'
  1432. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1433. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1434. self.app.expected_service_calls[
  1435. ('test-vm', 'qubes.TemplateSearch')] = \
  1436. 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|
  1437. 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|
  1438. '''
  1439. with self.assertRaisesRegex(ConnectionError,
  1440. "unexpected data format"):
  1441. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1442. 'qubes-template-fedora-32')
  1443. self.assertEqual(self.app.service_calls, [
  1444. ('test-vm', 'qubes.TemplateSearch',
  1445. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1446. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1447. ])
  1448. self.assertEqual(mock_payload.mock_calls, [
  1449. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1450. ])
  1451. self.assertAllCalled()
  1452. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1453. def test_126_qrexec_repoquery_badfieldname_fail(self, mock_payload):
  1454. args = argparse.Namespace(updatevm='test-vm')
  1455. mock_payload.return_value = 'str1\nstr2'
  1456. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1457. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1458. self.app.expected_service_calls[
  1459. ('test-vm', 'qubes.TemplateSearch')] = \
  1460. 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|
  1461. 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|
  1462. '''
  1463. with self.assertRaisesRegex(ConnectionError,
  1464. "unexpected data format"):
  1465. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1466. 'qubes-template-fedora-32')
  1467. self.assertEqual(self.app.service_calls, [
  1468. ('test-vm', 'qubes.TemplateSearch',
  1469. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1470. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1471. ])
  1472. self.assertEqual(mock_payload.mock_calls, [
  1473. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1474. ])
  1475. self.assertAllCalled()
  1476. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1477. def test_126_qrexec_repoquery_badfieldepoch_fail(self, mock_payload):
  1478. args = argparse.Namespace(updatevm='test-vm')
  1479. mock_payload.return_value = 'str1\nstr2'
  1480. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1481. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1482. self.app.expected_service_calls[
  1483. ('test-vm', 'qubes.TemplateSearch')] = \
  1484. 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|
  1485. 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|
  1486. '''
  1487. with self.assertRaisesRegex(ConnectionError,
  1488. "unexpected data format"):
  1489. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1490. 'qubes-template-fedora-32')
  1491. self.assertEqual(self.app.service_calls, [
  1492. ('test-vm', 'qubes.TemplateSearch',
  1493. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1494. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1495. ])
  1496. self.assertEqual(mock_payload.mock_calls, [
  1497. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1498. ])
  1499. self.assertAllCalled()
  1500. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1501. def test_126_qrexec_repoquery_badfieldreponame_fail(self, mock_payload):
  1502. args = argparse.Namespace(updatevm='test-vm')
  1503. mock_payload.return_value = 'str1\nstr2'
  1504. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1505. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1506. self.app.expected_service_calls[
  1507. ('test-vm', 'qubes.TemplateSearch')] = \
  1508. 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|
  1509. 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|
  1510. '''
  1511. with self.assertRaisesRegex(ConnectionError,
  1512. "unexpected data format"):
  1513. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1514. 'qubes-template-fedora-32')
  1515. self.assertEqual(self.app.service_calls, [
  1516. ('test-vm', 'qubes.TemplateSearch',
  1517. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1518. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1519. ])
  1520. self.assertEqual(mock_payload.mock_calls, [
  1521. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1522. ])
  1523. self.assertAllCalled()
  1524. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1525. def test_126_qrexec_repoquery_badfielddlsize_fail(self, mock_payload):
  1526. args = argparse.Namespace(updatevm='test-vm')
  1527. mock_payload.return_value = 'str1\nstr2'
  1528. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1529. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1530. self.app.expected_service_calls[
  1531. ('test-vm', 'qubes.TemplateSearch')] = \
  1532. 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|
  1533. 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|
  1534. '''
  1535. with self.assertRaisesRegex(ConnectionError,
  1536. "unexpected data format"):
  1537. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1538. 'qubes-template-fedora-32')
  1539. self.assertEqual(self.app.service_calls, [
  1540. ('test-vm', 'qubes.TemplateSearch',
  1541. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1542. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1543. ])
  1544. self.assertEqual(mock_payload.mock_calls, [
  1545. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1546. ])
  1547. self.assertAllCalled()
  1548. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1549. def test_126_qrexec_repoquery_badfielddate_fail(self, mock_payload):
  1550. args = argparse.Namespace(updatevm='test-vm')
  1551. mock_payload.return_value = 'str1\nstr2'
  1552. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1553. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1554. self.app.expected_service_calls[
  1555. ('test-vm', 'qubes.TemplateSearch')] = \
  1556. 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|
  1557. 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|
  1558. '''
  1559. with self.assertRaisesRegex(ConnectionError,
  1560. "unexpected data format"):
  1561. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1562. 'qubes-template-fedora-32')
  1563. self.assertEqual(self.app.service_calls, [
  1564. ('test-vm', 'qubes.TemplateSearch',
  1565. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1566. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1567. ])
  1568. self.assertEqual(mock_payload.mock_calls, [
  1569. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1570. ])
  1571. self.assertAllCalled()
  1572. @mock.patch('qubesadmin.tools.qvm_template.qrexec_payload')
  1573. def test_126_qrexec_repoquery_license_fail(self, mock_payload):
  1574. args = argparse.Namespace(updatevm='test-vm')
  1575. mock_payload.return_value = 'str1\nstr2'
  1576. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1577. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1578. self.app.expected_service_calls[
  1579. ('test-vm', 'qubes.TemplateSearch')] = \
  1580. 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|
  1581. 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|
  1582. '''
  1583. with self.assertRaisesRegex(ConnectionError,
  1584. "unexpected data format"):
  1585. qubesadmin.tools.qvm_template.qrexec_repoquery(args, self.app,
  1586. 'qubes-template-fedora-32')
  1587. self.assertEqual(self.app.service_calls, [
  1588. ('test-vm', 'qubes.TemplateSearch',
  1589. {'filter_esc': True, 'stdout': subprocess.PIPE}),
  1590. ('test-vm', 'qubes.TemplateSearch', b'str1\nstr2')
  1591. ])
  1592. self.assertEqual(mock_payload.mock_calls, [
  1593. mock.call(args, self.app, 'qubes-template-fedora-32', False)
  1594. ])
  1595. self.assertAllCalled()
  1596. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1597. def test_130_get_dl_list_latest_success(self, mock_query):
  1598. mock_query.return_value = [
  1599. qubesadmin.tools.qvm_template.Template(
  1600. 'fedora-32',
  1601. '1',
  1602. '4.1',
  1603. '20200101',
  1604. 'qubes-templates-itl',
  1605. 1048576,
  1606. datetime.datetime(2020, 1, 23, 4, 56),
  1607. 'GPL',
  1608. 'https://qubes-os.org',
  1609. 'Qubes template for fedora-32',
  1610. 'Qubes template\n for fedora-32\n'
  1611. ),
  1612. qubesadmin.tools.qvm_template.Template(
  1613. 'fedora-32',
  1614. '0',
  1615. '4.2',
  1616. '20200201',
  1617. 'qubes-templates-itl-testing',
  1618. 2048576,
  1619. datetime.datetime(2020, 2, 23, 4, 56),
  1620. 'GPLv2',
  1621. 'https://qubes-os.org/?',
  1622. 'Qubes template for fedora-32 v2',
  1623. 'Qubes template\n for fedora-32 v2\n'
  1624. )
  1625. ]
  1626. args = argparse.Namespace(
  1627. templates=['some.local.file.rpm', 'fedora-32']
  1628. )
  1629. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1630. self.assertEqual(ret, {
  1631. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1632. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  1633. })
  1634. self.assertEqual(mock_query.mock_calls, [
  1635. mock.call(args, self.app, 'qubes-template-fedora-32')
  1636. ])
  1637. self.assertAllCalled()
  1638. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1639. def test_131_get_dl_list_latest_notfound_fail(self, mock_query):
  1640. mock_query.return_value = []
  1641. args = argparse.Namespace(
  1642. templates=['some.local.file.rpm', 'fedora-31']
  1643. )
  1644. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1645. with self.assertRaises(SystemExit):
  1646. qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1647. self.assertTrue('not found' in mock_err.getvalue())
  1648. self.assertEqual(mock_query.mock_calls, [
  1649. mock.call(args, self.app, 'qubes-template-fedora-31')
  1650. ])
  1651. self.assertAllCalled()
  1652. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1653. def test_132_get_dl_list_multimerge0_success(self, mock_query):
  1654. counter = 0
  1655. def f(*args):
  1656. nonlocal counter
  1657. counter += 1
  1658. if counter == 1:
  1659. return [
  1660. qubesadmin.tools.qvm_template.Template(
  1661. 'fedora-32',
  1662. '0',
  1663. '4.2',
  1664. '20200201',
  1665. 'qubes-templates-itl-testing',
  1666. 2048576,
  1667. datetime.datetime(2020, 2, 23, 4, 56),
  1668. 'GPLv2',
  1669. 'https://qubes-os.org/?',
  1670. 'Qubes template for fedora-32 v2',
  1671. 'Qubes template\n for fedora-32 v2\n'
  1672. )
  1673. ]
  1674. return [
  1675. qubesadmin.tools.qvm_template.Template(
  1676. 'fedora-32',
  1677. '1',
  1678. '4.1',
  1679. '20200101',
  1680. 'qubes-templates-itl',
  1681. 1048576,
  1682. datetime.datetime(2020, 1, 23, 4, 56),
  1683. 'GPL',
  1684. 'https://qubes-os.org',
  1685. 'Qubes template for fedora-32',
  1686. 'Qubes template\n for fedora-32\n'
  1687. )
  1688. ]
  1689. mock_query.side_effect = f
  1690. args = argparse.Namespace(
  1691. templates=['some.local.file.rpm', 'fedora-32:0', 'fedora-32:1']
  1692. )
  1693. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1694. self.assertEqual(ret, {
  1695. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1696. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  1697. })
  1698. self.assertEqual(mock_query.mock_calls, [
  1699. mock.call(args, self.app, 'qubes-template-fedora-32:0'),
  1700. mock.call(args, self.app, 'qubes-template-fedora-32:1')
  1701. ])
  1702. self.assertAllCalled()
  1703. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1704. def test_132_get_dl_list_multimerge1_success(self, mock_query):
  1705. counter = 0
  1706. def f(*args):
  1707. nonlocal counter
  1708. counter += 1
  1709. if counter == 1:
  1710. return [
  1711. qubesadmin.tools.qvm_template.Template(
  1712. 'fedora-32',
  1713. '2',
  1714. '4.2',
  1715. '20200201',
  1716. 'qubes-templates-itl-testing',
  1717. 2048576,
  1718. datetime.datetime(2020, 2, 23, 4, 56),
  1719. 'GPLv2',
  1720. 'https://qubes-os.org/?',
  1721. 'Qubes template for fedora-32 v2',
  1722. 'Qubes template\n for fedora-32 v2\n'
  1723. )
  1724. ]
  1725. return [
  1726. qubesadmin.tools.qvm_template.Template(
  1727. 'fedora-32',
  1728. '1',
  1729. '4.1',
  1730. '20200101',
  1731. 'qubes-templates-itl',
  1732. 1048576,
  1733. datetime.datetime(2020, 1, 23, 4, 56),
  1734. 'GPL',
  1735. 'https://qubes-os.org',
  1736. 'Qubes template for fedora-32',
  1737. 'Qubes template\n for fedora-32\n'
  1738. )
  1739. ]
  1740. mock_query.side_effect = f
  1741. args = argparse.Namespace(
  1742. templates=['some.local.file.rpm', 'fedora-32:2', 'fedora-32:1']
  1743. )
  1744. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app)
  1745. self.assertEqual(ret, {
  1746. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  1747. ('2', '4.2', '20200201'),
  1748. 'qubes-templates-itl-testing',
  1749. 2048576)
  1750. })
  1751. self.assertEqual(mock_query.mock_calls, [
  1752. mock.call(args, self.app, 'qubes-template-fedora-32:2'),
  1753. mock.call(args, self.app, 'qubes-template-fedora-32:1')
  1754. ])
  1755. self.assertAllCalled()
  1756. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1757. def test_133_get_dl_list_reinstall_success(self, mock_query):
  1758. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1759. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1760. self.app.expected_calls[(
  1761. 'test-vm',
  1762. 'admin.vm.feature.Get',
  1763. f'template-name',
  1764. None)] = b'0\0test-vm'
  1765. self.app.expected_calls[(
  1766. 'test-vm',
  1767. 'admin.vm.feature.Get',
  1768. f'template-epoch',
  1769. None)] = b'0\x000'
  1770. self.app.expected_calls[(
  1771. 'test-vm',
  1772. 'admin.vm.feature.Get',
  1773. f'template-version',
  1774. None)] = b'0\x004.2'
  1775. self.app.expected_calls[(
  1776. 'test-vm',
  1777. 'admin.vm.feature.Get',
  1778. f'template-release',
  1779. None)] = b'0\x0020200201'
  1780. mock_query.return_value = [
  1781. qubesadmin.tools.qvm_template.Template(
  1782. 'test-vm',
  1783. '1',
  1784. '4.1',
  1785. '20200101',
  1786. 'qubes-templates-itl',
  1787. 1048576,
  1788. datetime.datetime(2020, 1, 23, 4, 56),
  1789. 'GPL',
  1790. 'https://qubes-os.org',
  1791. 'Qubes template for test-vm',
  1792. 'Qubes template\n for test-vm\n'
  1793. ),
  1794. qubesadmin.tools.qvm_template.Template(
  1795. 'test-vm',
  1796. '0',
  1797. '4.2',
  1798. '20200201',
  1799. 'qubes-templates-itl-testing',
  1800. 2048576,
  1801. datetime.datetime(2020, 2, 23, 4, 56),
  1802. 'GPLv2',
  1803. 'https://qubes-os.org/?',
  1804. 'Qubes template for test-vm v2',
  1805. 'Qubes template\n for test-vm v2\n'
  1806. )
  1807. ]
  1808. args = argparse.Namespace(
  1809. templates=['some.local.file.rpm', 'test-vm']
  1810. )
  1811. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1812. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1813. self.assertEqual(ret, {
  1814. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  1815. ('0', '4.2', '20200201'),
  1816. 'qubes-templates-itl-testing',
  1817. 2048576
  1818. )
  1819. })
  1820. self.assertEqual(mock_query.mock_calls, [
  1821. mock.call(args, self.app, 'qubes-template-test-vm')
  1822. ])
  1823. self.assertAllCalled()
  1824. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1825. def test_134_get_dl_list_reinstall_nolocal_fail(self, mock_query):
  1826. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1827. b'0\x00'
  1828. mock_query.return_value = [
  1829. qubesadmin.tools.qvm_template.Template(
  1830. 'test-vm',
  1831. '1',
  1832. '4.1',
  1833. '20200101',
  1834. 'qubes-templates-itl',
  1835. 1048576,
  1836. datetime.datetime(2020, 1, 23, 4, 56),
  1837. 'GPL',
  1838. 'https://qubes-os.org',
  1839. 'Qubes template for test-vm',
  1840. 'Qubes template\n for test-vm\n'
  1841. ),
  1842. qubesadmin.tools.qvm_template.Template(
  1843. 'test-vm',
  1844. '0',
  1845. '4.2',
  1846. '20200201',
  1847. 'qubes-templates-itl-testing',
  1848. 2048576,
  1849. datetime.datetime(2020, 2, 23, 4, 56),
  1850. 'GPLv2',
  1851. 'https://qubes-os.org/?',
  1852. 'Qubes template for test-vm v2',
  1853. 'Qubes template\n for test-vm v2\n'
  1854. )
  1855. ]
  1856. args = argparse.Namespace(templates=['test-vm'])
  1857. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1858. with self.assertRaises(SystemExit):
  1859. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1860. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1861. self.assertTrue('not already installed' in mock_err.getvalue())
  1862. self.assertEqual(mock_query.mock_calls, [
  1863. mock.call(args, self.app, 'qubes-template-test-vm')
  1864. ])
  1865. self.assertAllCalled()
  1866. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1867. def test_135_get_dl_list_reinstall_nonmanaged_fail(self, mock_query):
  1868. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1869. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1870. mock_query.return_value = [
  1871. qubesadmin.tools.qvm_template.Template(
  1872. 'test-vm',
  1873. '1',
  1874. '4.1',
  1875. '20200101',
  1876. 'qubes-templates-itl',
  1877. 1048576,
  1878. datetime.datetime(2020, 1, 23, 4, 56),
  1879. 'GPL',
  1880. 'https://qubes-os.org',
  1881. 'Qubes template for test-vm',
  1882. 'Qubes template\n for test-vm\n'
  1883. ),
  1884. qubesadmin.tools.qvm_template.Template(
  1885. 'test-vm',
  1886. '0',
  1887. '4.2',
  1888. '20200201',
  1889. 'qubes-templates-itl-testing',
  1890. 2048576,
  1891. datetime.datetime(2020, 2, 23, 4, 56),
  1892. 'GPLv2',
  1893. 'https://qubes-os.org/?',
  1894. 'Qubes template for test-vm v2',
  1895. 'Qubes template\n for test-vm v2\n'
  1896. )
  1897. ]
  1898. args = argparse.Namespace(templates=['test-vm'])
  1899. def qubesd_call(dest, method,
  1900. arg=None, payload=None, payload_stream=None,
  1901. orig_func=self.app.qubesd_call):
  1902. if method == 'admin.vm.feature.Get':
  1903. raise KeyError
  1904. return orig_func(dest, method, arg, payload, payload_stream)
  1905. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  1906. mock.patch.object(self.app, 'qubesd_call') as mock_call:
  1907. mock_call.side_effect = qubesd_call
  1908. with self.assertRaises(SystemExit):
  1909. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1910. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1911. self.assertTrue('not managed' in mock_err.getvalue())
  1912. self.assertEqual(mock_query.mock_calls, [
  1913. mock.call(args, self.app, 'qubes-template-test-vm')
  1914. ])
  1915. self.assertAllCalled()
  1916. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1917. def test_135_get_dl_list_reinstall_nonmanagednoname_fail(self, mock_query):
  1918. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1919. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1920. self.app.expected_calls[(
  1921. 'test-vm',
  1922. 'admin.vm.feature.Get',
  1923. f'template-name',
  1924. None)] = b'0\0test-vm-2'
  1925. mock_query.return_value = [
  1926. qubesadmin.tools.qvm_template.Template(
  1927. 'test-vm',
  1928. '1',
  1929. '4.1',
  1930. '20200101',
  1931. 'qubes-templates-itl',
  1932. 1048576,
  1933. datetime.datetime(2020, 1, 23, 4, 56),
  1934. 'GPL',
  1935. 'https://qubes-os.org',
  1936. 'Qubes template for test-vm',
  1937. 'Qubes template\n for test-vm\n'
  1938. ),
  1939. qubesadmin.tools.qvm_template.Template(
  1940. 'test-vm',
  1941. '0',
  1942. '4.2',
  1943. '20200201',
  1944. 'qubes-templates-itl-testing',
  1945. 2048576,
  1946. datetime.datetime(2020, 2, 23, 4, 56),
  1947. 'GPLv2',
  1948. 'https://qubes-os.org/?',
  1949. 'Qubes template for test-vm v2',
  1950. 'Qubes template\n for test-vm v2\n'
  1951. )
  1952. ]
  1953. args = argparse.Namespace(templates=['test-vm'])
  1954. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  1955. with self.assertRaises(SystemExit):
  1956. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  1957. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  1958. self.assertTrue('not managed' in mock_err.getvalue())
  1959. self.assertEqual(mock_query.mock_calls, [
  1960. mock.call(args, self.app, 'qubes-template-test-vm')
  1961. ])
  1962. self.assertAllCalled()
  1963. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  1964. def test_136_get_dl_list_downgrade_success(self, mock_query):
  1965. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  1966. b'0\x00test-vm class=TemplateVM state=Halted\n'
  1967. self.app.expected_calls[(
  1968. 'test-vm',
  1969. 'admin.vm.feature.Get',
  1970. f'template-name',
  1971. None)] = b'0\0test-vm'
  1972. self.app.expected_calls[(
  1973. 'test-vm',
  1974. 'admin.vm.feature.Get',
  1975. f'template-epoch',
  1976. None)] = b'0\x000'
  1977. self.app.expected_calls[(
  1978. 'test-vm',
  1979. 'admin.vm.feature.Get',
  1980. f'template-version',
  1981. None)] = b'0\x004.3'
  1982. self.app.expected_calls[(
  1983. 'test-vm',
  1984. 'admin.vm.feature.Get',
  1985. f'template-release',
  1986. None)] = b'0\x0020200201'
  1987. mock_query.return_value = [
  1988. qubesadmin.tools.qvm_template.Template(
  1989. 'test-vm',
  1990. '0',
  1991. '4.2',
  1992. '20200201',
  1993. 'qubes-templates-itl-testing',
  1994. 2048576,
  1995. datetime.datetime(2020, 2, 23, 4, 56),
  1996. 'GPLv2',
  1997. 'https://qubes-os.org/?',
  1998. 'Qubes template for test-vm v2',
  1999. 'Qubes template\n for test-vm v2\n'
  2000. ),
  2001. qubesadmin.tools.qvm_template.Template(
  2002. 'test-vm',
  2003. '0',
  2004. '4.1',
  2005. '20200101',
  2006. 'qubes-templates-itl',
  2007. 1048576,
  2008. datetime.datetime(2020, 1, 23, 4, 56),
  2009. 'GPL',
  2010. 'https://qubes-os.org',
  2011. 'Qubes template for test-vm',
  2012. 'Qubes template\n for test-vm\n'
  2013. ),
  2014. qubesadmin.tools.qvm_template.Template(
  2015. 'test-vm',
  2016. '1',
  2017. '4.1',
  2018. '20200101',
  2019. 'qubes-templates-itl',
  2020. 1048576,
  2021. datetime.datetime(2020, 1, 23, 4, 56),
  2022. 'GPL',
  2023. 'https://qubes-os.org',
  2024. 'Qubes template for test-vm',
  2025. 'Qubes template\n for test-vm\n'
  2026. )
  2027. ]
  2028. args = argparse.Namespace(templates=['test-vm'])
  2029. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2030. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  2031. self.assertEqual(ret, {
  2032. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  2033. ('0', '4.2', '20200201'),
  2034. 'qubes-templates-itl-testing',
  2035. 2048576
  2036. )
  2037. })
  2038. self.assertEqual(mock_query.mock_calls, [
  2039. mock.call(args, self.app, 'qubes-template-test-vm')
  2040. ])
  2041. self.assertAllCalled()
  2042. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2043. def test_137_get_dl_list_downgrade_nonmanaged_fail(self, mock_query):
  2044. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2045. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2046. self.app.expected_calls[(
  2047. 'test-vm',
  2048. 'admin.vm.feature.Get',
  2049. f'template-name',
  2050. None)] = b'0\0test-vm-2'
  2051. mock_query.return_value = [
  2052. qubesadmin.tools.qvm_template.Template(
  2053. 'test-vm',
  2054. '0',
  2055. '4.2',
  2056. '20200201',
  2057. 'qubes-templates-itl-testing',
  2058. 2048576,
  2059. datetime.datetime(2020, 2, 23, 4, 56),
  2060. 'GPLv2',
  2061. 'https://qubes-os.org/?',
  2062. 'Qubes template for test-vm v2',
  2063. 'Qubes template\n for test-vm v2\n'
  2064. ),
  2065. qubesadmin.tools.qvm_template.Template(
  2066. 'test-vm',
  2067. '0',
  2068. '4.1',
  2069. '20200101',
  2070. 'qubes-templates-itl',
  2071. 1048576,
  2072. datetime.datetime(2020, 1, 23, 4, 56),
  2073. 'GPL',
  2074. 'https://qubes-os.org',
  2075. 'Qubes template for test-vm',
  2076. 'Qubes template\n for test-vm\n'
  2077. ),
  2078. qubesadmin.tools.qvm_template.Template(
  2079. 'test-vm',
  2080. '1',
  2081. '4.1',
  2082. '20200101',
  2083. 'qubes-templates-itl',
  2084. 1048576,
  2085. datetime.datetime(2020, 1, 23, 4, 56),
  2086. 'GPL',
  2087. 'https://qubes-os.org',
  2088. 'Qubes template for test-vm',
  2089. 'Qubes template\n for test-vm\n'
  2090. )
  2091. ]
  2092. args = argparse.Namespace(templates=['test-vm'])
  2093. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2094. with self.assertRaises(SystemExit):
  2095. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2096. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  2097. self.assertTrue('not managed' in mock_err.getvalue())
  2098. self.assertEqual(mock_query.mock_calls, [
  2099. mock.call(args, self.app, 'qubes-template-test-vm')
  2100. ])
  2101. self.assertAllCalled()
  2102. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2103. def test_138_get_dl_list_downgrade_notfound_skip(self, mock_query):
  2104. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2105. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2106. self.app.expected_calls[(
  2107. 'test-vm',
  2108. 'admin.vm.feature.Get',
  2109. f'template-name',
  2110. None)] = b'0\0test-vm'
  2111. self.app.expected_calls[(
  2112. 'test-vm',
  2113. 'admin.vm.feature.Get',
  2114. f'template-epoch',
  2115. None)] = b'0\x000'
  2116. self.app.expected_calls[(
  2117. 'test-vm',
  2118. 'admin.vm.feature.Get',
  2119. f'template-version',
  2120. None)] = b'0\x004.3'
  2121. self.app.expected_calls[(
  2122. 'test-vm',
  2123. 'admin.vm.feature.Get',
  2124. f'template-release',
  2125. None)] = b'0\x0020200201'
  2126. mock_query.return_value = [
  2127. qubesadmin.tools.qvm_template.Template(
  2128. 'test-vm',
  2129. '1',
  2130. '4.1',
  2131. '20200101',
  2132. 'qubes-templates-itl',
  2133. 1048576,
  2134. datetime.datetime(2020, 1, 23, 4, 56),
  2135. 'GPL',
  2136. 'https://qubes-os.org',
  2137. 'Qubes template for test-vm',
  2138. 'Qubes template\n for test-vm\n'
  2139. )
  2140. ]
  2141. args = argparse.Namespace(templates=['test-vm'])
  2142. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2143. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2144. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  2145. self.assertTrue('lowest version' in mock_err.getvalue())
  2146. self.assertEqual(ret, {})
  2147. self.assertEqual(mock_query.mock_calls, [
  2148. mock.call(args, self.app, 'qubes-template-test-vm')
  2149. ])
  2150. self.assertAllCalled()
  2151. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2152. def test_139_get_dl_list_upgrade_success(self, mock_query):
  2153. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2154. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2155. self.app.expected_calls[(
  2156. 'test-vm',
  2157. 'admin.vm.feature.Get',
  2158. f'template-name',
  2159. None)] = b'0\0test-vm'
  2160. self.app.expected_calls[(
  2161. 'test-vm',
  2162. 'admin.vm.feature.Get',
  2163. f'template-epoch',
  2164. None)] = b'0\x000'
  2165. self.app.expected_calls[(
  2166. 'test-vm',
  2167. 'admin.vm.feature.Get',
  2168. f'template-version',
  2169. None)] = b'0\x004.3'
  2170. self.app.expected_calls[(
  2171. 'test-vm',
  2172. 'admin.vm.feature.Get',
  2173. f'template-release',
  2174. None)] = b'0\x0020200201'
  2175. mock_query.return_value = [
  2176. qubesadmin.tools.qvm_template.Template(
  2177. 'test-vm',
  2178. '1',
  2179. '4.1',
  2180. '20200101',
  2181. 'qubes-templates-itl',
  2182. 1048576,
  2183. datetime.datetime(2020, 1, 23, 4, 56),
  2184. 'GPL',
  2185. 'https://qubes-os.org',
  2186. 'Qubes template for test-vm',
  2187. 'Qubes template\n for test-vm\n'
  2188. )
  2189. ]
  2190. args = argparse.Namespace(templates=['test-vm'])
  2191. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2192. qubesadmin.tools.qvm_template.VersionSelector.LATEST_HIGHER)
  2193. self.assertEqual(ret, {
  2194. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  2195. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576
  2196. )
  2197. })
  2198. self.assertEqual(mock_query.mock_calls, [
  2199. mock.call(args, self.app, 'qubes-template-test-vm')
  2200. ])
  2201. self.assertAllCalled()
  2202. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2203. def test_140_get_dl_list_downgrade_notfound_skip(self, mock_query):
  2204. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2205. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2206. self.app.expected_calls[(
  2207. 'test-vm',
  2208. 'admin.vm.feature.Get',
  2209. f'template-name',
  2210. None)] = b'0\0test-vm'
  2211. self.app.expected_calls[(
  2212. 'test-vm',
  2213. 'admin.vm.feature.Get',
  2214. f'template-epoch',
  2215. None)] = b'0\x000'
  2216. self.app.expected_calls[(
  2217. 'test-vm',
  2218. 'admin.vm.feature.Get',
  2219. f'template-version',
  2220. None)] = b'0\x004.3'
  2221. self.app.expected_calls[(
  2222. 'test-vm',
  2223. 'admin.vm.feature.Get',
  2224. f'template-release',
  2225. None)] = b'0\x0020200201'
  2226. mock_query.return_value = [
  2227. qubesadmin.tools.qvm_template.Template(
  2228. 'test-vm',
  2229. '0',
  2230. '4.1',
  2231. '20200101',
  2232. 'qubes-templates-itl',
  2233. 1048576,
  2234. datetime.datetime(2020, 1, 23, 4, 56),
  2235. 'GPL',
  2236. 'https://qubes-os.org',
  2237. 'Qubes template for test-vm',
  2238. 'Qubes template\n for test-vm\n'
  2239. )
  2240. ]
  2241. args = argparse.Namespace(templates=['test-vm'])
  2242. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2243. ret = qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2244. qubesadmin.tools.qvm_template.VersionSelector.LATEST_HIGHER)
  2245. self.assertTrue('highest version' in mock_err.getvalue())
  2246. self.assertEqual(ret, {})
  2247. self.assertEqual(mock_query.mock_calls, [
  2248. mock.call(args, self.app, 'qubes-template-test-vm')
  2249. ])
  2250. self.assertAllCalled()
  2251. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2252. def test_141_get_dl_list_reinstall_notfound_fail(self, mock_query):
  2253. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2254. b'0\x00test-vm class=TemplateVM state=Halted\n'
  2255. self.app.expected_calls[(
  2256. 'test-vm',
  2257. 'admin.vm.feature.Get',
  2258. f'template-name',
  2259. None)] = b'0\0test-vm'
  2260. self.app.expected_calls[(
  2261. 'test-vm',
  2262. 'admin.vm.feature.Get',
  2263. f'template-epoch',
  2264. None)] = b'0\x000'
  2265. self.app.expected_calls[(
  2266. 'test-vm',
  2267. 'admin.vm.feature.Get',
  2268. f'template-version',
  2269. None)] = b'0\x004.3'
  2270. self.app.expected_calls[(
  2271. 'test-vm',
  2272. 'admin.vm.feature.Get',
  2273. f'template-release',
  2274. None)] = b'0\x0020200201'
  2275. mock_query.return_value = [
  2276. qubesadmin.tools.qvm_template.Template(
  2277. 'test-vm',
  2278. '0',
  2279. '4.1',
  2280. '20200101',
  2281. 'qubes-templates-itl',
  2282. 1048576,
  2283. datetime.datetime(2020, 1, 23, 4, 56),
  2284. 'GPL',
  2285. 'https://qubes-os.org',
  2286. 'Qubes template for test-vm',
  2287. 'Qubes template\n for test-vm\n'
  2288. )
  2289. ]
  2290. args = argparse.Namespace(templates=['test-vm'])
  2291. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2292. with self.assertRaises(SystemExit):
  2293. qubesadmin.tools.qvm_template.get_dl_list(args, self.app,
  2294. qubesadmin.tools.qvm_template.VersionSelector.REINSTALL)
  2295. self.assertTrue('Same version' in mock_err.getvalue())
  2296. self.assertTrue('not found' in mock_err.getvalue())
  2297. self.assertEqual(mock_query.mock_calls, [
  2298. mock.call(args, self.app, 'qubes-template-test-vm')
  2299. ])
  2300. self.assertAllCalled()
  2301. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2302. def test_150_list_templates_installed_success(self, mock_query):
  2303. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2304. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2305. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2306. b'non-spec class=TemplateVM state=Halted\n'
  2307. build_time = '2020-09-01 14:30:00' # 1598970600
  2308. install_time = '2020-09-01 15:30:00'
  2309. for key, val in [
  2310. ('name', 'test-vm'),
  2311. ('epoch', '2'),
  2312. ('version', '4.1'),
  2313. ('release', '2020'),
  2314. ('reponame', '@commandline'),
  2315. ('buildtime', build_time),
  2316. ('installtime', install_time),
  2317. ('license', 'GPL'),
  2318. ('url', 'https://qubes-os.org'),
  2319. ('summary', 'Summary'),
  2320. ('description', 'Desc|desc')]:
  2321. self.app.expected_calls[(
  2322. 'test-vm',
  2323. 'admin.vm.feature.Get',
  2324. f'template-{key}',
  2325. None)] = b'0\0' + val.encode()
  2326. for key, val in [('name', 'test-vm-2-not-managed')]:
  2327. self.app.expected_calls[(
  2328. 'test-vm-2',
  2329. 'admin.vm.feature.Get',
  2330. f'template-{key}',
  2331. None)] = b'0\0' + val.encode()
  2332. for key, val in [
  2333. ('name', 'non-spec'),
  2334. ('epoch', '0'),
  2335. ('version', '4.3'),
  2336. ('release', '20200201')]:
  2337. self.app.expected_calls[(
  2338. 'non-spec',
  2339. 'admin.vm.feature.Get',
  2340. f'template-{key}',
  2341. None)] = b'0\0' + val.encode()
  2342. args = argparse.Namespace(
  2343. all=False,
  2344. installed=True,
  2345. available=False,
  2346. extras=False,
  2347. upgrades=False,
  2348. all_versions=True,
  2349. machine_readable=False,
  2350. machine_readable_json=False,
  2351. templates=['test-vm*']
  2352. )
  2353. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2354. mock.patch.object(self.app.domains['test-vm'],
  2355. 'get_disk_utilization') as mock_disk:
  2356. mock_disk.return_value = 1234321
  2357. qubesadmin.tools.qvm_template.list_templates(
  2358. args, self.app, 'list')
  2359. self.assertEqual(mock_out.getvalue(),
  2360. '''Installed Templates
  2361. [('test-vm', '2:4.1-2020', '@commandline')]
  2362. ''')
  2363. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2364. self.assertEqual(mock_query.mock_calls, [])
  2365. self.assertAllCalled()
  2366. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2367. def test_151_list_templates_available_success(self, mock_query):
  2368. counter = 0
  2369. def f(*args):
  2370. nonlocal counter
  2371. counter += 1
  2372. if counter == 1:
  2373. return [
  2374. qubesadmin.tools.qvm_template.Template(
  2375. 'fedora-32',
  2376. '0',
  2377. '4.2',
  2378. '20200201',
  2379. 'qubes-templates-itl-testing',
  2380. 2048576,
  2381. datetime.datetime(2020, 2, 23, 4, 56),
  2382. 'GPLv2',
  2383. 'https://qubes-os.org/?',
  2384. 'Qubes template for fedora-32 v2',
  2385. 'Qubes template\n for fedora-32 v2\n'
  2386. )
  2387. ]
  2388. return [
  2389. qubesadmin.tools.qvm_template.Template(
  2390. 'fedora-31',
  2391. '1',
  2392. '4.1',
  2393. '20200101',
  2394. 'qubes-templates-itl',
  2395. 1048576,
  2396. datetime.datetime(2020, 1, 23, 4, 56),
  2397. 'GPL',
  2398. 'https://qubes-os.org',
  2399. 'Qubes template for fedora-31',
  2400. 'Qubes template\n for fedora-31\n'
  2401. )
  2402. ]
  2403. mock_query.side_effect = f
  2404. args = argparse.Namespace(
  2405. all=False,
  2406. installed=False,
  2407. available=True,
  2408. extras=False,
  2409. upgrades=False,
  2410. all_versions=True,
  2411. machine_readable=False,
  2412. machine_readable_json=False,
  2413. templates=['fedora-32', 'fedora-31']
  2414. )
  2415. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2416. qubesadmin.tools.qvm_template.list_templates(
  2417. args, self.app, 'list')
  2418. # Order not determinstic because of sets
  2419. expected = [
  2420. ('fedora-31', '1:4.1-20200101', 'qubes-templates-itl'),
  2421. ('fedora-32', '0:4.2-20200201', 'qubes-templates-itl-testing')
  2422. ]
  2423. self.assertTrue(mock_out.getvalue() == \
  2424. f'''Available Templates
  2425. {str([expected[1], expected[0]])}
  2426. ''' \
  2427. or mock_out.getvalue() == \
  2428. f'''Available Templates
  2429. {str([expected[0], expected[1]])}
  2430. ''')
  2431. self.assertEqual(mock_query.mock_calls, [
  2432. mock.call(args, self.app, 'fedora-32'),
  2433. mock.call(args, self.app, 'fedora-31')
  2434. ])
  2435. self.assertAllCalled()
  2436. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2437. def test_151_list_templates_available_all_success(self, mock_query):
  2438. mock_query.return_value = [
  2439. qubesadmin.tools.qvm_template.Template(
  2440. 'fedora-31',
  2441. '1',
  2442. '4.1',
  2443. '20190101',
  2444. 'qubes-templates-itl',
  2445. 1048576,
  2446. datetime.datetime(2019, 1, 23, 4, 56),
  2447. 'GPL',
  2448. 'https://qubes-os.org',
  2449. 'Qubes template for fedora-31',
  2450. 'Qubes template\n for fedora-31\n'
  2451. ),
  2452. qubesadmin.tools.qvm_template.Template(
  2453. 'fedora-31',
  2454. '1',
  2455. '4.1',
  2456. '20200101',
  2457. 'qubes-templates-itl',
  2458. 1048576,
  2459. datetime.datetime(2020, 1, 23, 4, 56),
  2460. 'GPL',
  2461. 'https://qubes-os.org',
  2462. 'Qubes template for fedora-31',
  2463. 'Qubes template\n for fedora-31\n'
  2464. ),
  2465. ]
  2466. args = argparse.Namespace(
  2467. all=False,
  2468. installed=False,
  2469. available=True,
  2470. extras=False,
  2471. upgrades=False,
  2472. all_versions=True,
  2473. machine_readable=False,
  2474. machine_readable_json=False,
  2475. templates=[]
  2476. )
  2477. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2478. qubesadmin.tools.qvm_template.list_templates(
  2479. args, self.app, 'list')
  2480. self.assertEqual(mock_out.getvalue(),
  2481. '''Available Templates
  2482. [('fedora-31', '1:4.1-20190101', 'qubes-templates-itl'), ('fedora-31', '1:4.1-20200101', 'qubes-templates-itl')]
  2483. ''')
  2484. self.assertEqual(mock_query.mock_calls, [
  2485. mock.call(args, self.app)
  2486. ])
  2487. self.assertAllCalled()
  2488. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2489. def test_151_list_templates_available_only_latest_success(self, mock_query):
  2490. mock_query.return_value = [
  2491. qubesadmin.tools.qvm_template.Template(
  2492. 'fedora-31',
  2493. '1',
  2494. '4.1',
  2495. '20190101',
  2496. 'qubes-templates-itl',
  2497. 1048576,
  2498. datetime.datetime(2019, 1, 23, 4, 56),
  2499. 'GPL',
  2500. 'https://qubes-os.org',
  2501. 'Qubes template for fedora-31',
  2502. 'Qubes template\n for fedora-31\n'
  2503. ),
  2504. qubesadmin.tools.qvm_template.Template(
  2505. 'fedora-31',
  2506. '1',
  2507. '4.1',
  2508. '20200101',
  2509. 'qubes-templates-itl',
  2510. 1048576,
  2511. datetime.datetime(2020, 1, 23, 4, 56),
  2512. 'GPL',
  2513. 'https://qubes-os.org',
  2514. 'Qubes template for fedora-31',
  2515. 'Qubes template\n for fedora-31\n'
  2516. ),
  2517. ]
  2518. args = argparse.Namespace(
  2519. all=False,
  2520. installed=False,
  2521. available=True,
  2522. extras=False,
  2523. upgrades=False,
  2524. all_versions=False,
  2525. machine_readable=False,
  2526. machine_readable_json=False,
  2527. templates=[]
  2528. )
  2529. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  2530. qubesadmin.tools.qvm_template.list_templates(
  2531. args, self.app, 'list')
  2532. self.assertEqual(mock_out.getvalue(),
  2533. '''Available Templates
  2534. [('fedora-31', '1:4.1-20200101', 'qubes-templates-itl')]
  2535. ''')
  2536. self.assertEqual(mock_query.mock_calls, [
  2537. mock.call(args, self.app)
  2538. ])
  2539. self.assertAllCalled()
  2540. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2541. def test_152_list_templates_extras_success(self, mock_query):
  2542. mock_query.return_value = [
  2543. qubesadmin.tools.qvm_template.Template(
  2544. 'test-vm',
  2545. '2',
  2546. '4.1',
  2547. '2020',
  2548. 'qubes-templates-itl',
  2549. 1048576,
  2550. datetime.datetime(2020, 9, 1, 14, 30,
  2551. tzinfo=datetime.timezone.utc),
  2552. 'GPL',
  2553. 'https://qubes-os.org',
  2554. 'Qubes template for fedora-31',
  2555. 'Qubes template\n for fedora-31\n'
  2556. )
  2557. ]
  2558. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2559. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2560. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2561. b'test-vm-3 class=TemplateVM state=Halted\n' \
  2562. b'non-spec class=TemplateVM state=Halted\n'
  2563. for key, val in [('name', 'test-vm')]:
  2564. self.app.expected_calls[(
  2565. 'test-vm',
  2566. 'admin.vm.feature.Get',
  2567. f'template-{key}',
  2568. None)] = b'0\0' + val.encode()
  2569. for key, val in [
  2570. ('name', 'test-vm-2'),
  2571. ('epoch', '1'),
  2572. ('version', '4.0'),
  2573. ('release', '2019'),
  2574. ('reponame', 'qubes-template-itl'),
  2575. ('buildtime', '2020-09-02 14:30:00'),
  2576. ('installtime', '2020-09-02 15:30:00'),
  2577. ('license', 'GPLv2'),
  2578. ('url', 'https://qubes-os.org/?'),
  2579. ('summary', 'Summary2'),
  2580. ('description', 'Desc|desc|2')]:
  2581. self.app.expected_calls[(
  2582. 'test-vm-2',
  2583. 'admin.vm.feature.Get',
  2584. f'template-{key}',
  2585. None)] = b'0\0' + val.encode()
  2586. for key, val in [('name', 'test-vm-3-non-managed')]:
  2587. self.app.expected_calls[(
  2588. 'test-vm-3',
  2589. 'admin.vm.feature.Get',
  2590. f'template-{key}',
  2591. None)] = b'0\0' + val.encode()
  2592. for key, val in [
  2593. ('name', 'non-spec'),
  2594. ('epoch', '1'),
  2595. ('version', '4.0'),
  2596. ('release', '2019')]:
  2597. self.app.expected_calls[(
  2598. 'non-spec',
  2599. 'admin.vm.feature.Get',
  2600. f'template-{key}',
  2601. None)] = b'0\0' + val.encode()
  2602. args = argparse.Namespace(
  2603. all=False,
  2604. installed=False,
  2605. available=False,
  2606. extras=True,
  2607. upgrades=False,
  2608. all_versions=True,
  2609. machine_readable=False,
  2610. machine_readable_json=False,
  2611. templates=['test-vm*']
  2612. )
  2613. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2614. mock.patch.object(self.app.domains['test-vm-2'],
  2615. 'get_disk_utilization') as mock_disk:
  2616. mock_disk.return_value = 1234321
  2617. qubesadmin.tools.qvm_template.list_templates(
  2618. args, self.app, 'list')
  2619. self.assertEqual(mock_out.getvalue(),
  2620. '''Extra Templates
  2621. [('test-vm-2', '1:4.0-2019', 'qubes-template-itl')]
  2622. ''')
  2623. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2624. self.assertEqual(mock_query.mock_calls, [
  2625. mock.call(args, self.app, 'test-vm*')
  2626. ])
  2627. self.assertAllCalled()
  2628. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2629. def test_153_list_templates_upgrades_success(self, mock_query):
  2630. mock_query.return_value = [
  2631. qubesadmin.tools.qvm_template.Template(
  2632. 'test-vm',
  2633. '2',
  2634. '4.1',
  2635. '2020',
  2636. 'qubes-templates-itl',
  2637. 1048576,
  2638. datetime.datetime(2020, 9, 1, 14, 30,
  2639. tzinfo=datetime.timezone.utc),
  2640. 'GPL',
  2641. 'https://qubes-os.org',
  2642. 'Qubes template for fedora-31',
  2643. 'Qubes template\n for fedora-31\n'
  2644. ),
  2645. qubesadmin.tools.qvm_template.Template(
  2646. 'test-vm',
  2647. '0',
  2648. '4.1',
  2649. '2020',
  2650. 'qubes-templates-itl',
  2651. 1048576,
  2652. datetime.datetime(2020, 9, 1, 14, 30,
  2653. tzinfo=datetime.timezone.utc),
  2654. 'GPL',
  2655. 'https://qubes-os.org',
  2656. 'Qubes template for fedora-31',
  2657. 'Qubes template\n for fedora-31\n'
  2658. ),
  2659. qubesadmin.tools.qvm_template.Template(
  2660. 'test-vm-3',
  2661. '0',
  2662. '4.1',
  2663. '2020',
  2664. 'qubes-templates-itl',
  2665. 1048576,
  2666. datetime.datetime(2020, 9, 1, 14, 30,
  2667. tzinfo=datetime.timezone.utc),
  2668. 'GPL',
  2669. 'https://qubes-os.org',
  2670. 'Qubes template for fedora-31',
  2671. 'Qubes template\n for fedora-31\n'
  2672. )
  2673. ]
  2674. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2675. b'0\x00test-vm class=TemplateVM state=Halted\n' \
  2676. b'test-vm-2 class=TemplateVM state=Halted\n' \
  2677. b'test-vm-3 class=TemplateVM state=Halted\n'
  2678. for key, val in [
  2679. ('name', 'test-vm'),
  2680. ('epoch', '1'),
  2681. ('version', '4.0'),
  2682. ('release', '2019')]:
  2683. self.app.expected_calls[(
  2684. 'test-vm',
  2685. 'admin.vm.feature.Get',
  2686. f'template-{key}',
  2687. None)] = b'0\0' + val.encode()
  2688. for key, val in [
  2689. ('name', 'test-vm-2'),
  2690. ('epoch', '1'),
  2691. ('version', '4.0'),
  2692. ('release', '2019')]:
  2693. self.app.expected_calls[(
  2694. 'test-vm-2',
  2695. 'admin.vm.feature.Get',
  2696. f'template-{key}',
  2697. None)] = b'0\0' + val.encode()
  2698. for key, val in [('name', 'test-vm-3-non-managed')]:
  2699. self.app.expected_calls[(
  2700. 'test-vm-3',
  2701. 'admin.vm.feature.Get',
  2702. f'template-{key}',
  2703. None)] = b'0\0' + val.encode()
  2704. args = argparse.Namespace(
  2705. all=False,
  2706. installed=False,
  2707. available=False,
  2708. extras=False,
  2709. upgrades=True,
  2710. all_versions=True,
  2711. machine_readable=False,
  2712. machine_readable_json=False,
  2713. templates=['test-vm*']
  2714. )
  2715. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2716. mock.patch.object(self.app.domains['test-vm-2'],
  2717. 'get_disk_utilization') as mock_disk:
  2718. mock_disk.return_value = 1234321
  2719. qubesadmin.tools.qvm_template.list_templates(
  2720. args, self.app, 'list')
  2721. self.assertEqual(mock_out.getvalue(),
  2722. '''Available Upgrades
  2723. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2724. ''')
  2725. self.assertEqual(mock_disk.mock_calls, [])
  2726. self.assertEqual(mock_query.mock_calls, [
  2727. mock.call(args, self.app, 'test-vm*')
  2728. ])
  2729. self.assertAllCalled()
  2730. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2731. def __test_list_templates_all_success(self, operation,
  2732. args, expected, mock_query):
  2733. mock_query.return_value = [
  2734. qubesadmin.tools.qvm_template.Template(
  2735. 'test-vm',
  2736. '2',
  2737. '4.1',
  2738. '2020',
  2739. 'qubes-templates-itl',
  2740. 1048576,
  2741. datetime.datetime(2020, 9, 1, 14, 30,
  2742. tzinfo=datetime.timezone.utc),
  2743. 'GPL',
  2744. 'https://qubes-os.org',
  2745. 'Qubes template for fedora-31',
  2746. 'Qubes template\n for fedora-31\n'
  2747. )
  2748. ]
  2749. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2750. b'0\x00test-vm-2 class=TemplateVM state=Halted\n'
  2751. for key, val in [
  2752. ('name', 'test-vm-2'),
  2753. ('epoch', '1'),
  2754. ('version', '4.0'),
  2755. ('release', '2019'),
  2756. ('reponame', '@commandline'),
  2757. ('buildtime', '2020-09-02 14:30:00'),
  2758. ('installtime', '2020-09-02 15:30:00'),
  2759. ('license', 'GPL'),
  2760. ('url', 'https://qubes-os.org'),
  2761. ('summary', 'Summary'),
  2762. ('description', 'Desc|desc')]:
  2763. self.app.expected_calls[(
  2764. 'test-vm-2',
  2765. 'admin.vm.feature.Get',
  2766. f'template-{key}',
  2767. None)] = b'0\0' + val.encode()
  2768. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2769. mock.patch.object(self.app.domains['test-vm-2'],
  2770. 'get_disk_utilization') as mock_disk:
  2771. mock_disk.return_value = 1234321
  2772. qubesadmin.tools.qvm_template.list_templates(
  2773. args, self.app, operation)
  2774. self.assertEqual(mock_out.getvalue(), expected)
  2775. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  2776. self.assertEqual(mock_query.mock_calls, [
  2777. mock.call(args, self.app, 'test-vm*')
  2778. ])
  2779. self.assertAllCalled()
  2780. def test_154_list_templates_all_success(self):
  2781. args = argparse.Namespace(
  2782. all=True,
  2783. installed=False,
  2784. available=False,
  2785. extras=False,
  2786. upgrades=False,
  2787. all_versions=True,
  2788. machine_readable=False,
  2789. machine_readable_json=False,
  2790. templates=['test-vm*']
  2791. )
  2792. expected = \
  2793. '''Installed Templates
  2794. [('test-vm-2', '1:4.0-2019', '@commandline')]
  2795. Available Templates
  2796. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2797. '''
  2798. self.__test_list_templates_all_success('list', args, expected)
  2799. def test_155_list_templates_all_implicit_success(self):
  2800. args = argparse.Namespace(
  2801. all=False,
  2802. installed=False,
  2803. available=False,
  2804. extras=False,
  2805. upgrades=False,
  2806. all_versions=True,
  2807. machine_readable=False,
  2808. machine_readable_json=False,
  2809. templates=['test-vm*']
  2810. )
  2811. expected = \
  2812. '''Installed Templates
  2813. [('test-vm-2', '1:4.0-2019', '@commandline')]
  2814. Available Templates
  2815. [('test-vm', '2:4.1-2020', 'qubes-templates-itl')]
  2816. '''
  2817. self.__test_list_templates_all_success('list', args, expected)
  2818. def test_156_list_templates_info_all_success(self):
  2819. args = argparse.Namespace(
  2820. all=False,
  2821. installed=False,
  2822. available=False,
  2823. extras=False,
  2824. upgrades=False,
  2825. all_versions=True,
  2826. machine_readable=False,
  2827. machine_readable_json=False,
  2828. templates=['test-vm*']
  2829. )
  2830. expected = \
  2831. '''Installed Templates
  2832. [('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'), (' ', ' ', ' ')]
  2833. Available Templates
  2834. [('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'), (' ', ' ', ' ')]
  2835. '''
  2836. self.__test_list_templates_all_success('info', args, expected)
  2837. def test_157_list_templates_list_all_machinereadable_success(self):
  2838. args = argparse.Namespace(
  2839. all=False,
  2840. installed=False,
  2841. available=False,
  2842. extras=False,
  2843. upgrades=False,
  2844. all_versions=True,
  2845. machine_readable=True,
  2846. machine_readable_json=False,
  2847. templates=['test-vm*']
  2848. )
  2849. expected = \
  2850. '''installed|test-vm-2|1:4.0-2019|@commandline
  2851. available|test-vm|2:4.1-2020|qubes-templates-itl
  2852. '''
  2853. self.__test_list_templates_all_success('list', args, expected)
  2854. def test_158_list_templates_info_all_machinereadable_success(self):
  2855. args = argparse.Namespace(
  2856. all=False,
  2857. installed=False,
  2858. available=False,
  2859. extras=False,
  2860. upgrades=False,
  2861. all_versions=True,
  2862. machine_readable=True,
  2863. machine_readable_json=False,
  2864. templates=['test-vm*']
  2865. )
  2866. expected = \
  2867. '''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
  2868. 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|
  2869. '''
  2870. self.__test_list_templates_all_success('info', args, expected)
  2871. def test_159_list_templates_list_all_machinereadablejson_success(self):
  2872. args = argparse.Namespace(
  2873. all=False,
  2874. installed=False,
  2875. available=False,
  2876. extras=False,
  2877. upgrades=False,
  2878. all_versions=True,
  2879. machine_readable=False,
  2880. machine_readable_json=True,
  2881. templates=['test-vm*']
  2882. )
  2883. expected = \
  2884. '''{"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"}]}
  2885. '''
  2886. self.__test_list_templates_all_success('list', args, expected)
  2887. def test_160_list_templates_info_all_machinereadablejson_success(self):
  2888. args = argparse.Namespace(
  2889. all=False,
  2890. installed=False,
  2891. available=False,
  2892. extras=False,
  2893. upgrades=False,
  2894. all_versions=True,
  2895. machine_readable=False,
  2896. machine_readable_json=True,
  2897. templates=['test-vm*']
  2898. )
  2899. expected = \
  2900. 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"}]}
  2901. '''
  2902. self.__test_list_templates_all_success('info', args, expected)
  2903. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2904. def test_161_list_templates_noresults_fail(self, mock_query):
  2905. mock_query.return_value = []
  2906. args = argparse.Namespace(
  2907. all=False,
  2908. installed=False,
  2909. available=True,
  2910. extras=False,
  2911. upgrades=False,
  2912. all_versions=True,
  2913. machine_readable=False,
  2914. machine_readable_json=False,
  2915. templates=[]
  2916. )
  2917. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  2918. with self.assertRaises(SystemExit):
  2919. qubesadmin.tools.qvm_template.list_templates(
  2920. args, self.app, 'list')
  2921. self.assertTrue('No matching templates' in mock_err.getvalue())
  2922. self.assertEqual(mock_query.mock_calls, [
  2923. mock.call(args, self.app)
  2924. ])
  2925. self.assertAllCalled()
  2926. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  2927. def test_170_search_success(self, mock_query):
  2928. mock_query.return_value = [
  2929. qubesadmin.tools.qvm_template.Template(
  2930. 'test-vm',
  2931. '2',
  2932. '4.1',
  2933. '2020',
  2934. 'qubes-templates-itl',
  2935. 1048576,
  2936. datetime.datetime(2020, 9, 1, 14, 30,
  2937. tzinfo=datetime.timezone.utc),
  2938. 'GPL',
  2939. 'https://qubes-os.org',
  2940. 'Qubes template for fedora-31',
  2941. 'Qubes template\n for fedora-31\n'
  2942. ),
  2943. qubesadmin.tools.qvm_template.Template(
  2944. 'test-vm',
  2945. '0',
  2946. '4.1',
  2947. '2020',
  2948. 'qubes-templates-itl',
  2949. 1048576,
  2950. datetime.datetime(2020, 9, 1, 14, 30,
  2951. tzinfo=datetime.timezone.utc),
  2952. 'GPL',
  2953. 'https://qubes-os.org',
  2954. 'Older Qubes template for fedora-31',
  2955. 'Older Qubes template\n for fedora-31\n'
  2956. ),
  2957. qubesadmin.tools.qvm_template.Template(
  2958. 'should-not-match-3',
  2959. '0',
  2960. '4.1',
  2961. '2020',
  2962. 'qubes-templates-itl',
  2963. 1048576,
  2964. datetime.datetime(2020, 9, 1, 14, 30,
  2965. tzinfo=datetime.timezone.utc),
  2966. 'GPL',
  2967. 'https://qubes-os.org/test-vm',
  2968. 'Qubes template for fedora-31',
  2969. 'test-vm Qubes template\n for fedora-31\n'
  2970. )
  2971. ]
  2972. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  2973. b'0\x00test-vm-2 class=TemplateVM state=Halted\n'
  2974. for key, val in [
  2975. ('name', 'test-vm-2'),
  2976. ('epoch', '1'),
  2977. ('version', '4.0'),
  2978. ('release', '2019'),
  2979. ('reponame', '@commandline'),
  2980. ('buildtime', '2020-09-02 14:30:00'),
  2981. ('license', 'GPL'),
  2982. ('url', 'https://qubes-os.org'),
  2983. ('summary', 'Summary'),
  2984. ('description', 'Desc|desc')]:
  2985. self.app.expected_calls[(
  2986. 'test-vm-2',
  2987. 'admin.vm.feature.Get',
  2988. f'template-{key}',
  2989. None)] = b'0\0' + val.encode()
  2990. args = argparse.Namespace(
  2991. all=False,
  2992. templates=['test-vm']
  2993. )
  2994. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out, \
  2995. mock.patch.object(self.app.domains['test-vm-2'],
  2996. 'get_disk_utilization') as mock_disk:
  2997. mock_disk.return_value = 1234321
  2998. qubesadmin.tools.qvm_template.search(args, self.app)
  2999. self.assertEqual(mock_out.getvalue(),
  3000. '''=== Name Exactly Matched: test-vm ===
  3001. test-vm : Qubes template for fedora-31
  3002. === Name Matched: test-vm ===
  3003. test-vm-2 : Summary
  3004. ''')
  3005. self.assertEqual(mock_disk.mock_calls, [mock.call()])
  3006. self.assertEqual(mock_query.mock_calls, [
  3007. mock.call(args, self.app)
  3008. ])
  3009. self.assertAllCalled()
  3010. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3011. def test_171_search_summary_success(self, mock_query):
  3012. mock_query.return_value = [
  3013. qubesadmin.tools.qvm_template.Template(
  3014. 'test-template',
  3015. '2',
  3016. '4.1',
  3017. '2020',
  3018. 'qubes-templates-itl',
  3019. 1048576,
  3020. datetime.datetime(2020, 9, 1, 14, 30,
  3021. tzinfo=datetime.timezone.utc),
  3022. 'GPL',
  3023. 'https://qubes-os.org',
  3024. 'Qubes template for test-vm :)',
  3025. 'Qubes template\n for fedora-31\n'
  3026. ),
  3027. qubesadmin.tools.qvm_template.Template(
  3028. 'test-template-exact',
  3029. '2',
  3030. '4.1',
  3031. '2020',
  3032. 'qubes-templates-itl',
  3033. 1048576,
  3034. datetime.datetime(2020, 9, 1, 14, 30,
  3035. tzinfo=datetime.timezone.utc),
  3036. 'GPL',
  3037. 'https://qubes-os.org',
  3038. 'test-vm',
  3039. 'Qubes template\n for fedora-31\n'
  3040. ),
  3041. qubesadmin.tools.qvm_template.Template(
  3042. 'test-vm',
  3043. '2',
  3044. '4.1',
  3045. '2020',
  3046. 'qubes-templates-itl',
  3047. 1048576,
  3048. datetime.datetime(2020, 9, 1, 14, 30,
  3049. tzinfo=datetime.timezone.utc),
  3050. 'GPL',
  3051. 'https://qubes-os.org',
  3052. 'Qubes template for test-vm',
  3053. 'Qubes template\n for fedora-31\n'
  3054. ),
  3055. qubesadmin.tools.qvm_template.Template(
  3056. 'test-vm-2',
  3057. '2',
  3058. '4.1',
  3059. '2020',
  3060. 'qubes-templates-itl',
  3061. 1048576,
  3062. datetime.datetime(2020, 9, 1, 14, 30,
  3063. tzinfo=datetime.timezone.utc),
  3064. 'GPL',
  3065. 'https://qubes-os.org',
  3066. 'Qubes template for test-vm-2',
  3067. 'Qubes template\n for fedora-31\n'
  3068. ),
  3069. ]
  3070. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3071. b'0\x00'
  3072. args = argparse.Namespace(
  3073. all=False,
  3074. templates=['test-vm']
  3075. )
  3076. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3077. qubesadmin.tools.qvm_template.search(args, self.app)
  3078. self.assertEqual(mock_out.getvalue(),
  3079. '''=== Name & Summary Matched: test-vm ===
  3080. test-vm : Qubes template for test-vm
  3081. test-vm-2 : Qubes template for test-vm-2
  3082. === Summary Matched: test-vm ===
  3083. test-template : Qubes template for test-vm :)
  3084. === Summary Exactly Matched: test-vm ===
  3085. test-template-exact : test-vm
  3086. ''')
  3087. self.assertEqual(mock_query.mock_calls, [
  3088. mock.call(args, self.app)
  3089. ])
  3090. self.assertAllCalled()
  3091. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3092. def test_172_search_namesummaryexact_success(self, mock_query):
  3093. mock_query.return_value = [
  3094. qubesadmin.tools.qvm_template.Template(
  3095. 'test-template-exact',
  3096. '2',
  3097. '4.1',
  3098. '2020',
  3099. 'qubes-templates-itl',
  3100. 1048576,
  3101. datetime.datetime(2020, 9, 1, 14, 30,
  3102. tzinfo=datetime.timezone.utc),
  3103. 'GPL',
  3104. 'https://qubes-os.org',
  3105. 'test-vm',
  3106. 'Qubes template\n for fedora-31\n'
  3107. ),
  3108. qubesadmin.tools.qvm_template.Template(
  3109. 'test-vm',
  3110. '2',
  3111. '4.1',
  3112. '2020',
  3113. 'qubes-templates-itl',
  3114. 1048576,
  3115. datetime.datetime(2020, 9, 1, 14, 30,
  3116. tzinfo=datetime.timezone.utc),
  3117. 'GPL',
  3118. 'https://qubes-os.org',
  3119. 'test-vm',
  3120. 'Qubes template\n for fedora-31\n'
  3121. ),
  3122. qubesadmin.tools.qvm_template.Template(
  3123. 'test-vm-2',
  3124. '2',
  3125. '4.1',
  3126. '2020',
  3127. 'qubes-templates-itl',
  3128. 1048576,
  3129. datetime.datetime(2020, 9, 1, 14, 30,
  3130. tzinfo=datetime.timezone.utc),
  3131. 'GPL',
  3132. 'https://qubes-os.org',
  3133. 'test-vm',
  3134. 'Qubes template\n for fedora-31\n'
  3135. )
  3136. ]
  3137. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3138. b'0\x00'
  3139. args = argparse.Namespace(
  3140. all=False,
  3141. templates=['test-vm']
  3142. )
  3143. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3144. qubesadmin.tools.qvm_template.search(args, self.app)
  3145. self.assertEqual(mock_out.getvalue(),
  3146. '''=== Name & Summary Exactly Matched: test-vm ===
  3147. test-vm : test-vm
  3148. === Name & Summary Matched: test-vm ===
  3149. test-vm-2 : test-vm
  3150. === Summary Exactly Matched: test-vm ===
  3151. test-template-exact : test-vm
  3152. ''')
  3153. self.assertEqual(mock_query.mock_calls, [
  3154. mock.call(args, self.app)
  3155. ])
  3156. self.assertAllCalled()
  3157. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3158. def test_173_search_multiquery_success(self, mock_query):
  3159. mock_query.return_value = [
  3160. qubesadmin.tools.qvm_template.Template(
  3161. 'test-template-exact',
  3162. '2',
  3163. '4.1',
  3164. '2020',
  3165. 'qubes-templates-itl',
  3166. 1048576,
  3167. datetime.datetime(2020, 9, 1, 14, 30,
  3168. tzinfo=datetime.timezone.utc),
  3169. 'GPL',
  3170. 'https://qubes-os.org',
  3171. 'test-vm',
  3172. 'Qubes template\n for fedora-31\n'
  3173. ),
  3174. qubesadmin.tools.qvm_template.Template(
  3175. 'test-vm',
  3176. '2',
  3177. '4.1',
  3178. '2020',
  3179. 'qubes-templates-itl',
  3180. 1048576,
  3181. datetime.datetime(2020, 9, 1, 14, 30,
  3182. tzinfo=datetime.timezone.utc),
  3183. 'GPL',
  3184. 'https://qubes-os.org',
  3185. 'test-vm',
  3186. 'Qubes template\n for fedora-31\n'
  3187. ),
  3188. qubesadmin.tools.qvm_template.Template(
  3189. 'should-not-match',
  3190. '2',
  3191. '4.1',
  3192. '2020',
  3193. 'qubes-templates-itl',
  3194. 1048576,
  3195. datetime.datetime(2020, 9, 1, 14, 30,
  3196. tzinfo=datetime.timezone.utc),
  3197. 'GPL',
  3198. 'https://qubes-os.org',
  3199. 'Summary',
  3200. 'test-vm Qubes template\n for fedora-31\n'
  3201. )
  3202. ]
  3203. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3204. b'0\x00'
  3205. args = argparse.Namespace(
  3206. all=False,
  3207. templates=['test-vm', 'test-template']
  3208. )
  3209. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3210. qubesadmin.tools.qvm_template.search(args, self.app)
  3211. self.assertEqual(mock_out.getvalue(),
  3212. '''=== Name & Summary Matched: test-template, test-vm ===
  3213. test-template-exact : test-vm
  3214. ''')
  3215. self.assertEqual(mock_query.mock_calls, [
  3216. mock.call(args, self.app)
  3217. ])
  3218. self.assertAllCalled()
  3219. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3220. def test_174_search_multiquery_exact_success(self, mock_query):
  3221. mock_query.return_value = [
  3222. qubesadmin.tools.qvm_template.Template(
  3223. 'test-vm',
  3224. '2',
  3225. '4.1',
  3226. '2020',
  3227. 'qubes-templates-itl',
  3228. 1048576,
  3229. datetime.datetime(2020, 9, 1, 14, 30,
  3230. tzinfo=datetime.timezone.utc),
  3231. 'GPL',
  3232. 'https://qubes-os.org',
  3233. 'summary',
  3234. 'Qubes template\n for fedora-31\n'
  3235. ),
  3236. qubesadmin.tools.qvm_template.Template(
  3237. 'summary',
  3238. '2',
  3239. '4.1',
  3240. '2020',
  3241. 'qubes-templates-itl',
  3242. 1048576,
  3243. datetime.datetime(2020, 9, 1, 14, 30,
  3244. tzinfo=datetime.timezone.utc),
  3245. 'GPL',
  3246. 'https://qubes-os.org',
  3247. 'test-vm Summary',
  3248. 'Qubes template\n for fedora-31\n'
  3249. )
  3250. ]
  3251. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3252. b'0\x00'
  3253. args = argparse.Namespace(
  3254. all=False,
  3255. templates=['test-vm', 'summary']
  3256. )
  3257. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3258. qubesadmin.tools.qvm_template.search(args, self.app)
  3259. self.assertEqual(mock_out.getvalue(),
  3260. '''=== Name & Summary Matched: summary, test-vm ===
  3261. summary : test-vm Summary
  3262. === Name & Summary Exactly Matched: summary, test-vm ===
  3263. test-vm : summary
  3264. ''')
  3265. self.assertEqual(mock_query.mock_calls, [
  3266. mock.call(args, self.app)
  3267. ])
  3268. self.assertAllCalled()
  3269. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3270. def test_175_search_all_success(self, mock_query):
  3271. mock_query.return_value = [
  3272. qubesadmin.tools.qvm_template.Template(
  3273. 'test-vm',
  3274. '2',
  3275. '4.1',
  3276. '2020',
  3277. 'qubes-templates-itl',
  3278. 1048576,
  3279. datetime.datetime(2020, 9, 1, 14, 30,
  3280. tzinfo=datetime.timezone.utc),
  3281. 'GPL',
  3282. 'https://qubes-os.org/keyword-url',
  3283. 'summary',
  3284. 'Qubes template\n for fedora-31\n'
  3285. ),
  3286. qubesadmin.tools.qvm_template.Template(
  3287. 'test-vm-exact',
  3288. '2',
  3289. '4.1',
  3290. '2020',
  3291. 'qubes-templates-itl',
  3292. 1048576,
  3293. datetime.datetime(2020, 9, 1, 14, 30,
  3294. tzinfo=datetime.timezone.utc),
  3295. 'GPL',
  3296. 'https://qubes-os.org',
  3297. 'test-vm Summary',
  3298. 'Qubes template\n for fedora-31\n'
  3299. ),
  3300. qubesadmin.tools.qvm_template.Template(
  3301. 'test-vm-exac2',
  3302. '2',
  3303. '4.1',
  3304. '2020',
  3305. 'qubes-templates-itl',
  3306. 1048576,
  3307. datetime.datetime(2020, 9, 1, 14, 30,
  3308. tzinfo=datetime.timezone.utc),
  3309. 'GPL',
  3310. 'test-vm-exac2',
  3311. 'test-vm Summary',
  3312. 'Qubes template\n for fedora-31\n'
  3313. ),
  3314. qubesadmin.tools.qvm_template.Template(
  3315. 'test-vm-2',
  3316. '2',
  3317. '4.1',
  3318. '2020',
  3319. 'qubes-templates-itl',
  3320. 1048576,
  3321. datetime.datetime(2020, 9, 1, 14, 30,
  3322. tzinfo=datetime.timezone.utc),
  3323. 'GPL',
  3324. 'https://qubes-os.org',
  3325. 'test-vm Summary',
  3326. 'keyword-desc'
  3327. ),
  3328. qubesadmin.tools.qvm_template.Template(
  3329. 'should-not-match',
  3330. '2',
  3331. '4.1',
  3332. '2020',
  3333. 'qubes-templates-itl',
  3334. 1048576,
  3335. datetime.datetime(2020, 9, 1, 14, 30,
  3336. tzinfo=datetime.timezone.utc),
  3337. 'GPL',
  3338. 'https://qubes-os.org',
  3339. 'Summary',
  3340. 'Description'
  3341. )
  3342. ]
  3343. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3344. b'0\x00'
  3345. args = argparse.Namespace(
  3346. all=True,
  3347. templates=['test-vm-exact', 'test-vm-exac2',
  3348. 'keyword-url', 'keyword-desc']
  3349. )
  3350. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3351. qubesadmin.tools.qvm_template.search(args, self.app)
  3352. self.assertEqual(mock_out.getvalue(),
  3353. '''=== Name & URL Exactly Matched: test-vm-exac2 ===
  3354. test-vm-exac2 : test-vm Summary
  3355. === Name Exactly Matched: test-vm-exact ===
  3356. test-vm-exact : test-vm Summary
  3357. === Description Exactly Matched: keyword-desc ===
  3358. test-vm-2 : test-vm Summary
  3359. === URL Matched: keyword-url ===
  3360. test-vm : summary
  3361. ''')
  3362. self.assertEqual(mock_query.mock_calls, [
  3363. mock.call(args, self.app)
  3364. ])
  3365. self.assertAllCalled()
  3366. @mock.patch('qubesadmin.tools.qvm_template.qrexec_repoquery')
  3367. def test_176_search_wildcard_success(self, mock_query):
  3368. mock_query.return_value = [
  3369. qubesadmin.tools.qvm_template.Template(
  3370. 'test-vm',
  3371. '2',
  3372. '4.1',
  3373. '2020',
  3374. 'qubes-templates-itl',
  3375. 1048576,
  3376. datetime.datetime(2020, 9, 1, 14, 30,
  3377. tzinfo=datetime.timezone.utc),
  3378. 'GPL',
  3379. 'https://qubes-os.org',
  3380. 'Qubes template for fedora-31',
  3381. 'Qubes template\n for fedora-31\n'
  3382. ),
  3383. qubesadmin.tools.qvm_template.Template(
  3384. 'should-not-match-3',
  3385. '0',
  3386. '4.1',
  3387. '2020',
  3388. 'qubes-templates-itl',
  3389. 1048576,
  3390. datetime.datetime(2020, 9, 1, 14, 30,
  3391. tzinfo=datetime.timezone.utc),
  3392. 'GPL',
  3393. 'https://qubes-os.org/test-vm',
  3394. 'Qubes template for fedora-31',
  3395. 'test-vm Qubes template\n for fedora-31\n'
  3396. )
  3397. ]
  3398. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3399. b'0\x00'
  3400. args = argparse.Namespace(
  3401. all=False,
  3402. templates=['t?st-vm']
  3403. )
  3404. with mock.patch('sys.stdout', new=io.StringIO()) as mock_out:
  3405. qubesadmin.tools.qvm_template.search(args, self.app)
  3406. self.assertEqual(mock_out.getvalue(),
  3407. '''=== Name Matched: t?st-vm ===
  3408. test-vm : Qubes template for fedora-31
  3409. ''')
  3410. self.assertEqual(mock_query.mock_calls, [
  3411. mock.call(args, self.app)
  3412. ])
  3413. self.assertAllCalled()
  3414. def _mock_qrexec_download(self, args, app, spec, path,
  3415. dlsize=None, refresh=False):
  3416. self.assertFalse(os.path.exists(path),
  3417. '{} should not exist before'.format(path))
  3418. # just create an empty file
  3419. with open(path, 'wb') as f:
  3420. if f is not None:
  3421. f.truncate(dlsize)
  3422. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3423. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3424. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3425. def test_180_download_success(self, mock_qrexec, mock_dllist,
  3426. mock_verify_rpm):
  3427. mock_qrexec.side_effect = self._mock_qrexec_download
  3428. with tempfile.TemporaryDirectory() as dir:
  3429. args = argparse.Namespace(
  3430. repo_files=[],
  3431. keyring='/tmp/keyring.gpg',
  3432. releasever='4.1',
  3433. nogpgcheck=False,
  3434. retries=1
  3435. )
  3436. qubesadmin.tools.qvm_template.download(args, self.app, dir, {
  3437. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3438. ('1', '2', '3'), 'qubes-templates-itl', 1048576),
  3439. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  3440. ('0', '1', '2'),
  3441. 'qubes-templates-itl-testing',
  3442. 2048576)
  3443. })
  3444. self.assertEqual(mock_qrexec.mock_calls, [
  3445. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3446. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3447. 1048576),
  3448. mock.call(args, self.app, 'qubes-template-fedora-32-0:1-2',
  3449. re_str(dir + '/.*/qubes-template-fedora-32-0:1-2.rpm.UNTRUSTED'),
  3450. 2048576)
  3451. ])
  3452. self.assertEqual(mock_dllist.mock_calls, [])
  3453. self.assertEqual(mock_verify_rpm.mock_calls, [
  3454. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3455. '/tmp/keyring.gpg', template_name='fedora-31'),
  3456. mock.call(re_str(dir + '/.*/qubes-template-fedora-32-0:1-2.rpm.UNTRUSTED'),
  3457. '/tmp/keyring.gpg', template_name='fedora-32'),
  3458. ])
  3459. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3460. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3461. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3462. def test_181_download_success_nosuffix(self, mock_qrexec, mock_dllist,
  3463. mock_verify_rpm):
  3464. mock_qrexec.side_effect = self._mock_qrexec_download
  3465. with tempfile.TemporaryDirectory() as dir:
  3466. args = argparse.Namespace(
  3467. retries=1,
  3468. repo_files=[],
  3469. keyring='/tmp/keyring.gpg',
  3470. releasever='4.1',
  3471. nogpgcheck=False,
  3472. downloaddir=dir
  3473. )
  3474. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3475. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3476. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3477. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3478. })
  3479. self.assertEqual(mock_qrexec.mock_calls, [
  3480. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3481. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3482. 1048576)
  3483. ])
  3484. self.assertEqual(mock_dllist.mock_calls, [])
  3485. self.assertEqual(mock_verify_rpm.mock_calls, [
  3486. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3487. '/tmp/keyring.gpg', template_name='fedora-31'),
  3488. ])
  3489. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3490. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3491. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3492. def test_182_download_success_getdllist(self, mock_qrexec, mock_dllist,
  3493. mock_verify_rpm):
  3494. mock_qrexec.side_effect = self._mock_qrexec_download
  3495. mock_dllist.return_value = {
  3496. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3497. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3498. }
  3499. with tempfile.TemporaryDirectory() as dir:
  3500. args = argparse.Namespace(
  3501. retries=1,
  3502. repo_files=[],
  3503. keyring='/tmp/keyring.gpg',
  3504. releasever='4.1',
  3505. nogpgcheck=False,
  3506. )
  3507. qubesadmin.tools.qvm_template.download(args, self.app,
  3508. dir, None,
  3509. qubesadmin.tools.qvm_template.VersionSelector.LATEST_LOWER)
  3510. self.assertEqual(mock_qrexec.mock_calls, [
  3511. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3512. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3513. 1048576)
  3514. ])
  3515. self.assertEqual(mock_dllist.mock_calls, [
  3516. mock.call(args, self.app,
  3517. version_selector=\
  3518. qubesadmin.tools.qvm_template.\
  3519. VersionSelector.LATEST_LOWER)
  3520. ])
  3521. self.assertEqual(mock_verify_rpm.mock_calls, [
  3522. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3523. '/tmp/keyring.gpg', template_name='fedora-31'),
  3524. ])
  3525. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3526. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3527. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3528. def test_183_download_success_downloaddir(self, mock_qrexec, mock_dllist,
  3529. mock_verify_rpm):
  3530. mock_qrexec.side_effect = self._mock_qrexec_download
  3531. with tempfile.TemporaryDirectory() as dir:
  3532. args = argparse.Namespace(
  3533. retries=1,
  3534. repo_files=[],
  3535. keyring='/tmp/keyring.gpg',
  3536. releasever='4.1',
  3537. nogpgcheck=False,
  3538. downloaddir=dir
  3539. )
  3540. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3541. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3542. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3543. })
  3544. self.assertEqual(mock_qrexec.mock_calls, [
  3545. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3546. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3547. 1048576)
  3548. ])
  3549. self.assertEqual(mock_dllist.mock_calls, [])
  3550. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3551. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3552. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3553. def test_184_download_success_exists(self, mock_qrexec, mock_dllist,
  3554. mock_verify_rpm):
  3555. mock_qrexec.side_effect = self._mock_qrexec_download
  3556. with tempfile.TemporaryDirectory() as dir:
  3557. with open(os.path.join(
  3558. dir, 'qubes-template-fedora-31-1:2-3.rpm'),
  3559. 'w') as _:
  3560. pass
  3561. args = argparse.Namespace(
  3562. retries=1,
  3563. repo_files=[],
  3564. keyring='/tmp/keyring.gpg',
  3565. releasever='4.1',
  3566. nogpgcheck=False,
  3567. downloaddir=dir
  3568. )
  3569. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3570. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3571. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3572. ('1', '2', '3'), 'qubes-templates-itl', 1048576),
  3573. 'fedora-32': qubesadmin.tools.qvm_template.DlEntry(
  3574. ('0', '1', '2'),
  3575. 'qubes-templates-itl-testing',
  3576. 2048576)
  3577. })
  3578. self.assertTrue('already exists, skipping'
  3579. in mock_err.getvalue())
  3580. self.assertEqual(mock_qrexec.mock_calls, [
  3581. mock.call(args, self.app, 'qubes-template-fedora-32-0:1-2',
  3582. re_str(dir + '/.*/qubes-template-fedora-32-0:1-2.rpm.UNTRUSTED'),
  3583. 2048576)
  3584. ])
  3585. self.assertEqual(mock_dllist.mock_calls, [])
  3586. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3587. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3588. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3589. def test_185_download_success_existsmove(self, mock_qrexec, mock_dllist,
  3590. mock_verify_rpm):
  3591. mock_qrexec.side_effect = self._mock_qrexec_download
  3592. with tempfile.TemporaryDirectory() as dir:
  3593. with open(os.path.join(
  3594. dir, 'qubes-template-fedora-31-1:2-3.rpm'),
  3595. 'w') as _:
  3596. pass
  3597. args = argparse.Namespace(
  3598. retries=1,
  3599. repo_files=[],
  3600. keyring='/tmp/keyring.gpg',
  3601. releasever='4.1',
  3602. nogpgcheck=False,
  3603. downloaddir=dir
  3604. )
  3605. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3606. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3607. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3608. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3609. })
  3610. self.assertTrue('already exists, skipping'
  3611. in mock_err.getvalue())
  3612. self.assertEqual(mock_qrexec.mock_calls, [])
  3613. self.assertEqual(mock_dllist.mock_calls, [])
  3614. self.assertTrue(os.path.exists(
  3615. dir + '/qubes-template-fedora-31-1:2-3.rpm'))
  3616. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3617. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3618. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3619. def test_186_download_success_existsnosuffix(self, mock_qrexec, mock_dllist,
  3620. mock_verify_rpm):
  3621. mock_qrexec.side_effect = self._mock_qrexec_download
  3622. with tempfile.TemporaryDirectory() as dir:
  3623. with open(os.path.join(
  3624. dir, 'qubes-template-fedora-31-1:2-3.rpm'),
  3625. 'w') as _:
  3626. pass
  3627. args = argparse.Namespace(
  3628. retries=1,
  3629. repo_files=[],
  3630. keyring='/tmp/keyring.gpg',
  3631. releasever='4.1',
  3632. nogpgcheck=False,
  3633. downloaddir=dir
  3634. )
  3635. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  3636. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3637. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3638. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3639. })
  3640. self.assertTrue('already exists, skipping'
  3641. in mock_err.getvalue())
  3642. self.assertEqual(mock_qrexec.mock_calls, [])
  3643. self.assertEqual(mock_dllist.mock_calls, [])
  3644. self.assertTrue(os.path.exists(
  3645. dir + '/qubes-template-fedora-31-1:2-3.rpm'))
  3646. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3647. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3648. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3649. def test_187_download_success_retry(self, mock_qrexec, mock_dllist,
  3650. mock_verify_rpm):
  3651. counter = 0
  3652. def f(*args, **kwargs):
  3653. nonlocal counter
  3654. counter += 1
  3655. if counter == 1:
  3656. raise ConnectionError
  3657. self._mock_qrexec_download(*args, **kwargs)
  3658. mock_qrexec.side_effect = f
  3659. with tempfile.TemporaryDirectory() as dir:
  3660. args = argparse.Namespace(
  3661. retries=2,
  3662. repo_files=[],
  3663. keyring='/tmp/keyring.gpg',
  3664. releasever='4.1',
  3665. nogpgcheck=False,
  3666. downloaddir=dir
  3667. )
  3668. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3669. mock.patch('os.remove') as mock_rm:
  3670. qubesadmin.tools.qvm_template.download(args, self.app, None, {
  3671. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3672. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3673. })
  3674. self.assertTrue('retrying...' in mock_err.getvalue())
  3675. self.assertEqual(mock_rm.mock_calls, [
  3676. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'))
  3677. ])
  3678. self.assertEqual(mock_qrexec.mock_calls, [
  3679. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3680. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3681. 1048576),
  3682. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3683. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3684. 1048576)
  3685. ])
  3686. self.assertEqual(mock_dllist.mock_calls, [])
  3687. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3688. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3689. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3690. def test_188_download_fail_retry(self, mock_qrexec, mock_dllist,
  3691. mock_verify_rpm):
  3692. mock_qrexec.side_effect = self._mock_qrexec_download
  3693. counter = 0
  3694. def f(*args, **kwargs):
  3695. nonlocal counter
  3696. counter += 1
  3697. if counter <= 3:
  3698. raise ConnectionError
  3699. self._mock_qrexec_download(*args, **kwargs)
  3700. mock_qrexec.side_effect = f
  3701. with tempfile.TemporaryDirectory() as dir:
  3702. args = argparse.Namespace(
  3703. retries=3,
  3704. repo_files=[],
  3705. keyring='/tmp/keyring.gpg',
  3706. releasever='4.1',
  3707. nogpgcheck=False,
  3708. downloaddir=dir
  3709. )
  3710. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3711. mock.patch('os.remove') as mock_rm:
  3712. with self.assertRaises(SystemExit):
  3713. qubesadmin.tools.qvm_template.download(
  3714. args, self.app, None, {
  3715. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3716. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3717. })
  3718. self.assertEqual(mock_err.getvalue().count('retrying...'), 2)
  3719. self.assertTrue('download failed' in mock_err.getvalue())
  3720. self.assertEqual(mock_rm.mock_calls, [
  3721. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED')),
  3722. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED')),
  3723. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'))
  3724. ])
  3725. self.assertEqual(mock_qrexec.mock_calls, [
  3726. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3727. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3728. 1048576),
  3729. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3730. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3731. 1048576),
  3732. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3733. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3734. 1048576)
  3735. ])
  3736. self.assertEqual(mock_dllist.mock_calls, [])
  3737. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3738. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3739. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3740. def test_189_download_fail_interrupt(self, mock_qrexec, mock_dllist,
  3741. mock_verify_rpm):
  3742. def f(*args):
  3743. raise RuntimeError
  3744. mock_qrexec.side_effect = f
  3745. with tempfile.TemporaryDirectory() as dir:
  3746. args = argparse.Namespace(
  3747. retries=3,
  3748. repo_files=[],
  3749. keyring='/tmp/keyring.gpg',
  3750. releasever='4.1',
  3751. nogpgcheck=False,
  3752. downloaddir=dir
  3753. )
  3754. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err, \
  3755. mock.patch('os.remove') as mock_rm:
  3756. with self.assertRaises(RuntimeError):
  3757. qubesadmin.tools.qvm_template.download(
  3758. args, self.app, None, {
  3759. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3760. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3761. })
  3762. self.assertEqual(mock_qrexec.mock_calls, [
  3763. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3764. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm'),
  3765. 1048576)
  3766. ])
  3767. self.assertEqual(mock_dllist.mock_calls, [])
  3768. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3769. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3770. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3771. def test_190_download_fail_verify(self, mock_qrexec, mock_dllist,
  3772. mock_verify_rpm):
  3773. mock_qrexec.side_effect = self._mock_qrexec_download
  3774. mock_verify_rpm.side_effect = \
  3775. qubesadmin.tools.qvm_template.SignatureVerificationError
  3776. with tempfile.TemporaryDirectory() as dir:
  3777. args = argparse.Namespace(
  3778. retries=3,
  3779. repo_files=[],
  3780. keyring='/tmp/keyring.gpg',
  3781. releasever='4.1',
  3782. nogpgcheck=True, # make sure it gets ignored
  3783. downloaddir=dir
  3784. )
  3785. with self.assertRaises(qubesadmin.tools.qvm_template.SignatureVerificationError):
  3786. qubesadmin.tools.qvm_template.download(
  3787. args, self.app, None, {
  3788. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3789. ('1', '2', '3'), 'qubes-templates-itl', 1048576)
  3790. })
  3791. self.assertEqual(mock_qrexec.mock_calls, [
  3792. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3793. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm'),
  3794. 1048576)
  3795. ])
  3796. self.assertEqual(mock_dllist.mock_calls, [])
  3797. self.assertEqual(os.listdir(dir), [])
  3798. self.assertEqual(mock_verify_rpm.mock_calls, [
  3799. mock.call(re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3800. '/tmp/keyring.gpg', template_name='fedora-31'),
  3801. ])
  3802. def _mock_qrexec_download_short(self, args, app, spec, path,
  3803. dlsize=None, refresh=False):
  3804. self.assertFalse(os.path.exists(path),
  3805. '{} should not exist before'.format(path))
  3806. # just create an empty file
  3807. with open(path, 'wb') as f:
  3808. if f is not None:
  3809. f.truncate(dlsize // 2)
  3810. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3811. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3812. @mock.patch('qubesadmin.tools.qvm_template.qrexec_download')
  3813. def test_191_download_fail_short(self, mock_qrexec, mock_dllist,
  3814. mock_verify_rpm):
  3815. mock_qrexec.side_effect = self._mock_qrexec_download_short
  3816. with tempfile.TemporaryDirectory() as dir, \
  3817. self.assertRaises(SystemExit):
  3818. args = argparse.Namespace(
  3819. repo_files=[],
  3820. keyring='/tmp/keyring.gpg',
  3821. releasever='4.1',
  3822. nogpgcheck=False,
  3823. retries=1
  3824. )
  3825. qubesadmin.tools.qvm_template.download(args, self.app, dir, {
  3826. 'fedora-31': qubesadmin.tools.qvm_template.DlEntry(
  3827. ('1', '2', '3'), 'qubes-templates-itl', 1048576),
  3828. })
  3829. self.assertEqual(mock_qrexec.mock_calls, [
  3830. mock.call(args, self.app, 'qubes-template-fedora-31-1:2-3',
  3831. re_str(dir + '/.*/qubes-template-fedora-31-1:2-3.rpm.UNTRUSTED'),
  3832. 1048576),
  3833. ])
  3834. self.assertEqual(mock_dllist.mock_calls, [])
  3835. self.assertEqual(mock_verify_rpm.mock_calls, [])
  3836. @mock.patch('os.rename')
  3837. @mock.patch('os.makedirs')
  3838. @mock.patch('subprocess.check_call')
  3839. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  3840. @mock.patch('qubesadmin.tools.qvm_template.extract_rpm')
  3841. @mock.patch('qubesadmin.tools.qvm_template.download')
  3842. @mock.patch('qubesadmin.tools.qvm_template.get_dl_list')
  3843. @mock.patch('qubesadmin.tools.qvm_template.verify_rpm')
  3844. def test_200_reinstall_success(
  3845. self,
  3846. mock_verify,
  3847. mock_dl_list,
  3848. mock_dl,
  3849. mock_extract,
  3850. mock_confirm,
  3851. mock_call,
  3852. mock_mkdirs,
  3853. mock_rename):
  3854. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  3855. b'0\x00test-vm class=TemplateVM state=Halted\n'
  3856. build_time = '2020-09-01 14:30:00' # 1598970600
  3857. install_time = '2020-09-01 15:30:00'
  3858. for key, val in [
  3859. ('name', 'test-vm'),
  3860. ('epoch', '2'),
  3861. ('version', '4.1'),
  3862. ('release', '2020')]:
  3863. self.app.expected_calls[(
  3864. 'test-vm',
  3865. 'admin.vm.feature.Get',
  3866. f'template-{key}',
  3867. None)] = b'0\0' + val.encode()
  3868. for key, val in [
  3869. ('name', 'test-vm'),
  3870. ('epoch', '2'),
  3871. ('version', '4.1'),
  3872. ('release', '2020'),
  3873. ('reponame', 'qubes-templates-itl'),
  3874. ('buildtime', build_time),
  3875. ('installtime', install_time),
  3876. ('license', 'GPL'),
  3877. ('url', 'https://qubes-os.org'),
  3878. ('summary', 'Summary'),
  3879. ('description', 'Desc|desc')]:
  3880. self.app.expected_calls[(
  3881. 'test-vm',
  3882. 'admin.vm.feature.Set',
  3883. f'template-{key}',
  3884. val.encode())] = b'0\0'
  3885. rpm_hdr = {
  3886. rpm.RPMTAG_NAME : 'qubes-template-test-vm',
  3887. rpm.RPMTAG_BUILDTIME : 1598970600,
  3888. rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
  3889. rpm.RPMTAG_EPOCHNUM : 2,
  3890. rpm.RPMTAG_LICENSE : 'GPL',
  3891. rpm.RPMTAG_RELEASE : '2020',
  3892. rpm.RPMTAG_SUMMARY : 'Summary',
  3893. rpm.RPMTAG_URL : 'https://qubes-os.org',
  3894. rpm.RPMTAG_VERSION : '4.1'
  3895. }
  3896. mock_dl.return_value = {'test-vm': rpm_hdr}
  3897. dl_list = {
  3898. 'test-vm': qubesadmin.tools.qvm_template.DlEntry(
  3899. ('1', '4.1', '20200101'), 'qubes-templates-itl', 1048576)
  3900. }
  3901. mock_dl_list.return_value = dl_list
  3902. mock_call.side_effect = self.add_new_vm_side_effect
  3903. mock_time = mock.Mock(wraps=datetime.datetime)
  3904. mock_time.now.return_value = \
  3905. datetime.datetime(2020, 9, 1, 15, 30, tzinfo=datetime.timezone.utc)
  3906. selector = qubesadmin.tools.qvm_template.VersionSelector.REINSTALL
  3907. with mock.patch('qubesadmin.tools.qvm_template.LOCK_FILE', '/tmp/test.lock'), \
  3908. mock.patch('datetime.datetime', new=mock_time), \
  3909. mock.patch('tempfile.TemporaryDirectory') as mock_tmpdir:
  3910. args = argparse.Namespace(
  3911. templates=['test-vm'],
  3912. keyring='/tmp/keyring.gpg',
  3913. nogpgcheck=False,
  3914. cachedir='/var/cache/qvm-template',
  3915. repo_files=[],
  3916. releasever='4.1',
  3917. yes=False,
  3918. keep_cache=True,
  3919. allow_pv=False,
  3920. pool=None
  3921. )
  3922. mock_tmpdir.return_value.__enter__.return_value = \
  3923. '/var/tmp/qvm-template-tmpdir'
  3924. qubesadmin.tools.qvm_template.install(args, self.app,
  3925. version_selector=selector,
  3926. override_existing=True)
  3927. # Attempt to get download list
  3928. self.assertEqual(mock_dl_list.mock_calls, [
  3929. mock.call(args, self.app, version_selector=selector)
  3930. ])
  3931. mock_dl.assert_called_with(args, self.app,
  3932. dl_list=dl_list, version_selector=selector)
  3933. # already verified by download()
  3934. self.assertEqual(mock_verify.mock_calls, [])
  3935. # Package is extracted
  3936. mock_extract.assert_called_with('test-vm',
  3937. '/var/cache/qvm-template/qubes-template-test-vm-1:4.1-20200101.rpm',
  3938. '/var/tmp/qvm-template-tmpdir')
  3939. # Expect override confirmation
  3940. self.assertEqual(mock_confirm.mock_calls,
  3941. [mock.call(re_str(r'.*override changes.*:'), ['test-vm'])])
  3942. # qvm-template-postprocess is called
  3943. self.assertEqual(mock_call.mock_calls, [
  3944. mock.call([
  3945. 'qvm-template-postprocess',
  3946. '--really',
  3947. '--no-installed-by-rpm',
  3948. 'post-install',
  3949. 'test-vm',
  3950. '/var/tmp/qvm-template-tmpdir'
  3951. '/var/lib/qubes/vm-templates/test-vm'
  3952. ])
  3953. ])
  3954. # Cache directory created
  3955. self.assertEqual(mock_mkdirs.mock_calls, [
  3956. mock.call(args.cachedir, exist_ok=True)
  3957. ])
  3958. self.assertAllCalled()
  3959. @mock.patch('qubesadmin.tools.qvm_remove.main')
  3960. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  3961. def test_210_remove_success(self, mock_confirm, mock_remove):
  3962. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  3963. b'0\x00vm1 class=TemplateVM state=Halted\n'
  3964. b'vm2 class=TemplateVM state=Halted\n'
  3965. )
  3966. args = argparse.Namespace(
  3967. templates=['vm1', 'vm2'],
  3968. yes=False
  3969. )
  3970. qubesadmin.tools.qvm_template.remove(args, self.app)
  3971. self.assertEqual(mock_confirm.mock_calls,
  3972. [mock.call(re_str(r'.*completely remove.*'), ['vm1', 'vm2'])])
  3973. self.assertEqual(mock_remove.mock_calls, [
  3974. mock.call(['--force', '--', 'vm1', 'vm2'], self.app)
  3975. ])
  3976. self.assertAllCalled()
  3977. @mock.patch('qubesadmin.tools.qvm_kill.main')
  3978. @mock.patch('qubesadmin.tools.qvm_remove.main')
  3979. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  3980. def test_211_remove_purge_disassoc_success(
  3981. self,
  3982. mock_confirm,
  3983. mock_remove,
  3984. mock_kill):
  3985. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  3986. b'0\x00vm1 class=TemplateVM state=Halted\n'
  3987. b'vm2 class=TemplateVM state=Halted\n'
  3988. b'vm3 class=TemplateVM state=Halted\n'
  3989. b'vm4 class=TemplateVM state=Halted\n'
  3990. b'dummy class=TemplateVM state=Halted\n'
  3991. b'dummy-1 class=TemplateVM state=Halted\n'
  3992. )
  3993. self.app.expected_calls[
  3994. ('dummy', 'admin.vm.feature.Get', 'template-dummy', None)] = \
  3995. b'0\x000'
  3996. self.app.expected_calls[
  3997. ('dummy-1', 'admin.vm.feature.Get',
  3998. 'template-dummy', None)] = \
  3999. b'0\x001'
  4000. self.app.expected_calls[
  4001. ('vm2', 'admin.vm.property.Set',
  4002. 'default_template', b'dummy-1')] = \
  4003. b'0\x00'
  4004. self.app.expected_calls[
  4005. ('vm2', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4006. b'0\x00'
  4007. self.app.expected_calls[
  4008. ('vm3', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
  4009. b'0\x00'
  4010. self.app.expected_calls[
  4011. ('vm3', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4012. b'0\x00'
  4013. self.app.expected_calls[
  4014. ('vm4', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
  4015. b'0\x00'
  4016. self.app.expected_calls[
  4017. ('vm4', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4018. b'0\x00'
  4019. self.app.expected_calls[
  4020. ('dom0', 'admin.property.Set', 'updatevm', b'')] = \
  4021. b'0\x00'
  4022. args = argparse.Namespace(
  4023. templates=['vm1'],
  4024. yes=False
  4025. )
  4026. def deps(app, vm):
  4027. if vm == 'vm1':
  4028. return [(self.app.domains['vm2'], 'default_template'),
  4029. (self.app.domains['vm3'], 'netvm')]
  4030. if vm == 'vm2' or vm == 'vm3':
  4031. return [(self.app.domains['vm4'], 'netvm')]
  4032. if vm == 'vm4':
  4033. return [(None, 'updatevm')]
  4034. return []
  4035. with mock.patch('qubesadmin.utils.vm_dependencies') as mock_deps:
  4036. mock_deps.side_effect = deps
  4037. qubesadmin.tools.qvm_template.remove(args, self.app, purge=True)
  4038. # Once for purge (dependency detection) and
  4039. # one for disassoc (actually disassociating the dependencies
  4040. self.assertEqual(mock_deps.mock_calls, [
  4041. mock.call(self.app, self.app.domains['vm1']),
  4042. mock.call(self.app, self.app.domains['vm2']),
  4043. mock.call(self.app, self.app.domains['vm3']),
  4044. mock.call(self.app, self.app.domains['vm4']),
  4045. mock.call(self.app, self.app.domains['vm1']),
  4046. mock.call(self.app, self.app.domains['vm2']),
  4047. mock.call(self.app, self.app.domains['vm3']),
  4048. mock.call(self.app, self.app.domains['vm4'])
  4049. ])
  4050. self.assertEqual(mock_confirm.mock_calls, [
  4051. mock.call(re_str(r'.*completely remove.*'),
  4052. ['vm1', 'vm2', 'vm3', 'vm4']),
  4053. mock.call(re_str(r'.*completely remove.*'),
  4054. ['vm1', 'vm2', 'vm3', 'vm4']),
  4055. mock.call(re_str(r'.*completely remove.*'),
  4056. ['vm1', 'vm2', 'vm3', 'vm4'])
  4057. ])
  4058. self.assertEqual(mock_remove.mock_calls, [
  4059. mock.call(['--force', '--', 'vm1', 'vm2', 'vm3', 'vm4', 'dummy-1'],
  4060. self.app)
  4061. ])
  4062. self.assertEqual(mock_kill.mock_calls, [
  4063. mock.call(['--', 'vm1', 'vm2', 'vm3', 'vm4', 'dummy-1'], self.app)
  4064. ])
  4065. self.assertAllCalled()
  4066. @mock.patch('qubesadmin.tools.qvm_kill.main')
  4067. @mock.patch('qubesadmin.tools.qvm_remove.main')
  4068. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  4069. def test_212_remove_disassoc_success(
  4070. self,
  4071. mock_confirm,
  4072. mock_remove,
  4073. mock_kill):
  4074. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  4075. b'0\x00vm1 class=TemplateVM state=Halted\n'
  4076. b'vm2 class=TemplateVM state=Halted\n'
  4077. b'vm3 class=TemplateVM state=Halted\n'
  4078. b'vm4 class=TemplateVM state=Halted\n'
  4079. b'dummy class=TemplateVM state=Halted\n'
  4080. b'dummy-1 class=TemplateVM state=Halted\n'
  4081. )
  4082. self.app.expected_calls[
  4083. ('dummy', 'admin.vm.feature.Get', 'template-dummy', None)] = \
  4084. b'0\x000'
  4085. self.app.expected_calls[
  4086. ('dummy-1', 'admin.vm.feature.Get',
  4087. 'template-dummy', None)] = \
  4088. b'0\x001'
  4089. self.app.expected_calls[
  4090. ('vm2', 'admin.vm.property.Set',
  4091. 'default_template', b'dummy-1')] = \
  4092. b'0\x00'
  4093. self.app.expected_calls[
  4094. ('vm2', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4095. b'0\x00'
  4096. self.app.expected_calls[
  4097. ('vm3', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
  4098. b'0\x00'
  4099. self.app.expected_calls[
  4100. ('vm3', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4101. b'0\x00'
  4102. self.app.expected_calls[
  4103. ('vm4', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
  4104. b'0\x00'
  4105. self.app.expected_calls[
  4106. ('vm4', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
  4107. b'0\x00'
  4108. self.app.expected_calls[
  4109. ('dom0', 'admin.property.Set', 'updatevm', b'')] = \
  4110. b'0\x00'
  4111. args = argparse.Namespace(
  4112. templates=['vm1', 'vm2', 'vm3', 'vm4'],
  4113. yes=False
  4114. )
  4115. def deps(app, vm):
  4116. if vm == 'vm1':
  4117. return [(self.app.domains['vm2'], 'default_template'),
  4118. (self.app.domains['vm3'], 'netvm')]
  4119. if vm == 'vm2' or vm == 'vm3':
  4120. return [(self.app.domains['vm4'], 'netvm')]
  4121. if vm == 'vm4':
  4122. return [(None, 'updatevm')]
  4123. return []
  4124. with mock.patch('qubesadmin.utils.vm_dependencies') as mock_deps:
  4125. mock_deps.side_effect = deps
  4126. qubesadmin.tools.qvm_template.remove(args, self.app, disassoc=True)
  4127. self.assertEqual(mock_deps.mock_calls, [
  4128. mock.call(self.app, self.app.domains['vm1']),
  4129. mock.call(self.app, self.app.domains['vm2']),
  4130. mock.call(self.app, self.app.domains['vm3']),
  4131. mock.call(self.app, self.app.domains['vm4'])
  4132. ])
  4133. self.assertEqual(mock_confirm.mock_calls, [
  4134. mock.call(re_str(r'.*completely remove.*'),
  4135. ['vm1', 'vm2', 'vm3', 'vm4'])
  4136. ])
  4137. self.assertEqual(mock_remove.mock_calls, [
  4138. mock.call(['--force', '--', 'vm1', 'vm2', 'vm3', 'vm4'],
  4139. self.app)
  4140. ])
  4141. self.assertEqual(mock_kill.mock_calls, [
  4142. mock.call(['--', 'vm1', 'vm2', 'vm3', 'vm4'], self.app)
  4143. ])
  4144. self.assertAllCalled()
  4145. def test_213_remove_fail_nodomain(self):
  4146. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  4147. b'0\x00vm1 class=TemplateVM state=Halted\n'
  4148. args = argparse.Namespace(
  4149. templates=['vm0'],
  4150. yes=False
  4151. )
  4152. with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
  4153. with self.assertRaises(SystemExit):
  4154. qubesadmin.tools.qvm_template.remove(args, self.app)
  4155. self.assertTrue('no such domain:' in mock_err.getvalue())
  4156. self.assertAllCalled()
  4157. @mock.patch('qubesadmin.tools.qvm_kill.main')
  4158. @mock.patch('qubesadmin.tools.qvm_remove.main')
  4159. @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
  4160. def test_214_remove_disassoc_success_newdummy(
  4161. self,
  4162. mock_confirm,
  4163. mock_remove,
  4164. mock_kill):
  4165. def append_new_vm_side_effect(*args, **kwargs):
  4166. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] += \
  4167. b'dummy-1 class=TemplateVM state=Halted\n'
  4168. self.app.domains.clear_cache()
  4169. return self.app.domains['dummy-1']
  4170. self.app.add_new_vm = mock.Mock(side_effect=append_new_vm_side_effect)
  4171. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
  4172. b'0\x00vm1 class=TemplateVM state=Halted\n'
  4173. b'vm2 class=TemplateVM state=Halted\n'
  4174. b'dummy class=TemplateVM state=Halted\n'
  4175. )
  4176. self.app.expected_calls[
  4177. ('dummy', 'admin.vm.feature.Get', 'template-dummy', None)] = \
  4178. b'0\x000'
  4179. self.app.expected_calls[
  4180. ('dummy-1', 'admin.vm.feature.Set',
  4181. 'template-dummy', b'1')] = \
  4182. b'0\x00'
  4183. self.app.expected_calls[
  4184. ('vm2', 'admin.vm.property.Set',
  4185. 'default_template', b'dummy-1')] = \
  4186. b'0\x00'
  4187. self.app.expected_calls[
  4188. ('vm2', 'admin.vm.property.Set',
  4189. 'template', b'dummy-1')] = \
  4190. b'0\x00'
  4191. args = argparse.Namespace(
  4192. templates=['vm1'],
  4193. yes=False
  4194. )
  4195. def deps(app, vm):
  4196. if vm == 'vm1':
  4197. return [(self.app.domains['vm2'], 'default_template')]
  4198. return []
  4199. with mock.patch('qubesadmin.utils.vm_dependencies') as mock_deps:
  4200. mock_deps.side_effect = deps
  4201. qubesadmin.tools.qvm_template.remove(args, self.app, disassoc=True)
  4202. self.assertEqual(mock_deps.mock_calls, [
  4203. mock.call(self.app, self.app.domains['vm1'])
  4204. ])
  4205. self.assertEqual(mock_confirm.mock_calls, [
  4206. mock.call(re_str(r'.*completely remove.*'), ['vm1'])
  4207. ])
  4208. self.assertEqual(mock_remove.mock_calls, [
  4209. mock.call(['--force', '--', 'vm1'], self.app)
  4210. ])
  4211. self.assertEqual(mock_kill.mock_calls, [
  4212. mock.call(['--', 'vm1'], self.app)
  4213. ])
  4214. self.assertAllCalled()
  4215. def test_220_get_keys_for_repos_success(self):
  4216. with tempfile.NamedTemporaryFile() as f:
  4217. f.write(
  4218. b'''[qubes-templates-itl]
  4219. name = Qubes Templates repository
  4220. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl
  4221. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl
  4222. metalink = https://yum.qubes-os.org/r$releasever/templates-itl/repodata/repomd.xml.metalink
  4223. enabled = 1
  4224. fastestmirror = 1
  4225. metadata_expire = 7d
  4226. gpgcheck = 1
  4227. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  4228. [qubes-templates-itl-testing-nokey]
  4229. name = Qubes Templates repository
  4230. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl-testing
  4231. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl-testing
  4232. metalink = https://yum.qubes-os.org/r$releasever/templates-itl-testing/repodata/repomd.xml.metalink
  4233. enabled = 0
  4234. fastestmirror = 1
  4235. gpgcheck = 1
  4236. #gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary
  4237. [qubes-templates-itl-testing]
  4238. name = Qubes Templates repository
  4239. #baseurl = https://yum.qubes-os.org/r$releasever/templates-itl-testing
  4240. #baseurl = http://yum.qubesosfasa4zl44o4tws22di6kepyzfeqv3tg4e3ztknltfxqrymdad.onion/r$releasever/templates-itl-testing
  4241. metalink = https://yum.qubes-os.org/r$releasever/templates-itl-testing/repodata/repomd.xml.metalink
  4242. enabled = 0
  4243. fastestmirror = 1
  4244. gpgcheck = 1
  4245. gpgkey = file:///etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-$releasever-primary-testing
  4246. ''')
  4247. f.flush()
  4248. ret = qubesadmin.tools.qvm_template.get_keys_for_repos(
  4249. [f.name], 'r4.1')
  4250. self.assertEqual(ret, {
  4251. 'qubes-templates-itl':
  4252. '/etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-r4.1-primary',
  4253. 'qubes-templates-itl-testing':
  4254. '/etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-r4.1-primary-testing'
  4255. })
  4256. self.assertAllCalled()