test_file.py 186 KB

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