qvm_template.py 214 KB

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