qvm_template.py 160 KB

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