test_file.py 187 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244
  1. """
  2. Tests for the file state
  3. """
  4. import errno
  5. import filecmp
  6. import logging
  7. import os
  8. import pathlib
  9. import re
  10. import shutil
  11. import stat
  12. import sys
  13. import tempfile
  14. import textwrap
  15. import pytest
  16. import salt.serializers.configparser
  17. import salt.serializers.plist
  18. import salt.utils.data
  19. import salt.utils.files
  20. import salt.utils.json
  21. import salt.utils.path
  22. import salt.utils.platform
  23. import salt.utils.stringutils
  24. from salt.ext import six
  25. from salt.ext.six.moves import range
  26. from salt.utils.versions import LooseVersion as _LooseVersion
  27. from tests.support.case import ModuleCase
  28. from tests.support.helpers import (
  29. Webserver,
  30. dedent,
  31. destructiveTest,
  32. skip_if_not_root,
  33. with_system_user_and_group,
  34. with_tempdir,
  35. with_tempfile,
  36. )
  37. from tests.support.mixins import SaltReturnAssertsMixin
  38. from tests.support.runtests import RUNTIME_VARS
  39. from tests.support.unit import skipIf
  40. log = logging.getLogger(__name__)
  41. HAS_PWD = True
  42. try:
  43. import pwd
  44. except ImportError:
  45. HAS_PWD = False
  46. HAS_GRP = True
  47. try:
  48. import grp
  49. except ImportError:
  50. HAS_GRP = False
  51. IS_WINDOWS = salt.utils.platform.is_windows()
  52. BINARY_FILE = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\x05\x04\x04\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"
  53. TEST_SYSTEM_USER = "test_system_user"
  54. TEST_SYSTEM_GROUP = "test_system_group"
  55. def _test_managed_file_mode_keep_helper(testcase, local=False):
  56. """
  57. DRY helper function to run the same test with a local or remote path
  58. """
  59. name = testcase.tmp_dir / "scene33"
  60. testcase.addCleanup(salt.utils.files.safe_rm, str(name))
  61. grail_fs_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene33")
  62. grail = "salt://grail/scene33" if not local else grail_fs_path
  63. # Get the current mode so that we can put the file back the way we
  64. # found it when we're done.
  65. grail_fs_mode = int(testcase.run_function("file.get_mode", [grail_fs_path]), 8)
  66. initial_mode = 0o770
  67. new_mode_1 = 0o600
  68. new_mode_2 = 0o644
  69. # Set the initial mode, so we can be assured that when we set the mode
  70. # to "keep", we're actually changing the permissions of the file to the
  71. # new mode.
  72. ret = testcase.run_state(
  73. "file.managed", name=str(name), mode=oct(initial_mode), source=grail,
  74. )
  75. if IS_WINDOWS:
  76. testcase.assertSaltFalseReturn(ret)
  77. return
  78. testcase.assertSaltTrueReturn(ret)
  79. try:
  80. # Update the mode on the fileserver (pass 1)
  81. os.chmod(grail_fs_path, new_mode_1)
  82. ret = testcase.run_state(
  83. "file.managed", name=str(name), mode="keep", source=grail,
  84. )
  85. testcase.assertSaltTrueReturn(ret)
  86. managed_mode = stat.S_IMODE(name.stat().st_mode)
  87. testcase.assertEqual(oct(managed_mode), oct(new_mode_1))
  88. # Update the mode on the fileserver (pass 2)
  89. # This assures us that if the file in file_roots was originally set
  90. # to the same mode as new_mode_1, we definitely get an updated mode
  91. # this time.
  92. os.chmod(grail_fs_path, new_mode_2)
  93. ret = testcase.run_state(
  94. "file.managed", name=str(name), mode="keep", source=grail,
  95. )
  96. testcase.assertSaltTrueReturn(ret)
  97. managed_mode = stat.S_IMODE(name.stat().st_mode)
  98. testcase.assertEqual(oct(managed_mode), oct(new_mode_2))
  99. finally:
  100. # Set the mode of the file in the file_roots back to what it
  101. # originally was.
  102. os.chmod(grail_fs_path, grail_fs_mode)
  103. @pytest.mark.windows_whitelisted
  104. class FileTest(ModuleCase, SaltReturnAssertsMixin):
  105. """
  106. Validate the file state
  107. """
  108. @classmethod
  109. def setUpClass(cls):
  110. cls.tmp_dir = pathlib.Path(tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)).resolve()
  111. @classmethod
  112. def tearDownClass(cls):
  113. salt.utils.files.rm_rf(str(cls.tmp_dir))
  114. def _delete_file(self, path):
  115. try:
  116. os.remove(path)
  117. except OSError as exc:
  118. if exc.errno != errno.ENOENT:
  119. log.error("Failed to remove %s: %s", path, exc)
  120. def tearDown(self):
  121. """
  122. remove files created in previous tests
  123. """
  124. user = "salt"
  125. if user in str(self.run_function("user.list_users")):
  126. self.run_function("user.delete", [user])
  127. def test_symlink(self):
  128. """
  129. file.symlink
  130. """
  131. name = self.tmp_dir / "symlink"
  132. tgt = self.tmp_dir / "target"
  133. # Windows must have a source directory to link to
  134. if IS_WINDOWS and not tgt.is_dir():
  135. tgt.mkdir()
  136. # Windows cannot create a symlink if it already exists
  137. if IS_WINDOWS and name.is_symlink():
  138. name.unlink()
  139. ret = self.run_state("file.symlink", name=str(name), target=str(tgt))
  140. self.assertSaltTrueReturn(ret)
  141. def test_test_symlink(self):
  142. """
  143. file.symlink test interface
  144. """
  145. name = self.tmp_dir / "symlink2"
  146. tgt = self.tmp_dir / "target2"
  147. ret = self.run_state("file.symlink", test=True, name=str(name), target=str(tgt))
  148. self.assertSaltNoneReturn(ret)
  149. def test_absent_file(self):
  150. """
  151. file.absent
  152. """
  153. name = self.tmp_dir / "file_to_kill"
  154. name.write_text("killme")
  155. ret = self.run_state("file.absent", name=str(name))
  156. self.assertSaltTrueReturn(ret)
  157. self.assertFalse(name.is_file())
  158. def test_absent_dir(self):
  159. """
  160. file.absent
  161. """
  162. name = self.tmp_dir / "dir_to_kill"
  163. name.mkdir(exist_ok=True)
  164. ret = self.run_state("file.absent", name=str(name))
  165. self.assertSaltTrueReturn(ret)
  166. self.assertFalse(name.is_dir())
  167. def test_absent_link(self):
  168. """
  169. file.absent
  170. """
  171. name = self.tmp_dir / "link_to_kill"
  172. self.addCleanup(salt.utils.files.safe_rm, str(name))
  173. tgt = self.tmp_dir / "link_to_kill.tgt"
  174. self.addCleanup(salt.utils.files.safe_rm, str(tgt))
  175. tgt.symlink_to(name, target_is_directory=IS_WINDOWS)
  176. ret = self.run_state("file.absent", name=str(name))
  177. self.assertSaltTrueReturn(ret)
  178. self.assertFalse(name.exists())
  179. self.assertFalse(name.is_symlink())
  180. @with_tempfile()
  181. def test_test_absent(self, name):
  182. """
  183. file.absent test interface
  184. """
  185. with salt.utils.files.fopen(name, "w+") as fp_:
  186. fp_.write("killme")
  187. ret = self.run_state("file.absent", test=True, name=name)
  188. self.assertSaltNoneReturn(ret)
  189. self.assertTrue(os.path.isfile(name))
  190. def test_managed(self):
  191. """
  192. file.managed
  193. """
  194. name = self.tmp_dir / "grail_scene33"
  195. self.addCleanup(salt.utils.files.safe_rm, str(name))
  196. ret = self.run_state(
  197. "file.managed", name=str(name), source="salt://grail/scene33"
  198. )
  199. src = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "grail" / "scene33"
  200. master_data = src.read_text()
  201. minion_data = name.read_text()
  202. self.assertEqual(master_data, minion_data)
  203. self.assertSaltTrueReturn(ret)
  204. def test_managed_file_mode(self):
  205. """
  206. file.managed, correct file permissions
  207. """
  208. desired_mode = 504 # 0770 octal
  209. name = self.tmp_dir / "grail_scene33"
  210. self.addCleanup(salt.utils.files.safe_rm, str(name))
  211. ret = self.run_state(
  212. "file.managed", name=str(name), mode="0770", source="salt://grail/scene33"
  213. )
  214. if IS_WINDOWS:
  215. expected = "The 'mode' option is not supported on Windows"
  216. self.assertEqual(ret[list(ret)[0]]["comment"], expected)
  217. self.assertSaltFalseReturn(ret)
  218. return
  219. resulting_mode = stat.S_IMODE(name.stat().st_mode)
  220. self.assertEqual(oct(desired_mode), oct(resulting_mode))
  221. self.assertSaltTrueReturn(ret)
  222. @skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
  223. def test_managed_file_mode_keep(self):
  224. """
  225. Test using "mode: keep" in a file.managed state
  226. """
  227. _test_managed_file_mode_keep_helper(self, local=False)
  228. @skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
  229. def test_managed_file_mode_keep_local_source(self):
  230. """
  231. Test using "mode: keep" in a file.managed state, with a local file path
  232. as the source.
  233. """
  234. _test_managed_file_mode_keep_helper(self, local=True)
  235. def test_managed_file_mode_file_exists_replace(self):
  236. """
  237. file.managed, existing file with replace=True, change permissions
  238. """
  239. initial_mode = 504 # 0770 octal
  240. desired_mode = 384 # 0600 octal
  241. name = self.tmp_dir / "grail_scene33"
  242. self.addCleanup(salt.utils.files.safe_rm, str(name))
  243. ret = self.run_state(
  244. "file.managed",
  245. name=str(name),
  246. mode=oct(initial_mode),
  247. source="salt://grail/scene33",
  248. )
  249. if IS_WINDOWS:
  250. expected = "The 'mode' option is not supported on Windows"
  251. self.assertEqual(ret[list(ret)[0]]["comment"], expected)
  252. self.assertSaltFalseReturn(ret)
  253. return
  254. resulting_mode = stat.S_IMODE(name.stat().st_mode)
  255. self.assertEqual(oct(initial_mode), oct(resulting_mode))
  256. ret = self.run_state(
  257. "file.managed",
  258. name=str(name),
  259. replace=True,
  260. mode=oct(desired_mode),
  261. source="salt://grail/scene33",
  262. )
  263. resulting_mode = stat.S_IMODE(name.stat().st_mode)
  264. self.assertEqual(oct(desired_mode), oct(resulting_mode))
  265. self.assertSaltTrueReturn(ret)
  266. def test_managed_file_mode_file_exists_noreplace(self):
  267. """
  268. file.managed, existing file with replace=False, change permissions
  269. """
  270. initial_mode = 504 # 0770 octal
  271. desired_mode = 384 # 0600 octal
  272. name = self.tmp_dir / "grail_scene33"
  273. self.addCleanup(salt.utils.files.safe_rm, str(name))
  274. ret = self.run_state(
  275. "file.managed",
  276. name=str(name),
  277. replace=True,
  278. mode=oct(initial_mode),
  279. source="salt://grail/scene33",
  280. )
  281. if IS_WINDOWS:
  282. expected = "The 'mode' option is not supported on Windows"
  283. self.assertEqual(ret[list(ret)[0]]["comment"], expected)
  284. self.assertSaltFalseReturn(ret)
  285. return
  286. ret = self.run_state(
  287. "file.managed",
  288. name=str(name),
  289. replace=False,
  290. mode=oct(desired_mode),
  291. source="salt://grail/scene33",
  292. )
  293. resulting_mode = stat.S_IMODE(name.stat().st_mode)
  294. self.assertEqual(oct(desired_mode), oct(resulting_mode))
  295. self.assertSaltTrueReturn(ret)
  296. def test_managed_file_with_grains_data(self):
  297. """
  298. Test to ensure we can render grains data into a managed
  299. file.
  300. """
  301. grain_path = self.tmp_dir / "file-grain-test"
  302. self.addCleanup(salt.utils.files.safe_rm, str(grain_path))
  303. state_file = "file-grainget"
  304. self.run_function(
  305. "state.sls", [state_file], pillar={"grain_path": str(grain_path)}
  306. )
  307. self.assertTrue(grain_path.exists())
  308. file_contents = grain_path.read_text().splitlines(True)
  309. match = "^minion\n"
  310. self.assertTrue(re.match(match, file_contents[0]))
  311. def test_managed_file_with_pillar_sls(self):
  312. """
  313. Test to ensure pillar data in sls file
  314. is rendered properly and file is created.
  315. """
  316. file_pillar = os.path.join(RUNTIME_VARS.TMP, "filepillar-python")
  317. self.addCleanup(self._delete_file, file_pillar)
  318. state_name = "file-pillarget"
  319. log.warning("File Path: %s", file_pillar)
  320. ret = self.run_function("state.sls", [state_name])
  321. self.assertSaltTrueReturn(ret)
  322. # Check to make sure the file was created
  323. check_file = self.run_function("file.file_exists", [file_pillar])
  324. self.assertTrue(check_file)
  325. def test_managed_file_with_pillardefault_sls(self):
  326. """
  327. Test to ensure when pillar data is not available
  328. in sls file with pillar.get it uses the default
  329. value.
  330. """
  331. file_pillar_def = os.path.join(RUNTIME_VARS.TMP, "filepillar-defaultvalue")
  332. self.addCleanup(self._delete_file, file_pillar_def)
  333. state_name = "file-pillardefaultget"
  334. log.warning("File Path: %s", file_pillar_def)
  335. ret = self.run_function("state.sls", [state_name])
  336. self.assertSaltTrueReturn(ret)
  337. # Check to make sure the file was created
  338. check_file = self.run_function("file.file_exists", [file_pillar_def])
  339. self.assertTrue(check_file)
  340. @skip_if_not_root
  341. def test_managed_dir_mode(self):
  342. """
  343. Tests to ensure that file.managed creates directories with the
  344. permissions requested with the dir_mode argument
  345. """
  346. desired_mode = 511 # 0777 in octal
  347. name = self.tmp_dir / "a" / "managed_dir_mode_test_file"
  348. self.addCleanup(salt.utils.files.safe_rm, str(name))
  349. desired_owner = "nobody"
  350. ret = self.run_state(
  351. "file.managed",
  352. name=str(name),
  353. source="salt://grail/scene33",
  354. mode=600,
  355. makedirs=True,
  356. user=desired_owner,
  357. dir_mode=oct(desired_mode), # 0777
  358. )
  359. if IS_WINDOWS:
  360. expected = "The 'mode' option is not supported on Windows"
  361. self.assertEqual(ret[list(ret)[0]]["comment"], expected)
  362. self.assertSaltFalseReturn(ret)
  363. return
  364. resulting_mode = stat.S_IMODE(name.parent.stat().st_mode)
  365. resulting_owner = pwd.getpwuid(name.parent.stat().st_uid).pw_name
  366. self.assertEqual(oct(desired_mode), oct(resulting_mode))
  367. self.assertSaltTrueReturn(ret)
  368. self.assertEqual(desired_owner, resulting_owner)
  369. def test_test_managed(self):
  370. """
  371. file.managed test interface
  372. """
  373. name = self.tmp_dir / "grail_not_not_scene33"
  374. self.addCleanup(salt.utils.files.safe_rm, str(name))
  375. ret = self.run_state(
  376. "file.managed", test=True, name=str(name), source="salt://grail/scene33"
  377. )
  378. self.assertSaltNoneReturn(ret)
  379. self.assertFalse(name.is_file())
  380. def test_managed_show_changes_false(self):
  381. """
  382. file.managed test interface
  383. """
  384. name = self.tmp_dir / "grail_not_scene33"
  385. self.addCleanup(salt.utils.files.safe_rm, str(name))
  386. name.write_bytes(b"test_managed_show_changes_false\n")
  387. ret = self.run_state(
  388. "file.managed",
  389. name=str(name),
  390. source="salt://grail/scene33",
  391. show_changes=False,
  392. )
  393. changes = next(iter(ret.values()))["changes"]
  394. self.assertEqual("<show_changes=False>", changes["diff"])
  395. def test_managed_show_changes_true(self):
  396. """
  397. file.managed test interface
  398. """
  399. name = self.tmp_dir / "grail_not_scene33"
  400. self.addCleanup(salt.utils.files.safe_rm, str(name))
  401. name.write_bytes(b"test_managed_show_changes_false\n")
  402. ret = self.run_state(
  403. "file.managed", name=str(name), source="salt://grail/scene33",
  404. )
  405. changes = next(iter(ret.values()))["changes"]
  406. self.assertIn("diff", changes)
  407. @skipIf(IS_WINDOWS, "Don't know how to fix for Windows")
  408. def test_managed_escaped_file_path(self):
  409. """
  410. file.managed test that 'salt://|' protects unusual characters in file path
  411. """
  412. funny_file = salt.utils.files.mkstemp(
  413. prefix="?f!le? n@=3&", suffix=".file type"
  414. )
  415. funny_file_name = os.path.split(funny_file)[1]
  416. funny_url = "salt://|" + funny_file_name
  417. funny_url_path = os.path.join(RUNTIME_VARS.BASE_FILES, funny_file_name)
  418. state_name = "funny_file"
  419. state_file_name = state_name + ".sls"
  420. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_file_name)
  421. state_key = "file_|-{0}_|-{0}_|-managed".format(funny_file)
  422. self.addCleanup(os.remove, state_file)
  423. self.addCleanup(os.remove, funny_file)
  424. self.addCleanup(os.remove, funny_url_path)
  425. with salt.utils.files.fopen(funny_url_path, "w"):
  426. pass
  427. with salt.utils.files.fopen(state_file, "w") as fp_:
  428. fp_.write(
  429. textwrap.dedent(
  430. """\
  431. {}:
  432. file.managed:
  433. - source: {}
  434. - makedirs: True
  435. """.format(
  436. funny_file, funny_url
  437. )
  438. )
  439. )
  440. ret = self.run_function("state.sls", [state_name])
  441. self.assertTrue(ret[state_key]["result"])
  442. def test_managed_contents(self):
  443. """
  444. test file.managed with contents that is a boolean, string, integer,
  445. float, list, and dictionary
  446. """
  447. state_name = "file-FileTest-test_managed_contents"
  448. state_filename = state_name + ".sls"
  449. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  450. managed_files = {}
  451. state_keys = {}
  452. for typ in ("bool", "str", "int", "float", "list", "dict"):
  453. managed_files[typ] = salt.utils.files.mkstemp()
  454. state_keys[typ] = "file_|-{} file_|-{}_|-managed".format(
  455. typ, managed_files[typ]
  456. )
  457. try:
  458. with salt.utils.files.fopen(state_file, "w") as fd_:
  459. fd_.write(
  460. textwrap.dedent(
  461. """\
  462. bool file:
  463. file.managed:
  464. - name: {bool}
  465. - contents: True
  466. str file:
  467. file.managed:
  468. - name: {str}
  469. - contents: Salt was here.
  470. int file:
  471. file.managed:
  472. - name: {int}
  473. - contents: 340282366920938463463374607431768211456
  474. float file:
  475. file.managed:
  476. - name: {float}
  477. - contents: 1.7518e-45 # gravitational coupling constant
  478. list file:
  479. file.managed:
  480. - name: {list}
  481. - contents: [1, 1, 2, 3, 5, 8, 13]
  482. dict file:
  483. file.managed:
  484. - name: {dict}
  485. - contents:
  486. C: charge
  487. P: parity
  488. T: time
  489. """.format(
  490. **managed_files
  491. )
  492. )
  493. )
  494. ret = self.run_function("state.sls", [state_name])
  495. self.assertSaltTrueReturn(ret)
  496. for typ in state_keys:
  497. self.assertTrue(ret[state_keys[typ]]["result"])
  498. self.assertIn("diff", ret[state_keys[typ]]["changes"])
  499. finally:
  500. if os.path.exists(state_file):
  501. os.remove(state_file)
  502. for typ in managed_files:
  503. if os.path.exists(managed_files[typ]):
  504. os.remove(managed_files[typ])
  505. def test_onchanges_any_recursive_error_issues_50811(self):
  506. """
  507. test that onchanges_any does not causes a recursive error
  508. """
  509. state_name = "onchanges_any_recursive_error"
  510. state_filename = state_name + ".sls"
  511. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  512. try:
  513. with salt.utils.files.fopen(state_file, "w") as fd_:
  514. fd_.write(
  515. textwrap.dedent(
  516. """\
  517. command-test:
  518. cmd.run:
  519. - name: ls
  520. - onchanges_any:
  521. - file: /tmp/an-unfollowed-file
  522. """
  523. )
  524. )
  525. ret = self.run_function("state.sls", [state_name])
  526. self.assertSaltFalseReturn(ret)
  527. finally:
  528. if os.path.exists(state_file):
  529. os.remove(state_file)
  530. def test_prerequired_issues_55775(self):
  531. """
  532. Test that __prereqired__ is filter from file.replace
  533. if __prereqired__ is not filter from file.replace an error will be raised
  534. """
  535. state_name = "Test_Issues_55775"
  536. state_filename = state_name + ".sls"
  537. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  538. test_file = os.path.join(RUNTIME_VARS.BASE_FILES, "Issues_55775.txt")
  539. try:
  540. with salt.utils.files.fopen(state_file, "w") as fd_:
  541. fd_.write(
  542. textwrap.dedent(
  543. """\
  544. /tmp/bug.txt:
  545. file.managed:
  546. - name: {0}
  547. - contents:
  548. - foo
  549. file.replace:
  550. file.replace:
  551. - name: {0}
  552. - pattern: 'foo'
  553. - repl: 'bar'
  554. - prereq:
  555. - test no changes
  556. - test changes
  557. test no changes:
  558. test.succeed_without_changes:
  559. - name: no changes
  560. test changes:
  561. test.succeed_with_changes:
  562. - name: changes
  563. - require:
  564. - test: test no changes
  565. """.format(
  566. test_file
  567. )
  568. )
  569. )
  570. ret = self.run_function("state.sls", [state_name])
  571. self.assertSaltTrueReturn(ret)
  572. finally:
  573. for fpath in (state_file, test_file):
  574. if os.path.exists(fpath):
  575. os.remove(fpath)
  576. def test_managed_contents_with_contents_newline(self):
  577. """
  578. test file.managed with contents by using the default content_newline
  579. flag.
  580. """
  581. contents = "test_managed_contents_with_newline_one"
  582. name = self.tmp_dir / "foo"
  583. self.addCleanup(salt.utils.files.safe_rm, str(name))
  584. # Create a file named foo with contents as above but with a \n at EOF
  585. self.run_state(
  586. "file.managed", name=str(name), contents=contents, contents_newline=True
  587. )
  588. last_line = name.read_text()
  589. self.assertEqual((contents + "\n"), last_line)
  590. def test_managed_contents_with_contents_newline_false(self):
  591. """
  592. test file.managed with contents by using the non default content_newline
  593. flag.
  594. """
  595. contents = "test_managed_contents_with_newline_one"
  596. name = self.tmp_dir / "bar"
  597. self.addCleanup(salt.utils.files.safe_rm, str(name))
  598. # Create a file named foo with contents as above but with a \n at EOF
  599. self.run_state(
  600. "file.managed", name=str(name), contents=contents, contents_newline=False
  601. )
  602. last_line = name.read_text()
  603. self.assertEqual(contents, last_line)
  604. def test_managed_multiline_contents_with_contents_newline(self):
  605. """
  606. test file.managed with contents by using the non default content_newline
  607. flag.
  608. """
  609. contents = "this is a cookie\nthis is another cookie"
  610. name = self.tmp_dir / "bar"
  611. self.addCleanup(salt.utils.files.safe_rm, str(name))
  612. # Create a file named foo with contents as above but with a \n at EOF
  613. self.run_state(
  614. "file.managed", name=str(name), contents=contents, contents_newline=True
  615. )
  616. last_line = name.read_text()
  617. self.assertEqual((contents + "\n"), last_line)
  618. def test_managed_multiline_contents_with_contents_newline_false(self):
  619. """
  620. test file.managed with contents by using the non default content_newline
  621. flag.
  622. """
  623. contents = "this is a cookie\nthis is another cookie"
  624. name = self.tmp_dir / "bar"
  625. self.addCleanup(salt.utils.files.safe_rm, str(name))
  626. # Create a file named foo with contents as above but with a \n at EOF
  627. self.run_state(
  628. "file.managed", name=str(name), contents=contents, contents_newline=False
  629. )
  630. last_line = name.read_text()
  631. self.assertEqual(contents, last_line)
  632. @skip_if_not_root
  633. @skipIf(IS_WINDOWS, 'Windows does not support "mode" kwarg. Skipping.')
  634. @skipIf(not salt.utils.path.which("visudo"), "sudo is missing")
  635. def test_managed_check_cmd(self):
  636. """
  637. Test file.managed passing a basic check_cmd kwarg. See Issue #38111.
  638. """
  639. r_group = "root"
  640. if salt.utils.platform.is_darwin() or salt.utils.platform.is_freebsd():
  641. r_group = "wheel"
  642. name = self.tmp_dir / "sudoers"
  643. self.addCleanup(salt.utils.files.safe_rm, str(name))
  644. ret = self.run_state(
  645. "file.managed",
  646. name=str(name),
  647. user="root",
  648. group=r_group,
  649. mode=440,
  650. check_cmd="visudo -c -s -f",
  651. )
  652. self.assertSaltTrueReturn(ret)
  653. self.assertInSaltComment("Empty file", ret)
  654. self.assertEqual(
  655. ret["file_|-{0}_|-{0}_|-managed".format(name)]["changes"],
  656. {"new": "file {} created".format(name), "mode": "0440"},
  657. )
  658. def test_managed_local_source_with_source_hash(self):
  659. """
  660. Make sure that we enforce the source_hash even with local files
  661. """
  662. name = self.tmp_dir / "local_source_with_source_hash"
  663. self.addCleanup(salt.utils.files.safe_rm, str(name))
  664. local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene33")
  665. actual_hash = "567fd840bf1548edc35c48eb66cdd78bfdfcccff"
  666. if IS_WINDOWS:
  667. # CRLF vs LF causes a different hash on windows
  668. actual_hash = "f658a0ec121d9c17088795afcc6ff3c43cb9842a"
  669. # Reverse the actual hash
  670. bad_hash = actual_hash[::-1]
  671. def remove_file():
  672. try:
  673. os.remove(str(name))
  674. except OSError as exc:
  675. if exc.errno != errno.ENOENT:
  676. raise
  677. def do_test(clean=False):
  678. for proto in ("file://", ""):
  679. source = proto + local_path
  680. log.debug("Trying source %s", source)
  681. try:
  682. ret = self.run_state(
  683. "file.managed",
  684. name=str(name),
  685. source=source,
  686. source_hash="sha1={}".format(bad_hash),
  687. )
  688. self.assertSaltFalseReturn(ret)
  689. ret = ret[next(iter(ret))]
  690. # Shouldn't be any changes
  691. self.assertFalse(ret["changes"])
  692. # Check that we identified a hash mismatch
  693. self.assertIn("does not match actual checksum", ret["comment"])
  694. ret = self.run_state(
  695. "file.managed",
  696. name=str(name),
  697. source=source,
  698. source_hash="sha1={}".format(actual_hash),
  699. )
  700. self.assertSaltTrueReturn(ret)
  701. finally:
  702. if clean:
  703. remove_file()
  704. remove_file()
  705. log.debug("Trying with nonexistant destination file")
  706. do_test()
  707. log.debug("Trying with destination file already present")
  708. name.write_text("")
  709. try:
  710. do_test(clean=False)
  711. finally:
  712. remove_file()
  713. def test_managed_local_source_does_not_exist(self):
  714. """
  715. Make sure that we exit gracefully when a local source doesn't exist
  716. """
  717. name = self.tmp_dir / "local_source_does_not_exist"
  718. self.addCleanup(salt.utils.files.safe_rm, str(name))
  719. local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene99")
  720. for proto in ("file://", ""):
  721. source = proto + local_path
  722. log.debug("Trying source %s", source)
  723. ret = self.run_state("file.managed", name=str(name), source=source)
  724. self.assertSaltFalseReturn(ret)
  725. ret = ret[next(iter(ret))]
  726. # Shouldn't be any changes
  727. self.assertFalse(ret["changes"])
  728. # Check that we identified a hash mismatch
  729. self.assertIn("does not exist", ret["comment"])
  730. def test_managed_unicode_jinja_with_tojson_filter(self):
  731. """
  732. Using {{ varname }} with a list or dictionary which contains unicode
  733. types on Python 2 will result in Jinja rendering the "u" prefix on each
  734. string. This tests that using the "tojson" jinja filter will dump them
  735. to a format which can be successfully loaded by our YAML loader.
  736. The two lines that should end up being rendered are meant to test two
  737. issues that would trip up PyYAML if the "tojson" filter were not used:
  738. 1. A unicode string type would be loaded as a unicode literal with the
  739. leading "u" as well as the quotes, rather than simply being loaded
  740. as the proper unicode type which matches the content of the string
  741. literal. In other words, u'foo' would be loaded literally as
  742. u"u'foo'". This test includes actual non-ascii unicode in one of the
  743. strings to confirm that this also handles these international
  744. characters properly.
  745. 2. Any unicode string type (such as a URL) which contains a colon would
  746. cause a ScannerError in PyYAML, as it would be assumed to delimit a
  747. mapping node.
  748. Dumping the data structure to JSON using the "tojson" jinja filter
  749. should produce an inline data structure which is valid YAML and will be
  750. loaded properly by our YAML loader.
  751. """
  752. test_file = self.tmp_dir / "test-tojson.txt"
  753. self.addCleanup(salt.utils.files.safe_rm, str(test_file))
  754. ret = self.run_function(
  755. "state.apply", mods="tojson", pillar={"tojson-file": str(test_file)}
  756. )
  757. ret = ret[next(iter(ret))]
  758. assert ret["result"], ret
  759. managed = salt.utils.stringutils.to_unicode(test_file.read_bytes())
  760. expected = dedent(
  761. """\
  762. Die Webseite ist https://saltstack.com.
  763. Der Zucker ist süß.
  764. """
  765. )
  766. assert managed == expected, "{!r} != {!r}".format(managed, expected)
  767. def test_managed_source_hash_indifferent_case(self):
  768. """
  769. Test passing a source_hash as an uppercase hash.
  770. This is a regression test for Issue #38914 and Issue #48230 (test=true use).
  771. """
  772. name = self.tmp_dir / "source_hash_indifferent_case"
  773. self.addCleanup(salt.utils.files.safe_rm, str(name))
  774. state_name = "file_|-{0}_|" "-{0}_|-managed".format(name)
  775. local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "hello_world.txt")
  776. actual_hash = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31"
  777. if IS_WINDOWS:
  778. # CRLF vs LF causes a differnt hash on windows
  779. actual_hash = (
  780. "92b772380a3f8e27a93e57e6deeca6c01da07f5aadce78bb2fbb20de10a66925"
  781. )
  782. uppercase_hash = actual_hash.upper()
  783. # Lay down tmp file to test against
  784. self.run_state(
  785. "file.managed", name=str(name), source=local_path, source_hash=actual_hash
  786. )
  787. # Test uppercase source_hash: should return True with no changes
  788. ret = self.run_state(
  789. "file.managed",
  790. name=str(name),
  791. source=local_path,
  792. source_hash=uppercase_hash,
  793. )
  794. assert ret[state_name]["result"] is True
  795. assert ret[state_name]["changes"] == {}
  796. # Test uppercase source_hash using test=true
  797. # Should return True with no changes
  798. ret = self.run_state(
  799. "file.managed",
  800. name=str(name),
  801. source=local_path,
  802. source_hash=uppercase_hash,
  803. test=True,
  804. )
  805. assert ret[state_name]["result"] is True
  806. assert ret[state_name]["changes"] == {}
  807. @with_tempfile(create=False)
  808. def test_managed_latin1_diff(self, name):
  809. """
  810. Tests that latin-1 file contents are represented properly in the diff
  811. """
  812. # Lay down the initial file
  813. ret = self.run_state(
  814. "file.managed", name=name, source="salt://issue-48777/old.html"
  815. )
  816. ret = ret[next(iter(ret))]
  817. assert ret["result"] is True, ret
  818. # Replace it with the new file and check the diff
  819. ret = self.run_state(
  820. "file.managed", name=name, source="salt://issue-48777/new.html"
  821. )
  822. ret = ret[next(iter(ret))]
  823. assert ret["result"] is True, ret
  824. diff_lines = ret["changes"]["diff"].split(os.linesep)
  825. assert "+räksmörgås" in diff_lines, diff_lines
  826. @with_tempfile()
  827. def test_managed_keep_source_false_salt(self, name):
  828. """
  829. This test ensures that we properly clean the cached file if keep_source
  830. is set to False, for source files using a salt:// URL
  831. """
  832. source = "salt://grail/scene33"
  833. saltenv = "base"
  834. # Run the state
  835. ret = self.run_state(
  836. "file.managed", name=name, source=source, saltenv=saltenv, keep_source=False
  837. )
  838. ret = ret[next(iter(ret))]
  839. assert ret["result"] is True
  840. # Now make sure that the file is not cached
  841. result = self.run_function("cp.is_cached", [source, saltenv])
  842. assert result == "", "File is still cached at {}".format(result)
  843. @with_tempfile(create=False)
  844. @with_tempfile(create=False)
  845. def test_file_managed_onchanges(self, file1, file2):
  846. """
  847. Test file.managed state with onchanges
  848. """
  849. pillar = {
  850. "file1": file1,
  851. "file2": file2,
  852. "source": "salt://testfile",
  853. "req": "onchanges",
  854. }
  855. # Lay down the file used in the below SLS to ensure that when it is
  856. # run, there are no changes.
  857. self.run_state("file.managed", name=pillar["file2"], source=pillar["source"])
  858. ret = self.repack_state_returns(
  859. self.run_function(
  860. "state.apply", mods="onchanges_prereq", pillar=pillar, test=True,
  861. )
  862. )
  863. # The file states should both exit with None
  864. assert ret["one"]["result"] is None, ret["one"]["result"]
  865. assert ret["three"]["result"] is True, ret["three"]["result"]
  866. # The first file state should have changes, since a new file was
  867. # created. The other one should not, since we already created that file
  868. # before applying the SLS file.
  869. assert ret["one"]["changes"]
  870. assert not ret["three"]["changes"], ret["three"]["changes"]
  871. # The state watching 'one' should have been run due to changes
  872. assert ret["two"]["comment"] == "Success!", ret["two"]["comment"]
  873. # The state watching 'three' should not have been run
  874. assert (
  875. ret["four"]["comment"]
  876. == "State was not run because none of the onchanges reqs changed"
  877. ), ret["four"]["comment"]
  878. @with_tempfile(create=False)
  879. @with_tempfile(create=False)
  880. def test_file_managed_prereq(self, file1, file2):
  881. """
  882. Test file.managed state with prereq
  883. """
  884. pillar = {
  885. "file1": file1,
  886. "file2": file2,
  887. "source": "salt://testfile",
  888. "req": "prereq",
  889. }
  890. # Lay down the file used in the below SLS to ensure that when it is
  891. # run, there are no changes.
  892. self.run_state("file.managed", name=pillar["file2"], source=pillar["source"])
  893. ret = self.repack_state_returns(
  894. self.run_function(
  895. "state.apply", mods="onchanges_prereq", pillar=pillar, test=True,
  896. )
  897. )
  898. # The file states should both exit with None
  899. assert ret["one"]["result"] is None, ret["one"]["result"]
  900. assert ret["three"]["result"] is True, ret["three"]["result"]
  901. # The first file state should have changes, since a new file was
  902. # created. The other one should not, since we already created that file
  903. # before applying the SLS file.
  904. assert ret["one"]["changes"]
  905. assert not ret["three"]["changes"], ret["three"]["changes"]
  906. # The state watching 'one' should have been run due to changes
  907. assert ret["two"]["comment"] == "Success!", ret["two"]["comment"]
  908. # The state watching 'three' should not have been run
  909. assert ret["four"]["comment"] == "No changes detected", ret["four"]["comment"]
  910. def test_directory(self):
  911. """
  912. file.directory
  913. """
  914. name = self.tmp_dir / "a_new_dir"
  915. self.addCleanup(salt.utils.files.rm_rf, str(name))
  916. ret = self.run_state("file.directory", name=str(name))
  917. self.assertSaltTrueReturn(ret)
  918. self.assertTrue(name.is_dir())
  919. def test_directory_symlink_dry_run(self):
  920. """
  921. Ensure that symlinks are followed when file.directory is run with
  922. test=True
  923. """
  924. tmp_dir = self.tmp_dir / "pgdata"
  925. self.addCleanup(salt.utils.files.rm_rf, str(tmp_dir))
  926. sym_dir = self.tmp_dir / "pg_data"
  927. self.addCleanup(salt.utils.files.safe_rm, str(sym_dir))
  928. tmp_dir.mkdir(0o0700)
  929. sym_dir.symlink_to(tmp_dir, target_is_directory=IS_WINDOWS)
  930. if IS_WINDOWS:
  931. ret = self.run_state(
  932. "file.directory",
  933. test=True,
  934. name=str(sym_dir),
  935. follow_symlinks=True,
  936. win_owner="Administrators",
  937. )
  938. else:
  939. ret = self.run_state(
  940. "file.directory",
  941. test=True,
  942. name=str(sym_dir),
  943. follow_symlinks=True,
  944. mode=700,
  945. )
  946. self.assertSaltTrueReturn(ret)
  947. @skip_if_not_root
  948. @skipIf(IS_WINDOWS, "Mode not available in Windows")
  949. def test_directory_max_depth(self):
  950. """
  951. file.directory
  952. Test the max_depth option by iteratively increasing the depth and
  953. checking that no changes deeper than max_depth have been attempted
  954. """
  955. def _get_oct_mode(name):
  956. """
  957. Return a string octal representation of the permissions for name
  958. """
  959. return salt.utils.files.normalize_mode(oct(os.stat(name).st_mode & 0o777))
  960. top = os.path.join(RUNTIME_VARS.TMP, "top_dir")
  961. self.addCleanup(salt.utils.files.rm_rf, top)
  962. sub = os.path.join(top, "sub_dir")
  963. subsub = os.path.join(sub, "sub_sub_dir")
  964. dirs = [top, sub, subsub]
  965. initial_mode = "0111"
  966. changed_mode = "0555"
  967. initial_modes = {
  968. 0: {sub: "0755", subsub: "0111"},
  969. 1: {sub: "0111", subsub: "0111"},
  970. 2: {sub: "0111", subsub: "0111"},
  971. }
  972. if not os.path.isdir(subsub):
  973. os.makedirs(subsub, int(initial_mode, 8))
  974. for depth in range(0, 3):
  975. ret = self.run_state(
  976. "file.directory",
  977. name=top,
  978. max_depth=depth,
  979. dir_mode=changed_mode,
  980. recurse=["mode"],
  981. )
  982. self.assertSaltTrueReturn(ret)
  983. for changed_dir in dirs[0 : depth + 1]:
  984. self.assertEqual(changed_mode, _get_oct_mode(changed_dir))
  985. for untouched_dir in dirs[depth + 1 :]:
  986. # Beginning in Python 3.7, os.makedirs no longer sets
  987. # the mode of intermediate directories to the mode that
  988. # is passed.
  989. if sys.version_info >= (3, 7):
  990. _mode = initial_modes[depth][untouched_dir]
  991. self.assertEqual(_mode, _get_oct_mode(untouched_dir))
  992. else:
  993. self.assertEqual(initial_mode, _get_oct_mode(untouched_dir))
  994. def test_test_directory(self):
  995. """
  996. file.directory
  997. """
  998. name = self.tmp_dir / "a_not_dir"
  999. self.addCleanup(shutil.rmtree, str(name), ignore_errors=True)
  1000. ret = self.run_state("file.directory", test=True, name=str(name))
  1001. self.assertSaltNoneReturn(ret)
  1002. self.assertFalse(name.is_dir())
  1003. @with_tempdir()
  1004. def test_directory_clean(self, base_dir):
  1005. """
  1006. file.directory with clean=True
  1007. """
  1008. name = os.path.join(base_dir, "directory_clean_dir")
  1009. os.mkdir(name)
  1010. strayfile = os.path.join(name, "strayfile")
  1011. with salt.utils.files.fopen(strayfile, "w"):
  1012. pass
  1013. straydir = os.path.join(name, "straydir")
  1014. if not os.path.isdir(straydir):
  1015. os.makedirs(straydir)
  1016. with salt.utils.files.fopen(os.path.join(straydir, "strayfile2"), "w"):
  1017. pass
  1018. ret = self.run_state("file.directory", name=name, clean=True)
  1019. self.assertSaltTrueReturn(ret)
  1020. self.assertFalse(os.path.exists(strayfile))
  1021. self.assertFalse(os.path.exists(straydir))
  1022. self.assertTrue(os.path.isdir(name))
  1023. def test_directory_is_idempotent(self):
  1024. """
  1025. Ensure the file.directory state produces no changes when rerun.
  1026. """
  1027. name = self.tmp_dir / "a_dir_twice"
  1028. self.addCleanup(salt.utils.files.rm_rf, str(name))
  1029. if IS_WINDOWS:
  1030. username = os.environ.get("USERNAME", "Administrators")
  1031. domain = os.environ.get("USERDOMAIN", "")
  1032. fullname = "{}\\{}".format(domain, username)
  1033. ret = self.run_state("file.directory", name=str(name), win_owner=fullname)
  1034. else:
  1035. ret = self.run_state("file.directory", name=str(name))
  1036. self.assertSaltTrueReturn(ret)
  1037. if IS_WINDOWS:
  1038. ret = self.run_state("file.directory", name=str(name), win_owner=username)
  1039. else:
  1040. ret = self.run_state("file.directory", name=str(name))
  1041. self.assertSaltTrueReturn(ret)
  1042. self.assertSaltStateChangesEqual(ret, {})
  1043. @with_tempdir()
  1044. def test_directory_clean_exclude(self, base_dir):
  1045. """
  1046. file.directory with clean=True and exclude_pat set
  1047. """
  1048. name = os.path.join(base_dir, "directory_clean_dir")
  1049. if not os.path.isdir(name):
  1050. os.makedirs(name)
  1051. strayfile = os.path.join(name, "strayfile")
  1052. with salt.utils.files.fopen(strayfile, "w"):
  1053. pass
  1054. straydir = os.path.join(name, "straydir")
  1055. if not os.path.isdir(straydir):
  1056. os.makedirs(straydir)
  1057. strayfile2 = os.path.join(straydir, "strayfile2")
  1058. with salt.utils.files.fopen(strayfile2, "w"):
  1059. pass
  1060. keepfile = os.path.join(straydir, "keepfile")
  1061. with salt.utils.files.fopen(keepfile, "w"):
  1062. pass
  1063. exclude_pat = "E@^straydir(|/keepfile)$"
  1064. if IS_WINDOWS:
  1065. exclude_pat = "E@^straydir(|\\\\keepfile)$"
  1066. ret = self.run_state(
  1067. "file.directory", name=name, clean=True, exclude_pat=exclude_pat
  1068. )
  1069. self.assertSaltTrueReturn(ret)
  1070. self.assertFalse(os.path.exists(strayfile))
  1071. self.assertFalse(os.path.exists(strayfile2))
  1072. self.assertTrue(os.path.exists(keepfile))
  1073. @skipIf(IS_WINDOWS, "Skip on windows")
  1074. @with_tempdir()
  1075. def test_test_directory_clean_exclude(self, base_dir):
  1076. """
  1077. file.directory with test=True, clean=True and exclude_pat set
  1078. Skipped on windows because clean and exclude_pat not supported by
  1079. salt.sates.file._check_directory_win
  1080. """
  1081. name = os.path.join(base_dir, "directory_clean_dir")
  1082. os.mkdir(name)
  1083. strayfile = os.path.join(name, "strayfile")
  1084. with salt.utils.files.fopen(strayfile, "w"):
  1085. pass
  1086. straydir = os.path.join(name, "straydir")
  1087. if not os.path.isdir(straydir):
  1088. os.makedirs(straydir)
  1089. strayfile2 = os.path.join(straydir, "strayfile2")
  1090. with salt.utils.files.fopen(strayfile2, "w"):
  1091. pass
  1092. keepfile = os.path.join(straydir, "keepfile")
  1093. with salt.utils.files.fopen(keepfile, "w"):
  1094. pass
  1095. exclude_pat = "E@^straydir(|/keepfile)$"
  1096. if IS_WINDOWS:
  1097. exclude_pat = "E@^straydir(|\\\\keepfile)$"
  1098. ret = self.run_state(
  1099. "file.directory", test=True, name=name, clean=True, exclude_pat=exclude_pat
  1100. )
  1101. comment = next(iter(ret.values()))["comment"]
  1102. self.assertSaltNoneReturn(ret)
  1103. self.assertTrue(os.path.exists(strayfile))
  1104. self.assertTrue(os.path.exists(strayfile2))
  1105. self.assertTrue(os.path.exists(keepfile))
  1106. self.assertIn(strayfile, comment)
  1107. self.assertIn(strayfile2, comment)
  1108. self.assertNotIn(keepfile, comment)
  1109. @with_tempdir()
  1110. def test_directory_clean_require_in(self, name):
  1111. """
  1112. file.directory test with clean=True and require_in file
  1113. """
  1114. state_name = "file-FileTest-test_directory_clean_require_in"
  1115. state_filename = state_name + ".sls"
  1116. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  1117. wrong_file = os.path.join(name, "wrong")
  1118. with salt.utils.files.fopen(wrong_file, "w") as fp:
  1119. fp.write("foo")
  1120. good_file = os.path.join(name, "bar")
  1121. with salt.utils.files.fopen(state_file, "w") as fp:
  1122. self.addCleanup(salt.utils.files.safe_rm, state_file)
  1123. fp.write(
  1124. textwrap.dedent(
  1125. """\
  1126. some_dir:
  1127. file.directory:
  1128. - name: {name}
  1129. - clean: true
  1130. {good_file}:
  1131. file.managed:
  1132. - require_in:
  1133. - file: some_dir
  1134. """.format(
  1135. name=name, good_file=good_file
  1136. )
  1137. )
  1138. )
  1139. ret = self.run_function("state.sls", [state_name])
  1140. self.assertTrue(os.path.exists(good_file))
  1141. self.assertFalse(os.path.exists(wrong_file))
  1142. @with_tempdir()
  1143. def test_directory_clean_require_in_with_id(self, name):
  1144. """
  1145. file.directory test with clean=True and require_in file with an ID
  1146. different from the file name
  1147. """
  1148. state_name = "file-FileTest-test_directory_clean_require_in_with_id"
  1149. state_filename = state_name + ".sls"
  1150. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  1151. wrong_file = os.path.join(name, "wrong")
  1152. with salt.utils.files.fopen(wrong_file, "w") as fp:
  1153. fp.write("foo")
  1154. good_file = os.path.join(name, "bar")
  1155. with salt.utils.files.fopen(state_file, "w") as fp:
  1156. self.addCleanup(salt.utils.files.safe_rm, state_file)
  1157. fp.write(
  1158. textwrap.dedent(
  1159. """\
  1160. some_dir:
  1161. file.directory:
  1162. - name: {name}
  1163. - clean: true
  1164. some_file:
  1165. file.managed:
  1166. - name: {good_file}
  1167. - require_in:
  1168. - file: some_dir
  1169. """.format(
  1170. name=name, good_file=good_file
  1171. )
  1172. )
  1173. )
  1174. ret = self.run_function("state.sls", [state_name])
  1175. self.assertTrue(os.path.exists(good_file))
  1176. self.assertFalse(os.path.exists(wrong_file))
  1177. @skipIf(
  1178. salt.utils.platform.is_darwin(),
  1179. "WAR ROOM TEMPORARY SKIP, Test is flaky on macosx",
  1180. )
  1181. @with_tempdir()
  1182. def test_directory_clean_require_with_name(self, name):
  1183. """
  1184. file.directory test with clean=True and require with a file state
  1185. relatively to the state's name, not its ID.
  1186. """
  1187. state_name = "file-FileTest-test_directory_clean_require_in_with_id"
  1188. state_filename = state_name + ".sls"
  1189. state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
  1190. wrong_file = os.path.join(name, "wrong")
  1191. with salt.utils.files.fopen(wrong_file, "w") as fp:
  1192. fp.write("foo")
  1193. good_file = os.path.join(name, "bar")
  1194. with salt.utils.files.fopen(state_file, "w") as fp:
  1195. self.addCleanup(salt.utils.files.safe_rm, state_file)
  1196. fp.write(
  1197. textwrap.dedent(
  1198. """\
  1199. some_dir:
  1200. file.directory:
  1201. - name: {name}
  1202. - clean: true
  1203. - require:
  1204. # This requirement refers to the name of the following
  1205. # state, not its ID.
  1206. - file: {good_file}
  1207. some_file:
  1208. file.managed:
  1209. - name: {good_file}
  1210. """.format(
  1211. name=name, good_file=good_file
  1212. )
  1213. )
  1214. )
  1215. ret = self.run_function("state.sls", [state_name])
  1216. self.assertTrue(os.path.exists(good_file))
  1217. self.assertFalse(os.path.exists(wrong_file))
  1218. def test_directory_broken_symlink(self):
  1219. """
  1220. Ensure that file.directory works even if a directory
  1221. contains broken symbolic link
  1222. """
  1223. tmp_dir = self.tmp_dir / "foo"
  1224. tmp_dir.mkdir(0o700)
  1225. self.addCleanup(salt.utils.files.rm_rf, str(tmp_dir))
  1226. null_file = tmp_dir / "null"
  1227. broken_link = tmp_dir / "broken"
  1228. broken_link.symlink_to(null_file)
  1229. if IS_WINDOWS:
  1230. ret = self.run_state(
  1231. "file.directory",
  1232. name=str(tmp_dir),
  1233. recurse=["mode"],
  1234. follow_symlinks=True,
  1235. win_owner="Administrators",
  1236. )
  1237. else:
  1238. ret = self.run_state(
  1239. "file.directory",
  1240. name=str(tmp_dir),
  1241. recurse=["mode"],
  1242. file_mode=644,
  1243. dir_mode=755,
  1244. )
  1245. self.assertSaltTrueReturn(ret)
  1246. @with_tempdir(create=False)
  1247. def test_recurse(self, name):
  1248. """
  1249. file.recurse
  1250. """
  1251. ret = self.run_state("file.recurse", name=name, source="salt://grail")
  1252. self.assertSaltTrueReturn(ret)
  1253. self.assertTrue(os.path.isfile(os.path.join(name, "36", "scene")))
  1254. @with_tempdir(create=False)
  1255. @with_tempdir(create=False)
  1256. def test_recurse_specific_env(self, dir1, dir2):
  1257. """
  1258. file.recurse passing __env__
  1259. """
  1260. ret = self.run_state(
  1261. "file.recurse", name=dir1, source="salt://holy", __env__="prod"
  1262. )
  1263. self.assertSaltTrueReturn(ret)
  1264. self.assertTrue(os.path.isfile(os.path.join(dir1, "32", "scene")))
  1265. ret = self.run_state(
  1266. "file.recurse", name=dir2, source="salt://holy", saltenv="prod"
  1267. )
  1268. self.assertSaltTrueReturn(ret)
  1269. self.assertTrue(os.path.isfile(os.path.join(dir2, "32", "scene")))
  1270. @with_tempdir(create=False)
  1271. @with_tempdir(create=False)
  1272. def test_recurse_specific_env_in_url(self, dir1, dir2):
  1273. """
  1274. file.recurse passing __env__
  1275. """
  1276. ret = self.run_state(
  1277. "file.recurse", name=dir1, source="salt://holy?saltenv=prod"
  1278. )
  1279. self.assertSaltTrueReturn(ret)
  1280. self.assertTrue(os.path.isfile(os.path.join(dir1, "32", "scene")))
  1281. ret = self.run_state(
  1282. "file.recurse", name=dir2, source="salt://holy?saltenv=prod"
  1283. )
  1284. self.assertSaltTrueReturn(ret)
  1285. self.assertTrue(os.path.isfile(os.path.join(dir2, "32", "scene")))
  1286. @with_tempdir(create=False)
  1287. def test_test_recurse(self, name):
  1288. """
  1289. file.recurse test interface
  1290. """
  1291. ret = self.run_state(
  1292. "file.recurse", test=True, name=name, source="salt://grail",
  1293. )
  1294. self.assertSaltNoneReturn(ret)
  1295. self.assertFalse(os.path.isfile(os.path.join(name, "36", "scene")))
  1296. self.assertFalse(os.path.exists(name))
  1297. @with_tempdir(create=False)
  1298. @with_tempdir(create=False)
  1299. def test_test_recurse_specific_env(self, dir1, dir2):
  1300. """
  1301. file.recurse test interface
  1302. """
  1303. ret = self.run_state(
  1304. "file.recurse", test=True, name=dir1, source="salt://holy", __env__="prod"
  1305. )
  1306. self.assertSaltNoneReturn(ret)
  1307. self.assertFalse(os.path.isfile(os.path.join(dir1, "32", "scene")))
  1308. self.assertFalse(os.path.exists(dir1))
  1309. ret = self.run_state(
  1310. "file.recurse", test=True, name=dir2, source="salt://holy", saltenv="prod"
  1311. )
  1312. self.assertSaltNoneReturn(ret)
  1313. self.assertFalse(os.path.isfile(os.path.join(dir2, "32", "scene")))
  1314. self.assertFalse(os.path.exists(dir2))
  1315. @with_tempdir(create=False)
  1316. def test_recurse_template(self, name):
  1317. """
  1318. file.recurse with jinja template enabled
  1319. """
  1320. _ts = "TEMPLATE TEST STRING"
  1321. ret = self.run_state(
  1322. "file.recurse",
  1323. name=name,
  1324. source="salt://grail",
  1325. template="jinja",
  1326. defaults={"spam": _ts},
  1327. )
  1328. self.assertSaltTrueReturn(ret)
  1329. with salt.utils.files.fopen(os.path.join(name, "scene33"), "r") as fp_:
  1330. contents = fp_.read()
  1331. self.assertIn(_ts, contents)
  1332. @with_tempdir()
  1333. def test_recurse_clean(self, name):
  1334. """
  1335. file.recurse with clean=True
  1336. """
  1337. strayfile = os.path.join(name, "strayfile")
  1338. with salt.utils.files.fopen(strayfile, "w"):
  1339. pass
  1340. # Corner cases: replacing file with a directory and vice versa
  1341. with salt.utils.files.fopen(os.path.join(name, "36"), "w"):
  1342. pass
  1343. os.makedirs(os.path.join(name, "scene33"))
  1344. ret = self.run_state(
  1345. "file.recurse", name=name, source="salt://grail", clean=True
  1346. )
  1347. self.assertSaltTrueReturn(ret)
  1348. self.assertFalse(os.path.exists(strayfile))
  1349. self.assertTrue(os.path.isfile(os.path.join(name, "36", "scene")))
  1350. self.assertTrue(os.path.isfile(os.path.join(name, "scene33")))
  1351. @with_tempdir()
  1352. def test_recurse_clean_specific_env(self, name):
  1353. """
  1354. file.recurse with clean=True and __env__=prod
  1355. """
  1356. strayfile = os.path.join(name, "strayfile")
  1357. with salt.utils.files.fopen(strayfile, "w"):
  1358. pass
  1359. # Corner cases: replacing file with a directory and vice versa
  1360. with salt.utils.files.fopen(os.path.join(name, "32"), "w"):
  1361. pass
  1362. os.makedirs(os.path.join(name, "scene34"))
  1363. ret = self.run_state(
  1364. "file.recurse", name=name, source="salt://holy", clean=True, __env__="prod"
  1365. )
  1366. self.assertSaltTrueReturn(ret)
  1367. self.assertFalse(os.path.exists(strayfile))
  1368. self.assertTrue(os.path.isfile(os.path.join(name, "32", "scene")))
  1369. self.assertTrue(os.path.isfile(os.path.join(name, "scene34")))
  1370. @skipIf(IS_WINDOWS, "Skip on windows")
  1371. @with_tempdir()
  1372. def test_recurse_issue_34945(self, base_dir):
  1373. """
  1374. This tests the case where the source dir for the file.recurse state
  1375. does not contain any files (only subdirectories), and the dir_mode is
  1376. being managed. For a long time, this corner case resulted in the top
  1377. level of the destination directory being created with the wrong initial
  1378. permissions, a problem that would be corrected later on in the
  1379. file.recurse state via running state.directory. However, the
  1380. file.directory state only gets called when there are files to be
  1381. managed in that directory, and when the source directory contains only
  1382. subdirectories, the incorrectly-set initial perms would not be
  1383. repaired.
  1384. This was fixed in https://github.com/saltstack/salt/pull/35309
  1385. Skipped on windows because dir_mode is not supported.
  1386. """
  1387. dir_mode = "2775"
  1388. issue_dir = "issue-34945"
  1389. name = os.path.join(base_dir, issue_dir)
  1390. ret = self.run_state(
  1391. "file.recurse", name=name, source="salt://" + issue_dir, dir_mode=dir_mode
  1392. )
  1393. self.assertSaltTrueReturn(ret)
  1394. actual_dir_mode = oct(stat.S_IMODE(os.stat(name).st_mode))[-4:]
  1395. self.assertEqual(dir_mode, actual_dir_mode)
  1396. @with_tempdir(create=False)
  1397. def test_recurse_issue_40578(self, name):
  1398. """
  1399. This ensures that the state doesn't raise an exception when it
  1400. encounters a file with a unicode filename in the process of invoking
  1401. file.source_list.
  1402. """
  1403. ret = self.run_state("file.recurse", name=name, source="salt://соль")
  1404. self.assertSaltTrueReturn(ret)
  1405. if six.PY2 and IS_WINDOWS:
  1406. # Providing unicode to os.listdir so that we avoid having listdir
  1407. # try to decode the filenames using the systemencoding on windows
  1408. # python 2.
  1409. files = os.listdir(name.decode("utf-8"))
  1410. else:
  1411. files = salt.utils.data.decode(os.listdir(name), normalize=True)
  1412. self.assertEqual(
  1413. sorted(files), sorted(["foo.txt", "спам.txt", "яйца.txt"]),
  1414. )
  1415. @with_tempfile()
  1416. def test_replace(self, name):
  1417. """
  1418. file.replace
  1419. """
  1420. with salt.utils.files.fopen(name, "w+") as fp_:
  1421. fp_.write("change_me")
  1422. ret = self.run_state(
  1423. "file.replace", name=name, pattern="change", repl="salt", backup=False
  1424. )
  1425. with salt.utils.files.fopen(name, "r") as fp_:
  1426. self.assertIn("salt", fp_.read())
  1427. self.assertSaltTrueReturn(ret)
  1428. @with_tempdir()
  1429. def test_replace_issue_18612(self, base_dir):
  1430. """
  1431. Test the (mis-)behaviour of file.replace as described in #18612:
  1432. Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
  1433. an infinitely growing file as 'file.replace' didn't check beforehand
  1434. whether the changes had already been done to the file
  1435. # Case description:
  1436. The tested file contains one commented line
  1437. The commented line should be uncommented in the end, nothing else should change
  1438. """
  1439. test_name = "test_replace_issue_18612"
  1440. path_test = os.path.join(base_dir, test_name)
  1441. with salt.utils.files.fopen(path_test, "w+") as fp_test_:
  1442. fp_test_.write("# en_US.UTF-8")
  1443. ret = []
  1444. for x in range(0, 3):
  1445. ret.append(
  1446. self.run_state(
  1447. "file.replace",
  1448. name=path_test,
  1449. pattern="^# en_US.UTF-8$",
  1450. repl="en_US.UTF-8",
  1451. append_if_not_found=True,
  1452. )
  1453. )
  1454. # ensure, the number of lines didn't change, even after invoking 'file.replace' 3 times
  1455. with salt.utils.files.fopen(path_test, "r") as fp_test_:
  1456. self.assertTrue(sum(1 for _ in fp_test_) == 1)
  1457. # ensure, the replacement succeeded
  1458. with salt.utils.files.fopen(path_test, "r") as fp_test_:
  1459. self.assertTrue(fp_test_.read().startswith("en_US.UTF-8"))
  1460. # ensure, all runs of 'file.replace' reported success
  1461. for item in ret:
  1462. self.assertSaltTrueReturn(item)
  1463. @with_tempdir()
  1464. def test_replace_issue_18612_prepend(self, base_dir):
  1465. """
  1466. Test the (mis-)behaviour of file.replace as described in #18612:
  1467. Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
  1468. an infinitely growing file as 'file.replace' didn't check beforehand
  1469. whether the changes had already been done to the file
  1470. # Case description:
  1471. The tested multifile contains multiple lines not matching the pattern or replacement in any way
  1472. The replacement pattern should be prepended to the file
  1473. """
  1474. test_name = "test_replace_issue_18612_prepend"
  1475. path_in = os.path.join(
  1476. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1477. )
  1478. path_out = os.path.join(
  1479. RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
  1480. )
  1481. path_test = os.path.join(base_dir, test_name)
  1482. # create test file based on initial template
  1483. shutil.copyfile(path_in, path_test)
  1484. ret = []
  1485. for x in range(0, 3):
  1486. ret.append(
  1487. self.run_state(
  1488. "file.replace",
  1489. name=path_test,
  1490. pattern="^# en_US.UTF-8$",
  1491. repl="en_US.UTF-8",
  1492. prepend_if_not_found=True,
  1493. )
  1494. )
  1495. # ensure, the resulting file contains the expected lines
  1496. self.assertTrue(filecmp.cmp(path_test, path_out))
  1497. # ensure the initial file was properly backed up
  1498. self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
  1499. # ensure, all runs of 'file.replace' reported success
  1500. for item in ret:
  1501. self.assertSaltTrueReturn(item)
  1502. @with_tempdir()
  1503. def test_replace_issue_18612_append(self, base_dir):
  1504. """
  1505. Test the (mis-)behaviour of file.replace as described in #18612:
  1506. Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
  1507. an infinitely growing file as 'file.replace' didn't check beforehand
  1508. whether the changes had already been done to the file
  1509. # Case description:
  1510. The tested multifile contains multiple lines not matching the pattern or replacement in any way
  1511. The replacement pattern should be appended to the file
  1512. """
  1513. test_name = "test_replace_issue_18612_append"
  1514. path_in = os.path.join(
  1515. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1516. )
  1517. path_out = os.path.join(
  1518. RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
  1519. )
  1520. path_test = os.path.join(base_dir, test_name)
  1521. # create test file based on initial template
  1522. shutil.copyfile(path_in, path_test)
  1523. ret = []
  1524. for x in range(0, 3):
  1525. ret.append(
  1526. self.run_state(
  1527. "file.replace",
  1528. name=path_test,
  1529. pattern="^# en_US.UTF-8$",
  1530. repl="en_US.UTF-8",
  1531. append_if_not_found=True,
  1532. )
  1533. )
  1534. # ensure, the resulting file contains the expected lines
  1535. self.assertTrue(filecmp.cmp(path_test, path_out))
  1536. # ensure the initial file was properly backed up
  1537. self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
  1538. # ensure, all runs of 'file.replace' reported success
  1539. for item in ret:
  1540. self.assertSaltTrueReturn(item)
  1541. @with_tempdir()
  1542. def test_replace_issue_18612_append_not_found_content(self, base_dir):
  1543. """
  1544. Test the (mis-)behaviour of file.replace as described in #18612:
  1545. Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
  1546. an infinitely growing file as 'file.replace' didn't check beforehand
  1547. whether the changes had already been done to the file
  1548. # Case description:
  1549. The tested multifile contains multiple lines not matching the pattern or replacement in any way
  1550. The 'not_found_content' value should be appended to the file
  1551. """
  1552. test_name = "test_replace_issue_18612_append_not_found_content"
  1553. path_in = os.path.join(
  1554. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1555. )
  1556. path_out = os.path.join(
  1557. RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
  1558. )
  1559. path_test = os.path.join(base_dir, test_name)
  1560. # create test file based on initial template
  1561. shutil.copyfile(path_in, path_test)
  1562. ret = []
  1563. for x in range(0, 3):
  1564. ret.append(
  1565. self.run_state(
  1566. "file.replace",
  1567. name=path_test,
  1568. pattern="^# en_US.UTF-8$",
  1569. repl="en_US.UTF-8",
  1570. append_if_not_found=True,
  1571. not_found_content="THIS LINE WASN'T FOUND! SO WE'RE APPENDING IT HERE!",
  1572. )
  1573. )
  1574. # ensure, the resulting file contains the expected lines
  1575. self.assertTrue(filecmp.cmp(path_test, path_out))
  1576. # ensure the initial file was properly backed up
  1577. self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
  1578. # ensure, all runs of 'file.replace' reported success
  1579. for item in ret:
  1580. self.assertSaltTrueReturn(item)
  1581. @with_tempdir()
  1582. def test_replace_issue_18612_change_mid_line_with_comment(self, base_dir):
  1583. """
  1584. Test the (mis-)behaviour of file.replace as described in #18612:
  1585. Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
  1586. an infinitely growing file as 'file.replace' didn't check beforehand
  1587. whether the changes had already been done to the file
  1588. # Case description:
  1589. The tested file contains 5 key=value pairs
  1590. The commented key=value pair #foo=bar should be changed to foo=salt
  1591. The comment char (#) in front of foo=bar should be removed
  1592. """
  1593. test_name = "test_replace_issue_18612_change_mid_line_with_comment"
  1594. path_in = os.path.join(
  1595. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1596. )
  1597. path_out = os.path.join(
  1598. RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
  1599. )
  1600. path_test = os.path.join(base_dir, test_name)
  1601. # create test file based on initial template
  1602. shutil.copyfile(path_in, path_test)
  1603. ret = []
  1604. for x in range(0, 3):
  1605. ret.append(
  1606. self.run_state(
  1607. "file.replace",
  1608. name=path_test,
  1609. pattern="^#foo=bar($|(?=\r\n))",
  1610. repl="foo=salt",
  1611. append_if_not_found=True,
  1612. )
  1613. )
  1614. # ensure, the resulting file contains the expected lines
  1615. self.assertTrue(filecmp.cmp(path_test, path_out))
  1616. # ensure the initial file was properly backed up
  1617. self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
  1618. # ensure, all 'file.replace' runs reported success
  1619. for item in ret:
  1620. self.assertSaltTrueReturn(item)
  1621. @with_tempdir()
  1622. def test_replace_issue_18841_no_changes(self, base_dir):
  1623. """
  1624. Test the (mis-)behaviour of file.replace as described in #18841:
  1625. Using file.replace in a way which shouldn't modify the file at all
  1626. results in changed mtime of the original file and a backup file being created.
  1627. # Case description
  1628. The tested file contains multiple lines
  1629. The tested file contains a line already matching the replacement (no change needed)
  1630. The tested file's content shouldn't change at all
  1631. The tested file's mtime shouldn't change at all
  1632. No backup file should be created
  1633. """
  1634. test_name = "test_replace_issue_18841_no_changes"
  1635. path_in = os.path.join(
  1636. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1637. )
  1638. path_test = os.path.join(base_dir, test_name)
  1639. # create test file based on initial template
  1640. shutil.copyfile(path_in, path_test)
  1641. # get (m|a)time of file
  1642. fstats_orig = os.stat(path_test)
  1643. # define how far we predate the file
  1644. age = 5 * 24 * 60 * 60
  1645. # set (m|a)time of file 5 days into the past
  1646. os.utime(path_test, (fstats_orig.st_mtime - age, fstats_orig.st_atime - age))
  1647. ret = self.run_state(
  1648. "file.replace",
  1649. name=path_test,
  1650. pattern="^hello world$",
  1651. repl="goodbye world",
  1652. show_changes=True,
  1653. flags=["IGNORECASE"],
  1654. backup=False,
  1655. )
  1656. # get (m|a)time of file
  1657. fstats_post = os.stat(path_test)
  1658. # ensure, the file content didn't change
  1659. self.assertTrue(filecmp.cmp(path_in, path_test))
  1660. # ensure no backup file was created
  1661. self.assertFalse(os.path.exists(path_test + ".bak"))
  1662. # ensure the file's mtime didn't change
  1663. self.assertTrue(fstats_post.st_mtime, fstats_orig.st_mtime - age)
  1664. # ensure, all 'file.replace' runs reported success
  1665. self.assertSaltTrueReturn(ret)
  1666. def test_serialize(self):
  1667. """
  1668. Test to ensure that file.serialize returns a data structure that's
  1669. both serialized and formatted properly
  1670. """
  1671. path_test = self.tmp_dir / "test_serialize"
  1672. self.addCleanup(salt.utils.files.safe_rm, str(path_test))
  1673. ret = self.run_state(
  1674. "file.serialize",
  1675. name=str(path_test),
  1676. dataset={
  1677. "name": "naive",
  1678. "description": "A basic test",
  1679. "a_list": ["first_element", "second_element"],
  1680. "finally": "the last item",
  1681. },
  1682. formatter="json",
  1683. )
  1684. serialized_file = salt.utils.stringutils.to_unicode(path_test.read_bytes())
  1685. # The JSON serializer uses LF even on OSes where os.sep is CRLF.
  1686. expected_file = "\n".join(
  1687. [
  1688. "{",
  1689. ' "a_list": [',
  1690. ' "first_element",',
  1691. ' "second_element"',
  1692. " ],",
  1693. ' "description": "A basic test",',
  1694. ' "finally": "the last item",',
  1695. ' "name": "naive"',
  1696. "}",
  1697. "",
  1698. ]
  1699. )
  1700. self.assertEqual(serialized_file, expected_file)
  1701. @with_tempfile(create=False)
  1702. def test_serializer_deserializer_opts(self, name):
  1703. """
  1704. Test the serializer_opts and deserializer_opts options
  1705. """
  1706. data1 = {"foo": {"bar": "%(x)s"}}
  1707. data2 = {"foo": {"abc": 123}}
  1708. merged = {"foo": {"y": "not_used", "x": "baz", "abc": 123, "bar": "baz"}}
  1709. ret = self.run_state(
  1710. "file.serialize",
  1711. name=name,
  1712. dataset=data1,
  1713. formatter="configparser",
  1714. deserializer_opts=[{"defaults": {"y": "not_used"}}],
  1715. )
  1716. ret = ret[next(iter(ret))]
  1717. assert ret["result"], ret
  1718. # We should have warned about deserializer_opts being used when
  1719. # merge_if_exists was not set to True.
  1720. assert "warnings" in ret
  1721. # Run with merge_if_exists, as well as serializer and deserializer opts
  1722. # deserializer opts will be used for string interpolation of the %(x)s
  1723. # that was written to the file with data1 (i.e. bar should become baz)
  1724. ret = self.run_state(
  1725. "file.serialize",
  1726. name=name,
  1727. dataset=data2,
  1728. formatter="configparser",
  1729. merge_if_exists=True,
  1730. serializer_opts=[{"defaults": {"y": "not_used"}}],
  1731. deserializer_opts=[{"defaults": {"x": "baz"}}],
  1732. )
  1733. ret = ret[next(iter(ret))]
  1734. assert ret["result"], ret
  1735. with salt.utils.files.fopen(name) as fp_:
  1736. serialized_data = salt.serializers.configparser.deserialize(fp_)
  1737. # If this test fails, this debug logging will help tell us how the
  1738. # serialized data differs from what was serialized.
  1739. log.debug("serialized_data = %r", serialized_data)
  1740. log.debug("merged = %r", merged)
  1741. # serializing with a default of 'y' will add y = not_used into foo
  1742. assert serialized_data["foo"]["y"] == merged["foo"]["y"]
  1743. # deserializing with default of x = baz will perform interpolation on %(x)s
  1744. # and bar will then = baz
  1745. assert serialized_data["foo"]["bar"] == merged["foo"]["bar"]
  1746. @with_tempfile(create=False)
  1747. def test_serializer_plist_binary_file_open(self, name):
  1748. """
  1749. Test the serialization and deserialization of plists which should include
  1750. the "rb" file open arguments change specifically for this formatter to handle
  1751. binary plists.
  1752. """
  1753. data1 = {"foo": {"bar": "%(x)s"}}
  1754. data2 = {"foo": {"abc": 123}}
  1755. merged = {"foo": {"abc": 123, "bar": "%(x)s"}}
  1756. ret = self.run_state(
  1757. "file.serialize",
  1758. name=name,
  1759. dataset=data1,
  1760. formatter="plist",
  1761. serializer_opts=[{"fmt": "FMT_BINARY"}],
  1762. )
  1763. ret = ret[next(iter(ret))]
  1764. assert ret["result"], ret
  1765. # Run with merge_if_exists so we test the deserializer.
  1766. ret = self.run_state(
  1767. "file.serialize",
  1768. name=name,
  1769. dataset=data2,
  1770. formatter="plist",
  1771. merge_if_exists=True,
  1772. serializer_opts=[{"fmt": "FMT_BINARY"}],
  1773. )
  1774. ret = ret[next(iter(ret))]
  1775. assert ret["result"], ret
  1776. with salt.utils.files.fopen(name, "rb") as fp_:
  1777. serialized_data = salt.serializers.plist.deserialize(fp_)
  1778. # make sure our serialized data matches what we expect
  1779. assert serialized_data["foo"] == merged["foo"]
  1780. @with_tempfile(create=False)
  1781. def test_serializer_plist_file_open(self, name):
  1782. """
  1783. Test the serialization and deserialization of non binary plists with
  1784. the new line concatenation.
  1785. """
  1786. data1 = {"foo": {"bar": "%(x)s"}}
  1787. data2 = {"foo": {"abc": 123}}
  1788. merged = {"foo": {"abc": 123, "bar": "%(x)s"}}
  1789. ret = self.run_state(
  1790. "file.serialize", name=name, dataset=data1, formatter="plist",
  1791. )
  1792. ret = ret[next(iter(ret))]
  1793. assert ret["result"], ret
  1794. # Run with merge_if_exists so we test the deserializer.
  1795. ret = self.run_state(
  1796. "file.serialize",
  1797. name=name,
  1798. dataset=data2,
  1799. formatter="plist",
  1800. merge_if_exists=True,
  1801. )
  1802. ret = ret[next(iter(ret))]
  1803. assert ret["result"], ret
  1804. with salt.utils.files.fopen(name, "rb") as fp_:
  1805. serialized_data = salt.serializers.plist.deserialize(fp_)
  1806. # make sure our serialized data matches what we expect
  1807. assert serialized_data["foo"] == merged["foo"]
  1808. @with_tempdir()
  1809. def test_replace_issue_18841_omit_backup(self, base_dir):
  1810. """
  1811. Test the (mis-)behaviour of file.replace as described in #18841:
  1812. Using file.replace in a way which shouldn't modify the file at all
  1813. results in changed mtime of the original file and a backup file being created.
  1814. # Case description
  1815. The tested file contains multiple lines
  1816. The tested file contains a line already matching the replacement (no change needed)
  1817. The tested file's content shouldn't change at all
  1818. The tested file's mtime shouldn't change at all
  1819. No backup file should be created, although backup=False isn't explicitly defined
  1820. """
  1821. test_name = "test_replace_issue_18841_omit_backup"
  1822. path_in = os.path.join(
  1823. RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
  1824. )
  1825. path_test = os.path.join(base_dir, test_name)
  1826. # create test file based on initial template
  1827. shutil.copyfile(path_in, path_test)
  1828. # get (m|a)time of file
  1829. fstats_orig = os.stat(path_test)
  1830. # define how far we predate the file
  1831. age = 5 * 24 * 60 * 60
  1832. # set (m|a)time of file 5 days into the past
  1833. os.utime(path_test, (fstats_orig.st_mtime - age, fstats_orig.st_atime - age))
  1834. ret = self.run_state(
  1835. "file.replace",
  1836. name=path_test,
  1837. pattern="^hello world$",
  1838. repl="goodbye world",
  1839. show_changes=True,
  1840. flags=["IGNORECASE"],
  1841. )
  1842. # get (m|a)time of file
  1843. fstats_post = os.stat(path_test)
  1844. # ensure, the file content didn't change
  1845. self.assertTrue(filecmp.cmp(path_in, path_test))
  1846. # ensure no backup file was created
  1847. self.assertFalse(os.path.exists(path_test + ".bak"))
  1848. # ensure the file's mtime didn't change
  1849. self.assertTrue(fstats_post.st_mtime, fstats_orig.st_mtime - age)
  1850. # ensure, all 'file.replace' runs reported success
  1851. self.assertSaltTrueReturn(ret)
  1852. @with_tempfile()
  1853. def test_comment(self, name):
  1854. """
  1855. file.comment
  1856. """
  1857. # write a line to file
  1858. with salt.utils.files.fopen(name, "w+") as fp_:
  1859. fp_.write("comment_me")
  1860. # Look for changes with test=True: return should be "None" at the first run
  1861. ret = self.run_state("file.comment", test=True, name=name, regex="^comment")
  1862. self.assertSaltNoneReturn(ret)
  1863. # comment once
  1864. ret = self.run_state("file.comment", name=name, regex="^comment")
  1865. # result is positive
  1866. self.assertSaltTrueReturn(ret)
  1867. # line is commented
  1868. with salt.utils.files.fopen(name, "r") as fp_:
  1869. self.assertTrue(fp_.read().startswith("#comment"))
  1870. # comment twice
  1871. ret = self.run_state("file.comment", name=name, regex="^comment")
  1872. # result is still positive
  1873. self.assertSaltTrueReturn(ret)
  1874. # line is still commented
  1875. with salt.utils.files.fopen(name, "r") as fp_:
  1876. self.assertTrue(fp_.read().startswith("#comment"))
  1877. # Test previously commented file returns "True" now and not "None" with test=True
  1878. ret = self.run_state("file.comment", test=True, name=name, regex="^comment")
  1879. self.assertSaltTrueReturn(ret)
  1880. @with_tempfile()
  1881. def test_test_comment(self, name):
  1882. """
  1883. file.comment test interface
  1884. """
  1885. with salt.utils.files.fopen(name, "w+") as fp_:
  1886. fp_.write("comment_me")
  1887. ret = self.run_state("file.comment", test=True, name=name, regex=".*comment.*",)
  1888. with salt.utils.files.fopen(name, "r") as fp_:
  1889. self.assertNotIn("#comment", fp_.read())
  1890. self.assertSaltNoneReturn(ret)
  1891. @with_tempfile()
  1892. def test_uncomment(self, name):
  1893. """
  1894. file.uncomment
  1895. """
  1896. with salt.utils.files.fopen(name, "w+") as fp_:
  1897. fp_.write("#comment_me")
  1898. ret = self.run_state("file.uncomment", name=name, regex="^comment")
  1899. with salt.utils.files.fopen(name, "r") as fp_:
  1900. self.assertNotIn("#comment", fp_.read())
  1901. self.assertSaltTrueReturn(ret)
  1902. @with_tempfile()
  1903. def test_test_uncomment(self, name):
  1904. """
  1905. file.comment test interface
  1906. """
  1907. with salt.utils.files.fopen(name, "w+") as fp_:
  1908. fp_.write("#comment_me")
  1909. ret = self.run_state("file.uncomment", test=True, name=name, regex="^comment.*")
  1910. with salt.utils.files.fopen(name, "r") as fp_:
  1911. self.assertIn("#comment", fp_.read())
  1912. self.assertSaltNoneReturn(ret)
  1913. @with_tempfile()
  1914. def test_append(self, name):
  1915. """
  1916. file.append
  1917. """
  1918. with salt.utils.files.fopen(name, "w+") as fp_:
  1919. fp_.write("#salty!")
  1920. ret = self.run_state("file.append", name=name, text="cheese")
  1921. with salt.utils.files.fopen(name, "r") as fp_:
  1922. self.assertIn("cheese", fp_.read())
  1923. self.assertSaltTrueReturn(ret)
  1924. @with_tempfile()
  1925. def test_test_append(self, name):
  1926. """
  1927. file.append test interface
  1928. """
  1929. with salt.utils.files.fopen(name, "w+") as fp_:
  1930. fp_.write("#salty!")
  1931. ret = self.run_state("file.append", test=True, name=name, text="cheese")
  1932. with salt.utils.files.fopen(name, "r") as fp_:
  1933. self.assertNotIn("cheese", fp_.read())
  1934. self.assertSaltNoneReturn(ret)
  1935. @with_tempdir()
  1936. def test_append_issue_1864_makedirs(self, base_dir):
  1937. """
  1938. file.append but create directories if needed as an option, and create
  1939. the file if it doesn't exist
  1940. """
  1941. fname = "append_issue_1864_makedirs"
  1942. name = os.path.join(base_dir, fname)
  1943. # Non existing file get's touched
  1944. ret = self.run_state("file.append", name=name, text="cheese", makedirs=True)
  1945. self.assertSaltTrueReturn(ret)
  1946. # Nested directory and file get's touched
  1947. name = os.path.join(base_dir, "issue_1864", fname)
  1948. ret = self.run_state("file.append", name=name, text="cheese", makedirs=True)
  1949. self.assertSaltTrueReturn(ret)
  1950. # Parent directory exists but file does not and makedirs is False
  1951. name = os.path.join(base_dir, "issue_1864", fname + "2")
  1952. ret = self.run_state("file.append", name=name, text="cheese")
  1953. self.assertSaltTrueReturn(ret)
  1954. self.assertTrue(os.path.isfile(name))
  1955. @with_tempdir()
  1956. def test_prepend_issue_27401_makedirs(self, base_dir):
  1957. """
  1958. file.prepend but create directories if needed as an option, and create
  1959. the file if it doesn't exist
  1960. """
  1961. fname = "prepend_issue_27401"
  1962. name = os.path.join(base_dir, fname)
  1963. # Non existing file get's touched
  1964. ret = self.run_state("file.prepend", name=name, text="cheese", makedirs=True)
  1965. self.assertSaltTrueReturn(ret)
  1966. # Nested directory and file get's touched
  1967. name = os.path.join(base_dir, "issue_27401", fname)
  1968. ret = self.run_state("file.prepend", name=name, text="cheese", makedirs=True)
  1969. self.assertSaltTrueReturn(ret)
  1970. # Parent directory exists but file does not and makedirs is False
  1971. name = os.path.join(base_dir, "issue_27401", fname + "2")
  1972. ret = self.run_state("file.prepend", name=name, text="cheese")
  1973. self.assertSaltTrueReturn(ret)
  1974. self.assertTrue(os.path.isfile(name))
  1975. @with_tempfile()
  1976. def test_touch(self, name):
  1977. """
  1978. file.touch
  1979. """
  1980. ret = self.run_state("file.touch", name=name)
  1981. self.assertTrue(os.path.isfile(name))
  1982. self.assertSaltTrueReturn(ret)
  1983. @with_tempfile(create=False)
  1984. def test_test_touch(self, name):
  1985. """
  1986. file.touch test interface
  1987. """
  1988. ret = self.run_state("file.touch", test=True, name=name)
  1989. self.assertFalse(os.path.isfile(name))
  1990. self.assertSaltNoneReturn(ret)
  1991. @with_tempdir()
  1992. def test_touch_directory(self, base_dir):
  1993. """
  1994. file.touch a directory
  1995. """
  1996. name = os.path.join(base_dir, "touch_test_dir")
  1997. os.mkdir(name)
  1998. ret = self.run_state("file.touch", name=name)
  1999. self.assertSaltTrueReturn(ret)
  2000. self.assertTrue(os.path.isdir(name))
  2001. @with_tempdir()
  2002. def test_issue_2227_file_append(self, base_dir):
  2003. """
  2004. Text to append includes a percent symbol
  2005. """
  2006. # let's make use of existing state to create a file with contents to
  2007. # test against
  2008. tmp_file_append = os.path.join(base_dir, "test.append")
  2009. self.run_state("file.touch", name=tmp_file_append)
  2010. self.run_state(
  2011. "file.append", name=tmp_file_append, source="salt://testappend/firstif"
  2012. )
  2013. self.run_state(
  2014. "file.append", name=tmp_file_append, source="salt://testappend/secondif"
  2015. )
  2016. # Now our real test
  2017. try:
  2018. ret = self.run_state(
  2019. "file.append", name=tmp_file_append, text="HISTTIMEFORMAT='%F %T '"
  2020. )
  2021. self.assertSaltTrueReturn(ret)
  2022. with salt.utils.files.fopen(tmp_file_append, "r") as fp_:
  2023. contents_pre = fp_.read()
  2024. # It should not append text again
  2025. ret = self.run_state(
  2026. "file.append", name=tmp_file_append, text="HISTTIMEFORMAT='%F %T '"
  2027. )
  2028. self.assertSaltTrueReturn(ret)
  2029. with salt.utils.files.fopen(tmp_file_append, "r") as fp_:
  2030. contents_post = fp_.read()
  2031. self.assertEqual(contents_pre, contents_post)
  2032. except AssertionError:
  2033. if os.path.exists(tmp_file_append):
  2034. shutil.copy(tmp_file_append, tmp_file_append + ".bak")
  2035. raise
  2036. @with_tempdir()
  2037. def test_issue_2401_file_comment(self, base_dir):
  2038. # Get a path to the temporary file
  2039. tmp_file = os.path.join(base_dir, "issue-2041-comment.txt")
  2040. # Write some data to it
  2041. with salt.utils.files.fopen(tmp_file, "w") as fp_:
  2042. fp_.write("hello\nworld\n")
  2043. # create the sls template
  2044. template_lines = [
  2045. "{}:".format(tmp_file),
  2046. " file.comment:",
  2047. " - regex: ^world",
  2048. ]
  2049. template = "\n".join(template_lines)
  2050. try:
  2051. ret = self.run_function("state.template_str", [template], timeout=120)
  2052. self.assertSaltTrueReturn(ret)
  2053. self.assertNotInSaltComment("Pattern already commented", ret)
  2054. self.assertInSaltComment("Commented lines successfully", ret)
  2055. # This next time, it is already commented.
  2056. ret = self.run_function("state.template_str", [template], timeout=120)
  2057. self.assertSaltTrueReturn(ret)
  2058. self.assertInSaltComment("Pattern already commented", ret)
  2059. except AssertionError:
  2060. shutil.copy(tmp_file, tmp_file + ".bak")
  2061. raise
  2062. @with_tempdir()
  2063. def test_issue_2379_file_append(self, base_dir):
  2064. # Get a path to the temporary file
  2065. tmp_file = os.path.join(base_dir, "issue-2379-file-append.txt")
  2066. # Write some data to it
  2067. with salt.utils.files.fopen(tmp_file, "w") as fp_:
  2068. fp_.write(
  2069. "hello\nworld\n" # Some junk
  2070. "#PermitRootLogin yes\n" # Commented text
  2071. "# PermitRootLogin yes\n" # Commented text with space
  2072. )
  2073. # create the sls template
  2074. template_lines = [
  2075. "{}:".format(tmp_file),
  2076. " file.append:",
  2077. " - text: PermitRootLogin yes",
  2078. ]
  2079. template = "\n".join(template_lines)
  2080. try:
  2081. ret = self.run_function("state.template_str", [template])
  2082. self.assertSaltTrueReturn(ret)
  2083. self.assertInSaltComment("Appended 1 lines", ret)
  2084. except AssertionError:
  2085. shutil.copy(tmp_file, tmp_file + ".bak")
  2086. raise
  2087. @skipIf(IS_WINDOWS, "Mode not available in Windows")
  2088. @with_tempdir(create=False)
  2089. @with_tempdir(create=False)
  2090. def test_issue_2726_mode_kwarg(self, dir1, dir2):
  2091. # Let's test for the wrong usage approach
  2092. bad_mode_kwarg_testfile = os.path.join(dir1, "bad_mode_kwarg", "testfile")
  2093. bad_template = [
  2094. "{}:".format(bad_mode_kwarg_testfile),
  2095. " file.recurse:",
  2096. " - source: salt://testfile",
  2097. " - mode: 644",
  2098. ]
  2099. ret = self.run_function("state.template_str", [os.linesep.join(bad_template)])
  2100. self.assertSaltFalseReturn(ret)
  2101. self.assertInSaltComment(
  2102. "'mode' is not allowed in 'file.recurse'. Please use "
  2103. "'file_mode' and 'dir_mode'.",
  2104. ret,
  2105. )
  2106. self.assertNotInSaltComment(
  2107. "TypeError: managed() got multiple values for keyword " "argument 'mode'",
  2108. ret,
  2109. )
  2110. # Now, the correct usage approach
  2111. good_mode_kwargs_testfile = os.path.join(dir2, "good_mode_kwargs", "testappend")
  2112. good_template = [
  2113. "{}:".format(good_mode_kwargs_testfile),
  2114. " file.recurse:",
  2115. " - source: salt://testappend",
  2116. " - dir_mode: 744",
  2117. " - file_mode: 644",
  2118. ]
  2119. ret = self.run_function("state.template_str", [os.linesep.join(good_template)])
  2120. self.assertSaltTrueReturn(ret)
  2121. @with_tempdir()
  2122. def test_issue_8343_accumulated_require_in(self, base_dir):
  2123. template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-8343.sls")
  2124. testcase_filedest = os.path.join(base_dir, "issue-8343.txt")
  2125. if os.path.exists(template_path):
  2126. os.remove(template_path)
  2127. if os.path.exists(testcase_filedest):
  2128. os.remove(testcase_filedest)
  2129. sls_template = [
  2130. "{0}:",
  2131. " file.managed:",
  2132. " - contents: |",
  2133. " #",
  2134. "",
  2135. "prepend-foo-accumulator-from-pillar:",
  2136. " file.accumulated:",
  2137. " - require_in:",
  2138. " - file: prepend-foo-management",
  2139. " - filename: {0}",
  2140. " - text: |",
  2141. " foo",
  2142. "",
  2143. "append-foo-accumulator-from-pillar:",
  2144. " file.accumulated:",
  2145. " - require_in:",
  2146. " - file: append-foo-management",
  2147. " - filename: {0}",
  2148. " - text: |",
  2149. " bar",
  2150. "",
  2151. "prepend-foo-management:",
  2152. " file.blockreplace:",
  2153. " - name: {0}",
  2154. ' - marker_start: "#-- start salt managed zonestart -- PLEASE, DO NOT EDIT"',
  2155. ' - marker_end: "#-- end salt managed zonestart --"',
  2156. " - content: ''",
  2157. " - prepend_if_not_found: True",
  2158. " - backup: '.bak'",
  2159. " - show_changes: True",
  2160. "",
  2161. "append-foo-management:",
  2162. " file.blockreplace:",
  2163. " - name: {0}",
  2164. ' - marker_start: "#-- start salt managed zoneend -- PLEASE, DO NOT EDIT"',
  2165. ' - marker_end: "#-- end salt managed zoneend --"',
  2166. " - content: ''",
  2167. " - append_if_not_found: True",
  2168. " - backup: '.bak2'",
  2169. " - show_changes: True",
  2170. "",
  2171. ]
  2172. with salt.utils.files.fopen(template_path, "w") as fp_:
  2173. fp_.write(os.linesep.join(sls_template).format(testcase_filedest))
  2174. ret = self.run_function("state.sls", mods="issue-8343")
  2175. for name, step in ret.items():
  2176. self.assertSaltTrueReturn({name: step})
  2177. with salt.utils.files.fopen(testcase_filedest) as fp_:
  2178. contents = fp_.read().split(os.linesep)
  2179. expected = [
  2180. "#-- start salt managed zonestart -- PLEASE, DO NOT EDIT",
  2181. "foo",
  2182. "#-- end salt managed zonestart --",
  2183. "#",
  2184. "#-- start salt managed zoneend -- PLEASE, DO NOT EDIT",
  2185. "bar",
  2186. "#-- end salt managed zoneend --",
  2187. "",
  2188. ]
  2189. self.assertEqual(
  2190. [salt.utils.stringutils.to_str(line) for line in expected], contents
  2191. )
  2192. @with_tempdir()
  2193. @skipIf(
  2194. salt.utils.platform.is_darwin() and six.PY2, "This test hangs on OS X on Py2"
  2195. )
  2196. def test_issue_11003_immutable_lazy_proxy_sum(self, base_dir):
  2197. # causes the Import-Module ServerManager error on Windows
  2198. template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-11003.sls")
  2199. testcase_filedest = os.path.join(base_dir, "issue-11003.txt")
  2200. sls_template = [
  2201. "a{0}:",
  2202. " file.absent:",
  2203. " - name: {0}",
  2204. "",
  2205. "{0}:",
  2206. " file.managed:",
  2207. " - contents: |",
  2208. " #",
  2209. "",
  2210. "test-acc1:",
  2211. " file.accumulated:",
  2212. " - require_in:",
  2213. " - file: final",
  2214. " - filename: {0}",
  2215. " - text: |",
  2216. " bar",
  2217. "",
  2218. "test-acc2:",
  2219. " file.accumulated:",
  2220. " - watch_in:",
  2221. " - file: final",
  2222. " - filename: {0}",
  2223. " - text: |",
  2224. " baz",
  2225. "",
  2226. "final:",
  2227. " file.blockreplace:",
  2228. " - name: {0}",
  2229. ' - marker_start: "#-- start managed zone PLEASE, DO NOT EDIT"',
  2230. ' - marker_end: "#-- end managed zone"',
  2231. " - content: ''",
  2232. " - append_if_not_found: True",
  2233. " - show_changes: True",
  2234. ]
  2235. with salt.utils.files.fopen(template_path, "w") as fp_:
  2236. fp_.write(os.linesep.join(sls_template).format(testcase_filedest))
  2237. ret = self.run_function("state.sls", mods="issue-11003", timeout=600)
  2238. for name, step in ret.items():
  2239. self.assertSaltTrueReturn({name: step})
  2240. with salt.utils.files.fopen(testcase_filedest) as fp_:
  2241. contents = fp_.read().split(os.linesep)
  2242. begin = contents.index("#-- start managed zone PLEASE, DO NOT EDIT") + 1
  2243. end = contents.index("#-- end managed zone")
  2244. block_contents = contents[begin:end]
  2245. for item in ("", "bar", "baz"):
  2246. block_contents.remove(item)
  2247. self.assertEqual(block_contents, [])
  2248. @with_tempdir()
  2249. def test_issue_8947_utf8_sls(self, base_dir):
  2250. """
  2251. Test some file operation with utf-8 characters on the sls
  2252. This is more generic than just a file test. Feel free to move
  2253. """
  2254. self.maxDiff = None
  2255. korean_1 = "한국어 시험"
  2256. korean_2 = "첫 번째 행"
  2257. korean_3 = "마지막 행"
  2258. test_file = os.path.join(base_dir, "{}.txt".format(korean_1))
  2259. test_file_encoded = test_file
  2260. template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-8947.sls")
  2261. # create the sls template
  2262. template = textwrap.dedent(
  2263. """\
  2264. some-utf8-file-create:
  2265. file.managed:
  2266. - name: {test_file}
  2267. - contents: {korean_1}
  2268. - makedirs: True
  2269. - replace: True
  2270. - show_diff: True
  2271. some-utf8-file-create2:
  2272. file.managed:
  2273. - name: {test_file}
  2274. - contents: |
  2275. {korean_2}
  2276. {korean_1}
  2277. {korean_3}
  2278. - replace: True
  2279. - show_diff: True
  2280. """.format(
  2281. **locals()
  2282. )
  2283. )
  2284. if not salt.utils.platform.is_windows():
  2285. template += textwrap.dedent(
  2286. """\
  2287. some-utf8-file-content-test:
  2288. cmd.run:
  2289. - name: 'cat "{test_file}"'
  2290. - require:
  2291. - file: some-utf8-file-create2
  2292. """.format(
  2293. **locals()
  2294. )
  2295. )
  2296. # Save template file
  2297. with salt.utils.files.fopen(template_path, "wb") as fp_:
  2298. fp_.write(salt.utils.stringutils.to_bytes(template))
  2299. try:
  2300. result = self.run_function("state.sls", mods="issue-8947")
  2301. if not isinstance(result, dict):
  2302. raise AssertionError(
  2303. (
  2304. "Something went really wrong while testing this sls:" " {}"
  2305. ).format(repr(result))
  2306. )
  2307. # difflib produces different output on python 2.6 than on >=2.7
  2308. if sys.version_info < (2, 7):
  2309. diff = "--- \n+++ \n@@ -1,1 +1,3 @@\n"
  2310. else:
  2311. diff = "--- \n+++ \n@@ -1 +1,3 @@\n"
  2312. diff += ("+첫 번째 행{0}" " 한국어 시험{0}" "+마지막 행{0}").format(os.linesep)
  2313. ret = {x.split("_|-")[1]: y for x, y in result.items()}
  2314. # Confirm initial creation of file
  2315. self.assertEqual(
  2316. ret["some-utf8-file-create"]["comment"],
  2317. "File {} updated".format(test_file_encoded),
  2318. )
  2319. self.assertEqual(
  2320. ret["some-utf8-file-create"]["changes"], {"diff": "New file"}
  2321. )
  2322. # Confirm file was modified and that the diff was as expected
  2323. self.assertEqual(
  2324. ret["some-utf8-file-create2"]["comment"],
  2325. "File {} updated".format(test_file_encoded),
  2326. )
  2327. self.assertEqual(ret["some-utf8-file-create2"]["changes"], {"diff": diff})
  2328. if salt.utils.platform.is_windows():
  2329. import subprocess
  2330. import win32api
  2331. p = subprocess.Popen(
  2332. salt.utils.stringutils.to_str(
  2333. "type {}".format(win32api.GetShortPathName(test_file))
  2334. ),
  2335. shell=True,
  2336. stdout=subprocess.PIPE,
  2337. stderr=subprocess.PIPE,
  2338. )
  2339. p.poll()
  2340. out = p.stdout.read()
  2341. self.assertEqual(
  2342. out.decode("utf-8"),
  2343. os.linesep.join((korean_2, korean_1, korean_3)) + os.linesep,
  2344. )
  2345. else:
  2346. self.assertEqual(
  2347. ret["some-utf8-file-content-test"]["comment"],
  2348. 'Command "cat "{}"" run'.format(test_file_encoded),
  2349. )
  2350. self.assertEqual(
  2351. ret["some-utf8-file-content-test"]["changes"]["stdout"],
  2352. "\n".join((korean_2, korean_1, korean_3)),
  2353. )
  2354. finally:
  2355. try:
  2356. os.remove(template_path)
  2357. except OSError:
  2358. pass
  2359. @skip_if_not_root
  2360. @skipIf(not HAS_PWD, "pwd not available. Skipping test")
  2361. @skipIf(not HAS_GRP, "grp not available. Skipping test")
  2362. @with_system_user_and_group(
  2363. TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
  2364. )
  2365. @with_tempdir()
  2366. @skipIf(salt.utils.platform.is_freebsd(), "Test is failing on FreeBSD")
  2367. def test_issue_12209_follow_symlinks(self, tempdir, user, group):
  2368. """
  2369. Ensure that symlinks are properly chowned when recursing (following
  2370. symlinks)
  2371. """
  2372. # Make the directories for this test
  2373. onedir = os.path.join(tempdir, "one")
  2374. twodir = os.path.join(tempdir, "two")
  2375. os.mkdir(onedir)
  2376. os.symlink(onedir, twodir)
  2377. # Run the state
  2378. ret = self.run_state(
  2379. "file.directory",
  2380. name=tempdir,
  2381. follow_symlinks=True,
  2382. user=user,
  2383. group=group,
  2384. recurse=["user", "group"],
  2385. )
  2386. self.assertSaltTrueReturn(ret)
  2387. # Double-check, in case state mis-reported a True result. Since we are
  2388. # following symlinks, we expect twodir to still be owned by root, but
  2389. # onedir should be owned by the 'issue12209' user.
  2390. onestats = os.stat(onedir)
  2391. twostats = os.lstat(twodir)
  2392. self.assertEqual(pwd.getpwuid(onestats.st_uid).pw_name, user)
  2393. self.assertEqual(pwd.getpwuid(twostats.st_uid).pw_name, "root")
  2394. self.assertEqual(grp.getgrgid(onestats.st_gid).gr_name, group)
  2395. if salt.utils.path.which("id"):
  2396. root_group = self.run_function("user.primary_group", ["root"])
  2397. self.assertEqual(grp.getgrgid(twostats.st_gid).gr_name, root_group)
  2398. @skip_if_not_root
  2399. @skipIf(not HAS_PWD, "pwd not available. Skipping test")
  2400. @skipIf(not HAS_GRP, "grp not available. Skipping test")
  2401. @with_system_user_and_group(
  2402. TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
  2403. )
  2404. @with_tempdir()
  2405. def test_issue_12209_no_follow_symlinks(self, tempdir, user, group):
  2406. """
  2407. Ensure that symlinks are properly chowned when recursing (not following
  2408. symlinks)
  2409. """
  2410. # Make the directories for this test
  2411. onedir = os.path.join(tempdir, "one")
  2412. twodir = os.path.join(tempdir, "two")
  2413. os.mkdir(onedir)
  2414. os.symlink(onedir, twodir)
  2415. # Run the state
  2416. ret = self.run_state(
  2417. "file.directory",
  2418. name=tempdir,
  2419. follow_symlinks=False,
  2420. user=user,
  2421. group=group,
  2422. recurse=["user", "group"],
  2423. )
  2424. self.assertSaltTrueReturn(ret)
  2425. # Double-check, in case state mis-reported a True result. Since we
  2426. # are not following symlinks, we expect twodir to now be owned by
  2427. # the 'issue12209' user, just link onedir.
  2428. onestats = os.stat(onedir)
  2429. twostats = os.lstat(twodir)
  2430. self.assertEqual(pwd.getpwuid(onestats.st_uid).pw_name, user)
  2431. self.assertEqual(pwd.getpwuid(twostats.st_uid).pw_name, user)
  2432. self.assertEqual(grp.getgrgid(onestats.st_gid).gr_name, group)
  2433. self.assertEqual(grp.getgrgid(twostats.st_gid).gr_name, group)
  2434. @with_tempfile(create=False)
  2435. @with_tempfile()
  2436. def test_template_local_file(self, source, dest):
  2437. """
  2438. Test a file.managed state with a local file as the source. Test both
  2439. with the file:// protocol designation prepended, and without it.
  2440. """
  2441. with salt.utils.files.fopen(source, "w") as fp_:
  2442. fp_.write("{{ foo }}\n")
  2443. for prefix in ("file://", ""):
  2444. ret = self.run_state(
  2445. "file.managed",
  2446. name=dest,
  2447. source=prefix + source,
  2448. template="jinja",
  2449. context={"foo": "Hello world!"},
  2450. )
  2451. self.assertSaltTrueReturn(ret)
  2452. @with_tempfile()
  2453. def test_template_local_file_noclobber(self, source):
  2454. """
  2455. Test the case where a source file is in the minion's local filesystem,
  2456. and the source path is the same as the destination path.
  2457. """
  2458. with salt.utils.files.fopen(source, "w") as fp_:
  2459. fp_.write("{{ foo }}\n")
  2460. ret = self.run_state(
  2461. "file.managed",
  2462. name=source,
  2463. source=source,
  2464. template="jinja",
  2465. context={"foo": "Hello world!"},
  2466. )
  2467. self.assertSaltFalseReturn(ret)
  2468. self.assertIn(
  2469. ("Source file cannot be the same as destination"),
  2470. ret[next(iter(ret))]["comment"],
  2471. )
  2472. @with_tempfile(create=False)
  2473. @with_tempfile(create=False)
  2474. def test_issue_25250_force_copy_deletes(self, source, dest):
  2475. """
  2476. ensure force option in copy state does not delete target file
  2477. """
  2478. shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "hosts"), source)
  2479. shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "file/base/cheese"), dest)
  2480. self.run_state("file.copy", name=dest, source=source, force=True)
  2481. self.assertTrue(os.path.exists(dest))
  2482. self.assertTrue(filecmp.cmp(source, dest))
  2483. os.remove(source)
  2484. os.remove(dest)
  2485. @destructiveTest
  2486. @skip_if_not_root
  2487. @skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
  2488. @with_tempfile()
  2489. def test_file_copy_make_dirs(self, source):
  2490. """
  2491. ensure make_dirs creates correct user perms
  2492. """
  2493. shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "hosts"), source)
  2494. dest = self.tmp_dir / "dir1" / "dir2" / "copied_file.txt"
  2495. self.addCleanup(salt.utils.files.rm_rf, str(dest.parent.parent))
  2496. user = "salt"
  2497. mode = "0644"
  2498. ret = self.run_function("user.add", [user])
  2499. self.assertTrue(ret, "Failed to add user. Are you running as sudo?")
  2500. ret = self.run_state(
  2501. "file.copy",
  2502. name=str(dest),
  2503. source=source,
  2504. user=user,
  2505. makedirs=True,
  2506. mode=mode,
  2507. )
  2508. self.assertSaltTrueReturn(ret)
  2509. file_checks = [str(dest), str(dest.parent), str(dest.parent.parent)]
  2510. for check in file_checks:
  2511. user_check = self.run_function("file.get_user", [check])
  2512. mode_check = self.run_function("file.get_mode", [check])
  2513. self.assertEqual(user_check, user)
  2514. self.assertEqual(salt.utils.files.normalize_mode(mode_check), mode)
  2515. def test_contents_pillar_with_pillar_list(self):
  2516. """
  2517. This tests for any regressions for this issue:
  2518. https://github.com/saltstack/salt/issues/30934
  2519. """
  2520. state_file = "file_contents_pillar"
  2521. ret = self.run_function("state.sls", mods=state_file)
  2522. self.assertSaltTrueReturn(ret)
  2523. @skip_if_not_root
  2524. @skipIf(not HAS_PWD, "pwd not available. Skipping test")
  2525. @skipIf(not HAS_GRP, "grp not available. Skipping test")
  2526. @with_system_user_and_group(
  2527. TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
  2528. )
  2529. def test_owner_after_setuid(self, user, group):
  2530. """
  2531. Test to check file user/group after setting setuid or setgid.
  2532. Because Python os.chown() does reset the setuid/setgid to 0.
  2533. https://github.com/saltstack/salt/pull/45257
  2534. """
  2535. # Desired configuration.
  2536. desired_file = self.tmp_dir / "file_with_setuid"
  2537. self.addCleanup(salt.utils.files.safe_rm, str(desired_file))
  2538. desired = {
  2539. "file": str(desired_file),
  2540. "user": user,
  2541. "group": group,
  2542. "mode": "4750",
  2543. }
  2544. # Run the state.
  2545. ret = self.run_state(
  2546. "file.managed",
  2547. name=desired["file"],
  2548. user=desired["user"],
  2549. group=desired["group"],
  2550. mode=desired["mode"],
  2551. )
  2552. # Check result.
  2553. file_stat = desired_file.stat()
  2554. result = {
  2555. "user": pwd.getpwuid(file_stat.st_uid).pw_name,
  2556. "group": grp.getgrgid(file_stat.st_gid).gr_name,
  2557. "mode": oct(stat.S_IMODE(file_stat.st_mode)),
  2558. }
  2559. self.assertSaltTrueReturn(ret)
  2560. self.assertEqual(desired["user"], result["user"])
  2561. self.assertEqual(desired["group"], result["group"])
  2562. self.assertEqual(desired["mode"], result["mode"].lstrip("0Oo"))
  2563. def test_binary_contents(self):
  2564. """
  2565. This tests to ensure that binary contents do not cause a traceback.
  2566. """
  2567. name = self.tmp_dir / "1px.gif"
  2568. self.addCleanup(salt.utils.files.safe_rm, str(name))
  2569. ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
  2570. self.assertSaltTrueReturn(ret)
  2571. def test_binary_contents_twice(self):
  2572. """
  2573. This test ensures that after a binary file is created, salt can confirm
  2574. that the file is in the correct state.
  2575. """
  2576. # Create a binary file
  2577. name = self.tmp_dir / "1px.gif"
  2578. self.addCleanup(salt.utils.files.safe_rm, str(name))
  2579. # First run state ensures file is created
  2580. ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
  2581. self.assertSaltTrueReturn(ret)
  2582. # Second run of state ensures file is in correct state
  2583. ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
  2584. self.assertSaltTrueReturn(ret)
  2585. @skip_if_not_root
  2586. @skipIf(not HAS_PWD, "pwd not available. Skipping test")
  2587. @skipIf(not HAS_GRP, "grp not available. Skipping test")
  2588. @with_system_user_and_group(
  2589. TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
  2590. )
  2591. @with_tempdir()
  2592. def test_issue_48336_file_managed_mode_setuid(self, tempdir, user, group):
  2593. """
  2594. Ensure that mode is correct with changing of ownership and group
  2595. symlinks)
  2596. """
  2597. tempfile = os.path.join(tempdir, "temp_file_issue_48336")
  2598. # Run the state
  2599. ret = self.run_state(
  2600. "file.managed", name=tempfile, user=user, group=group, mode="4750",
  2601. )
  2602. self.assertSaltTrueReturn(ret)
  2603. # Check that the owner and group are correct, and
  2604. # the mode is what we expect
  2605. temp_file_stats = os.stat(tempfile)
  2606. # Normalize the mode
  2607. temp_file_mode = str(oct(stat.S_IMODE(temp_file_stats.st_mode)))
  2608. temp_file_mode = salt.utils.files.normalize_mode(temp_file_mode)
  2609. self.assertEqual(temp_file_mode, "4750")
  2610. self.assertEqual(pwd.getpwuid(temp_file_stats.st_uid).pw_name, user)
  2611. self.assertEqual(grp.getgrgid(temp_file_stats.st_gid).gr_name, group)
  2612. @with_tempdir()
  2613. def test_issue_48557(self, tempdir):
  2614. tempfile = os.path.join(tempdir, "temp_file_issue_48557")
  2615. with salt.utils.files.fopen(tempfile, "wb") as fp:
  2616. fp.write(os.linesep.join(["test1", "test2", "test3", ""]).encode("utf-8"))
  2617. ret = self.run_state(
  2618. "file.line", name=tempfile, after="test2", mode="insert", content="test4"
  2619. )
  2620. self.assertSaltTrueReturn(ret)
  2621. with salt.utils.files.fopen(tempfile, "rb") as fp:
  2622. content = fp.read()
  2623. self.assertEqual(
  2624. content,
  2625. os.linesep.join(["test1", "test2", "test4", "test3", ""]).encode("utf-8"),
  2626. )
  2627. @with_tempfile()
  2628. def test_issue_50221(self, name):
  2629. expected = "abc{0}{0}{0}".format(os.linesep)
  2630. ret = self.run_function("pillar.get", ["issue-50221"])
  2631. assert ret == expected
  2632. ret = self.run_function("state.apply", ["issue-50221"], pillar={"name": name},)
  2633. self.assertSaltTrueReturn(ret)
  2634. with salt.utils.files.fopen(name, "r") as fp:
  2635. contents = fp.read()
  2636. assert contents == expected
  2637. def test_managed_file_issue_51208(self):
  2638. """
  2639. Test to ensure we can handle a file with escaped double-quotes
  2640. """
  2641. name = self.tmp_dir / "issue_51208.txt"
  2642. self.addCleanup(salt.utils.files.safe_rm, str(name))
  2643. ret = self.run_state(
  2644. "file.managed", name=str(name), source="salt://issue-51208/vimrc.stub"
  2645. )
  2646. src = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "issue-51208" / "vimrc.stub"
  2647. master_data = src.read_text()
  2648. minion_data = name.read_text()
  2649. self.assertEqual(master_data, minion_data)
  2650. self.assertSaltTrueReturn(ret)
  2651. @with_tempfile()
  2652. def test_keyvalue(self, name):
  2653. """
  2654. file.keyvalue
  2655. """
  2656. content = dedent(
  2657. """\
  2658. # This is the sshd server system-wide configuration file. See
  2659. # sshd_config(5) for more information.
  2660. # The strategy used for options in the default sshd_config shipped with
  2661. # OpenSSH is to specify options with their default value where
  2662. # possible, but leave them commented. Uncommented options override the
  2663. # default value.
  2664. #Port 22
  2665. #AddressFamily any
  2666. #ListenAddress 0.0.0.0
  2667. #ListenAddress ::
  2668. #HostKey /etc/ssh/ssh_host_rsa_key
  2669. #HostKey /etc/ssh/ssh_host_ecdsa_key
  2670. #HostKey /etc/ssh/ssh_host_ed25519_key
  2671. # Ciphers and keying
  2672. #RekeyLimit default none
  2673. # Logging
  2674. #SyslogFacility AUTH
  2675. #LogLevel INFO
  2676. # Authentication:
  2677. #LoginGraceTime 2m
  2678. #PermitRootLogin prohibit-password
  2679. #StrictModes yes
  2680. #MaxAuthTries 6
  2681. #MaxSessions 10
  2682. """
  2683. )
  2684. with salt.utils.files.fopen(name, "w+") as fp_:
  2685. fp_.write(content)
  2686. ret = self.run_state(
  2687. "file.keyvalue",
  2688. name=name,
  2689. key="permitrootlogin",
  2690. value="no",
  2691. separator=" ",
  2692. uncomment=" #",
  2693. key_ignore_case=True,
  2694. )
  2695. with salt.utils.files.fopen(name, "r") as fp_:
  2696. file_contents = fp_.read()
  2697. self.assertNotIn("#PermitRootLogin", file_contents)
  2698. self.assertNotIn("prohibit-password", file_contents)
  2699. self.assertIn("PermitRootLogin no", file_contents)
  2700. self.assertSaltTrueReturn(ret)
  2701. @pytest.mark.windows_whitelisted
  2702. class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
  2703. marker_start = "# start"
  2704. marker_end = "# end"
  2705. content = dedent(
  2706. """\
  2707. Line 1 of block
  2708. Line 2 of block
  2709. """
  2710. )
  2711. without_block = dedent(
  2712. """\
  2713. Hello world!
  2714. # comment here
  2715. """
  2716. )
  2717. with_non_matching_block = dedent(
  2718. """\
  2719. Hello world!
  2720. # start
  2721. No match here
  2722. # end
  2723. # comment here
  2724. """
  2725. )
  2726. with_non_matching_block_and_marker_end_not_after_newline = dedent(
  2727. """\
  2728. Hello world!
  2729. # start
  2730. No match here# end
  2731. # comment here
  2732. """
  2733. )
  2734. with_matching_block = dedent(
  2735. """\
  2736. Hello world!
  2737. # start
  2738. Line 1 of block
  2739. Line 2 of block
  2740. # end
  2741. # comment here
  2742. """
  2743. )
  2744. with_matching_block_and_extra_newline = dedent(
  2745. """\
  2746. Hello world!
  2747. # start
  2748. Line 1 of block
  2749. Line 2 of block
  2750. # end
  2751. # comment here
  2752. """
  2753. )
  2754. with_matching_block_and_marker_end_not_after_newline = dedent(
  2755. """\
  2756. Hello world!
  2757. # start
  2758. Line 1 of block
  2759. Line 2 of block# end
  2760. # comment here
  2761. """
  2762. )
  2763. content_explicit_posix_newlines = "Line 1 of block\n" "Line 2 of block\n"
  2764. content_explicit_windows_newlines = "Line 1 of block\r\n" "Line 2 of block\r\n"
  2765. without_block_explicit_posix_newlines = "Hello world!\n\n" "# comment here\n"
  2766. without_block_explicit_windows_newlines = (
  2767. "Hello world!\r\n\r\n" "# comment here\r\n"
  2768. )
  2769. with_block_prepended_explicit_posix_newlines = (
  2770. "# start\n"
  2771. "Line 1 of block\n"
  2772. "Line 2 of block\n"
  2773. "# end\n"
  2774. "Hello world!\n\n"
  2775. "# comment here\n"
  2776. )
  2777. with_block_prepended_explicit_windows_newlines = (
  2778. "# start\r\n"
  2779. "Line 1 of block\r\n"
  2780. "Line 2 of block\r\n"
  2781. "# end\r\n"
  2782. "Hello world!\r\n\r\n"
  2783. "# comment here\r\n"
  2784. )
  2785. with_block_appended_explicit_posix_newlines = (
  2786. "Hello world!\n\n"
  2787. "# comment here\n"
  2788. "# start\n"
  2789. "Line 1 of block\n"
  2790. "Line 2 of block\n"
  2791. "# end\n"
  2792. )
  2793. with_block_appended_explicit_windows_newlines = (
  2794. "Hello world!\r\n\r\n"
  2795. "# comment here\r\n"
  2796. "# start\r\n"
  2797. "Line 1 of block\r\n"
  2798. "Line 2 of block\r\n"
  2799. "# end\r\n"
  2800. )
  2801. @staticmethod
  2802. def _write(dest, content):
  2803. with salt.utils.files.fopen(dest, "wb") as fp_:
  2804. fp_.write(salt.utils.stringutils.to_bytes(content))
  2805. @staticmethod
  2806. def _read(src):
  2807. with salt.utils.files.fopen(src, "rb") as fp_:
  2808. return salt.utils.stringutils.to_unicode(fp_.read())
  2809. @with_tempfile()
  2810. def test_prepend(self, name):
  2811. """
  2812. Test blockreplace when prepend_if_not_found=True and block doesn't
  2813. exist in file.
  2814. """
  2815. expected = (
  2816. self.marker_start
  2817. + os.linesep
  2818. + self.content
  2819. + self.marker_end
  2820. + os.linesep
  2821. + self.without_block
  2822. )
  2823. # Pass 1: content ends in newline
  2824. self._write(name, self.without_block)
  2825. ret = self.run_state(
  2826. "file.blockreplace",
  2827. name=name,
  2828. content=self.content,
  2829. marker_start=self.marker_start,
  2830. marker_end=self.marker_end,
  2831. prepend_if_not_found=True,
  2832. )
  2833. self.assertSaltTrueReturn(ret)
  2834. self.assertTrue(ret[next(iter(ret))]["changes"])
  2835. self.assertEqual(self._read(name), expected)
  2836. # Pass 1a: Re-run state, no changes should be made
  2837. ret = self.run_state(
  2838. "file.blockreplace",
  2839. name=name,
  2840. content=self.content,
  2841. marker_start=self.marker_start,
  2842. marker_end=self.marker_end,
  2843. prepend_if_not_found=True,
  2844. )
  2845. self.assertSaltTrueReturn(ret)
  2846. self.assertFalse(ret[next(iter(ret))]["changes"])
  2847. self.assertEqual(self._read(name), expected)
  2848. # Pass 2: content does not end in newline
  2849. self._write(name, self.without_block)
  2850. ret = self.run_state(
  2851. "file.blockreplace",
  2852. name=name,
  2853. content=self.content.rstrip("\r\n"),
  2854. marker_start=self.marker_start,
  2855. marker_end=self.marker_end,
  2856. prepend_if_not_found=True,
  2857. )
  2858. self.assertSaltTrueReturn(ret)
  2859. self.assertTrue(ret[next(iter(ret))]["changes"])
  2860. self.assertEqual(self._read(name), expected)
  2861. # Pass 2a: Re-run state, no changes should be made
  2862. ret = self.run_state(
  2863. "file.blockreplace",
  2864. name=name,
  2865. content=self.content.rstrip("\r\n"),
  2866. marker_start=self.marker_start,
  2867. marker_end=self.marker_end,
  2868. prepend_if_not_found=True,
  2869. )
  2870. self.assertSaltTrueReturn(ret)
  2871. self.assertFalse(ret[next(iter(ret))]["changes"])
  2872. self.assertEqual(self._read(name), expected)
  2873. @with_tempfile()
  2874. def test_prepend_append_newline(self, name):
  2875. """
  2876. Test blockreplace when prepend_if_not_found=True and block doesn't
  2877. exist in file. Test with append_newline explicitly set to True.
  2878. """
  2879. # Pass 1: content ends in newline
  2880. expected = (
  2881. self.marker_start
  2882. + os.linesep
  2883. + self.content
  2884. + os.linesep
  2885. + self.marker_end
  2886. + os.linesep
  2887. + self.without_block
  2888. )
  2889. self._write(name, self.without_block)
  2890. ret = self.run_state(
  2891. "file.blockreplace",
  2892. name=name,
  2893. content=self.content,
  2894. marker_start=self.marker_start,
  2895. marker_end=self.marker_end,
  2896. prepend_if_not_found=True,
  2897. append_newline=True,
  2898. )
  2899. self.assertSaltTrueReturn(ret)
  2900. self.assertTrue(ret[next(iter(ret))]["changes"])
  2901. self.assertEqual(self._read(name), expected)
  2902. # Pass 1a: Re-run state, no changes should be made
  2903. ret = self.run_state(
  2904. "file.blockreplace",
  2905. name=name,
  2906. content=self.content,
  2907. marker_start=self.marker_start,
  2908. marker_end=self.marker_end,
  2909. prepend_if_not_found=True,
  2910. append_newline=True,
  2911. )
  2912. self.assertSaltTrueReturn(ret)
  2913. self.assertFalse(ret[next(iter(ret))]["changes"])
  2914. self.assertEqual(self._read(name), expected)
  2915. # Pass 2: content does not end in newline
  2916. expected = (
  2917. self.marker_start
  2918. + os.linesep
  2919. + self.content
  2920. + self.marker_end
  2921. + os.linesep
  2922. + self.without_block
  2923. )
  2924. self._write(name, self.without_block)
  2925. ret = self.run_state(
  2926. "file.blockreplace",
  2927. name=name,
  2928. content=self.content.rstrip("\r\n"),
  2929. marker_start=self.marker_start,
  2930. marker_end=self.marker_end,
  2931. prepend_if_not_found=True,
  2932. append_newline=True,
  2933. )
  2934. self.assertSaltTrueReturn(ret)
  2935. self.assertTrue(ret[next(iter(ret))]["changes"])
  2936. self.assertEqual(self._read(name), expected)
  2937. # Pass 2a: Re-run state, no changes should be made
  2938. ret = self.run_state(
  2939. "file.blockreplace",
  2940. name=name,
  2941. content=self.content.rstrip("\r\n"),
  2942. marker_start=self.marker_start,
  2943. marker_end=self.marker_end,
  2944. prepend_if_not_found=True,
  2945. append_newline=True,
  2946. )
  2947. self.assertSaltTrueReturn(ret)
  2948. self.assertFalse(ret[next(iter(ret))]["changes"])
  2949. self.assertEqual(self._read(name), expected)
  2950. @with_tempfile()
  2951. def test_prepend_no_append_newline(self, name):
  2952. """
  2953. Test blockreplace when prepend_if_not_found=True and block doesn't
  2954. exist in file. Test with append_newline explicitly set to False.
  2955. """
  2956. # Pass 1: content ends in newline
  2957. expected = (
  2958. self.marker_start
  2959. + os.linesep
  2960. + self.content
  2961. + self.marker_end
  2962. + os.linesep
  2963. + self.without_block
  2964. )
  2965. self._write(name, self.without_block)
  2966. ret = self.run_state(
  2967. "file.blockreplace",
  2968. name=name,
  2969. content=self.content,
  2970. marker_start=self.marker_start,
  2971. marker_end=self.marker_end,
  2972. prepend_if_not_found=True,
  2973. append_newline=False,
  2974. )
  2975. self.assertSaltTrueReturn(ret)
  2976. self.assertTrue(ret[next(iter(ret))]["changes"])
  2977. self.assertEqual(self._read(name), expected)
  2978. # Pass 1a: Re-run state, no changes should be made
  2979. ret = self.run_state(
  2980. "file.blockreplace",
  2981. name=name,
  2982. content=self.content,
  2983. marker_start=self.marker_start,
  2984. marker_end=self.marker_end,
  2985. prepend_if_not_found=True,
  2986. append_newline=False,
  2987. )
  2988. self.assertSaltTrueReturn(ret)
  2989. self.assertFalse(ret[next(iter(ret))]["changes"])
  2990. self.assertEqual(self._read(name), expected)
  2991. # Pass 2: content does not end in newline
  2992. expected = (
  2993. self.marker_start
  2994. + os.linesep
  2995. + self.content.rstrip("\r\n")
  2996. + self.marker_end
  2997. + os.linesep
  2998. + self.without_block
  2999. )
  3000. self._write(name, self.without_block)
  3001. ret = self.run_state(
  3002. "file.blockreplace",
  3003. name=name,
  3004. content=self.content.rstrip("\r\n"),
  3005. marker_start=self.marker_start,
  3006. marker_end=self.marker_end,
  3007. prepend_if_not_found=True,
  3008. append_newline=False,
  3009. )
  3010. self.assertSaltTrueReturn(ret)
  3011. self.assertTrue(ret[next(iter(ret))]["changes"])
  3012. self.assertEqual(self._read(name), expected)
  3013. # Pass 2a: Re-run state, no changes should be made
  3014. ret = self.run_state(
  3015. "file.blockreplace",
  3016. name=name,
  3017. content=self.content.rstrip("\r\n"),
  3018. marker_start=self.marker_start,
  3019. marker_end=self.marker_end,
  3020. prepend_if_not_found=True,
  3021. append_newline=False,
  3022. )
  3023. self.assertSaltTrueReturn(ret)
  3024. self.assertFalse(ret[next(iter(ret))]["changes"])
  3025. self.assertEqual(self._read(name), expected)
  3026. @with_tempfile()
  3027. def test_append(self, name):
  3028. """
  3029. Test blockreplace when append_if_not_found=True and block doesn't
  3030. exist in file.
  3031. """
  3032. expected = (
  3033. self.without_block
  3034. + self.marker_start
  3035. + os.linesep
  3036. + self.content
  3037. + self.marker_end
  3038. + os.linesep
  3039. )
  3040. # Pass 1: content ends in newline
  3041. self._write(name, self.without_block)
  3042. ret = self.run_state(
  3043. "file.blockreplace",
  3044. name=name,
  3045. content=self.content,
  3046. marker_start=self.marker_start,
  3047. marker_end=self.marker_end,
  3048. append_if_not_found=True,
  3049. )
  3050. self.assertSaltTrueReturn(ret)
  3051. self.assertTrue(ret[next(iter(ret))]["changes"])
  3052. self.assertEqual(self._read(name), expected)
  3053. # Pass 1a: Re-run state, no changes should be made
  3054. ret = self.run_state(
  3055. "file.blockreplace",
  3056. name=name,
  3057. content=self.content,
  3058. marker_start=self.marker_start,
  3059. marker_end=self.marker_end,
  3060. append_if_not_found=True,
  3061. )
  3062. self.assertSaltTrueReturn(ret)
  3063. self.assertFalse(ret[next(iter(ret))]["changes"])
  3064. self.assertEqual(self._read(name), expected)
  3065. # Pass 2: content does not end in newline
  3066. self._write(name, self.without_block)
  3067. ret = self.run_state(
  3068. "file.blockreplace",
  3069. name=name,
  3070. content=self.content.rstrip("\r\n"),
  3071. marker_start=self.marker_start,
  3072. marker_end=self.marker_end,
  3073. append_if_not_found=True,
  3074. )
  3075. self.assertSaltTrueReturn(ret)
  3076. self.assertTrue(ret[next(iter(ret))]["changes"])
  3077. self.assertEqual(self._read(name), expected)
  3078. # Pass 2a: Re-run state, no changes should be made
  3079. ret = self.run_state(
  3080. "file.blockreplace",
  3081. name=name,
  3082. content=self.content.rstrip("\r\n"),
  3083. marker_start=self.marker_start,
  3084. marker_end=self.marker_end,
  3085. append_if_not_found=True,
  3086. )
  3087. self.assertSaltTrueReturn(ret)
  3088. self.assertFalse(ret[next(iter(ret))]["changes"])
  3089. self.assertEqual(self._read(name), expected)
  3090. @with_tempfile()
  3091. def test_append_append_newline(self, name):
  3092. """
  3093. Test blockreplace when append_if_not_found=True and block doesn't
  3094. exist in file. Test with append_newline explicitly set to True.
  3095. """
  3096. # Pass 1: content ends in newline
  3097. expected = (
  3098. self.without_block
  3099. + self.marker_start
  3100. + os.linesep
  3101. + self.content
  3102. + os.linesep
  3103. + self.marker_end
  3104. + os.linesep
  3105. )
  3106. self._write(name, self.without_block)
  3107. ret = self.run_state(
  3108. "file.blockreplace",
  3109. name=name,
  3110. content=self.content,
  3111. marker_start=self.marker_start,
  3112. marker_end=self.marker_end,
  3113. append_if_not_found=True,
  3114. append_newline=True,
  3115. )
  3116. self.assertSaltTrueReturn(ret)
  3117. self.assertTrue(ret[next(iter(ret))]["changes"])
  3118. self.assertEqual(self._read(name), expected)
  3119. # Pass 1a: Re-run state, no changes should be made
  3120. ret = self.run_state(
  3121. "file.blockreplace",
  3122. name=name,
  3123. content=self.content,
  3124. marker_start=self.marker_start,
  3125. marker_end=self.marker_end,
  3126. append_if_not_found=True,
  3127. append_newline=True,
  3128. )
  3129. self.assertSaltTrueReturn(ret)
  3130. self.assertFalse(ret[next(iter(ret))]["changes"])
  3131. self.assertEqual(self._read(name), expected)
  3132. # Pass 2: content does not end in newline
  3133. expected = (
  3134. self.without_block
  3135. + self.marker_start
  3136. + os.linesep
  3137. + self.content
  3138. + self.marker_end
  3139. + os.linesep
  3140. )
  3141. self._write(name, self.without_block)
  3142. ret = self.run_state(
  3143. "file.blockreplace",
  3144. name=name,
  3145. content=self.content.rstrip("\r\n"),
  3146. marker_start=self.marker_start,
  3147. marker_end=self.marker_end,
  3148. append_if_not_found=True,
  3149. append_newline=True,
  3150. )
  3151. self.assertSaltTrueReturn(ret)
  3152. self.assertTrue(ret[next(iter(ret))]["changes"])
  3153. self.assertEqual(self._read(name), expected)
  3154. # Pass 2a: Re-run state, no changes should be made
  3155. ret = self.run_state(
  3156. "file.blockreplace",
  3157. name=name,
  3158. content=self.content.rstrip("\r\n"),
  3159. marker_start=self.marker_start,
  3160. marker_end=self.marker_end,
  3161. append_if_not_found=True,
  3162. append_newline=True,
  3163. )
  3164. self.assertSaltTrueReturn(ret)
  3165. self.assertFalse(ret[next(iter(ret))]["changes"])
  3166. self.assertEqual(self._read(name), expected)
  3167. @with_tempfile()
  3168. def test_append_no_append_newline(self, name):
  3169. """
  3170. Test blockreplace when append_if_not_found=True and block doesn't
  3171. exist in file. Test with append_newline explicitly set to False.
  3172. """
  3173. # Pass 1: content ends in newline
  3174. expected = (
  3175. self.without_block
  3176. + self.marker_start
  3177. + os.linesep
  3178. + self.content
  3179. + self.marker_end
  3180. + os.linesep
  3181. )
  3182. self._write(name, self.without_block)
  3183. ret = self.run_state(
  3184. "file.blockreplace",
  3185. name=name,
  3186. content=self.content,
  3187. marker_start=self.marker_start,
  3188. marker_end=self.marker_end,
  3189. append_if_not_found=True,
  3190. append_newline=False,
  3191. )
  3192. self.assertSaltTrueReturn(ret)
  3193. self.assertTrue(ret[next(iter(ret))]["changes"])
  3194. self.assertEqual(self._read(name), expected)
  3195. # Pass 1a: Re-run state, no changes should be made
  3196. ret = self.run_state(
  3197. "file.blockreplace",
  3198. name=name,
  3199. content=self.content,
  3200. marker_start=self.marker_start,
  3201. marker_end=self.marker_end,
  3202. append_if_not_found=True,
  3203. append_newline=False,
  3204. )
  3205. self.assertSaltTrueReturn(ret)
  3206. self.assertFalse(ret[next(iter(ret))]["changes"])
  3207. self.assertEqual(self._read(name), expected)
  3208. # Pass 2: content does not end in newline
  3209. expected = (
  3210. self.without_block
  3211. + self.marker_start
  3212. + os.linesep
  3213. + self.content.rstrip("\r\n")
  3214. + self.marker_end
  3215. + os.linesep
  3216. )
  3217. self._write(name, self.without_block)
  3218. ret = self.run_state(
  3219. "file.blockreplace",
  3220. name=name,
  3221. content=self.content.rstrip("\r\n"),
  3222. marker_start=self.marker_start,
  3223. marker_end=self.marker_end,
  3224. append_if_not_found=True,
  3225. append_newline=False,
  3226. )
  3227. self.assertSaltTrueReturn(ret)
  3228. self.assertTrue(ret[next(iter(ret))]["changes"])
  3229. self.assertEqual(self._read(name), expected)
  3230. # Pass 2a: Re-run state, no changes should be made
  3231. ret = self.run_state(
  3232. "file.blockreplace",
  3233. name=name,
  3234. content=self.content.rstrip("\r\n"),
  3235. marker_start=self.marker_start,
  3236. marker_end=self.marker_end,
  3237. append_if_not_found=True,
  3238. append_newline=False,
  3239. )
  3240. self.assertSaltTrueReturn(ret)
  3241. self.assertFalse(ret[next(iter(ret))]["changes"])
  3242. self.assertEqual(self._read(name), expected)
  3243. @with_tempfile()
  3244. def test_prepend_auto_line_separator(self, name):
  3245. """
  3246. This tests the line separator auto-detection when prepending the block
  3247. """
  3248. # POSIX newlines to Windows newlines
  3249. self._write(name, self.without_block_explicit_windows_newlines)
  3250. ret = self.run_state(
  3251. "file.blockreplace",
  3252. name=name,
  3253. content=self.content_explicit_posix_newlines,
  3254. marker_start=self.marker_start,
  3255. marker_end=self.marker_end,
  3256. prepend_if_not_found=True,
  3257. )
  3258. self.assertSaltTrueReturn(ret)
  3259. self.assertTrue(ret[next(iter(ret))]["changes"])
  3260. self.assertEqual(
  3261. self._read(name), self.with_block_prepended_explicit_windows_newlines
  3262. )
  3263. # Re-run state, no changes should be made
  3264. ret = self.run_state(
  3265. "file.blockreplace",
  3266. name=name,
  3267. content=self.content_explicit_posix_newlines,
  3268. marker_start=self.marker_start,
  3269. marker_end=self.marker_end,
  3270. prepend_if_not_found=True,
  3271. )
  3272. self.assertSaltTrueReturn(ret)
  3273. self.assertFalse(ret[next(iter(ret))]["changes"])
  3274. self.assertEqual(
  3275. self._read(name), self.with_block_prepended_explicit_windows_newlines
  3276. )
  3277. # Windows newlines to POSIX newlines
  3278. self._write(name, self.without_block_explicit_posix_newlines)
  3279. ret = self.run_state(
  3280. "file.blockreplace",
  3281. name=name,
  3282. content=self.content_explicit_windows_newlines,
  3283. marker_start=self.marker_start,
  3284. marker_end=self.marker_end,
  3285. prepend_if_not_found=True,
  3286. )
  3287. self.assertSaltTrueReturn(ret)
  3288. self.assertTrue(ret[next(iter(ret))]["changes"])
  3289. self.assertEqual(
  3290. self._read(name), self.with_block_prepended_explicit_posix_newlines
  3291. )
  3292. # Re-run state, no changes should be made
  3293. ret = self.run_state(
  3294. "file.blockreplace",
  3295. name=name,
  3296. content=self.content_explicit_windows_newlines,
  3297. marker_start=self.marker_start,
  3298. marker_end=self.marker_end,
  3299. prepend_if_not_found=True,
  3300. )
  3301. self.assertSaltTrueReturn(ret)
  3302. self.assertFalse(ret[next(iter(ret))]["changes"])
  3303. self.assertEqual(
  3304. self._read(name), self.with_block_prepended_explicit_posix_newlines
  3305. )
  3306. @with_tempfile()
  3307. def test_append_auto_line_separator(self, name):
  3308. """
  3309. This tests the line separator auto-detection when appending the block
  3310. """
  3311. # POSIX newlines to Windows newlines
  3312. self._write(name, self.without_block_explicit_windows_newlines)
  3313. ret = self.run_state(
  3314. "file.blockreplace",
  3315. name=name,
  3316. content=self.content_explicit_posix_newlines,
  3317. marker_start=self.marker_start,
  3318. marker_end=self.marker_end,
  3319. append_if_not_found=True,
  3320. )
  3321. self.assertSaltTrueReturn(ret)
  3322. self.assertTrue(ret[next(iter(ret))]["changes"])
  3323. self.assertEqual(
  3324. self._read(name), self.with_block_appended_explicit_windows_newlines
  3325. )
  3326. # Re-run state, no changes should be made
  3327. ret = self.run_state(
  3328. "file.blockreplace",
  3329. name=name,
  3330. content=self.content_explicit_posix_newlines,
  3331. marker_start=self.marker_start,
  3332. marker_end=self.marker_end,
  3333. append_if_not_found=True,
  3334. )
  3335. self.assertSaltTrueReturn(ret)
  3336. self.assertFalse(ret[next(iter(ret))]["changes"])
  3337. self.assertEqual(
  3338. self._read(name), self.with_block_appended_explicit_windows_newlines
  3339. )
  3340. # Windows newlines to POSIX newlines
  3341. self._write(name, self.without_block_explicit_posix_newlines)
  3342. ret = self.run_state(
  3343. "file.blockreplace",
  3344. name=name,
  3345. content=self.content_explicit_windows_newlines,
  3346. marker_start=self.marker_start,
  3347. marker_end=self.marker_end,
  3348. append_if_not_found=True,
  3349. )
  3350. self.assertSaltTrueReturn(ret)
  3351. self.assertTrue(ret[next(iter(ret))]["changes"])
  3352. self.assertEqual(
  3353. self._read(name), self.with_block_appended_explicit_posix_newlines
  3354. )
  3355. # Re-run state, no changes should be made
  3356. ret = self.run_state(
  3357. "file.blockreplace",
  3358. name=name,
  3359. content=self.content_explicit_windows_newlines,
  3360. marker_start=self.marker_start,
  3361. marker_end=self.marker_end,
  3362. append_if_not_found=True,
  3363. )
  3364. self.assertSaltTrueReturn(ret)
  3365. self.assertFalse(ret[next(iter(ret))]["changes"])
  3366. self.assertEqual(
  3367. self._read(name), self.with_block_appended_explicit_posix_newlines
  3368. )
  3369. @with_tempfile()
  3370. def test_non_matching_block(self, name):
  3371. """
  3372. Test blockreplace when block exists but its contents are not a
  3373. match.
  3374. """
  3375. # Pass 1: content ends in newline
  3376. self._write(name, self.with_non_matching_block)
  3377. ret = self.run_state(
  3378. "file.blockreplace",
  3379. name=name,
  3380. content=self.content,
  3381. marker_start=self.marker_start,
  3382. marker_end=self.marker_end,
  3383. )
  3384. self.assertSaltTrueReturn(ret)
  3385. self.assertTrue(ret[next(iter(ret))]["changes"])
  3386. self.assertEqual(self._read(name), self.with_matching_block)
  3387. # Pass 1a: Re-run state, no changes should be made
  3388. ret = self.run_state(
  3389. "file.blockreplace",
  3390. name=name,
  3391. content=self.content,
  3392. marker_start=self.marker_start,
  3393. marker_end=self.marker_end,
  3394. )
  3395. self.assertSaltTrueReturn(ret)
  3396. self.assertFalse(ret[next(iter(ret))]["changes"])
  3397. self.assertEqual(self._read(name), self.with_matching_block)
  3398. # Pass 2: content does not end in newline
  3399. self._write(name, self.with_non_matching_block)
  3400. ret = self.run_state(
  3401. "file.blockreplace",
  3402. name=name,
  3403. content=self.content.rstrip("\r\n"),
  3404. marker_start=self.marker_start,
  3405. marker_end=self.marker_end,
  3406. )
  3407. self.assertSaltTrueReturn(ret)
  3408. self.assertTrue(ret[next(iter(ret))]["changes"])
  3409. self.assertEqual(self._read(name), self.with_matching_block)
  3410. # Pass 2a: Re-run state, no changes should be made
  3411. ret = self.run_state(
  3412. "file.blockreplace",
  3413. name=name,
  3414. content=self.content.rstrip("\r\n"),
  3415. marker_start=self.marker_start,
  3416. marker_end=self.marker_end,
  3417. )
  3418. self.assertSaltTrueReturn(ret)
  3419. self.assertFalse(ret[next(iter(ret))]["changes"])
  3420. self.assertEqual(self._read(name), self.with_matching_block)
  3421. @with_tempfile()
  3422. def test_non_matching_block_append_newline(self, name):
  3423. """
  3424. Test blockreplace when block exists but its contents are not a
  3425. match. Test with append_newline explicitly set to True.
  3426. """
  3427. # Pass 1: content ends in newline
  3428. self._write(name, self.with_non_matching_block)
  3429. ret = self.run_state(
  3430. "file.blockreplace",
  3431. name=name,
  3432. content=self.content,
  3433. marker_start=self.marker_start,
  3434. marker_end=self.marker_end,
  3435. append_newline=True,
  3436. )
  3437. self.assertSaltTrueReturn(ret)
  3438. self.assertTrue(ret[next(iter(ret))]["changes"])
  3439. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3440. # Pass 1a: Re-run state, no changes should be made
  3441. ret = self.run_state(
  3442. "file.blockreplace",
  3443. name=name,
  3444. content=self.content,
  3445. marker_start=self.marker_start,
  3446. marker_end=self.marker_end,
  3447. append_newline=True,
  3448. )
  3449. self.assertSaltTrueReturn(ret)
  3450. self.assertFalse(ret[next(iter(ret))]["changes"])
  3451. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3452. # Pass 2: content does not end in newline
  3453. self._write(name, self.with_non_matching_block)
  3454. ret = self.run_state(
  3455. "file.blockreplace",
  3456. name=name,
  3457. content=self.content.rstrip("\r\n"),
  3458. marker_start=self.marker_start,
  3459. marker_end=self.marker_end,
  3460. append_newline=True,
  3461. )
  3462. self.assertSaltTrueReturn(ret)
  3463. self.assertTrue(ret[next(iter(ret))]["changes"])
  3464. self.assertEqual(self._read(name), self.with_matching_block)
  3465. # Pass 2a: Re-run state, no changes should be made
  3466. ret = self.run_state(
  3467. "file.blockreplace",
  3468. name=name,
  3469. content=self.content.rstrip("\r\n"),
  3470. marker_start=self.marker_start,
  3471. marker_end=self.marker_end,
  3472. append_newline=True,
  3473. )
  3474. self.assertSaltTrueReturn(ret)
  3475. self.assertFalse(ret[next(iter(ret))]["changes"])
  3476. self.assertEqual(self._read(name), self.with_matching_block)
  3477. @with_tempfile()
  3478. def test_non_matching_block_no_append_newline(self, name):
  3479. """
  3480. Test blockreplace when block exists but its contents are not a
  3481. match. Test with append_newline explicitly set to False.
  3482. """
  3483. # Pass 1: content ends in newline
  3484. self._write(name, self.with_non_matching_block)
  3485. ret = self.run_state(
  3486. "file.blockreplace",
  3487. name=name,
  3488. content=self.content,
  3489. marker_start=self.marker_start,
  3490. marker_end=self.marker_end,
  3491. append_newline=False,
  3492. )
  3493. self.assertSaltTrueReturn(ret)
  3494. self.assertTrue(ret[next(iter(ret))]["changes"])
  3495. self.assertEqual(self._read(name), self.with_matching_block)
  3496. # Pass 1a: Re-run state, no changes should be made
  3497. ret = self.run_state(
  3498. "file.blockreplace",
  3499. name=name,
  3500. content=self.content,
  3501. marker_start=self.marker_start,
  3502. marker_end=self.marker_end,
  3503. append_newline=False,
  3504. )
  3505. self.assertSaltTrueReturn(ret)
  3506. self.assertFalse(ret[next(iter(ret))]["changes"])
  3507. self.assertEqual(self._read(name), self.with_matching_block)
  3508. # Pass 2: content does not end in newline
  3509. self._write(name, self.with_non_matching_block)
  3510. ret = self.run_state(
  3511. "file.blockreplace",
  3512. name=name,
  3513. content=self.content.rstrip("\r\n"),
  3514. marker_start=self.marker_start,
  3515. marker_end=self.marker_end,
  3516. append_newline=False,
  3517. )
  3518. self.assertSaltTrueReturn(ret)
  3519. self.assertTrue(ret[next(iter(ret))]["changes"])
  3520. self.assertEqual(
  3521. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3522. )
  3523. # Pass 2a: Re-run state, no changes should be made
  3524. ret = self.run_state(
  3525. "file.blockreplace",
  3526. name=name,
  3527. content=self.content.rstrip("\r\n"),
  3528. marker_start=self.marker_start,
  3529. marker_end=self.marker_end,
  3530. append_newline=False,
  3531. )
  3532. self.assertSaltTrueReturn(ret)
  3533. self.assertFalse(ret[next(iter(ret))]["changes"])
  3534. self.assertEqual(
  3535. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3536. )
  3537. @with_tempfile()
  3538. def test_non_matching_block_and_marker_not_after_newline(self, name):
  3539. """
  3540. Test blockreplace when block exists but its contents are not a
  3541. match, and the marker_end is not directly preceded by a newline.
  3542. """
  3543. # Pass 1: content ends in newline
  3544. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3545. ret = self.run_state(
  3546. "file.blockreplace",
  3547. name=name,
  3548. content=self.content,
  3549. marker_start=self.marker_start,
  3550. marker_end=self.marker_end,
  3551. )
  3552. self.assertSaltTrueReturn(ret)
  3553. self.assertTrue(ret[next(iter(ret))]["changes"])
  3554. self.assertEqual(self._read(name), self.with_matching_block)
  3555. # Pass 1a: Re-run state, no changes should be made
  3556. ret = self.run_state(
  3557. "file.blockreplace",
  3558. name=name,
  3559. content=self.content,
  3560. marker_start=self.marker_start,
  3561. marker_end=self.marker_end,
  3562. )
  3563. self.assertSaltTrueReturn(ret)
  3564. self.assertFalse(ret[next(iter(ret))]["changes"])
  3565. self.assertEqual(self._read(name), self.with_matching_block)
  3566. # Pass 2: content does not end in newline
  3567. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3568. ret = self.run_state(
  3569. "file.blockreplace",
  3570. name=name,
  3571. content=self.content.rstrip("\r\n"),
  3572. marker_start=self.marker_start,
  3573. marker_end=self.marker_end,
  3574. )
  3575. self.assertSaltTrueReturn(ret)
  3576. self.assertTrue(ret[next(iter(ret))]["changes"])
  3577. self.assertEqual(self._read(name), self.with_matching_block)
  3578. # Pass 2a: Re-run state, no changes should be made
  3579. ret = self.run_state(
  3580. "file.blockreplace",
  3581. name=name,
  3582. content=self.content.rstrip("\r\n"),
  3583. marker_start=self.marker_start,
  3584. marker_end=self.marker_end,
  3585. )
  3586. self.assertSaltTrueReturn(ret)
  3587. self.assertFalse(ret[next(iter(ret))]["changes"])
  3588. self.assertEqual(self._read(name), self.with_matching_block)
  3589. @with_tempfile()
  3590. def test_non_matching_block_and_marker_not_after_newline_append_newline(self, name):
  3591. """
  3592. Test blockreplace when block exists but its contents are not a match,
  3593. and the marker_end is not directly preceded by a newline. Test with
  3594. append_newline explicitly set to True.
  3595. """
  3596. # Pass 1: content ends in newline
  3597. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3598. ret = self.run_state(
  3599. "file.blockreplace",
  3600. name=name,
  3601. content=self.content,
  3602. marker_start=self.marker_start,
  3603. marker_end=self.marker_end,
  3604. append_newline=True,
  3605. )
  3606. self.assertSaltTrueReturn(ret)
  3607. self.assertTrue(ret[next(iter(ret))]["changes"])
  3608. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3609. # Pass 1a: Re-run state, no changes should be made
  3610. ret = self.run_state(
  3611. "file.blockreplace",
  3612. name=name,
  3613. content=self.content,
  3614. marker_start=self.marker_start,
  3615. marker_end=self.marker_end,
  3616. append_newline=True,
  3617. )
  3618. self.assertSaltTrueReturn(ret)
  3619. self.assertFalse(ret[next(iter(ret))]["changes"])
  3620. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3621. # Pass 2: content does not end in newline
  3622. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3623. ret = self.run_state(
  3624. "file.blockreplace",
  3625. name=name,
  3626. content=self.content.rstrip("\r\n"),
  3627. marker_start=self.marker_start,
  3628. marker_end=self.marker_end,
  3629. append_newline=True,
  3630. )
  3631. self.assertSaltTrueReturn(ret)
  3632. self.assertTrue(ret[next(iter(ret))]["changes"])
  3633. self.assertEqual(self._read(name), self.with_matching_block)
  3634. # Pass 2a: Re-run state, no changes should be made
  3635. ret = self.run_state(
  3636. "file.blockreplace",
  3637. name=name,
  3638. content=self.content.rstrip("\r\n"),
  3639. marker_start=self.marker_start,
  3640. marker_end=self.marker_end,
  3641. append_newline=True,
  3642. )
  3643. self.assertSaltTrueReturn(ret)
  3644. self.assertFalse(ret[next(iter(ret))]["changes"])
  3645. self.assertEqual(self._read(name), self.with_matching_block)
  3646. @with_tempfile()
  3647. def test_non_matching_block_and_marker_not_after_newline_no_append_newline(
  3648. self, name
  3649. ):
  3650. """
  3651. Test blockreplace when block exists but its contents are not a match,
  3652. and the marker_end is not directly preceded by a newline. Test with
  3653. append_newline explicitly set to False.
  3654. """
  3655. # Pass 1: content ends in newline
  3656. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3657. ret = self.run_state(
  3658. "file.blockreplace",
  3659. name=name,
  3660. content=self.content,
  3661. marker_start=self.marker_start,
  3662. marker_end=self.marker_end,
  3663. append_newline=False,
  3664. )
  3665. self.assertSaltTrueReturn(ret)
  3666. self.assertTrue(ret[next(iter(ret))]["changes"])
  3667. self.assertEqual(self._read(name), self.with_matching_block)
  3668. # Pass 1a: Re-run state, no changes should be made
  3669. ret = self.run_state(
  3670. "file.blockreplace",
  3671. name=name,
  3672. content=self.content,
  3673. marker_start=self.marker_start,
  3674. marker_end=self.marker_end,
  3675. append_newline=False,
  3676. )
  3677. self.assertSaltTrueReturn(ret)
  3678. self.assertFalse(ret[next(iter(ret))]["changes"])
  3679. self.assertEqual(self._read(name), self.with_matching_block)
  3680. # Pass 2: content does not end in newline
  3681. self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
  3682. ret = self.run_state(
  3683. "file.blockreplace",
  3684. name=name,
  3685. content=self.content.rstrip("\r\n"),
  3686. marker_start=self.marker_start,
  3687. marker_end=self.marker_end,
  3688. append_newline=False,
  3689. )
  3690. self.assertSaltTrueReturn(ret)
  3691. self.assertTrue(ret[next(iter(ret))]["changes"])
  3692. self.assertEqual(
  3693. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3694. )
  3695. # Pass 2a: Re-run state, no changes should be made
  3696. ret = self.run_state(
  3697. "file.blockreplace",
  3698. name=name,
  3699. content=self.content.rstrip("\r\n"),
  3700. marker_start=self.marker_start,
  3701. marker_end=self.marker_end,
  3702. append_newline=False,
  3703. )
  3704. self.assertSaltTrueReturn(ret)
  3705. self.assertFalse(ret[next(iter(ret))]["changes"])
  3706. self.assertEqual(
  3707. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3708. )
  3709. @with_tempfile()
  3710. def test_matching_block(self, name):
  3711. """
  3712. Test blockreplace when block exists and its contents are a match. No
  3713. changes should be made.
  3714. """
  3715. # Pass 1: content ends in newline
  3716. self._write(name, self.with_matching_block)
  3717. ret = self.run_state(
  3718. "file.blockreplace",
  3719. name=name,
  3720. content=self.content,
  3721. marker_start=self.marker_start,
  3722. marker_end=self.marker_end,
  3723. )
  3724. self.assertSaltTrueReturn(ret)
  3725. self.assertFalse(ret[next(iter(ret))]["changes"])
  3726. self.assertEqual(self._read(name), self.with_matching_block)
  3727. # Pass 1a: Re-run state, no changes should be made
  3728. ret = self.run_state(
  3729. "file.blockreplace",
  3730. name=name,
  3731. content=self.content,
  3732. marker_start=self.marker_start,
  3733. marker_end=self.marker_end,
  3734. )
  3735. self.assertSaltTrueReturn(ret)
  3736. self.assertFalse(ret[next(iter(ret))]["changes"])
  3737. self.assertEqual(self._read(name), self.with_matching_block)
  3738. # Pass 2: content does not end in newline
  3739. self._write(name, self.with_matching_block)
  3740. ret = self.run_state(
  3741. "file.blockreplace",
  3742. name=name,
  3743. content=self.content.rstrip("\r\n"),
  3744. marker_start=self.marker_start,
  3745. marker_end=self.marker_end,
  3746. )
  3747. self.assertSaltTrueReturn(ret)
  3748. self.assertFalse(ret[next(iter(ret))]["changes"])
  3749. self.assertEqual(self._read(name), self.with_matching_block)
  3750. # Pass 2a: Re-run state, no changes should be made
  3751. ret = self.run_state(
  3752. "file.blockreplace",
  3753. name=name,
  3754. content=self.content.rstrip("\r\n"),
  3755. marker_start=self.marker_start,
  3756. marker_end=self.marker_end,
  3757. )
  3758. self.assertSaltTrueReturn(ret)
  3759. self.assertFalse(ret[next(iter(ret))]["changes"])
  3760. self.assertEqual(self._read(name), self.with_matching_block)
  3761. @with_tempfile()
  3762. def test_matching_block_append_newline(self, name):
  3763. """
  3764. Test blockreplace when block exists and its contents are a match. Test
  3765. with append_newline explicitly set to True. This will result in an
  3766. extra newline when the content ends in a newline, and will not when the
  3767. content does not end in a newline.
  3768. """
  3769. # Pass 1: content ends in newline
  3770. self._write(name, self.with_matching_block)
  3771. ret = self.run_state(
  3772. "file.blockreplace",
  3773. name=name,
  3774. content=self.content,
  3775. marker_start=self.marker_start,
  3776. marker_end=self.marker_end,
  3777. append_newline=True,
  3778. )
  3779. self.assertSaltTrueReturn(ret)
  3780. self.assertTrue(ret[next(iter(ret))]["changes"])
  3781. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3782. # Pass 1a: Re-run state, no changes should be made
  3783. ret = self.run_state(
  3784. "file.blockreplace",
  3785. name=name,
  3786. content=self.content,
  3787. marker_start=self.marker_start,
  3788. marker_end=self.marker_end,
  3789. append_newline=True,
  3790. )
  3791. self.assertSaltTrueReturn(ret)
  3792. self.assertFalse(ret[next(iter(ret))]["changes"])
  3793. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3794. # Pass 2: content does not end in newline
  3795. self._write(name, self.with_matching_block)
  3796. ret = self.run_state(
  3797. "file.blockreplace",
  3798. name=name,
  3799. content=self.content.rstrip("\r\n"),
  3800. marker_start=self.marker_start,
  3801. marker_end=self.marker_end,
  3802. append_newline=True,
  3803. )
  3804. self.assertSaltTrueReturn(ret)
  3805. self.assertFalse(ret[next(iter(ret))]["changes"])
  3806. self.assertEqual(self._read(name), self.with_matching_block)
  3807. # Pass 2a: Re-run state, no changes should be made
  3808. ret = self.run_state(
  3809. "file.blockreplace",
  3810. name=name,
  3811. content=self.content.rstrip("\r\n"),
  3812. marker_start=self.marker_start,
  3813. marker_end=self.marker_end,
  3814. append_newline=True,
  3815. )
  3816. self.assertSaltTrueReturn(ret)
  3817. self.assertFalse(ret[next(iter(ret))]["changes"])
  3818. self.assertEqual(self._read(name), self.with_matching_block)
  3819. @with_tempfile()
  3820. def test_matching_block_no_append_newline(self, name):
  3821. """
  3822. Test blockreplace when block exists and its contents are a match. Test
  3823. with append_newline explicitly set to False. This will result in the
  3824. marker_end not being directly preceded by a newline when the content
  3825. does not end in a newline.
  3826. """
  3827. # Pass 1: content ends in newline
  3828. self._write(name, self.with_matching_block)
  3829. ret = self.run_state(
  3830. "file.blockreplace",
  3831. name=name,
  3832. content=self.content,
  3833. marker_start=self.marker_start,
  3834. marker_end=self.marker_end,
  3835. append_newline=False,
  3836. )
  3837. self.assertSaltTrueReturn(ret)
  3838. self.assertFalse(ret[next(iter(ret))]["changes"])
  3839. self.assertEqual(self._read(name), self.with_matching_block)
  3840. # Pass 1a: Re-run state, no changes should be made
  3841. ret = self.run_state(
  3842. "file.blockreplace",
  3843. name=name,
  3844. content=self.content,
  3845. marker_start=self.marker_start,
  3846. marker_end=self.marker_end,
  3847. append_newline=False,
  3848. )
  3849. self.assertSaltTrueReturn(ret)
  3850. self.assertFalse(ret[next(iter(ret))]["changes"])
  3851. self.assertEqual(self._read(name), self.with_matching_block)
  3852. # Pass 2: content does not end in newline
  3853. self._write(name, self.with_matching_block)
  3854. ret = self.run_state(
  3855. "file.blockreplace",
  3856. name=name,
  3857. content=self.content.rstrip("\r\n"),
  3858. marker_start=self.marker_start,
  3859. marker_end=self.marker_end,
  3860. append_newline=False,
  3861. )
  3862. self.assertSaltTrueReturn(ret)
  3863. self.assertTrue(ret[next(iter(ret))]["changes"])
  3864. self.assertEqual(
  3865. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3866. )
  3867. # Pass 2a: Re-run state, no changes should be made
  3868. ret = self.run_state(
  3869. "file.blockreplace",
  3870. name=name,
  3871. content=self.content.rstrip("\r\n"),
  3872. marker_start=self.marker_start,
  3873. marker_end=self.marker_end,
  3874. append_newline=False,
  3875. )
  3876. self.assertSaltTrueReturn(ret)
  3877. self.assertFalse(ret[next(iter(ret))]["changes"])
  3878. self.assertEqual(
  3879. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  3880. )
  3881. @with_tempfile()
  3882. def test_matching_block_and_marker_not_after_newline(self, name):
  3883. """
  3884. Test blockreplace when block exists and its contents are a match, but
  3885. the marker_end is not directly preceded by a newline.
  3886. """
  3887. # Pass 1: content ends in newline
  3888. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  3889. ret = self.run_state(
  3890. "file.blockreplace",
  3891. name=name,
  3892. content=self.content,
  3893. marker_start=self.marker_start,
  3894. marker_end=self.marker_end,
  3895. )
  3896. self.assertSaltTrueReturn(ret)
  3897. self.assertTrue(ret[next(iter(ret))]["changes"])
  3898. self.assertEqual(self._read(name), self.with_matching_block)
  3899. # Pass 1a: Re-run state, no changes should be made
  3900. ret = self.run_state(
  3901. "file.blockreplace",
  3902. name=name,
  3903. content=self.content,
  3904. marker_start=self.marker_start,
  3905. marker_end=self.marker_end,
  3906. )
  3907. self.assertSaltTrueReturn(ret)
  3908. self.assertFalse(ret[next(iter(ret))]["changes"])
  3909. self.assertEqual(self._read(name), self.with_matching_block)
  3910. # Pass 2: content does not end in newline
  3911. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  3912. ret = self.run_state(
  3913. "file.blockreplace",
  3914. name=name,
  3915. content=self.content.rstrip("\r\n"),
  3916. marker_start=self.marker_start,
  3917. marker_end=self.marker_end,
  3918. )
  3919. self.assertSaltTrueReturn(ret)
  3920. self.assertTrue(ret[next(iter(ret))]["changes"])
  3921. self.assertEqual(self._read(name), self.with_matching_block)
  3922. # Pass 2a: Re-run state, no changes should be made
  3923. ret = self.run_state(
  3924. "file.blockreplace",
  3925. name=name,
  3926. content=self.content.rstrip("\r\n"),
  3927. marker_start=self.marker_start,
  3928. marker_end=self.marker_end,
  3929. )
  3930. self.assertSaltTrueReturn(ret)
  3931. self.assertFalse(ret[next(iter(ret))]["changes"])
  3932. self.assertEqual(self._read(name), self.with_matching_block)
  3933. @with_tempfile()
  3934. def test_matching_block_and_marker_not_after_newline_append_newline(self, name):
  3935. """
  3936. Test blockreplace when block exists and its contents are a match, but
  3937. the marker_end is not directly preceded by a newline. Test with
  3938. append_newline explicitly set to True. This will result in an extra
  3939. newline when the content ends in a newline, and will not when the
  3940. content does not end in a newline.
  3941. """
  3942. # Pass 1: content ends in newline
  3943. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  3944. ret = self.run_state(
  3945. "file.blockreplace",
  3946. name=name,
  3947. content=self.content,
  3948. marker_start=self.marker_start,
  3949. marker_end=self.marker_end,
  3950. append_newline=True,
  3951. )
  3952. self.assertSaltTrueReturn(ret)
  3953. self.assertTrue(ret[next(iter(ret))]["changes"])
  3954. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3955. # Pass 1a: Re-run state, no changes should be made
  3956. ret = self.run_state(
  3957. "file.blockreplace",
  3958. name=name,
  3959. content=self.content,
  3960. marker_start=self.marker_start,
  3961. marker_end=self.marker_end,
  3962. append_newline=True,
  3963. )
  3964. self.assertSaltTrueReturn(ret)
  3965. self.assertFalse(ret[next(iter(ret))]["changes"])
  3966. self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
  3967. # Pass 2: content does not end in newline
  3968. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  3969. ret = self.run_state(
  3970. "file.blockreplace",
  3971. name=name,
  3972. content=self.content.rstrip("\r\n"),
  3973. marker_start=self.marker_start,
  3974. marker_end=self.marker_end,
  3975. append_newline=True,
  3976. )
  3977. self.assertSaltTrueReturn(ret)
  3978. self.assertTrue(ret[next(iter(ret))]["changes"])
  3979. self.assertEqual(self._read(name), self.with_matching_block)
  3980. # Pass 2a: Re-run state, no changes should be made
  3981. ret = self.run_state(
  3982. "file.blockreplace",
  3983. name=name,
  3984. content=self.content.rstrip("\r\n"),
  3985. marker_start=self.marker_start,
  3986. marker_end=self.marker_end,
  3987. append_newline=True,
  3988. )
  3989. self.assertSaltTrueReturn(ret)
  3990. self.assertFalse(ret[next(iter(ret))]["changes"])
  3991. self.assertEqual(self._read(name), self.with_matching_block)
  3992. @with_tempfile()
  3993. def test_matching_block_and_marker_not_after_newline_no_append_newline(self, name):
  3994. """
  3995. Test blockreplace when block exists and its contents are a match, but
  3996. the marker_end is not directly preceded by a newline. Test with
  3997. append_newline explicitly set to False.
  3998. """
  3999. # Pass 1: content ends in newline
  4000. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  4001. ret = self.run_state(
  4002. "file.blockreplace",
  4003. name=name,
  4004. content=self.content,
  4005. marker_start=self.marker_start,
  4006. marker_end=self.marker_end,
  4007. append_newline=False,
  4008. )
  4009. self.assertSaltTrueReturn(ret)
  4010. self.assertTrue(ret[next(iter(ret))]["changes"])
  4011. self.assertEqual(self._read(name), self.with_matching_block)
  4012. # Pass 1a: Re-run state, no changes should be made
  4013. ret = self.run_state(
  4014. "file.blockreplace",
  4015. name=name,
  4016. content=self.content,
  4017. marker_start=self.marker_start,
  4018. marker_end=self.marker_end,
  4019. append_newline=False,
  4020. )
  4021. self.assertSaltTrueReturn(ret)
  4022. self.assertFalse(ret[next(iter(ret))]["changes"])
  4023. self.assertEqual(self._read(name), self.with_matching_block)
  4024. # Pass 2: content does not end in newline
  4025. self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
  4026. ret = self.run_state(
  4027. "file.blockreplace",
  4028. name=name,
  4029. content=self.content.rstrip("\r\n"),
  4030. marker_start=self.marker_start,
  4031. marker_end=self.marker_end,
  4032. append_newline=False,
  4033. )
  4034. self.assertSaltTrueReturn(ret)
  4035. self.assertFalse(ret[next(iter(ret))]["changes"])
  4036. self.assertEqual(
  4037. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  4038. )
  4039. # Pass 2a: Re-run state, no changes should be made
  4040. ret = self.run_state(
  4041. "file.blockreplace",
  4042. name=name,
  4043. content=self.content.rstrip("\r\n"),
  4044. marker_start=self.marker_start,
  4045. marker_end=self.marker_end,
  4046. append_newline=False,
  4047. )
  4048. self.assertSaltTrueReturn(ret)
  4049. self.assertFalse(ret[next(iter(ret))]["changes"])
  4050. self.assertEqual(
  4051. self._read(name), self.with_matching_block_and_marker_end_not_after_newline
  4052. )
  4053. @with_tempfile()
  4054. def test_issue_49043(self, name):
  4055. ret = self.run_function("state.sls", mods="issue-49043", pillar={"name": name},)
  4056. log.error("ret = %s", repr(ret))
  4057. diff = "--- \n+++ \n@@ -0,0 +1,3 @@\n"
  4058. diff += dedent(
  4059. """\
  4060. +#-- start managed zone --
  4061. +äöü
  4062. +#-- end managed zone --
  4063. """
  4064. )
  4065. job = "file_|-somefile-blockreplace_|-{}_|-blockreplace".format(name)
  4066. self.assertEqual(ret[job]["changes"]["diff"], diff)
  4067. @pytest.mark.windows_whitelisted
  4068. class RemoteFileTest(ModuleCase, SaltReturnAssertsMixin):
  4069. """
  4070. Uses a local tornado webserver to test http(s) file.managed states with and
  4071. without skip_verify
  4072. """
  4073. @classmethod
  4074. def setUpClass(cls):
  4075. cls.webserver = Webserver()
  4076. cls.webserver.start()
  4077. cls.source = cls.webserver.url("grail/scene33")
  4078. if IS_WINDOWS:
  4079. # CRLF vs LF causes a different hash on windows
  4080. cls.source_hash = "21438b3d5fd2c0028bcab92f7824dc69"
  4081. else:
  4082. cls.source_hash = "d2feb3beb323c79fc7a0f44f1408b4a3"
  4083. @classmethod
  4084. def tearDownClass(cls):
  4085. cls.webserver.stop()
  4086. @with_tempfile(create=False)
  4087. def setUp(self, name): # pylint: disable=arguments-differ
  4088. self.name = name
  4089. def tearDown(self):
  4090. try:
  4091. os.remove(self.name)
  4092. except OSError as exc:
  4093. if exc.errno != errno.ENOENT:
  4094. raise
  4095. def run_state(self, *args, **kwargs): # pylint: disable=arguments-differ
  4096. ret = super().run_state(*args, **kwargs)
  4097. log.debug("ret = %s", ret)
  4098. return ret
  4099. def test_file_managed_http_source_no_hash(self):
  4100. """
  4101. Test a remote file with no hash
  4102. """
  4103. ret = self.run_state(
  4104. "file.managed", name=self.name, source=self.source, skip_verify=False
  4105. )
  4106. # This should fail because no hash was provided
  4107. self.assertSaltFalseReturn(ret)
  4108. def test_file_managed_http_source(self):
  4109. """
  4110. Test a remote file with no hash
  4111. """
  4112. ret = self.run_state(
  4113. "file.managed",
  4114. name=self.name,
  4115. source=self.source,
  4116. source_hash=self.source_hash,
  4117. skip_verify=False,
  4118. )
  4119. self.assertSaltTrueReturn(ret)
  4120. def test_file_managed_http_source_skip_verify(self):
  4121. """
  4122. Test a remote file using skip_verify
  4123. """
  4124. ret = self.run_state(
  4125. "file.managed", name=self.name, source=self.source, skip_verify=True
  4126. )
  4127. self.assertSaltTrueReturn(ret)
  4128. def test_file_managed_keep_source_false_http(self):
  4129. """
  4130. This test ensures that we properly clean the cached file if keep_source
  4131. is set to False, for source files using an http:// URL
  4132. """
  4133. # Run the state
  4134. ret = self.run_state(
  4135. "file.managed",
  4136. name=self.name,
  4137. source=self.source,
  4138. source_hash=self.source_hash,
  4139. keep_source=False,
  4140. )
  4141. ret = ret[next(iter(ret))]
  4142. assert ret["result"] is True
  4143. # Now make sure that the file is not cached
  4144. result = self.run_function("cp.is_cached", [self.source])
  4145. assert result == "", "File is still cached at {}".format(result)
  4146. @skipIf(not salt.utils.path.which("patch"), "patch is not installed")
  4147. @pytest.mark.windows_whitelisted
  4148. class PatchTest(ModuleCase, SaltReturnAssertsMixin):
  4149. def _check_patch_version(self, min_version):
  4150. """
  4151. patch version check
  4152. """
  4153. version = self.run_function("cmd.run", ["patch --version"]).splitlines()[0]
  4154. version = version.split()[1]
  4155. if _LooseVersion(version) < _LooseVersion(min_version):
  4156. self.skipTest(
  4157. "Minimum patch version required: {}. "
  4158. "Patch version installed: {}".format(min_version, version)
  4159. )
  4160. @classmethod
  4161. def setUpClass(cls):
  4162. cls.webserver = Webserver()
  4163. cls.webserver.start()
  4164. cls.numbers_patch_name = "numbers.patch"
  4165. cls.math_patch_name = "math.patch"
  4166. cls.all_patch_name = "all.patch"
  4167. cls.numbers_patch_template_name = cls.numbers_patch_name + ".jinja"
  4168. cls.math_patch_template_name = cls.math_patch_name + ".jinja"
  4169. cls.all_patch_template_name = cls.all_patch_name + ".jinja"
  4170. cls.numbers_patch_path = "patches/" + cls.numbers_patch_name
  4171. cls.math_patch_path = "patches/" + cls.math_patch_name
  4172. cls.all_patch_path = "patches/" + cls.all_patch_name
  4173. cls.numbers_patch_template_path = "patches/" + cls.numbers_patch_template_name
  4174. cls.math_patch_template_path = "patches/" + cls.math_patch_template_name
  4175. cls.all_patch_template_path = "patches/" + cls.all_patch_template_name
  4176. cls.numbers_patch = "salt://" + cls.numbers_patch_path
  4177. cls.math_patch = "salt://" + cls.math_patch_path
  4178. cls.all_patch = "salt://" + cls.all_patch_path
  4179. cls.numbers_patch_template = "salt://" + cls.numbers_patch_template_path
  4180. cls.math_patch_template = "salt://" + cls.math_patch_template_path
  4181. cls.all_patch_template = "salt://" + cls.all_patch_template_path
  4182. cls.numbers_patch_http = cls.webserver.url(cls.numbers_patch_path)
  4183. cls.math_patch_http = cls.webserver.url(cls.math_patch_path)
  4184. cls.all_patch_http = cls.webserver.url(cls.all_patch_path)
  4185. cls.numbers_patch_template_http = cls.webserver.url(
  4186. cls.numbers_patch_template_path
  4187. )
  4188. cls.math_patch_template_http = cls.webserver.url(cls.math_patch_template_path)
  4189. cls.all_patch_template_http = cls.webserver.url(cls.all_patch_template_path)
  4190. patches_dir = os.path.join(RUNTIME_VARS.FILES, "file", "base", "patches")
  4191. cls.numbers_patch_hash = salt.utils.hashutils.get_hash(
  4192. os.path.join(patches_dir, cls.numbers_patch_name)
  4193. )
  4194. cls.math_patch_hash = salt.utils.hashutils.get_hash(
  4195. os.path.join(patches_dir, cls.math_patch_name)
  4196. )
  4197. cls.all_patch_hash = salt.utils.hashutils.get_hash(
  4198. os.path.join(patches_dir, cls.all_patch_name)
  4199. )
  4200. cls.numbers_patch_template_hash = salt.utils.hashutils.get_hash(
  4201. os.path.join(patches_dir, cls.numbers_patch_template_name)
  4202. )
  4203. cls.math_patch_template_hash = salt.utils.hashutils.get_hash(
  4204. os.path.join(patches_dir, cls.math_patch_template_name)
  4205. )
  4206. cls.all_patch_template_hash = salt.utils.hashutils.get_hash(
  4207. os.path.join(patches_dir, cls.all_patch_template_name)
  4208. )
  4209. cls.context = {"two": "two", "ten": 10}
  4210. @classmethod
  4211. def tearDownClass(cls):
  4212. cls.webserver.stop()
  4213. def setUp(self):
  4214. """
  4215. Create a new unpatched set of files
  4216. """
  4217. self.base_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  4218. os.makedirs(os.path.join(self.base_dir, "foo", "bar"))
  4219. self.numbers_file = os.path.join(self.base_dir, "foo", "numbers.txt")
  4220. self.math_file = os.path.join(self.base_dir, "foo", "bar", "math.txt")
  4221. with salt.utils.files.fopen(self.numbers_file, "w") as fp_:
  4222. fp_.write(
  4223. textwrap.dedent(
  4224. """\
  4225. one
  4226. two
  4227. three
  4228. 1
  4229. 2
  4230. 3
  4231. """
  4232. )
  4233. )
  4234. with salt.utils.files.fopen(self.math_file, "w") as fp_:
  4235. fp_.write(
  4236. textwrap.dedent(
  4237. """\
  4238. Five plus five is ten
  4239. Four squared is sixteen
  4240. """
  4241. )
  4242. )
  4243. self.addCleanup(shutil.rmtree, self.base_dir, ignore_errors=True)
  4244. def test_patch_single_file(self):
  4245. """
  4246. Test file.patch using a patch applied to a single file
  4247. """
  4248. ret = self.run_state(
  4249. "file.patch", name=self.numbers_file, source=self.numbers_patch,
  4250. )
  4251. self.assertSaltTrueReturn(ret)
  4252. ret = ret[next(iter(ret))]
  4253. self.assertEqual(ret["comment"], "Patch successfully applied")
  4254. # Re-run the state, should succeed and there should be a message about
  4255. # a partially-applied hunk.
  4256. ret = self.run_state(
  4257. "file.patch", name=self.numbers_file, source=self.numbers_patch,
  4258. )
  4259. self.assertSaltTrueReturn(ret)
  4260. ret = ret[next(iter(ret))]
  4261. self.assertEqual(ret["comment"], "Patch was already applied")
  4262. self.assertEqual(ret["changes"], {})
  4263. def test_patch_directory(self):
  4264. """
  4265. Test file.patch using a patch applied to a directory, with changes
  4266. spanning multiple files.
  4267. """
  4268. self._check_patch_version("2.6")
  4269. ret = self.run_state(
  4270. "file.patch", name=self.base_dir, source=self.all_patch, strip=1,
  4271. )
  4272. self.assertSaltTrueReturn(ret)
  4273. ret = ret[next(iter(ret))]
  4274. self.assertEqual(ret["comment"], "Patch successfully applied")
  4275. # Re-run the state, should succeed and there should be a message about
  4276. # a partially-applied hunk.
  4277. ret = self.run_state(
  4278. "file.patch", name=self.base_dir, source=self.all_patch, strip=1,
  4279. )
  4280. self.assertSaltTrueReturn(ret)
  4281. ret = ret[next(iter(ret))]
  4282. self.assertEqual(ret["comment"], "Patch was already applied")
  4283. self.assertEqual(ret["changes"], {})
  4284. def test_patch_strip_parsing(self):
  4285. """
  4286. Test that we successfuly parse -p/--strip when included in the options
  4287. """
  4288. self._check_patch_version("2.6")
  4289. # Run the state using -p1
  4290. ret = self.run_state(
  4291. "file.patch", name=self.base_dir, source=self.all_patch, options="-p1",
  4292. )
  4293. self.assertSaltTrueReturn(ret)
  4294. ret = ret[next(iter(ret))]
  4295. self.assertEqual(ret["comment"], "Patch successfully applied")
  4296. # Re-run the state using --strip=1
  4297. ret = self.run_state(
  4298. "file.patch",
  4299. name=self.base_dir,
  4300. source=self.all_patch,
  4301. options="--strip=1",
  4302. )
  4303. self.assertSaltTrueReturn(ret)
  4304. ret = ret[next(iter(ret))]
  4305. self.assertEqual(ret["comment"], "Patch was already applied")
  4306. self.assertEqual(ret["changes"], {})
  4307. # Re-run the state using --strip 1
  4308. ret = self.run_state(
  4309. "file.patch",
  4310. name=self.base_dir,
  4311. source=self.all_patch,
  4312. options="--strip 1",
  4313. )
  4314. self.assertSaltTrueReturn(ret)
  4315. ret = ret[next(iter(ret))]
  4316. self.assertEqual(ret["comment"], "Patch was already applied")
  4317. self.assertEqual(ret["changes"], {})
  4318. def test_patch_saltenv(self):
  4319. """
  4320. Test that we attempt to download the patch from a non-base saltenv
  4321. """
  4322. # This state will fail because we don't have a patch file in that
  4323. # environment, but that is OK, we just want to test that we're looking
  4324. # in an environment other than base.
  4325. ret = self.run_state(
  4326. "file.patch", name=self.math_file, source=self.math_patch, saltenv="prod",
  4327. )
  4328. self.assertSaltFalseReturn(ret)
  4329. ret = ret[next(iter(ret))]
  4330. self.assertEqual(
  4331. ret["comment"],
  4332. "Source file {} not found in saltenv 'prod'".format(self.math_patch),
  4333. )
  4334. def test_patch_single_file_failure(self):
  4335. """
  4336. Test file.patch using a patch applied to a single file. This tests a
  4337. failed patch.
  4338. """
  4339. # Empty the file to ensure that the patch doesn't apply cleanly
  4340. with salt.utils.files.fopen(self.numbers_file, "w"):
  4341. pass
  4342. ret = self.run_state(
  4343. "file.patch", name=self.numbers_file, source=self.numbers_patch,
  4344. )
  4345. self.assertSaltFalseReturn(ret)
  4346. ret = ret[next(iter(ret))]
  4347. self.assertIn("Patch would not apply cleanly", ret["comment"])
  4348. # Test the reject_file option and ensure that the rejects are written
  4349. # to the path specified.
  4350. reject_file = salt.utils.files.mkstemp()
  4351. ret = self.run_state(
  4352. "file.patch",
  4353. name=self.numbers_file,
  4354. source=self.numbers_patch,
  4355. reject_file=reject_file,
  4356. strip=1,
  4357. )
  4358. self.assertSaltFalseReturn(ret)
  4359. ret = ret[next(iter(ret))]
  4360. self.assertIn("Patch would not apply cleanly", ret["comment"])
  4361. self.assertRegex(
  4362. ret["comment"], "saving rejects to (file )?{}".format(reject_file)
  4363. )
  4364. def test_patch_directory_failure(self):
  4365. """
  4366. Test file.patch using a patch applied to a directory, with changes
  4367. spanning multiple files.
  4368. """
  4369. # Empty the file to ensure that the patch doesn't apply
  4370. with salt.utils.files.fopen(self.math_file, "w"):
  4371. pass
  4372. ret = self.run_state(
  4373. "file.patch", name=self.base_dir, source=self.all_patch, strip=1,
  4374. )
  4375. self.assertSaltFalseReturn(ret)
  4376. ret = ret[next(iter(ret))]
  4377. self.assertIn("Patch would not apply cleanly", ret["comment"])
  4378. # Test the reject_file option and ensure that the rejects are written
  4379. # to the path specified.
  4380. reject_file = salt.utils.files.mkstemp()
  4381. ret = self.run_state(
  4382. "file.patch",
  4383. name=self.base_dir,
  4384. source=self.all_patch,
  4385. reject_file=reject_file,
  4386. strip=1,
  4387. )
  4388. self.assertSaltFalseReturn(ret)
  4389. ret = ret[next(iter(ret))]
  4390. self.assertIn("Patch would not apply cleanly", ret["comment"])
  4391. self.assertRegex(
  4392. ret["comment"], "saving rejects to (file )?{}".format(reject_file)
  4393. )
  4394. def test_patch_single_file_remote_source(self):
  4395. """
  4396. Test file.patch using a patch applied to a single file, with the patch
  4397. coming from a remote source.
  4398. """
  4399. # Try without a source_hash and without skip_verify=True, this should
  4400. # fail with a message about the source_hash
  4401. ret = self.run_state(
  4402. "file.patch", name=self.math_file, source=self.math_patch_http,
  4403. )
  4404. self.assertSaltFalseReturn(ret)
  4405. ret = ret[next(iter(ret))]
  4406. self.assertIn("Unable to verify upstream hash", ret["comment"])
  4407. # Re-run the state with a source hash, it should now succeed
  4408. ret = self.run_state(
  4409. "file.patch",
  4410. name=self.math_file,
  4411. source=self.math_patch_http,
  4412. source_hash=self.math_patch_hash,
  4413. )
  4414. self.assertSaltTrueReturn(ret)
  4415. ret = ret[next(iter(ret))]
  4416. self.assertEqual(ret["comment"], "Patch successfully applied")
  4417. # Re-run again, this time with no hash and skip_verify=True to test
  4418. # skipping hash verification
  4419. ret = self.run_state(
  4420. "file.patch",
  4421. name=self.math_file,
  4422. source=self.math_patch_http,
  4423. skip_verify=True,
  4424. )
  4425. self.assertSaltTrueReturn(ret)
  4426. ret = ret[next(iter(ret))]
  4427. self.assertEqual(ret["comment"], "Patch was already applied")
  4428. self.assertEqual(ret["changes"], {})
  4429. def test_patch_directory_remote_source(self):
  4430. """
  4431. Test file.patch using a patch applied to a directory, with changes
  4432. spanning multiple files, and the patch file coming from a remote
  4433. source.
  4434. """
  4435. self._check_patch_version("2.6")
  4436. # Try without a source_hash and without skip_verify=True, this should
  4437. # fail with a message about the source_hash
  4438. ret = self.run_state(
  4439. "file.patch", name=self.base_dir, source=self.all_patch_http, strip=1,
  4440. )
  4441. self.assertSaltFalseReturn(ret)
  4442. ret = ret[next(iter(ret))]
  4443. self.assertIn("Unable to verify upstream hash", ret["comment"])
  4444. # Re-run the state with a source hash, it should now succeed
  4445. ret = self.run_state(
  4446. "file.patch",
  4447. name=self.base_dir,
  4448. source=self.all_patch_http,
  4449. source_hash=self.all_patch_hash,
  4450. strip=1,
  4451. )
  4452. self.assertSaltTrueReturn(ret)
  4453. ret = ret[next(iter(ret))]
  4454. self.assertEqual(ret["comment"], "Patch successfully applied")
  4455. # Re-run again, this time with no hash and skip_verify=True to test
  4456. # skipping hash verification
  4457. ret = self.run_state(
  4458. "file.patch",
  4459. name=self.base_dir,
  4460. source=self.all_patch_http,
  4461. strip=1,
  4462. skip_verify=True,
  4463. )
  4464. self.assertSaltTrueReturn(ret)
  4465. ret = ret[next(iter(ret))]
  4466. self.assertEqual(ret["comment"], "Patch was already applied")
  4467. self.assertEqual(ret["changes"], {})
  4468. def test_patch_single_file_template(self):
  4469. """
  4470. Test file.patch using a patch applied to a single file, with jinja
  4471. templating applied to the patch file.
  4472. """
  4473. ret = self.run_state(
  4474. "file.patch",
  4475. name=self.numbers_file,
  4476. source=self.numbers_patch_template,
  4477. template="jinja",
  4478. context=self.context,
  4479. )
  4480. self.assertSaltTrueReturn(ret)
  4481. ret = ret[next(iter(ret))]
  4482. self.assertEqual(ret["comment"], "Patch successfully applied")
  4483. # Re-run the state, should succeed and there should be a message about
  4484. # a partially-applied hunk.
  4485. ret = self.run_state(
  4486. "file.patch",
  4487. name=self.numbers_file,
  4488. source=self.numbers_patch_template,
  4489. template="jinja",
  4490. context=self.context,
  4491. )
  4492. self.assertSaltTrueReturn(ret)
  4493. ret = ret[next(iter(ret))]
  4494. self.assertEqual(ret["comment"], "Patch was already applied")
  4495. self.assertEqual(ret["changes"], {})
  4496. def test_patch_directory_template(self):
  4497. """
  4498. Test file.patch using a patch applied to a directory, with changes
  4499. spanning multiple files, and with jinja templating applied to the patch
  4500. file.
  4501. """
  4502. self._check_patch_version("2.6")
  4503. ret = self.run_state(
  4504. "file.patch",
  4505. name=self.base_dir,
  4506. source=self.all_patch_template,
  4507. template="jinja",
  4508. context=self.context,
  4509. strip=1,
  4510. )
  4511. self.assertSaltTrueReturn(ret)
  4512. ret = ret[next(iter(ret))]
  4513. self.assertEqual(ret["comment"], "Patch successfully applied")
  4514. # Re-run the state, should succeed and there should be a message about
  4515. # a partially-applied hunk.
  4516. ret = self.run_state(
  4517. "file.patch",
  4518. name=self.base_dir,
  4519. source=self.all_patch_template,
  4520. template="jinja",
  4521. context=self.context,
  4522. strip=1,
  4523. )
  4524. self.assertSaltTrueReturn(ret)
  4525. ret = ret[next(iter(ret))]
  4526. self.assertEqual(ret["comment"], "Patch was already applied")
  4527. self.assertEqual(ret["changes"], {})
  4528. def test_patch_single_file_remote_source_template(self):
  4529. """
  4530. Test file.patch using a patch applied to a single file, with the patch
  4531. coming from a remote source.
  4532. """
  4533. # Try without a source_hash and without skip_verify=True, this should
  4534. # fail with a message about the source_hash
  4535. ret = self.run_state(
  4536. "file.patch",
  4537. name=self.math_file,
  4538. source=self.math_patch_template_http,
  4539. template="jinja",
  4540. context=self.context,
  4541. )
  4542. self.assertSaltFalseReturn(ret)
  4543. ret = ret[next(iter(ret))]
  4544. self.assertIn("Unable to verify upstream hash", ret["comment"])
  4545. # Re-run the state with a source hash, it should now succeed
  4546. ret = self.run_state(
  4547. "file.patch",
  4548. name=self.math_file,
  4549. source=self.math_patch_template_http,
  4550. source_hash=self.math_patch_template_hash,
  4551. template="jinja",
  4552. context=self.context,
  4553. )
  4554. self.assertSaltTrueReturn(ret)
  4555. ret = ret[next(iter(ret))]
  4556. self.assertEqual(ret["comment"], "Patch successfully applied")
  4557. # Re-run again, this time with no hash and skip_verify=True to test
  4558. # skipping hash verification
  4559. ret = self.run_state(
  4560. "file.patch",
  4561. name=self.math_file,
  4562. source=self.math_patch_template_http,
  4563. template="jinja",
  4564. context=self.context,
  4565. skip_verify=True,
  4566. )
  4567. self.assertSaltTrueReturn(ret)
  4568. ret = ret[next(iter(ret))]
  4569. self.assertEqual(ret["comment"], "Patch was already applied")
  4570. self.assertEqual(ret["changes"], {})
  4571. def test_patch_directory_remote_source_template(self):
  4572. """
  4573. Test file.patch using a patch applied to a directory, with changes
  4574. spanning multiple files, and the patch file coming from a remote
  4575. source.
  4576. """
  4577. self._check_patch_version("2.6")
  4578. # Try without a source_hash and without skip_verify=True, this should
  4579. # fail with a message about the source_hash
  4580. ret = self.run_state(
  4581. "file.patch",
  4582. name=self.base_dir,
  4583. source=self.all_patch_template_http,
  4584. template="jinja",
  4585. context=self.context,
  4586. strip=1,
  4587. )
  4588. self.assertSaltFalseReturn(ret)
  4589. ret = ret[next(iter(ret))]
  4590. self.assertIn("Unable to verify upstream hash", ret["comment"])
  4591. # Re-run the state with a source hash, it should now succeed
  4592. ret = self.run_state(
  4593. "file.patch",
  4594. name=self.base_dir,
  4595. source=self.all_patch_template_http,
  4596. source_hash=self.all_patch_template_hash,
  4597. template="jinja",
  4598. context=self.context,
  4599. strip=1,
  4600. )
  4601. self.assertSaltTrueReturn(ret)
  4602. ret = ret[next(iter(ret))]
  4603. self.assertEqual(ret["comment"], "Patch successfully applied")
  4604. # Re-run again, this time with no hash and skip_verify=True to test
  4605. # skipping hash verification
  4606. ret = self.run_state(
  4607. "file.patch",
  4608. name=self.base_dir,
  4609. source=self.all_patch_template_http,
  4610. template="jinja",
  4611. context=self.context,
  4612. strip=1,
  4613. skip_verify=True,
  4614. )
  4615. self.assertSaltTrueReturn(ret)
  4616. ret = ret[next(iter(ret))]
  4617. self.assertEqual(ret["comment"], "Patch was already applied")
  4618. self.assertEqual(ret["changes"], {})
  4619. def test_patch_test_mode(self):
  4620. """
  4621. Test file.patch using test=True
  4622. """
  4623. # Try without a source_hash and without skip_verify=True, this should
  4624. # fail with a message about the source_hash
  4625. ret = self.run_state(
  4626. "file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
  4627. )
  4628. self.assertSaltNoneReturn(ret)
  4629. ret = ret[next(iter(ret))]
  4630. self.assertEqual(ret["comment"], "The patch would be applied")
  4631. self.assertTrue(ret["changes"])
  4632. # Apply the patch for real. We'll then be able to test below that we
  4633. # exit with a True rather than a None result if test=True is used on an
  4634. # already-applied patch.
  4635. ret = self.run_state(
  4636. "file.patch", name=self.numbers_file, source=self.numbers_patch,
  4637. )
  4638. self.assertSaltTrueReturn(ret)
  4639. ret = ret[next(iter(ret))]
  4640. self.assertEqual(ret["comment"], "Patch successfully applied")
  4641. self.assertTrue(ret["changes"])
  4642. # Run again with test=True. Since the pre-check happens before we do
  4643. # the __opts__['test'] check, we should exit with a True result just
  4644. # the same as if we try to run this state on an already-patched file
  4645. # *without* test=True.
  4646. ret = self.run_state(
  4647. "file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
  4648. )
  4649. self.assertSaltTrueReturn(ret)
  4650. ret = ret[next(iter(ret))]
  4651. self.assertEqual(ret["comment"], "Patch was already applied")
  4652. self.assertEqual(ret["changes"], {})
  4653. # Empty the file to ensure that the patch doesn't apply cleanly
  4654. with salt.utils.files.fopen(self.numbers_file, "w"):
  4655. pass
  4656. # Run again with test=True. Similar to the above run, we are testing
  4657. # that we return before we reach the __opts__['test'] check. In this
  4658. # case we should return a False result because we should already know
  4659. # by this point that the patch will not apply cleanly.
  4660. ret = self.run_state(
  4661. "file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
  4662. )
  4663. self.assertSaltFalseReturn(ret)
  4664. ret = ret[next(iter(ret))]
  4665. self.assertIn("Patch would not apply cleanly", ret["comment"])
  4666. self.assertEqual(ret["changes"], {})
  4667. WIN_TEST_FILE = "c:/testfile"
  4668. @destructiveTest
  4669. @skipIf(not IS_WINDOWS, "windows test only")
  4670. @pytest.mark.windows_whitelisted
  4671. class WinFileTest(ModuleCase):
  4672. """
  4673. Test for the file state on Windows
  4674. """
  4675. def setUp(self):
  4676. self.run_state(
  4677. "file.managed", name=WIN_TEST_FILE, makedirs=True, contents="Only a test"
  4678. )
  4679. def tearDown(self):
  4680. self.run_state("file.absent", name=WIN_TEST_FILE)
  4681. def test_file_managed(self):
  4682. """
  4683. Test file.managed on Windows
  4684. """
  4685. self.assertTrue(self.run_state("file.exists", name=WIN_TEST_FILE))
  4686. def test_file_copy(self):
  4687. """
  4688. Test file.copy on Windows
  4689. """
  4690. ret = self.run_state(
  4691. "file.copy", name="c:/testfile_copy", makedirs=True, source=WIN_TEST_FILE
  4692. )
  4693. self.assertTrue(ret)
  4694. def test_file_comment(self):
  4695. """
  4696. Test file.comment on Windows
  4697. """
  4698. self.run_state("file.comment", name=WIN_TEST_FILE, regex="^Only")
  4699. with salt.utils.files.fopen(WIN_TEST_FILE, "r") as fp_:
  4700. self.assertTrue(fp_.read().startswith("#Only"))
  4701. def test_file_replace(self):
  4702. """
  4703. Test file.replace on Windows
  4704. """
  4705. self.run_state(
  4706. "file.replace", name=WIN_TEST_FILE, pattern="test", repl="testing"
  4707. )
  4708. with salt.utils.files.fopen(WIN_TEST_FILE, "r") as fp_:
  4709. self.assertIn("testing", fp_.read())
  4710. def test_file_absent(self):
  4711. """
  4712. Test file.absent on Windows
  4713. """
  4714. ret = self.run_state("file.absent", name=WIN_TEST_FILE)
  4715. self.assertTrue(ret)