qvm_template.py 216 KB

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