qvm_template.py 142 KB

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