test_file.py 122 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function, unicode_literals
  3. import logging
  4. import os
  5. import pprint
  6. import shutil
  7. from datetime import datetime
  8. import salt.modules.file as filemod
  9. import salt.serializers.json as jsonserializer
  10. import salt.serializers.python as pythonserializer
  11. import salt.serializers.yaml as yamlserializer
  12. import salt.states.file as filestate
  13. import salt.utils.files
  14. import salt.utils.json
  15. import salt.utils.platform
  16. import salt.utils.win_functions
  17. import salt.utils.yaml
  18. from salt.exceptions import CommandExecutionError
  19. from salt.ext.six.moves import range
  20. from tests.support.helpers import destructiveTest, slowTest
  21. from tests.support.mixins import LoaderModuleMockMixin
  22. from tests.support.mock import MagicMock, Mock, call, mock_open, patch
  23. from tests.support.runtests import RUNTIME_VARS
  24. from tests.support.unit import TestCase, skipIf
  25. try:
  26. from dateutil.relativedelta import relativedelta
  27. HAS_DATEUTIL = True
  28. except ImportError:
  29. HAS_DATEUTIL = False
  30. NO_DATEUTIL_REASON = "python-dateutil is not installed"
  31. log = logging.getLogger(__name__)
  32. class TestFileState(TestCase, LoaderModuleMockMixin):
  33. def setup_loader_modules(self):
  34. return {
  35. filestate: {
  36. "__env__": "base",
  37. "__salt__": {"file.manage_file": False},
  38. "__serializers__": {
  39. "yaml.serialize": yamlserializer.serialize,
  40. "python.serialize": pythonserializer.serialize,
  41. "json.serialize": jsonserializer.serialize,
  42. },
  43. "__opts__": {"test": False, "cachedir": ""},
  44. "__instance_id__": "",
  45. "__low__": {},
  46. "__utils__": {},
  47. }
  48. }
  49. def tearDown(self):
  50. remove_dir = "/tmp/etc"
  51. if salt.utils.platform.is_windows():
  52. remove_dir = "c:\\tmp\\etc"
  53. try:
  54. salt.utils.files.rm_rf(remove_dir)
  55. except OSError:
  56. pass
  57. def test_serialize(self):
  58. def returner(contents, *args, **kwargs):
  59. returner.returned = contents
  60. returner.returned = None
  61. with patch.dict(filestate.__salt__, {"file.manage_file": returner}):
  62. dataset = {"foo": True, "bar": 42, "baz": [1, 2, 3], "qux": 2.0}
  63. filestate.serialize("/tmp", dataset)
  64. self.assertEqual(salt.utils.yaml.safe_load(returner.returned), dataset)
  65. filestate.serialize("/tmp", dataset, formatter="yaml")
  66. self.assertEqual(salt.utils.yaml.safe_load(returner.returned), dataset)
  67. filestate.serialize("/tmp", dataset, formatter="json")
  68. self.assertEqual(salt.utils.json.loads(returner.returned), dataset)
  69. filestate.serialize("/tmp", dataset, formatter="python")
  70. self.assertEqual(returner.returned, pprint.pformat(dataset) + "\n")
  71. mock_serializer = Mock(return_value="")
  72. with patch.dict(
  73. filestate.__serializers__, {"json.serialize": mock_serializer}
  74. ):
  75. filestate.serialize(
  76. "/tmp", dataset, formatter="json", serializer_opts=[{"indent": 8}]
  77. )
  78. mock_serializer.assert_called_with(
  79. dataset, indent=8, separators=(",", ": "), sort_keys=True
  80. )
  81. def test_contents_and_contents_pillar(self):
  82. def returner(contents, *args, **kwargs):
  83. returner.returned = contents
  84. returner.returned = None
  85. manage_mode_mock = MagicMock()
  86. with patch.dict(
  87. filestate.__salt__,
  88. {"file.manage_file": returner, "config.manage_mode": manage_mode_mock},
  89. ):
  90. ret = filestate.managed(
  91. "/tmp/foo", contents="hi", contents_pillar="foo:bar"
  92. )
  93. self.assertEqual(False, ret["result"])
  94. def test_contents_pillar_doesnt_add_more_newlines(self):
  95. # make sure the newline
  96. pillar_value = "i am the pillar value{0}".format(os.linesep)
  97. self.run_contents_pillar(pillar_value, expected=pillar_value)
  98. def run_contents_pillar(self, pillar_value, expected):
  99. returner = MagicMock(return_value=None)
  100. path = "/tmp/foo"
  101. pillar_path = "foo:bar"
  102. # the values don't matter here
  103. pillar_mock = MagicMock(return_value=pillar_value)
  104. with patch.dict(
  105. filestate.__salt__,
  106. {
  107. "file.manage_file": returner,
  108. "config.manage_mode": MagicMock(),
  109. "file.source_list": MagicMock(return_value=[None, None]),
  110. "file.get_managed": MagicMock(return_value=[None, None, None]),
  111. "pillar.get": pillar_mock,
  112. },
  113. ):
  114. ret = filestate.managed(path, contents_pillar=pillar_path)
  115. # make sure no errors are returned
  116. self.assertEqual(None, ret)
  117. # Make sure the contents value matches the expected value.
  118. # returner.call_args[0] will be an args tuple containing all the args
  119. # passed to the mocked returner for file.manage_file. Any changes to
  120. # the arguments for file.manage_file may make this assertion fail.
  121. # If the test is failing, check the position of the "contents" param
  122. # in the manage_file() function in salt/modules/file.py, the fix is
  123. # likely as simple as updating the 2nd index below.
  124. self.assertEqual(expected, returner.call_args[0][-5])
  125. def test_symlink(self):
  126. """
  127. Test to create a symlink.
  128. """
  129. name = os.sep + os.path.join("tmp", "testfile.txt")
  130. target = salt.utils.files.mkstemp()
  131. test_dir = os.sep + "tmp"
  132. user = "salt"
  133. if salt.utils.platform.is_windows():
  134. group = "salt"
  135. else:
  136. group = "saltstack"
  137. def return_val(kwargs):
  138. val = {
  139. "name": name,
  140. "result": False,
  141. "comment": "",
  142. "changes": {},
  143. }
  144. val.update(kwargs)
  145. return val
  146. mock_t = MagicMock(return_value=True)
  147. mock_f = MagicMock(return_value=False)
  148. mock_empty = MagicMock(return_value="")
  149. mock_uid = MagicMock(return_value="U1001")
  150. mock_gid = MagicMock(return_value="g1001")
  151. mock_target = MagicMock(return_value=target)
  152. mock_user = MagicMock(return_value=user)
  153. mock_grp = MagicMock(return_value=group)
  154. mock_os_error = MagicMock(side_effect=OSError)
  155. with patch.dict(filestate.__salt__, {"config.manage_mode": mock_t}):
  156. comt = "Must provide name to file.symlink"
  157. ret = return_val({"comment": comt, "name": ""})
  158. self.assertDictEqual(filestate.symlink("", target), ret)
  159. with patch.dict(
  160. filestate.__salt__,
  161. {
  162. "config.manage_mode": mock_t,
  163. "file.user_to_uid": mock_empty,
  164. "file.group_to_gid": mock_empty,
  165. "user.info": mock_empty,
  166. "user.current": mock_user,
  167. },
  168. ):
  169. if salt.utils.platform.is_windows():
  170. comt = "User {0} does not exist".format(user)
  171. ret = return_val({"comment": comt, "name": name})
  172. else:
  173. comt = "User {0} does not exist. Group {1} does not exist.".format(
  174. user, group
  175. )
  176. ret = return_val({"comment": comt, "name": name})
  177. self.assertDictEqual(
  178. filestate.symlink(name, target, user=user, group=group), ret
  179. )
  180. with patch.dict(
  181. filestate.__salt__,
  182. {
  183. "config.manage_mode": mock_t,
  184. "file.user_to_uid": mock_uid,
  185. "file.group_to_gid": mock_gid,
  186. "file.is_link": mock_f,
  187. "user.info": mock_empty,
  188. "user.current": mock_user,
  189. },
  190. ), patch.dict(filestate.__opts__, {"test": True}), patch.object(
  191. os.path, "exists", mock_f
  192. ):
  193. if salt.utils.platform.is_windows():
  194. comt = "User {0} does not exist".format(user)
  195. ret = return_val(
  196. {"comment": comt, "result": False, "name": name, "changes": {}}
  197. )
  198. else:
  199. comt = "Symlink {0} to {1} is set for creation".format(name, target)
  200. ret = return_val(
  201. {"comment": comt, "result": None, "changes": {"new": name}}
  202. )
  203. self.assertDictEqual(
  204. filestate.symlink(name, target, user=user, group=group), ret
  205. )
  206. with patch.dict(
  207. filestate.__salt__,
  208. {
  209. "config.manage_mode": mock_t,
  210. "file.user_to_uid": mock_uid,
  211. "file.group_to_gid": mock_gid,
  212. "file.is_link": mock_f,
  213. "user.info": mock_empty,
  214. "user.current": mock_user,
  215. },
  216. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  217. os.path, "isdir", mock_f
  218. ), patch.object(
  219. os.path, "exists", mock_f
  220. ):
  221. if salt.utils.platform.is_windows():
  222. comt = "User {0} does not exist".format(user)
  223. ret = return_val(
  224. {"comment": comt, "result": False, "name": name, "changes": {}}
  225. )
  226. else:
  227. comt = "Directory {0} for symlink is not present".format(test_dir)
  228. ret = return_val({"comment": comt, "result": False, "changes": {}})
  229. self.assertDictEqual(
  230. filestate.symlink(name, target, user=user, group=group), ret
  231. )
  232. with patch.dict(
  233. filestate.__salt__,
  234. {
  235. "config.manage_mode": mock_t,
  236. "file.user_to_uid": mock_uid,
  237. "file.group_to_gid": mock_gid,
  238. "file.is_link": mock_t,
  239. "file.readlink": mock_target,
  240. "user.info": mock_empty,
  241. "user.current": mock_user,
  242. },
  243. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  244. os.path, "isdir", mock_t
  245. ), patch.object(
  246. salt.states.file, "_check_symlink_ownership", mock_t
  247. ), patch(
  248. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  249. ):
  250. if salt.utils.platform.is_windows():
  251. comt = "Symlink {0} is present and owned by {1}".format(name, user)
  252. else:
  253. comt = "Symlink {0} is present and owned by {1}:{2}".format(
  254. name, user, group
  255. )
  256. ret = return_val({"comment": comt, "result": True, "changes": {}})
  257. self.assertDictEqual(
  258. filestate.symlink(name, target, user=user, group=group), ret
  259. )
  260. with patch.dict(
  261. filestate.__salt__,
  262. {
  263. "config.manage_mode": mock_t,
  264. "file.user_to_uid": mock_uid,
  265. "file.group_to_gid": mock_gid,
  266. "file.is_link": mock_f,
  267. "file.readlink": mock_target,
  268. "user.info": mock_empty,
  269. "user.current": mock_user,
  270. },
  271. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  272. os.path, "isdir", mock_t
  273. ), patch.object(
  274. os.path, "exists", mock_t
  275. ), patch.object(
  276. os.path, "lexists", mock_t
  277. ), patch(
  278. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  279. ):
  280. comt = (
  281. "Symlink & backup dest exists and Force not set. {0} -> "
  282. "{1} - backup: {2}".format(name, target, os.path.join(test_dir, "SALT"))
  283. )
  284. ret.update({"comment": comt, "result": False, "changes": {}})
  285. self.assertDictEqual(
  286. filestate.symlink(
  287. name, target, user=user, group=group, backupname="SALT"
  288. ),
  289. ret,
  290. )
  291. with patch.dict(
  292. filestate.__salt__,
  293. {
  294. "config.manage_mode": mock_t,
  295. "file.user_to_uid": mock_uid,
  296. "file.group_to_gid": mock_gid,
  297. "file.is_link": mock_f,
  298. "file.readlink": mock_target,
  299. "user.info": mock_empty,
  300. "user.current": mock_user,
  301. },
  302. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  303. os.path, "exists", mock_t
  304. ), patch.object(
  305. os.path, "isfile", mock_t
  306. ), patch.object(
  307. os.path, "isdir", mock_t
  308. ), patch(
  309. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  310. ):
  311. comt = "Backupname must be an absolute path or a file name: {0}".format(
  312. "tmp/SALT"
  313. )
  314. ret.update({"comment": comt, "result": False, "changes": {}})
  315. self.assertDictEqual(
  316. filestate.symlink(
  317. name, target, user=user, group=group, backupname="tmp/SALT"
  318. ),
  319. ret,
  320. )
  321. with patch.dict(
  322. filestate.__salt__,
  323. {
  324. "config.manage_mode": mock_t,
  325. "file.user_to_uid": mock_uid,
  326. "file.group_to_gid": mock_gid,
  327. "file.is_link": mock_f,
  328. "file.readlink": mock_target,
  329. "user.info": mock_empty,
  330. "user.current": mock_user,
  331. },
  332. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  333. os.path, "isdir", mock_t
  334. ), patch.object(
  335. os.path, "exists", mock_t
  336. ), patch.object(
  337. os.path, "isfile", mock_t
  338. ), patch(
  339. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  340. ):
  341. comt = "File exists where the symlink {0} should be".format(name)
  342. ret = return_val({"comment": comt, "changes": {}, "result": False})
  343. self.assertDictEqual(
  344. filestate.symlink(name, target, user=user, group=group), ret
  345. )
  346. with patch.dict(
  347. filestate.__salt__,
  348. {
  349. "config.manage_mode": mock_t,
  350. "file.user_to_uid": mock_uid,
  351. "file.group_to_gid": mock_gid,
  352. "file.is_link": mock_f,
  353. "file.readlink": mock_target,
  354. "file.symlink": mock_t,
  355. "user.info": mock_t,
  356. "file.lchown": mock_f,
  357. },
  358. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  359. os.path, "isdir", MagicMock(side_effect=[True, False])
  360. ), patch.object(
  361. os.path, "isdir", mock_t
  362. ), patch.object(
  363. os.path, "exists", mock_t
  364. ), patch(
  365. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  366. ):
  367. comt = "Directory exists where the symlink {0} should be".format(name)
  368. ret = return_val({"comment": comt, "result": False, "changes": {}})
  369. self.assertDictEqual(
  370. filestate.symlink(name, target, user=user, group=group), ret
  371. )
  372. with patch.dict(
  373. filestate.__salt__,
  374. {
  375. "config.manage_mode": mock_t,
  376. "file.user_to_uid": mock_uid,
  377. "file.group_to_gid": mock_gid,
  378. "file.is_link": mock_f,
  379. "file.readlink": mock_target,
  380. "file.symlink": mock_os_error,
  381. "user.info": mock_t,
  382. "file.lchown": mock_f,
  383. },
  384. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  385. os.path, "isdir", MagicMock(side_effect=[True, False])
  386. ), patch.object(
  387. os.path, "isfile", mock_f
  388. ), patch(
  389. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  390. ):
  391. comt = "Unable to create new symlink {0} -> {1}: ".format(name, target)
  392. ret = return_val({"comment": comt, "result": False, "changes": {}})
  393. self.assertDictEqual(
  394. filestate.symlink(name, target, user=user, group=group), ret
  395. )
  396. with patch.dict(
  397. filestate.__salt__,
  398. {
  399. "config.manage_mode": mock_t,
  400. "file.user_to_uid": mock_uid,
  401. "file.group_to_gid": mock_gid,
  402. "file.is_link": mock_f,
  403. "file.readlink": mock_target,
  404. "file.symlink": mock_t,
  405. "user.info": mock_t,
  406. "file.lchown": mock_f,
  407. "file.get_user": mock_user,
  408. "file.get_group": mock_grp,
  409. },
  410. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  411. os.path, "isdir", MagicMock(side_effect=[True, False])
  412. ), patch.object(
  413. os.path, "isfile", mock_f
  414. ), patch(
  415. "salt.states.file._check_symlink_ownership", return_value=True
  416. ), patch(
  417. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  418. ):
  419. comt = "Created new symlink {0} -> {1}".format(name, target)
  420. ret = return_val(
  421. {"comment": comt, "result": True, "changes": {"new": name}}
  422. )
  423. self.assertDictEqual(
  424. filestate.symlink(name, target, user=user, group=group), ret
  425. )
  426. with patch.dict(
  427. filestate.__salt__,
  428. {
  429. "config.manage_mode": mock_t,
  430. "file.user_to_uid": mock_uid,
  431. "file.group_to_gid": mock_gid,
  432. "file.is_link": mock_f,
  433. "file.readlink": mock_target,
  434. "file.symlink": mock_t,
  435. "user.info": mock_t,
  436. "file.lchown": mock_f,
  437. "file.get_user": mock_empty,
  438. "file.get_group": mock_empty,
  439. },
  440. ), patch.dict(filestate.__opts__, {"test": False}), patch.object(
  441. os.path, "isdir", MagicMock(side_effect=[True, False])
  442. ), patch.object(
  443. os.path, "isfile", mock_f
  444. ), patch(
  445. "salt.utils.win_functions.get_sid_from_name", return_value="test-sid"
  446. ), patch(
  447. "salt.states.file._set_symlink_ownership", return_value=False
  448. ), patch(
  449. "salt.states.file._check_symlink_ownership", return_value=False
  450. ):
  451. comt = (
  452. "Created new symlink {0} -> {1}, but was unable to set "
  453. "ownership to {2}:{3}".format(name, target, user, group)
  454. )
  455. ret = return_val(
  456. {"comment": comt, "result": False, "changes": {"new": name}}
  457. )
  458. self.assertDictEqual(
  459. filestate.symlink(name, target, user=user, group=group), ret
  460. )
  461. @skipIf(salt.utils.platform.is_windows(), "Do not run on Windows")
  462. def test_hardlink(self):
  463. """
  464. Test to create a hardlink.
  465. """
  466. name = os.path.join(os.sep, "tmp", "testfile.txt")
  467. target = salt.utils.files.mkstemp()
  468. test_dir = os.path.join(os.sep, "tmp")
  469. user, group = "salt", "saltstack"
  470. def return_val(**kwargs):
  471. res = {
  472. "name": name,
  473. "result": False,
  474. "comment": "",
  475. "changes": {},
  476. }
  477. res.update(kwargs)
  478. return res
  479. mock_t = MagicMock(return_value=True)
  480. mock_f = MagicMock(return_value=False)
  481. mock_empty = MagicMock(return_value="")
  482. mock_uid = MagicMock(return_value="U1001")
  483. mock_gid = MagicMock(return_value="g1001")
  484. mock_nothing = MagicMock(return_value={})
  485. mock_stats = MagicMock(return_value={"inode": 1})
  486. mock_execerror = MagicMock(side_effect=CommandExecutionError)
  487. patches = {}
  488. patches["file.user_to_uid"] = mock_empty
  489. patches["file.group_to_gid"] = mock_empty
  490. patches["user.info"] = mock_empty
  491. patches["file.is_hardlink"] = mock_t
  492. patches["file.stats"] = mock_empty
  493. # Argument validation
  494. with patch.dict(filestate.__salt__, patches):
  495. expected = "Must provide name to file.hardlink"
  496. ret = return_val(comment=expected, name="")
  497. self.assertDictEqual(filestate.hardlink("", target), ret)
  498. # User validation for dir_mode
  499. with patch.dict(filestate.__salt__, patches), patch.dict(
  500. filestate.__salt__, {"file.user_to_uid": mock_empty}
  501. ), patch.dict(
  502. filestate.__salt__, {"file.group_to_gid": mock_gid}
  503. ), patch.object(
  504. os.path, "isabs", mock_t
  505. ):
  506. expected = "User {0} does not exist".format(user)
  507. ret = return_val(comment=expected, name=name)
  508. self.assertDictEqual(
  509. filestate.hardlink(name, target, user=user, group=group), ret
  510. )
  511. # Group validation for dir_mode
  512. with patch.dict(filestate.__salt__, patches), patch.dict(
  513. filestate.__salt__, {"file.user_to_uid": mock_uid}
  514. ), patch.dict(
  515. filestate.__salt__, {"file.group_to_gid": mock_empty}
  516. ), patch.object(
  517. os.path, "isabs", mock_t
  518. ):
  519. expected = "Group {0} does not exist".format(group)
  520. ret = return_val(comment=expected, name=name)
  521. self.assertDictEqual(
  522. filestate.hardlink(name, target, user=user, group=group), ret
  523. )
  524. # Absolute path for name
  525. nonabs = "./non-existent-path/to/non-existent-file"
  526. with patch.dict(filestate.__salt__, patches), patch.dict(
  527. filestate.__salt__, {"file.user_to_uid": mock_uid}
  528. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}):
  529. expected = "Specified file {0} is not an absolute path".format(nonabs)
  530. ret = return_val(comment=expected, name=nonabs)
  531. self.assertDictEqual(
  532. filestate.hardlink(nonabs, target, user=user, group=group), ret
  533. )
  534. # Absolute path for target
  535. with patch.dict(filestate.__salt__, patches), patch.dict(
  536. filestate.__salt__, {"file.user_to_uid": mock_uid}
  537. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}):
  538. expected = "Specified target {0} is not an absolute path".format(nonabs)
  539. ret = return_val(comment=expected, name=name)
  540. self.assertDictEqual(
  541. filestate.hardlink(name, nonabs, user=user, group=group), ret
  542. )
  543. # Test option -- nonexistent target
  544. with patch.dict(filestate.__salt__, patches), patch.dict(
  545. filestate.__salt__, {"file.user_to_uid": mock_uid}
  546. ), patch.dict(
  547. filestate.__salt__, {"file.group_to_gid": mock_gid}
  548. ), patch.object(
  549. os.path, "exists", mock_f
  550. ), patch.dict(
  551. filestate.__opts__, {"test": True}
  552. ):
  553. expected = "Target {0} for hard link does not exist".format(target)
  554. ret = return_val(comment=expected, name=name)
  555. self.assertDictEqual(
  556. filestate.hardlink(name, target, user=user, group=group), ret
  557. )
  558. # Test option -- target is a directory
  559. with patch.dict(filestate.__salt__, patches), patch.dict(
  560. filestate.__salt__, {"file.user_to_uid": mock_uid}
  561. ), patch.dict(
  562. filestate.__salt__, {"file.group_to_gid": mock_gid}
  563. ), patch.object(
  564. os.path, "exists", mock_t
  565. ), patch.dict(
  566. filestate.__opts__, {"test": True}
  567. ):
  568. expected = "Unable to hard link from directory {0}".format(test_dir)
  569. ret = return_val(comment=expected, name=name)
  570. self.assertDictEqual(
  571. filestate.hardlink(name, test_dir, user=user, group=group), ret
  572. )
  573. # Test option -- name is a directory
  574. with patch.dict(filestate.__salt__, patches), patch.dict(
  575. filestate.__salt__, {"file.user_to_uid": mock_uid}
  576. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  577. filestate.__opts__, {"test": True}
  578. ):
  579. expected = "Unable to hard link to directory {0}".format(test_dir)
  580. ret = return_val(comment=expected, name=test_dir)
  581. self.assertDictEqual(
  582. filestate.hardlink(test_dir, target, user=user, group=group), ret
  583. )
  584. # Test option -- name does not exist
  585. with patch.dict(filestate.__salt__, patches), patch.dict(
  586. filestate.__salt__, {"file.user_to_uid": mock_uid}
  587. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  588. filestate.__opts__, {"test": True}
  589. ):
  590. expected = "Hard link {0} to {1} is set for creation".format(name, target)
  591. changes = dict(new=name)
  592. ret = return_val(result=None, comment=expected, name=name, changes=changes)
  593. self.assertDictEqual(
  594. filestate.hardlink(name, target, user=user, group=group), ret
  595. )
  596. # Test option -- hardlink matches
  597. with patch.dict(filestate.__salt__, patches), patch.dict(
  598. filestate.__salt__, {"file.user_to_uid": mock_uid}
  599. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  600. filestate.__salt__, {"file.is_hardlink": mock_t}
  601. ), patch.dict(
  602. filestate.__salt__, {"file.stats": mock_stats}
  603. ), patch.object(
  604. os.path, "exists", mock_t
  605. ), patch.dict(
  606. filestate.__opts__, {"test": True}
  607. ):
  608. expected = "The hard link {0} is presently targetting {1}".format(
  609. name, target
  610. )
  611. ret = return_val(result=True, comment=expected, name=name)
  612. self.assertDictEqual(
  613. filestate.hardlink(name, target, user=user, group=group), ret
  614. )
  615. # Test option -- hardlink does not match
  616. with patch.dict(filestate.__salt__, patches), patch.dict(
  617. filestate.__salt__, {"file.user_to_uid": mock_uid}
  618. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  619. filestate.__salt__, {"file.is_hardlink": mock_t}
  620. ), patch.dict(
  621. filestate.__salt__, {"file.stats": mock_nothing}
  622. ), patch.object(
  623. os.path, "exists", mock_t
  624. ), patch.dict(
  625. filestate.__opts__, {"test": True}
  626. ):
  627. expected = "Link {0} target is set to be changed to {1}".format(
  628. name, target
  629. )
  630. changes = dict(change=name)
  631. ret = return_val(result=None, comment=expected, name=name, changes=changes)
  632. self.assertDictEqual(
  633. filestate.hardlink(name, target, user=user, group=group), ret
  634. )
  635. # Test option -- force removal
  636. with patch.dict(filestate.__salt__, patches), patch.dict(
  637. filestate.__salt__, {"file.user_to_uid": mock_uid}
  638. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  639. filestate.__salt__, {"file.is_hardlink": mock_f}
  640. ), patch.object(
  641. os.path, "exists", mock_t
  642. ), patch.dict(
  643. filestate.__opts__, {"test": True}
  644. ):
  645. expected = (
  646. "The file or directory {0} is set for removal to "
  647. "make way for a new hard link targeting {1}".format(name, target)
  648. )
  649. ret = return_val(result=None, comment=expected, name=name)
  650. self.assertDictEqual(
  651. filestate.hardlink(name, target, force=True, user=user, group=group),
  652. ret,
  653. )
  654. # Test option -- without force removal
  655. with patch.dict(filestate.__salt__, patches), patch.dict(
  656. filestate.__salt__, {"file.user_to_uid": mock_uid}
  657. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  658. filestate.__salt__, {"file.is_hardlink": mock_f}
  659. ), patch.object(
  660. os.path, "exists", mock_t
  661. ), patch.dict(
  662. filestate.__opts__, {"test": True}
  663. ):
  664. expected = (
  665. "File or directory exists where the hard link {0} "
  666. "should be. Did you mean to use force?".format(name)
  667. )
  668. ret = return_val(result=False, comment=expected, name=name)
  669. self.assertDictEqual(
  670. filestate.hardlink(name, target, force=False, user=user, group=group),
  671. ret,
  672. )
  673. # Target is a directory
  674. with patch.dict(filestate.__salt__, patches), patch.dict(
  675. filestate.__salt__, {"file.user_to_uid": mock_uid}
  676. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}):
  677. expected = "Unable to hard link from directory {0}".format(test_dir)
  678. ret = return_val(comment=expected, name=name)
  679. self.assertDictEqual(
  680. filestate.hardlink(name, test_dir, user=user, group=group), ret
  681. )
  682. # Name is a directory
  683. with patch.dict(filestate.__salt__, patches), patch.dict(
  684. filestate.__salt__, {"file.user_to_uid": mock_uid}
  685. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}):
  686. expected = "Unable to hard link to directory {0}".format(test_dir)
  687. ret = return_val(comment=expected, name=test_dir)
  688. self.assertDictEqual(
  689. filestate.hardlink(test_dir, target, user=user, group=group), ret
  690. )
  691. # Try overwrite file with link
  692. with patch.dict(filestate.__salt__, patches), patch.dict(
  693. filestate.__salt__, {"file.user_to_uid": mock_uid}
  694. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  695. filestate.__salt__, {"file.is_hardlink": mock_f}
  696. ), patch.object(
  697. os.path, "isfile", mock_t
  698. ):
  699. expected = "File exists where the hard link {0} should be".format(name)
  700. ret = return_val(comment=expected, name=name)
  701. self.assertDictEqual(
  702. filestate.hardlink(name, target, user=user, group=group), ret
  703. )
  704. # Try overwrite link with same
  705. with patch.dict(filestate.__salt__, patches), patch.dict(
  706. filestate.__salt__, {"file.user_to_uid": mock_uid}
  707. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  708. filestate.__salt__, {"file.is_hardlink": mock_t}
  709. ), patch.dict(
  710. filestate.__salt__, {"file.stats": mock_stats}
  711. ), patch.object(
  712. os.path, "isfile", mock_f
  713. ):
  714. expected = "Target of hard link {0} is already pointing " "to {1}".format(
  715. name, target
  716. )
  717. ret = return_val(result=True, comment=expected, name=name)
  718. self.assertDictEqual(
  719. filestate.hardlink(name, target, user=user, group=group), ret
  720. )
  721. # Really overwrite link with same
  722. with patch.dict(filestate.__salt__, patches), patch.dict(
  723. filestate.__salt__, {"file.user_to_uid": mock_uid}
  724. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  725. filestate.__salt__, {"file.is_hardlink": mock_t}
  726. ), patch.dict(
  727. filestate.__salt__, {"file.link": mock_t}
  728. ), patch.dict(
  729. filestate.__salt__, {"file.stats": mock_nothing}
  730. ), patch.object(
  731. os, "remove", mock_t
  732. ), patch.object(
  733. os.path, "isfile", mock_f
  734. ):
  735. expected = "Set target of hard link {0} -> {1}".format(name, target)
  736. changes = dict(new=name)
  737. ret = return_val(result=True, comment=expected, name=name, changes=changes)
  738. self.assertDictEqual(
  739. filestate.hardlink(name, target, user=user, group=group), ret
  740. )
  741. # Fail at overwriting link with same
  742. with patch.dict(filestate.__salt__, patches), patch.dict(
  743. filestate.__salt__, {"file.user_to_uid": mock_uid}
  744. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  745. filestate.__salt__, {"file.is_hardlink": mock_t}
  746. ), patch.dict(
  747. filestate.__salt__, {"file.link": mock_execerror}
  748. ), patch.dict(
  749. filestate.__salt__, {"file.stats": mock_nothing}
  750. ), patch.object(
  751. os, "remove", mock_t
  752. ), patch.object(
  753. os.path, "isfile", mock_f
  754. ):
  755. expected = "Unable to set target of hard link {0} -> " "{1}: {2}".format(
  756. name, target, ""
  757. )
  758. ret = return_val(result=False, comment=expected, name=name)
  759. self.assertDictEqual(
  760. filestate.hardlink(name, target, user=user, group=group), ret
  761. )
  762. # Make new link
  763. with patch.dict(filestate.__salt__, patches), patch.dict(
  764. filestate.__salt__, {"file.user_to_uid": mock_uid}
  765. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  766. filestate.__salt__, {"file.is_hardlink": mock_f}
  767. ), patch.dict(
  768. filestate.__salt__, {"file.link": mock_f}
  769. ), patch.dict(
  770. filestate.__salt__, {"file.stats": mock_nothing}
  771. ), patch.object(
  772. os, "remove", mock_t
  773. ), patch.object(
  774. os.path, "isfile", mock_f
  775. ):
  776. expected = "Created new hard link {0} -> {1}".format(name, target)
  777. changes = dict(new=name)
  778. ret = return_val(result=True, comment=expected, name=name, changes=changes)
  779. self.assertDictEqual(
  780. filestate.hardlink(name, target, user=user, group=group), ret
  781. )
  782. # Fail while making new link
  783. with patch.dict(filestate.__salt__, patches), patch.dict(
  784. filestate.__salt__, {"file.user_to_uid": mock_uid}
  785. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  786. filestate.__salt__, {"file.is_hardlink": mock_f}
  787. ), patch.dict(
  788. filestate.__salt__, {"file.link": mock_execerror}
  789. ), patch.dict(
  790. filestate.__salt__, {"file.stats": mock_nothing}
  791. ), patch.object(
  792. os, "remove", mock_t
  793. ), patch.object(
  794. os.path, "isfile", mock_f
  795. ):
  796. expected = "Unable to create new hard link {0} -> " "{1}: {2}".format(
  797. name, target, ""
  798. )
  799. ret = return_val(result=False, comment=expected, name=name)
  800. self.assertDictEqual(
  801. filestate.hardlink(name, target, user=user, group=group), ret
  802. )
  803. # Force making new link over file
  804. with patch.dict(filestate.__salt__, patches), patch.dict(
  805. filestate.__salt__, {"file.user_to_uid": mock_uid}
  806. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  807. filestate.__salt__, {"file.is_hardlink": mock_f}
  808. ), patch.dict(
  809. filestate.__salt__, {"file.link": mock_t}
  810. ), patch.dict(
  811. filestate.__salt__, {"file.stats": mock_nothing}
  812. ), patch.object(
  813. os, "remove", mock_t
  814. ), patch.object(
  815. os.path, "isfile", mock_t
  816. ):
  817. expected = "Created new hard link {0} -> {1}".format(name, target)
  818. changes = dict(new=name)
  819. changes["forced"] = "File for hard link was forcibly replaced"
  820. ret = return_val(result=True, comment=expected, name=name, changes=changes)
  821. self.assertDictEqual(
  822. filestate.hardlink(name, target, user=user, force=True, group=group),
  823. ret,
  824. )
  825. # Force making new link over file but error out
  826. with patch.dict(filestate.__salt__, patches), patch.dict(
  827. filestate.__salt__, {"file.user_to_uid": mock_uid}
  828. ), patch.dict(filestate.__salt__, {"file.group_to_gid": mock_gid}), patch.dict(
  829. filestate.__salt__, {"file.is_hardlink": mock_f}
  830. ), patch.dict(
  831. filestate.__salt__, {"file.link": mock_execerror}
  832. ), patch.dict(
  833. filestate.__salt__, {"file.stats": mock_nothing}
  834. ), patch.object(
  835. os, "remove", mock_t
  836. ), patch.object(
  837. os.path, "isfile", mock_t
  838. ):
  839. expected = "Unable to create new hard link {0} -> " "{1}: {2}".format(
  840. name, target, ""
  841. )
  842. changes = dict(forced="File for hard link was forcibly replaced")
  843. ret = return_val(result=False, comment=expected, name=name, changes=changes)
  844. self.assertDictEqual(
  845. filestate.hardlink(name, target, user=user, force=True, group=group),
  846. ret,
  847. )
  848. # 'absent' function tests: 1
  849. def test_absent(self):
  850. """
  851. Test to make sure that the named file or directory is absent.
  852. """
  853. name = "/fake/file.conf"
  854. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  855. mock_t = MagicMock(return_value=True)
  856. mock_f = MagicMock(return_value=False)
  857. mock_file = MagicMock(side_effect=[True, CommandExecutionError])
  858. mock_tree = MagicMock(side_effect=[True, OSError])
  859. comt = "Must provide name to file.absent"
  860. ret.update({"comment": comt, "name": ""})
  861. with patch.object(os.path, "islink", MagicMock(return_value=False)):
  862. self.assertDictEqual(filestate.absent(""), ret)
  863. with patch.object(os.path, "isabs", mock_f):
  864. comt = "Specified file {0} is not an absolute path".format(name)
  865. ret.update({"comment": comt, "name": name})
  866. self.assertDictEqual(filestate.absent(name), ret)
  867. with patch.object(os.path, "isabs", mock_t):
  868. comt = 'Refusing to make "/" absent'
  869. ret.update({"comment": comt, "name": "/"})
  870. self.assertDictEqual(filestate.absent("/"), ret)
  871. with patch.object(os.path, "isfile", mock_t):
  872. with patch.dict(filestate.__opts__, {"test": True}):
  873. comt = "File {0} is set for removal".format(name)
  874. ret.update(
  875. {
  876. "comment": comt,
  877. "name": name,
  878. "result": None,
  879. "changes": {"removed": "/fake/file.conf"},
  880. }
  881. )
  882. self.assertDictEqual(filestate.absent(name), ret)
  883. with patch.dict(filestate.__opts__, {"test": False}):
  884. with patch.dict(filestate.__salt__, {"file.remove": mock_file}):
  885. comt = "Removed file {0}".format(name)
  886. ret.update(
  887. {
  888. "comment": comt,
  889. "result": True,
  890. "changes": {"removed": name},
  891. }
  892. )
  893. self.assertDictEqual(filestate.absent(name), ret)
  894. comt = "Removed file {0}".format(name)
  895. ret.update({"comment": "", "result": False, "changes": {}})
  896. self.assertDictEqual(filestate.absent(name), ret)
  897. with patch.object(os.path, "isfile", mock_f):
  898. with patch.object(os.path, "isdir", mock_t):
  899. with patch.dict(filestate.__opts__, {"test": True}):
  900. comt = "Directory {0} is set for removal".format(name)
  901. ret.update(
  902. {
  903. "comment": comt,
  904. "changes": {"removed": name},
  905. "result": None,
  906. }
  907. )
  908. self.assertDictEqual(filestate.absent(name), ret)
  909. with patch.dict(filestate.__opts__, {"test": False}):
  910. with patch.dict(filestate.__salt__, {"file.remove": mock_tree}):
  911. comt = "Removed directory {0}".format(name)
  912. ret.update(
  913. {
  914. "comment": comt,
  915. "result": True,
  916. "changes": {"removed": name},
  917. }
  918. )
  919. self.assertDictEqual(filestate.absent(name), ret)
  920. comt = "Failed to remove directory {0}".format(name)
  921. ret.update(
  922. {"comment": comt, "result": False, "changes": {}}
  923. )
  924. self.assertDictEqual(filestate.absent(name), ret)
  925. with patch.object(os.path, "isdir", mock_f):
  926. with patch.dict(filestate.__opts__, {"test": True}):
  927. comt = "File {0} is not present".format(name)
  928. ret.update({"comment": comt, "result": True})
  929. self.assertDictEqual(filestate.absent(name), ret)
  930. # 'exists' function tests: 1
  931. def test_exists(self):
  932. """
  933. Test to verify that the named file or directory is present or exists.
  934. """
  935. name = "/etc/grub.conf"
  936. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  937. mock_t = MagicMock(return_value=True)
  938. mock_f = MagicMock(return_value=False)
  939. comt = "Must provide name to file.exists"
  940. ret.update({"comment": comt, "name": ""})
  941. self.assertDictEqual(filestate.exists(""), ret)
  942. with patch.object(os.path, "exists", mock_f):
  943. comt = "Specified path {0} does not exist".format(name)
  944. ret.update({"comment": comt, "name": name})
  945. self.assertDictEqual(filestate.exists(name), ret)
  946. with patch.object(os.path, "exists", mock_t):
  947. comt = "Path {0} exists".format(name)
  948. ret.update({"comment": comt, "result": True})
  949. self.assertDictEqual(filestate.exists(name), ret)
  950. # 'missing' function tests: 1
  951. def test_missing(self):
  952. """
  953. Test to verify that the named file or directory is missing.
  954. """
  955. name = "/etc/grub.conf"
  956. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  957. mock_t = MagicMock(return_value=True)
  958. mock_f = MagicMock(return_value=False)
  959. comt = "Must provide name to file.missing"
  960. ret.update({"comment": comt, "name": "", "changes": {}})
  961. self.assertDictEqual(filestate.missing(""), ret)
  962. with patch.object(os.path, "exists", mock_t):
  963. comt = "Specified path {0} exists".format(name)
  964. ret.update({"comment": comt, "name": name})
  965. self.assertDictEqual(filestate.missing(name), ret)
  966. with patch.object(os.path, "exists", mock_f):
  967. comt = "Path {0} is missing".format(name)
  968. ret.update({"comment": comt, "result": True})
  969. self.assertDictEqual(filestate.missing(name), ret)
  970. # 'managed' function tests: 1
  971. def test_file_managed_should_fall_back_to_binary(self):
  972. expected_contents = b"\x8b"
  973. filename = "/tmp/blarg"
  974. mock_manage = MagicMock(return_value={"fnord": "fnords"})
  975. with patch(
  976. "salt.states.file._load_accumulators", MagicMock(return_value=([], []))
  977. ):
  978. with patch.dict(
  979. filestate.__salt__,
  980. {
  981. "file.get_managed": MagicMock(return_value=["", "", ""]),
  982. "file.source_list": MagicMock(return_value=["", ""]),
  983. "file.manage_file": mock_manage,
  984. "pillar.get": MagicMock(return_value=expected_contents),
  985. },
  986. ):
  987. ret = filestate.managed(
  988. filename, contents_pillar="fnord", encoding="utf-8"
  989. )
  990. actual_contents = mock_manage.call_args[0][14]
  991. self.assertEqual(actual_contents, expected_contents)
  992. def test_managed(self):
  993. """
  994. Test to manage a given file, this function allows for a file to be
  995. downloaded from the salt master and potentially run through a templating
  996. system.
  997. """
  998. with patch(
  999. "salt.states.file._load_accumulators", MagicMock(return_value=([], []))
  1000. ):
  1001. name = "/etc/grub.conf"
  1002. user = "salt"
  1003. group = "saltstack"
  1004. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1005. mock_t = MagicMock(return_value=True)
  1006. mock_f = MagicMock(return_value=False)
  1007. mock_cmd_fail = MagicMock(return_value={"retcode": 1})
  1008. mock_uid = MagicMock(
  1009. side_effect=[
  1010. "",
  1011. "U12",
  1012. "U12",
  1013. "U12",
  1014. "U12",
  1015. "U12",
  1016. "U12",
  1017. "U12",
  1018. "U12",
  1019. "U12",
  1020. "U12",
  1021. "U12",
  1022. "U12",
  1023. "U12",
  1024. "U12",
  1025. "U12",
  1026. ]
  1027. )
  1028. mock_gid = MagicMock(
  1029. side_effect=[
  1030. "",
  1031. "G12",
  1032. "G12",
  1033. "G12",
  1034. "G12",
  1035. "G12",
  1036. "G12",
  1037. "G12",
  1038. "G12",
  1039. "G12",
  1040. "G12",
  1041. "G12",
  1042. "G12",
  1043. "G12",
  1044. "G12",
  1045. "G12",
  1046. ]
  1047. )
  1048. mock_if = MagicMock(
  1049. side_effect=[True, False, False, False, False, False, False, False]
  1050. )
  1051. if salt.utils.platform.is_windows():
  1052. mock_ret = MagicMock(return_value=ret)
  1053. else:
  1054. mock_ret = MagicMock(return_value=(ret, None))
  1055. mock_dict = MagicMock(return_value={})
  1056. mock_cp = MagicMock(side_effect=[Exception, True])
  1057. mock_ex = MagicMock(
  1058. side_effect=[Exception, {"changes": {name: name}}, True, Exception]
  1059. )
  1060. mock_mng = MagicMock(
  1061. side_effect=[
  1062. Exception,
  1063. ("", "", ""),
  1064. ("", "", ""),
  1065. ("", "", True),
  1066. ("", "", True),
  1067. ("", "", ""),
  1068. ("", "", ""),
  1069. ]
  1070. )
  1071. mock_file = MagicMock(
  1072. side_effect=[
  1073. CommandExecutionError,
  1074. ("", ""),
  1075. ("", ""),
  1076. ("", ""),
  1077. ("", ""),
  1078. ("", ""),
  1079. ("", ""),
  1080. ("", ""),
  1081. ("", ""),
  1082. ]
  1083. )
  1084. with patch.dict(
  1085. filestate.__salt__,
  1086. {
  1087. "config.manage_mode": mock_t,
  1088. "file.user_to_uid": mock_uid,
  1089. "file.group_to_gid": mock_gid,
  1090. "file.file_exists": mock_if,
  1091. "file.check_perms": mock_ret,
  1092. "file.check_managed_changes": mock_dict,
  1093. "file.get_managed": mock_mng,
  1094. "file.source_list": mock_file,
  1095. "file.copy": mock_cp,
  1096. "file.manage_file": mock_ex,
  1097. "cmd.run_all": mock_cmd_fail,
  1098. },
  1099. ):
  1100. comt = "Destination file name is required"
  1101. ret.update({"comment": comt, "name": "", "changes": {}})
  1102. self.assertDictEqual(filestate.managed(""), ret)
  1103. with patch.object(os.path, "isfile", mock_f):
  1104. comt = (
  1105. "File {0} is not present and is not set for "
  1106. "creation".format(name)
  1107. )
  1108. ret.update({"comment": comt, "name": name, "result": True})
  1109. self.assertDictEqual(filestate.managed(name, create=False), ret)
  1110. # Group argument is ignored on Windows systems. Group is set to
  1111. # user
  1112. if salt.utils.platform.is_windows():
  1113. comt = "User salt is not available Group salt" " is not available"
  1114. else:
  1115. comt = (
  1116. "User salt is not available Group saltstack" " is not available"
  1117. )
  1118. ret.update({"comment": comt, "result": False})
  1119. self.assertDictEqual(
  1120. filestate.managed(name, user=user, group=group), ret
  1121. )
  1122. with patch.object(os.path, "isabs", mock_f):
  1123. comt = "Specified file {0} is not an absolute path".format(name)
  1124. ret.update({"comment": comt, "result": False})
  1125. self.assertDictEqual(
  1126. filestate.managed(name, user=user, group=group), ret
  1127. )
  1128. with patch.object(os.path, "isabs", mock_t):
  1129. with patch.object(os.path, "isdir", mock_t):
  1130. comt = "Specified target {0} is a directory".format(name)
  1131. ret.update({"comment": comt})
  1132. self.assertDictEqual(
  1133. filestate.managed(name, user=user, group=group), ret
  1134. )
  1135. with patch.object(os.path, "isdir", mock_f):
  1136. comt = "Context must be formed as a dict"
  1137. ret.update({"comment": comt})
  1138. self.assertDictEqual(
  1139. filestate.managed(
  1140. name, user=user, group=group, context=True
  1141. ),
  1142. ret,
  1143. )
  1144. comt = "Defaults must be formed as a dict"
  1145. ret.update({"comment": comt})
  1146. self.assertDictEqual(
  1147. filestate.managed(
  1148. name, user=user, group=group, defaults=True
  1149. ),
  1150. ret,
  1151. )
  1152. comt = (
  1153. "Only one of 'contents', 'contents_pillar', "
  1154. "and 'contents_grains' is permitted"
  1155. )
  1156. ret.update({"comment": comt})
  1157. self.assertDictEqual(
  1158. filestate.managed(
  1159. name,
  1160. user=user,
  1161. group=group,
  1162. contents="A",
  1163. contents_grains="B",
  1164. contents_pillar="C",
  1165. ),
  1166. ret,
  1167. )
  1168. with patch.object(os.path, "exists", mock_t):
  1169. with patch.dict(filestate.__opts__, {"test": True}):
  1170. comt = "File {0} not updated".format(name)
  1171. ret.update({"comment": comt})
  1172. self.assertDictEqual(
  1173. filestate.managed(
  1174. name, user=user, group=group, replace=False
  1175. ),
  1176. ret,
  1177. )
  1178. comt = "The file {0} is in the correct state".format(
  1179. name
  1180. )
  1181. ret.update({"comment": comt, "result": True})
  1182. self.assertDictEqual(
  1183. filestate.managed(
  1184. name, user=user, contents="A", group=group
  1185. ),
  1186. ret,
  1187. )
  1188. with patch.object(os.path, "exists", mock_f):
  1189. with patch.dict(filestate.__opts__, {"test": False}):
  1190. comt = "Unable to manage file: "
  1191. ret.update({"comment": comt, "result": False})
  1192. self.assertDictEqual(
  1193. filestate.managed(
  1194. name, user=user, group=group, contents="A"
  1195. ),
  1196. ret,
  1197. )
  1198. comt = "Unable to manage file: "
  1199. ret.update({"comment": comt, "result": False})
  1200. self.assertDictEqual(
  1201. filestate.managed(
  1202. name, user=user, group=group, contents="A"
  1203. ),
  1204. ret,
  1205. )
  1206. with patch.object(
  1207. salt.utils.files, "mkstemp", return_value=name
  1208. ):
  1209. comt = "Unable to copy file {0} to {0}: ".format(
  1210. name
  1211. )
  1212. ret.update({"comment": comt, "result": False})
  1213. self.assertDictEqual(
  1214. filestate.managed(
  1215. name, user=user, group=group, check_cmd="A"
  1216. ),
  1217. ret,
  1218. )
  1219. comt = "Unable to check_cmd file: "
  1220. ret.update({"comment": comt, "result": False})
  1221. self.assertDictEqual(
  1222. filestate.managed(
  1223. name, user=user, group=group, check_cmd="A"
  1224. ),
  1225. ret,
  1226. )
  1227. comt = "check_cmd execution failed"
  1228. ret.update(
  1229. {
  1230. "comment": comt,
  1231. "result": False,
  1232. "skip_watch": True,
  1233. }
  1234. )
  1235. self.assertDictEqual(
  1236. filestate.managed(
  1237. name, user=user, group=group, check_cmd="A"
  1238. ),
  1239. ret,
  1240. )
  1241. comt = "check_cmd execution failed"
  1242. ret.update({"comment": True, "changes": {}})
  1243. ret.pop("skip_watch", None)
  1244. self.assertDictEqual(
  1245. filestate.managed(name, user=user, group=group), ret
  1246. )
  1247. self.assertTrue(
  1248. filestate.managed(name, user=user, group=group)
  1249. )
  1250. comt = "Unable to manage file: "
  1251. ret.update({"comment": comt})
  1252. self.assertDictEqual(
  1253. filestate.managed(name, user=user, group=group), ret
  1254. )
  1255. if salt.utils.platform.is_windows():
  1256. mock_ret = MagicMock(return_value=ret)
  1257. comt = "File {0} not updated".format(name)
  1258. else:
  1259. perms = {"luser": user, "lmode": "0644", "lgroup": group}
  1260. mock_ret = MagicMock(return_value=(ret, perms))
  1261. comt = (
  1262. "File {0} will be updated with "
  1263. "permissions 0400 from its current "
  1264. "state of 0644".format(name)
  1265. )
  1266. with patch.dict(
  1267. filestate.__salt__, {"file.check_perms": mock_ret}
  1268. ):
  1269. with patch.object(os.path, "exists", mock_t):
  1270. with patch.dict(filestate.__opts__, {"test": True}):
  1271. ret.update({"comment": comt})
  1272. if salt.utils.platform.is_windows():
  1273. self.assertDictEqual(
  1274. filestate.managed(
  1275. name, user=user, group=group
  1276. ),
  1277. ret,
  1278. )
  1279. else:
  1280. self.assertDictEqual(
  1281. filestate.managed(
  1282. name, user=user, group=group, mode=400
  1283. ),
  1284. ret,
  1285. )
  1286. # 'directory' function tests: 1
  1287. def test_directory(self):
  1288. """
  1289. Test to ensure that a named directory is present and has the right perms
  1290. """
  1291. name = "/etc/testdir"
  1292. user = "salt"
  1293. group = "saltstack"
  1294. if salt.utils.platform.is_windows():
  1295. name = name.replace("/", "\\")
  1296. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1297. check_perms_ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1298. comt = "Must provide name to file.directory"
  1299. ret.update({"comment": comt, "name": ""})
  1300. self.assertDictEqual(filestate.directory(""), ret)
  1301. comt = "Cannot specify both max_depth and clean"
  1302. ret.update({"comment": comt, "name": name})
  1303. self.assertDictEqual(filestate.directory(name, clean=True, max_depth=2), ret)
  1304. mock_t = MagicMock(return_value=True)
  1305. mock_f = MagicMock(return_value=False)
  1306. if salt.utils.platform.is_windows():
  1307. mock_perms = MagicMock(return_value=check_perms_ret)
  1308. else:
  1309. mock_perms = MagicMock(return_value=(check_perms_ret, ""))
  1310. mock_uid = MagicMock(
  1311. side_effect=[
  1312. "",
  1313. "U12",
  1314. "U12",
  1315. "U12",
  1316. "U12",
  1317. "U12",
  1318. "U12",
  1319. "U12",
  1320. "U12",
  1321. "U12",
  1322. "U12",
  1323. ]
  1324. )
  1325. mock_gid = MagicMock(
  1326. side_effect=[
  1327. "",
  1328. "G12",
  1329. "G12",
  1330. "G12",
  1331. "G12",
  1332. "G12",
  1333. "G12",
  1334. "G12",
  1335. "G12",
  1336. "G12",
  1337. "G12",
  1338. ]
  1339. )
  1340. mock_check = MagicMock(
  1341. return_value=(
  1342. None,
  1343. 'The directory "{0}" will be changed'.format(name),
  1344. {name: {"directory": "new"}},
  1345. )
  1346. )
  1347. mock_error = CommandExecutionError
  1348. with patch.dict(
  1349. filestate.__salt__,
  1350. {
  1351. "config.manage_mode": mock_t,
  1352. "file.user_to_uid": mock_uid,
  1353. "file.group_to_gid": mock_gid,
  1354. "file.stats": mock_f,
  1355. "file.check_perms": mock_perms,
  1356. "file.mkdir": mock_t,
  1357. },
  1358. ), patch("salt.utils.win_dacl.get_sid", mock_error), patch(
  1359. "os.path.isdir", mock_t
  1360. ), patch(
  1361. "salt.states.file._check_directory_win", mock_check
  1362. ):
  1363. if salt.utils.platform.is_windows():
  1364. comt = ""
  1365. else:
  1366. comt = "User salt is not available Group saltstack" " is not available"
  1367. ret.update({"comment": comt, "name": name})
  1368. self.assertDictEqual(filestate.directory(name, user=user, group=group), ret)
  1369. with patch.object(os.path, "isabs", mock_f):
  1370. comt = "Specified file {0} is not an absolute path".format(name)
  1371. ret.update({"comment": comt})
  1372. self.assertDictEqual(
  1373. filestate.directory(name, user=user, group=group), ret
  1374. )
  1375. with patch.object(os.path, "isabs", mock_t):
  1376. with patch.object(
  1377. os.path,
  1378. "isfile",
  1379. MagicMock(side_effect=[True, True, False, True, True, True, False]),
  1380. ):
  1381. with patch.object(os.path, "lexists", mock_t):
  1382. comt = "File exists where the backup target" " A should go"
  1383. ret.update({"comment": comt})
  1384. self.assertDictEqual(
  1385. filestate.directory(
  1386. name, user=user, group=group, backupname="A"
  1387. ),
  1388. ret,
  1389. )
  1390. with patch.object(os.path, "isfile", mock_t):
  1391. comt = "Specified location {0} exists and is a file".format(
  1392. name
  1393. )
  1394. ret.update({"comment": comt})
  1395. self.assertDictEqual(
  1396. filestate.directory(name, user=user, group=group), ret
  1397. )
  1398. with patch.object(os.path, "islink", mock_t):
  1399. comt = "Specified location {0} exists and is a symlink".format(
  1400. name
  1401. )
  1402. ret.update({"comment": comt})
  1403. self.assertDictEqual(
  1404. filestate.directory(name, user=user, group=group), ret
  1405. )
  1406. with patch.object(os.path, "isdir", mock_f):
  1407. with patch.dict(filestate.__opts__, {"test": True}):
  1408. if salt.utils.platform.is_windows():
  1409. comt = 'The directory "{0}" will be changed' "".format(name)
  1410. else:
  1411. comt = (
  1412. "The following files will be changed:\n{0}:"
  1413. " directory - new\n".format(name)
  1414. )
  1415. ret.update(
  1416. {
  1417. "comment": comt,
  1418. "result": None,
  1419. "changes": {name: {"directory": "new"}},
  1420. }
  1421. )
  1422. self.assertDictEqual(
  1423. filestate.directory(name, user=user, group=group), ret
  1424. )
  1425. with patch.dict(filestate.__opts__, {"test": False}):
  1426. with patch.object(os.path, "isdir", mock_f):
  1427. comt = "No directory to create {0} in".format(name)
  1428. ret.update({"comment": comt, "result": False})
  1429. self.assertDictEqual(
  1430. filestate.directory(name, user=user, group=group), ret
  1431. )
  1432. if salt.utils.platform.is_windows():
  1433. isdir_side_effect = [False, True, False]
  1434. else:
  1435. isdir_side_effect = [True, False, True, False]
  1436. with patch.object(
  1437. os.path, "isdir", MagicMock(side_effect=isdir_side_effect)
  1438. ):
  1439. comt = "Failed to create directory {0}".format(name)
  1440. ret.update(
  1441. {
  1442. "comment": comt,
  1443. "result": False,
  1444. "changes": {name: "New Dir"},
  1445. }
  1446. )
  1447. self.assertDictEqual(
  1448. filestate.directory(name, user=user, group=group), ret
  1449. )
  1450. check_perms_ret = {
  1451. "name": name,
  1452. "result": False,
  1453. "comment": "",
  1454. "changes": {},
  1455. }
  1456. if salt.utils.platform.is_windows():
  1457. mock_perms = MagicMock(return_value=check_perms_ret)
  1458. else:
  1459. mock_perms = MagicMock(return_value=(check_perms_ret, ""))
  1460. recurse = ["silent"]
  1461. ret = {
  1462. "name": name,
  1463. "result": False,
  1464. "comment": "Directory /etc/testdir updated",
  1465. "changes": {"recursion": "Changes silenced"},
  1466. }
  1467. if salt.utils.platform.is_windows():
  1468. ret["comment"] = ret["comment"].replace("/", "\\")
  1469. with patch.dict(
  1470. filestate.__salt__, {"file.check_perms": mock_perms}
  1471. ):
  1472. with patch.object(os.path, "isdir", mock_t):
  1473. self.assertDictEqual(
  1474. filestate.directory(
  1475. name, user=user, recurse=recurse, group=group
  1476. ),
  1477. ret,
  1478. )
  1479. check_perms_ret = {
  1480. "name": name,
  1481. "result": False,
  1482. "comment": "",
  1483. "changes": {},
  1484. }
  1485. if salt.utils.platform.is_windows():
  1486. mock_perms = MagicMock(return_value=check_perms_ret)
  1487. else:
  1488. mock_perms = MagicMock(return_value=(check_perms_ret, ""))
  1489. recurse = ["ignore_files", "ignore_dirs"]
  1490. ret = {
  1491. "name": name,
  1492. "result": False,
  1493. "comment": 'Must not specify "recurse" '
  1494. 'options "ignore_files" and '
  1495. '"ignore_dirs" at the same '
  1496. "time.",
  1497. "changes": {},
  1498. }
  1499. with patch.dict(
  1500. filestate.__salt__, {"file.check_perms": mock_perms}
  1501. ):
  1502. with patch.object(os.path, "isdir", mock_t):
  1503. self.assertDictEqual(
  1504. filestate.directory(
  1505. name, user=user, recurse=recurse, group=group
  1506. ),
  1507. ret,
  1508. )
  1509. comt = "Directory {0} updated".format(name)
  1510. ret = {
  1511. "name": name,
  1512. "result": True,
  1513. "comment": comt,
  1514. "changes": {
  1515. "group": "group",
  1516. "mode": "0777",
  1517. "user": "user",
  1518. },
  1519. }
  1520. check_perms_ret = {
  1521. "name": name,
  1522. "result": True,
  1523. "comment": "",
  1524. "changes": {
  1525. "group": "group",
  1526. "mode": "0777",
  1527. "user": "user",
  1528. },
  1529. }
  1530. if salt.utils.platform.is_windows():
  1531. _mock_perms = MagicMock(return_value=check_perms_ret)
  1532. else:
  1533. _mock_perms = MagicMock(return_value=(check_perms_ret, ""))
  1534. with patch.object(os.path, "isdir", mock_t):
  1535. with patch.dict(
  1536. filestate.__salt__, {"file.check_perms": _mock_perms}
  1537. ):
  1538. self.assertDictEqual(
  1539. filestate.directory(name, user=user, group=group),
  1540. ret,
  1541. )
  1542. # 'recurse' function tests: 1
  1543. def test_recurse(self):
  1544. """
  1545. Test to recurse through a subdirectory on the master
  1546. and copy said subdirectory over to the specified path.
  1547. """
  1548. name = "/opt/code/flask"
  1549. source = "salt://code/flask"
  1550. user = "salt"
  1551. group = "saltstack"
  1552. if salt.utils.platform.is_windows():
  1553. name = name.replace("/", "\\")
  1554. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1555. comt = (
  1556. "'mode' is not allowed in 'file.recurse'."
  1557. " Please use 'file_mode' and 'dir_mode'."
  1558. )
  1559. ret.update({"comment": comt})
  1560. self.assertDictEqual(filestate.recurse(name, source, mode="W"), ret)
  1561. mock_t = MagicMock(return_value=True)
  1562. mock_f = MagicMock(return_value=False)
  1563. mock_uid = MagicMock(return_value="")
  1564. mock_gid = MagicMock(return_value="")
  1565. mock_l = MagicMock(return_value=[])
  1566. mock_emt = MagicMock(side_effect=[[], ["code/flask"], ["code/flask"]])
  1567. mock_lst = MagicMock(
  1568. side_effect=[
  1569. CommandExecutionError,
  1570. (source, ""),
  1571. (source, ""),
  1572. (source, ""),
  1573. ]
  1574. )
  1575. with patch.dict(
  1576. filestate.__salt__,
  1577. {
  1578. "config.manage_mode": mock_t,
  1579. "file.user_to_uid": mock_uid,
  1580. "file.group_to_gid": mock_gid,
  1581. "file.source_list": mock_lst,
  1582. "cp.list_master_dirs": mock_emt,
  1583. "cp.list_master": mock_l,
  1584. },
  1585. ):
  1586. # Group argument is ignored on Windows systems. Group is set to user
  1587. if salt.utils.platform.is_windows():
  1588. comt = "User salt is not available Group salt" " is not available"
  1589. else:
  1590. comt = "User salt is not available Group saltstack" " is not available"
  1591. ret.update({"comment": comt})
  1592. self.assertDictEqual(
  1593. filestate.recurse(name, source, user=user, group=group), ret
  1594. )
  1595. with patch.object(os.path, "isabs", mock_f):
  1596. comt = "Specified file {0} is not an absolute path".format(name)
  1597. ret.update({"comment": comt})
  1598. self.assertDictEqual(filestate.recurse(name, source), ret)
  1599. with patch.object(os.path, "isabs", mock_t):
  1600. comt = "Invalid source '1' (must be a salt:// URI)"
  1601. ret.update({"comment": comt})
  1602. self.assertDictEqual(filestate.recurse(name, 1), ret)
  1603. comt = "Invalid source '//code/flask' (must be a salt:// URI)"
  1604. ret.update({"comment": comt})
  1605. self.assertDictEqual(filestate.recurse(name, "//code/flask"), ret)
  1606. comt = "Recurse failed: "
  1607. ret.update({"comment": comt})
  1608. self.assertDictEqual(filestate.recurse(name, source), ret)
  1609. comt = (
  1610. "The directory 'code/flask' does not exist"
  1611. " on the salt fileserver in saltenv 'base'"
  1612. )
  1613. ret.update({"comment": comt})
  1614. self.assertDictEqual(filestate.recurse(name, source), ret)
  1615. with patch.object(os.path, "isdir", mock_f):
  1616. with patch.object(os.path, "exists", mock_t):
  1617. comt = "The path {0} exists and is not a directory".format(name)
  1618. ret.update({"comment": comt})
  1619. self.assertDictEqual(filestate.recurse(name, source), ret)
  1620. with patch.object(os.path, "isdir", mock_t):
  1621. comt = "The directory {0} is in the correct state".format(name)
  1622. ret.update({"comment": comt, "result": True})
  1623. self.assertDictEqual(filestate.recurse(name, source), ret)
  1624. # 'replace' function tests: 1
  1625. def test_replace(self):
  1626. """
  1627. Test to maintain an edit in a file.
  1628. """
  1629. name = "/etc/grub.conf"
  1630. pattern = "CentOS +"
  1631. repl = "salt"
  1632. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1633. comt = "Must provide name to file.replace"
  1634. ret.update({"comment": comt, "name": "", "changes": {}})
  1635. self.assertDictEqual(filestate.replace("", pattern, repl), ret)
  1636. mock_t = MagicMock(return_value=True)
  1637. mock_f = MagicMock(return_value=False)
  1638. with patch.object(os.path, "isabs", mock_f):
  1639. comt = "Specified file {0} is not an absolute path".format(name)
  1640. ret.update({"comment": comt, "name": name})
  1641. self.assertDictEqual(filestate.replace(name, pattern, repl), ret)
  1642. with patch.object(os.path, "isabs", mock_t):
  1643. with patch.object(os.path, "exists", mock_t):
  1644. with patch.dict(filestate.__salt__, {"file.replace": mock_f}):
  1645. with patch.dict(filestate.__opts__, {"test": False}):
  1646. comt = "No changes needed to be made"
  1647. ret.update({"comment": comt, "name": name, "result": True})
  1648. self.assertDictEqual(
  1649. filestate.replace(name, pattern, repl), ret
  1650. )
  1651. # 'blockreplace' function tests: 1
  1652. def test_blockreplace(self):
  1653. """
  1654. Test to maintain an edit in a file in a zone
  1655. delimited by two line markers.
  1656. """
  1657. with patch(
  1658. "salt.states.file._load_accumulators", MagicMock(return_value=([], []))
  1659. ):
  1660. name = "/etc/hosts"
  1661. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1662. comt = "Must provide name to file.blockreplace"
  1663. ret.update({"comment": comt, "name": ""})
  1664. self.assertDictEqual(filestate.blockreplace(""), ret)
  1665. mock_t = MagicMock(return_value=True)
  1666. mock_f = MagicMock(return_value=False)
  1667. with patch.object(os.path, "isabs", mock_f):
  1668. comt = "Specified file {0} is not an absolute path".format(name)
  1669. ret.update({"comment": comt, "name": name})
  1670. self.assertDictEqual(filestate.blockreplace(name), ret)
  1671. with patch.object(os.path, "isabs", mock_t), patch.object(
  1672. os.path, "exists", mock_t
  1673. ):
  1674. with patch.dict(filestate.__salt__, {"file.blockreplace": mock_t}):
  1675. with patch.dict(filestate.__opts__, {"test": True}):
  1676. comt = "Changes would be made"
  1677. ret.update(
  1678. {"comment": comt, "result": None, "changes": {"diff": True}}
  1679. )
  1680. self.assertDictEqual(filestate.blockreplace(name), ret)
  1681. # 'comment' function tests: 1
  1682. def test_comment(self):
  1683. """
  1684. Test to comment out specified lines in a file.
  1685. """
  1686. with patch.object(os.path, "exists", MagicMock(return_value=True)):
  1687. name = "/etc/aliases" if salt.utils.platform.is_darwin() else "/etc/fstab"
  1688. regex = "bind 127.0.0.1"
  1689. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1690. comt = "Must provide name to file.comment"
  1691. ret.update({"comment": comt, "name": ""})
  1692. self.assertDictEqual(filestate.comment("", regex), ret)
  1693. mock_t = MagicMock(return_value=True)
  1694. mock_f = MagicMock(return_value=False)
  1695. with patch.object(os.path, "isabs", mock_f):
  1696. comt = "Specified file {0} is not an absolute path".format(name)
  1697. ret.update({"comment": comt, "name": name})
  1698. self.assertDictEqual(filestate.comment(name, regex), ret)
  1699. with patch.object(os.path, "isabs", mock_t):
  1700. with patch.dict(
  1701. filestate.__salt__,
  1702. {"file.search": MagicMock(side_effect=[False, True, False, False])},
  1703. ):
  1704. comt = "Pattern already commented"
  1705. ret.update({"comment": comt, "result": True})
  1706. self.assertDictEqual(filestate.comment(name, regex), ret)
  1707. comt = "{0}: Pattern not found".format(regex)
  1708. ret.update({"comment": comt, "result": False})
  1709. self.assertDictEqual(filestate.comment(name, regex), ret)
  1710. with patch.dict(
  1711. filestate.__salt__,
  1712. {
  1713. "file.search": MagicMock(side_effect=[True, True, True]),
  1714. "file.comment": mock_t,
  1715. "file.comment_line": mock_t,
  1716. },
  1717. ):
  1718. with patch.dict(filestate.__opts__, {"test": True}):
  1719. comt = "File {0} is set to be updated".format(name)
  1720. ret.update(
  1721. {
  1722. "comment": comt,
  1723. "result": None,
  1724. "changes": {name: "updated"},
  1725. }
  1726. )
  1727. self.assertDictEqual(filestate.comment(name, regex), ret)
  1728. with patch.dict(filestate.__opts__, {"test": False}):
  1729. with patch.object(
  1730. salt.utils.files, "fopen", MagicMock(mock_open())
  1731. ):
  1732. comt = "Commented lines successfully"
  1733. ret.update({"comment": comt, "result": True, "changes": {}})
  1734. self.assertDictEqual(filestate.comment(name, regex), ret)
  1735. # 'uncomment' function tests: 1
  1736. def test_uncomment(self):
  1737. """
  1738. Test to uncomment specified commented lines in a file
  1739. """
  1740. with patch.object(os.path, "exists", MagicMock(return_value=True)):
  1741. name = "/etc/aliases" if salt.utils.platform.is_darwin() else "/etc/fstab"
  1742. regex = "bind 127.0.0.1"
  1743. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1744. comt = "Must provide name to file.uncomment"
  1745. ret.update({"comment": comt, "name": ""})
  1746. self.assertDictEqual(filestate.uncomment("", regex), ret)
  1747. mock_t = MagicMock(return_value=True)
  1748. mock_f = MagicMock(return_value=False)
  1749. mock = MagicMock(side_effect=[False, True, False, False, True, True, True])
  1750. with patch.object(os.path, "isabs", mock_f):
  1751. comt = "Specified file {0} is not an absolute path".format(name)
  1752. ret.update({"comment": comt, "name": name})
  1753. self.assertDictEqual(filestate.uncomment(name, regex), ret)
  1754. with patch.object(os.path, "isabs", mock_t):
  1755. with patch.dict(
  1756. filestate.__salt__,
  1757. {
  1758. "file.search": mock,
  1759. "file.uncomment": mock_t,
  1760. "file.comment_line": mock_t,
  1761. },
  1762. ):
  1763. comt = "Pattern already uncommented"
  1764. ret.update({"comment": comt, "result": True})
  1765. self.assertDictEqual(filestate.uncomment(name, regex), ret)
  1766. comt = "{0}: Pattern not found".format(regex)
  1767. ret.update({"comment": comt, "result": False})
  1768. self.assertDictEqual(filestate.uncomment(name, regex), ret)
  1769. with patch.dict(filestate.__opts__, {"test": True}):
  1770. comt = "File {0} is set to be updated".format(name)
  1771. ret.update(
  1772. {
  1773. "comment": comt,
  1774. "result": None,
  1775. "changes": {name: "updated"},
  1776. }
  1777. )
  1778. self.assertDictEqual(filestate.uncomment(name, regex), ret)
  1779. with patch.dict(filestate.__opts__, {"test": False}):
  1780. with patch.object(
  1781. salt.utils.files, "fopen", MagicMock(mock_open())
  1782. ):
  1783. comt = "Uncommented lines successfully"
  1784. ret.update({"comment": comt, "result": True, "changes": {}})
  1785. self.assertDictEqual(filestate.uncomment(name, regex), ret)
  1786. # 'prepend' function tests: 1
  1787. def test_prepend(self):
  1788. """
  1789. Test to ensure that some text appears at the beginning of a file.
  1790. """
  1791. name = "/tmp/etc/motd"
  1792. if salt.utils.platform.is_windows():
  1793. name = "c:\\tmp\\etc\\motd"
  1794. assert not os.path.exists(os.path.split(name)[0])
  1795. source = ["salt://motd/hr-messages.tmpl"]
  1796. sources = ["salt://motd/devops-messages.tmpl"]
  1797. text = ["Trust no one unless you have eaten much salt with him."]
  1798. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1799. comt = "Must provide name to file.prepend"
  1800. ret.update({"comment": comt, "name": ""})
  1801. self.assertDictEqual(filestate.prepend(""), ret)
  1802. comt = "source and sources are mutually exclusive"
  1803. ret.update({"comment": comt, "name": name})
  1804. self.assertDictEqual(
  1805. filestate.prepend(name, source=source, sources=sources), ret
  1806. )
  1807. mock_t = MagicMock(return_value=True)
  1808. mock_f = MagicMock(return_value=False)
  1809. with patch.dict(
  1810. filestate.__salt__,
  1811. {
  1812. "file.directory_exists": mock_f,
  1813. "file.makedirs": mock_t,
  1814. "file.stats": mock_f,
  1815. "cp.get_template": mock_f,
  1816. "file.search": mock_f,
  1817. "file.prepend": mock_t,
  1818. },
  1819. ):
  1820. comt = (
  1821. "The following files will be changed:\n/tmp/etc:" " directory - new\n"
  1822. )
  1823. changes = {"/tmp/etc": {"directory": "new"}}
  1824. if salt.utils.platform.is_windows():
  1825. comt = 'The directory "c:\\tmp\\etc" will be changed'
  1826. changes = {"c:\\tmp\\etc": {"directory": "new"}}
  1827. ret.update({"comment": comt, "name": name, "changes": changes})
  1828. self.assertDictEqual(filestate.prepend(name, makedirs=True), ret)
  1829. with patch.object(os.path, "isabs", mock_f):
  1830. comt = "Specified file {0} is not an absolute path".format(name)
  1831. ret.update({"comment": comt, "changes": {}})
  1832. self.assertDictEqual(filestate.prepend(name), ret)
  1833. with patch.object(os.path, "isabs", mock_t):
  1834. with patch.object(os.path, "exists", mock_t):
  1835. comt = "Failed to load template file {0}".format(source)
  1836. ret.update({"comment": comt, "name": source, "data": []})
  1837. self.assertDictEqual(filestate.prepend(name, source=source), ret)
  1838. ret.pop("data", None)
  1839. ret.update({"name": name})
  1840. with patch.object(
  1841. salt.utils.files, "fopen", MagicMock(mock_open(read_data=""))
  1842. ):
  1843. with patch.dict(filestate.__utils__, {"files.is_text": mock_f}):
  1844. with patch.dict(filestate.__opts__, {"test": True}):
  1845. change = {"diff": "Replace binary file"}
  1846. comt = "File {0} is set to be updated".format(name)
  1847. ret.update(
  1848. {"comment": comt, "result": None, "changes": change}
  1849. )
  1850. self.assertDictEqual(
  1851. filestate.prepend(name, text=text), ret
  1852. )
  1853. with patch.dict(filestate.__opts__, {"test": False}):
  1854. comt = "Prepended 1 lines"
  1855. ret.update(
  1856. {"comment": comt, "result": True, "changes": {}}
  1857. )
  1858. self.assertDictEqual(
  1859. filestate.prepend(name, text=text), ret
  1860. )
  1861. # 'touch' function tests: 1
  1862. def test_touch(self):
  1863. """
  1864. Test to replicate the 'nix "touch" command to create a new empty
  1865. file or update the atime and mtime of an existing file.
  1866. """
  1867. name = "/var/log/httpd/logrotate.empty"
  1868. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1869. comt = "Must provide name to file.touch"
  1870. ret.update({"comment": comt, "name": ""})
  1871. self.assertDictEqual(filestate.touch(""), ret)
  1872. mock_t = MagicMock(return_value=True)
  1873. mock_f = MagicMock(return_value=False)
  1874. with patch.object(os.path, "isabs", mock_f):
  1875. comt = "Specified file {0} is not an absolute path".format(name)
  1876. ret.update({"comment": comt, "name": name})
  1877. self.assertDictEqual(filestate.touch(name), ret)
  1878. with patch.object(os.path, "isabs", mock_t):
  1879. with patch.object(os.path, "exists", mock_f):
  1880. with patch.dict(filestate.__opts__, {"test": True}):
  1881. comt = "File {0} is set to be created".format(name)
  1882. ret.update(
  1883. {"comment": comt, "result": None, "changes": {"new": name}}
  1884. )
  1885. self.assertDictEqual(filestate.touch(name), ret)
  1886. with patch.dict(filestate.__opts__, {"test": False}):
  1887. with patch.object(os.path, "isdir", mock_f):
  1888. comt = "Directory not present to touch file {0}".format(name)
  1889. ret.update({"comment": comt, "result": False, "changes": {}})
  1890. self.assertDictEqual(filestate.touch(name), ret)
  1891. with patch.object(os.path, "isdir", mock_t):
  1892. with patch.dict(filestate.__salt__, {"file.touch": mock_t}):
  1893. comt = "Created empty file {0}".format(name)
  1894. ret.update(
  1895. {"comment": comt, "result": True, "changes": {"new": name}}
  1896. )
  1897. self.assertDictEqual(filestate.touch(name), ret)
  1898. # 'copy' function tests: 1
  1899. def test_copy(self):
  1900. """
  1901. Test if the source file exists on the system, copy it to the named file.
  1902. """
  1903. name = "/tmp/salt"
  1904. source = "/tmp/salt/salt"
  1905. user = "salt"
  1906. group = "saltstack"
  1907. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  1908. comt = "Must provide name to file.copy"
  1909. ret.update({"comment": comt, "name": ""})
  1910. self.assertDictEqual(filestate.copy_("", source), ret)
  1911. mock_t = MagicMock(return_value=True)
  1912. mock_f = MagicMock(return_value=False)
  1913. mock_uid = MagicMock(side_effect=[""])
  1914. mock_gid = MagicMock(side_effect=[""])
  1915. mock_user = MagicMock(return_value=user)
  1916. mock_grp = MagicMock(return_value=group)
  1917. mock_io = MagicMock(side_effect=IOError)
  1918. with patch.object(os.path, "isabs", mock_f):
  1919. comt = "Specified file {0} is not an absolute path".format(name)
  1920. ret.update({"comment": comt, "name": name})
  1921. self.assertDictEqual(filestate.copy_(name, source), ret)
  1922. with patch.object(os.path, "isabs", mock_t):
  1923. with patch.object(os.path, "exists", mock_f):
  1924. comt = 'Source file "{0}" is not present'.format(source)
  1925. ret.update({"comment": comt, "result": False})
  1926. self.assertDictEqual(filestate.copy_(name, source), ret)
  1927. with patch.object(os.path, "exists", mock_t):
  1928. with patch.dict(
  1929. filestate.__salt__,
  1930. {
  1931. "file.user_to_uid": mock_uid,
  1932. "file.group_to_gid": mock_gid,
  1933. "file.get_user": mock_user,
  1934. "file.get_group": mock_grp,
  1935. "file.get_mode": mock_grp,
  1936. "file.check_perms": mock_t,
  1937. },
  1938. ):
  1939. # Group argument is ignored on Windows systems. Group is set
  1940. # to user
  1941. if salt.utils.platform.is_windows():
  1942. comt = (
  1943. "User salt is not available Group salt" " is not available"
  1944. )
  1945. else:
  1946. comt = (
  1947. "User salt is not available Group saltstack"
  1948. " is not available"
  1949. )
  1950. ret.update({"comment": comt, "result": False})
  1951. self.assertDictEqual(
  1952. filestate.copy_(name, source, user=user, group=group), ret
  1953. )
  1954. comt1 = (
  1955. 'Failed to delete "{0}" in preparation for'
  1956. " forced move".format(name)
  1957. )
  1958. comt2 = (
  1959. 'The target file "{0}" exists and will not be '
  1960. "overwritten".format(name)
  1961. )
  1962. comt3 = 'File "{0}" is set to be copied to "{1}"'.format(
  1963. source, name
  1964. )
  1965. with patch.object(os.path, "isdir", mock_f):
  1966. with patch.object(os.path, "lexists", mock_t):
  1967. with patch.dict(filestate.__opts__, {"test": False}):
  1968. with patch.dict(
  1969. filestate.__salt__, {"file.remove": mock_io}
  1970. ):
  1971. ret.update({"comment": comt1, "result": False})
  1972. self.assertDictEqual(
  1973. filestate.copy_(
  1974. name, source, preserve=True, force=True
  1975. ),
  1976. ret,
  1977. )
  1978. with patch.object(os.path, "isfile", mock_t):
  1979. ret.update({"comment": comt2, "result": True})
  1980. self.assertDictEqual(
  1981. filestate.copy_(name, source, preserve=True),
  1982. ret,
  1983. )
  1984. with patch.object(os.path, "lexists", mock_f):
  1985. with patch.dict(filestate.__opts__, {"test": True}):
  1986. ret.update({"comment": comt3, "result": None})
  1987. self.assertDictEqual(
  1988. filestate.copy_(name, source, preserve=True), ret
  1989. )
  1990. with patch.dict(filestate.__opts__, {"test": False}):
  1991. comt = "The target directory /tmp is" " not present"
  1992. ret.update({"comment": comt, "result": False})
  1993. self.assertDictEqual(
  1994. filestate.copy_(name, source, preserve=True), ret
  1995. )
  1996. # 'rename' function tests: 1
  1997. def test_rename(self):
  1998. """
  1999. Test if the source file exists on the system,
  2000. rename it to the named file.
  2001. """
  2002. name = "/tmp/salt"
  2003. source = "/tmp/salt/salt"
  2004. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  2005. comt = "Must provide name to file.rename"
  2006. ret.update({"comment": comt, "name": ""})
  2007. self.assertDictEqual(filestate.rename("", source), ret)
  2008. mock_t = MagicMock(return_value=True)
  2009. mock_f = MagicMock(return_value=False)
  2010. mock_lex = MagicMock(side_effect=[False, True, True])
  2011. with patch.object(os.path, "isabs", mock_f):
  2012. comt = "Specified file {0} is not an absolute path".format(name)
  2013. ret.update({"comment": comt, "name": name})
  2014. self.assertDictEqual(filestate.rename(name, source), ret)
  2015. mock_lex = MagicMock(return_value=False)
  2016. with patch.object(os.path, "isabs", mock_t):
  2017. with patch.object(os.path, "lexists", mock_lex):
  2018. comt = (
  2019. 'Source file "{0}" has already been moved out of '
  2020. "place".format(source)
  2021. )
  2022. ret.update({"comment": comt, "result": True})
  2023. self.assertDictEqual(filestate.rename(name, source), ret)
  2024. mock_lex = MagicMock(side_effect=[True, True, True])
  2025. with patch.object(os.path, "isabs", mock_t):
  2026. with patch.object(os.path, "lexists", mock_lex):
  2027. comt = (
  2028. 'The target file "{0}" exists and will not be '
  2029. "overwritten".format(name)
  2030. )
  2031. ret.update({"comment": comt, "result": True})
  2032. self.assertDictEqual(filestate.rename(name, source), ret)
  2033. mock_lex = MagicMock(side_effect=[True, True, True])
  2034. mock_rem = MagicMock(side_effect=IOError)
  2035. with patch.object(os.path, "isabs", mock_t):
  2036. with patch.object(os.path, "lexists", mock_lex):
  2037. with patch.dict(filestate.__opts__, {"test": False}):
  2038. comt = (
  2039. 'Failed to delete "{0}" in preparation for '
  2040. "forced move".format(name)
  2041. )
  2042. with patch.dict(filestate.__salt__, {"file.remove": mock_rem}):
  2043. ret.update({"name": name, "comment": comt, "result": False})
  2044. self.assertDictEqual(
  2045. filestate.rename(name, source, force=True), ret
  2046. )
  2047. mock_lex = MagicMock(side_effect=[True, False, False])
  2048. with patch.object(os.path, "isabs", mock_t):
  2049. with patch.object(os.path, "lexists", mock_lex):
  2050. with patch.dict(filestate.__opts__, {"test": True}):
  2051. comt = 'File "{0}" is set to be moved to "{1}"'.format(source, name)
  2052. ret.update({"name": name, "comment": comt, "result": None})
  2053. self.assertDictEqual(filestate.rename(name, source), ret)
  2054. mock_lex = MagicMock(side_effect=[True, False, False])
  2055. with patch.object(os.path, "isabs", mock_t):
  2056. with patch.object(os.path, "lexists", mock_lex):
  2057. with patch.object(os.path, "isdir", mock_f):
  2058. with patch.dict(filestate.__opts__, {"test": False}):
  2059. comt = "The target directory /tmp is not present"
  2060. ret.update({"name": name, "comment": comt, "result": False})
  2061. self.assertDictEqual(filestate.rename(name, source), ret)
  2062. mock_lex = MagicMock(side_effect=[True, False, False])
  2063. with patch.object(os.path, "isabs", mock_t):
  2064. with patch.object(os.path, "lexists", mock_lex):
  2065. with patch.object(os.path, "isdir", mock_t):
  2066. with patch.object(os.path, "islink", mock_f):
  2067. with patch.dict(filestate.__opts__, {"test": False}):
  2068. with patch.object(
  2069. shutil, "move", MagicMock(side_effect=IOError)
  2070. ):
  2071. comt = 'Failed to move "{0}" to "{1}"'.format(
  2072. source, name
  2073. )
  2074. ret.update(
  2075. {"name": name, "comment": comt, "result": False}
  2076. )
  2077. self.assertDictEqual(
  2078. filestate.rename(name, source), ret
  2079. )
  2080. mock_lex = MagicMock(side_effect=[True, False, False])
  2081. with patch.object(os.path, "isabs", mock_t):
  2082. with patch.object(os.path, "lexists", mock_lex):
  2083. with patch.object(os.path, "isdir", mock_t):
  2084. with patch.object(os.path, "islink", mock_f):
  2085. with patch.dict(filestate.__opts__, {"test": False}):
  2086. with patch.object(shutil, "move", MagicMock()):
  2087. comt = 'Moved "{0}" to "{1}"'.format(source, name)
  2088. ret.update(
  2089. {
  2090. "name": name,
  2091. "comment": comt,
  2092. "result": True,
  2093. "changes": {name: source},
  2094. }
  2095. )
  2096. self.assertDictEqual(
  2097. filestate.rename(name, source), ret
  2098. )
  2099. # 'accumulated' function tests: 1
  2100. def test_accumulated(self):
  2101. """
  2102. Test to prepare accumulator which can be used in template in file.
  2103. """
  2104. with patch(
  2105. "salt.states.file._load_accumulators", MagicMock(return_value=({}, {}))
  2106. ), patch(
  2107. "salt.states.file._persist_accummulators", MagicMock(return_value=True)
  2108. ):
  2109. name = "animals_doing_things"
  2110. filename = "/tmp/animal_file.txt"
  2111. text = " jumps over the lazy dog."
  2112. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  2113. comt = "Must provide name to file.accumulated"
  2114. ret.update({"comment": comt, "name": ""})
  2115. self.assertDictEqual(filestate.accumulated("", filename, text), ret)
  2116. comt = "No text supplied for accumulator"
  2117. ret.update({"comment": comt, "name": name})
  2118. self.assertDictEqual(filestate.accumulated(name, filename, None), ret)
  2119. with patch.dict(
  2120. filestate.__low__,
  2121. {
  2122. "require_in": "file",
  2123. "watch_in": "salt",
  2124. "__sls__": "SLS",
  2125. "__id__": "ID",
  2126. },
  2127. ):
  2128. comt = "Orphaned accumulator animals_doing_things in SLS:ID"
  2129. ret.update({"comment": comt, "name": name})
  2130. self.assertDictEqual(filestate.accumulated(name, filename, text), ret)
  2131. with patch.dict(
  2132. filestate.__low__,
  2133. {
  2134. "require_in": [{"file": "A"}],
  2135. "watch_in": [{"B": "C"}],
  2136. "__sls__": "SLS",
  2137. "__id__": "ID",
  2138. },
  2139. ):
  2140. comt = "Accumulator {0} for file {1} " "was charged by text".format(
  2141. name, filename
  2142. )
  2143. ret.update({"comment": comt, "name": name, "result": True})
  2144. self.assertDictEqual(filestate.accumulated(name, filename, text), ret)
  2145. # 'serialize' function tests: 1
  2146. def test_serialize_into_managed_file(self):
  2147. """
  2148. Test to serializes dataset and store it into managed file.
  2149. """
  2150. name = "/etc/dummy/package.json"
  2151. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  2152. comt = "Must provide name to file.serialize"
  2153. ret.update({"comment": comt, "name": ""})
  2154. self.assertDictEqual(filestate.serialize(""), ret)
  2155. mock_t = MagicMock(return_value=True)
  2156. mock_f = MagicMock(return_value=False)
  2157. with patch.object(os.path, "isfile", mock_f):
  2158. comt = "File {0} is not present and is not set for " "creation".format(
  2159. name
  2160. )
  2161. ret.update({"comment": comt, "name": name, "result": True})
  2162. self.assertDictEqual(filestate.serialize(name, create=False), ret)
  2163. comt = "Only one of 'dataset' and 'dataset_pillar' is permitted"
  2164. ret.update({"comment": comt, "result": False})
  2165. self.assertDictEqual(
  2166. filestate.serialize(name, dataset=True, dataset_pillar=True), ret
  2167. )
  2168. comt = "Neither 'dataset' nor 'dataset_pillar' was defined"
  2169. ret.update({"comment": comt, "result": False})
  2170. self.assertDictEqual(filestate.serialize(name), ret)
  2171. with patch.object(os.path, "isfile", mock_t):
  2172. comt = "Python format is not supported for merging"
  2173. ret.update({"comment": comt, "result": False})
  2174. self.assertDictEqual(
  2175. filestate.serialize(
  2176. name, dataset=True, merge_if_exists=True, formatter="python"
  2177. ),
  2178. ret,
  2179. )
  2180. comt = "A format is not supported"
  2181. ret.update({"comment": comt, "result": False})
  2182. self.assertDictEqual(
  2183. filestate.serialize(name, dataset=True, formatter="A"), ret
  2184. )
  2185. mock_changes = MagicMock(return_value=True)
  2186. mock_no_changes = MagicMock(return_value=False)
  2187. # __opts__['test']=True with changes
  2188. with patch.dict(
  2189. filestate.__salt__, {"file.check_managed_changes": mock_changes}
  2190. ):
  2191. with patch.dict(filestate.__opts__, {"test": True}):
  2192. comt = "Dataset will be serialized and stored into {0}".format(name)
  2193. ret.update({"comment": comt, "result": None, "changes": True})
  2194. self.assertDictEqual(
  2195. filestate.serialize(name, dataset=True, formatter="python"), ret
  2196. )
  2197. # __opts__['test']=True without changes
  2198. with patch.dict(
  2199. filestate.__salt__, {"file.check_managed_changes": mock_no_changes}
  2200. ):
  2201. with patch.dict(filestate.__opts__, {"test": True}):
  2202. comt = "The file {0} is in the correct state".format(name)
  2203. ret.update({"comment": comt, "result": True, "changes": False})
  2204. self.assertDictEqual(
  2205. filestate.serialize(name, dataset=True, formatter="python"), ret
  2206. )
  2207. mock = MagicMock(return_value=ret)
  2208. with patch.dict(filestate.__opts__, {"test": False}):
  2209. with patch.dict(filestate.__salt__, {"file.manage_file": mock}):
  2210. comt = "Dataset will be serialized and stored into {0}".format(name)
  2211. ret.update({"comment": comt, "result": None})
  2212. self.assertDictEqual(
  2213. filestate.serialize(name, dataset=True, formatter="python"), ret
  2214. )
  2215. # 'mknod' function tests: 1
  2216. def test_mknod(self):
  2217. """
  2218. Test to create a special file similar to the 'nix mknod command.
  2219. """
  2220. name = "/dev/AA"
  2221. ntype = "a"
  2222. ret = {"name": name, "result": False, "comment": "", "changes": {}}
  2223. comt = "Must provide name to file.mknod"
  2224. ret.update({"comment": comt, "name": ""})
  2225. self.assertDictEqual(filestate.mknod("", ntype), ret)
  2226. comt = (
  2227. "Node type unavailable: 'a'. Available node types are "
  2228. "character ('c'), block ('b'), and pipe ('p')"
  2229. )
  2230. ret.update({"comment": comt, "name": name})
  2231. self.assertDictEqual(filestate.mknod(name, ntype), ret)
  2232. # 'mod_run_check_cmd' function tests: 1
  2233. def test_mod_run_check_cmd(self):
  2234. """
  2235. Test to execute the check_cmd logic.
  2236. """
  2237. cmd = "A"
  2238. filename = "B"
  2239. ret = {
  2240. "comment": "check_cmd execution failed",
  2241. "result": False,
  2242. "skip_watch": True,
  2243. }
  2244. mock = MagicMock(side_effect=[{"retcode": 1}, {"retcode": 0}])
  2245. with patch.dict(filestate.__salt__, {"cmd.run_all": mock}):
  2246. self.assertDictEqual(filestate.mod_run_check_cmd(cmd, filename), ret)
  2247. self.assertTrue(filestate.mod_run_check_cmd(cmd, filename))
  2248. @skipIf(not HAS_DATEUTIL, NO_DATEUTIL_REASON)
  2249. @slowTest
  2250. def test_retention_schedule(self):
  2251. """
  2252. Test to execute the retention_schedule logic.
  2253. This test takes advantage of knowing which files it is generating,
  2254. which means it can easily generate list of which files it should keep.
  2255. """
  2256. def generate_fake_files(
  2257. format="example_name_%Y%m%dT%H%M%S.tar.bz2",
  2258. starting=datetime(2016, 2, 8, 9),
  2259. every=relativedelta(minutes=30),
  2260. ending=datetime(2015, 12, 25),
  2261. maxfiles=None,
  2262. ):
  2263. """
  2264. For starting, make sure that it's over a week from the beginning of the month
  2265. For every, pick only one of minutes, hours, days, weeks, months or years
  2266. For ending, the further away it is from starting, the slower the tests run
  2267. Full coverage requires over a year of separation, but that's painfully slow.
  2268. """
  2269. if every.years:
  2270. ts = datetime(starting.year, 1, 1)
  2271. elif every.months:
  2272. ts = datetime(starting.year, starting.month, 1)
  2273. elif every.days:
  2274. ts = datetime(starting.year, starting.month, starting.day)
  2275. elif every.hours:
  2276. ts = datetime(
  2277. starting.year, starting.month, starting.day, starting.hour
  2278. )
  2279. elif every.minutes:
  2280. ts = datetime(
  2281. starting.year, starting.month, starting.day, starting.hour, 0
  2282. )
  2283. else:
  2284. raise NotImplementedError("not sure what you're trying to do here")
  2285. fake_files = []
  2286. count = 0
  2287. while ending < ts:
  2288. fake_files.append(ts.strftime(format=format))
  2289. count += 1
  2290. if maxfiles and maxfiles == "all" or maxfiles and count >= maxfiles:
  2291. break
  2292. ts -= every
  2293. return fake_files
  2294. fake_name = "/some/dir/name"
  2295. fake_retain = {
  2296. "most_recent": 2,
  2297. "first_of_hour": 4,
  2298. "first_of_day": 7,
  2299. "first_of_week": 6,
  2300. "first_of_month": 6,
  2301. "first_of_year": "all",
  2302. }
  2303. fake_strptime_format = "example_name_%Y%m%dT%H%M%S.tar.bz2"
  2304. fake_matching_file_list = generate_fake_files()
  2305. # Add some files which do not match fake_strptime_format
  2306. fake_no_match_file_list = generate_fake_files(
  2307. format="no_match_%Y%m%dT%H%M%S.tar.bz2", every=relativedelta(days=1)
  2308. )
  2309. def lstat_side_effect(path):
  2310. import re
  2311. from time import mktime
  2312. x = re.match(r"^[^\d]*(\d{8}T\d{6})\.tar\.bz2$", path).group(1)
  2313. ts = mktime(datetime.strptime(x, "%Y%m%dT%H%M%S").timetuple())
  2314. return {
  2315. "st_atime": 0.0,
  2316. "st_ctime": 0.0,
  2317. "st_gid": 0,
  2318. "st_mode": 33188,
  2319. "st_mtime": ts,
  2320. "st_nlink": 1,
  2321. "st_size": 0,
  2322. "st_uid": 0,
  2323. }
  2324. mock_t = MagicMock(return_value=True)
  2325. mock_f = MagicMock(return_value=False)
  2326. mock_lstat = MagicMock(side_effect=lstat_side_effect)
  2327. mock_remove = MagicMock()
  2328. def run_checks(isdir=mock_t, strptime_format=None, test=False):
  2329. expected_ret = {
  2330. "name": fake_name,
  2331. "changes": {"retained": [], "deleted": [], "ignored": []},
  2332. "result": True,
  2333. "comment": "Name provided to file.retention must be a directory",
  2334. }
  2335. if strptime_format:
  2336. fake_file_list = sorted(
  2337. fake_matching_file_list + fake_no_match_file_list
  2338. )
  2339. else:
  2340. fake_file_list = sorted(fake_matching_file_list)
  2341. mock_readdir = MagicMock(return_value=fake_file_list)
  2342. with patch.dict(filestate.__opts__, {"test": test}):
  2343. with patch.object(os.path, "isdir", isdir):
  2344. mock_readdir.reset_mock()
  2345. with patch.dict(filestate.__salt__, {"file.readdir": mock_readdir}):
  2346. with patch.dict(filestate.__salt__, {"file.lstat": mock_lstat}):
  2347. mock_remove.reset_mock()
  2348. with patch.dict(
  2349. filestate.__salt__, {"file.remove": mock_remove}
  2350. ):
  2351. if strptime_format:
  2352. actual_ret = filestate.retention_schedule(
  2353. fake_name,
  2354. fake_retain,
  2355. strptime_format=fake_strptime_format,
  2356. )
  2357. else:
  2358. actual_ret = filestate.retention_schedule(
  2359. fake_name, fake_retain
  2360. )
  2361. if not isdir():
  2362. mock_readdir.assert_has_calls([])
  2363. expected_ret["result"] = False
  2364. else:
  2365. mock_readdir.assert_called_once_with(fake_name)
  2366. ignored_files = fake_no_match_file_list if strptime_format else []
  2367. retained_files = set(
  2368. generate_fake_files(maxfiles=fake_retain["most_recent"])
  2369. )
  2370. junk_list = [
  2371. ("first_of_hour", relativedelta(hours=1)),
  2372. ("first_of_day", relativedelta(days=1)),
  2373. ("first_of_week", relativedelta(weeks=1)),
  2374. ("first_of_month", relativedelta(months=1)),
  2375. ("first_of_year", relativedelta(years=1)),
  2376. ]
  2377. for retainable, retain_interval in junk_list:
  2378. new_retains = set(
  2379. generate_fake_files(
  2380. maxfiles=fake_retain[retainable], every=retain_interval
  2381. )
  2382. )
  2383. # if we generate less than the number of files expected,
  2384. # then the oldest file will also be retained
  2385. # (correctly, since its the first in it's category)
  2386. if (
  2387. fake_retain[retainable] == "all"
  2388. or len(new_retains) < fake_retain[retainable]
  2389. ):
  2390. new_retains.add(fake_file_list[0])
  2391. retained_files |= new_retains
  2392. deleted_files = sorted(
  2393. list(set(fake_file_list) - retained_files - set(ignored_files)),
  2394. reverse=True,
  2395. )
  2396. retained_files = sorted(list(retained_files), reverse=True)
  2397. expected_ret["changes"] = {
  2398. "retained": retained_files,
  2399. "deleted": deleted_files,
  2400. "ignored": ignored_files,
  2401. }
  2402. if test:
  2403. expected_ret["result"] = None
  2404. expected_ret["comment"] = (
  2405. "{0} backups would have been removed from {1}.\n"
  2406. "".format(len(deleted_files), fake_name)
  2407. )
  2408. else:
  2409. expected_ret["comment"] = (
  2410. "{0} backups were removed from {1}.\n"
  2411. "".format(len(deleted_files), fake_name)
  2412. )
  2413. mock_remove.assert_has_calls(
  2414. [call(os.path.join(fake_name, x)) for x in deleted_files],
  2415. any_order=True,
  2416. )
  2417. self.assertDictEqual(actual_ret, expected_ret)
  2418. run_checks(isdir=mock_f)
  2419. run_checks()
  2420. run_checks(test=True)
  2421. run_checks(strptime_format=fake_strptime_format)
  2422. run_checks(strptime_format=fake_strptime_format, test=True)
  2423. class TestFindKeepFiles(TestCase):
  2424. @skipIf(salt.utils.platform.is_windows(), "Do not run on Windows")
  2425. def test__find_keep_files_unix(self):
  2426. keep = filestate._find_keep_files(
  2427. "/test/parent_folder", ["/test/parent_folder/meh.txt"]
  2428. )
  2429. expected = [
  2430. "/",
  2431. "/test",
  2432. "/test/parent_folder",
  2433. "/test/parent_folder/meh.txt",
  2434. ]
  2435. actual = sorted(list(keep))
  2436. assert actual == expected, actual
  2437. @skipIf(not salt.utils.platform.is_windows(), "Only run on Windows")
  2438. def test__find_keep_files_win32(self):
  2439. """
  2440. Test _find_keep_files. The `_find_keep_files` function is only called by
  2441. _clean_dir, so case doesn't matter. Should return all lower case.
  2442. """
  2443. keep = filestate._find_keep_files(
  2444. "c:\\test\\parent_folder",
  2445. [
  2446. "C:\\test\\parent_folder\\meh-1.txt",
  2447. "C:\\Test\\Parent_folder\\Meh-2.txt",
  2448. ],
  2449. )
  2450. expected = [
  2451. "c:\\",
  2452. "c:\\test",
  2453. "c:\\test\\parent_folder",
  2454. "c:\\test\\parent_folder\\meh-1.txt",
  2455. "c:\\test\\parent_folder\\meh-2.txt",
  2456. ]
  2457. actual = sorted(list(keep))
  2458. self.assertListEqual(actual, expected)
  2459. class TestFileTidied(TestCase):
  2460. def setUp(self):
  2461. setattr(filestate, "__opts__", {})
  2462. setattr(filestate, "__salt__", {})
  2463. def tearDown(self):
  2464. delattr(filestate, "__opts__")
  2465. delattr(filestate, "__salt__")
  2466. def test__tidied(self):
  2467. name = os.sep + "test"
  2468. if salt.utils.platform.is_windows():
  2469. name = "c:" + name
  2470. walker = [
  2471. (os.path.join("test", "test1"), [], ["file1"]),
  2472. (os.path.join("test", "test2", "test3"), [], []),
  2473. (os.path.join("test", "test2"), ["test3"], ["file2"]),
  2474. ("test", ["test1", "test2"], ["file3"]),
  2475. ]
  2476. today_delta = datetime.today() - datetime.utcfromtimestamp(0)
  2477. remove = MagicMock(name="file.remove")
  2478. with patch("os.walk", return_value=walker), patch(
  2479. "os.path.islink", return_value=False
  2480. ), patch("os.path.getatime", return_value=today_delta.total_seconds()), patch(
  2481. "os.path.getsize", return_value=10
  2482. ), patch.dict(
  2483. filestate.__opts__, {"test": False}
  2484. ), patch.dict(
  2485. filestate.__salt__, {"file.remove": remove}
  2486. ), patch(
  2487. "os.path.isdir", return_value=True
  2488. ):
  2489. ret = filestate.tidied(name=name)
  2490. exp = {
  2491. "name": name,
  2492. "changes": {
  2493. "removed": [
  2494. os.path.join("test", "test1", "file1"),
  2495. os.path.join("test", "test2", "file2"),
  2496. os.path.join("test", "file3"),
  2497. ]
  2498. },
  2499. "pchanges": {},
  2500. "result": True,
  2501. "comment": "Removed 3 files or directories from directory {0}".format(name),
  2502. }
  2503. self.assertDictEqual(exp, ret)
  2504. assert remove.call_count == 3
  2505. remove.reset_mock()
  2506. with patch("os.walk", return_value=walker), patch(
  2507. "os.path.islink", return_value=False
  2508. ), patch("os.path.getatime", return_value=today_delta.total_seconds()), patch(
  2509. "os.path.getsize", return_value=10
  2510. ), patch.dict(
  2511. filestate.__opts__, {"test": False}
  2512. ), patch.dict(
  2513. filestate.__salt__, {"file.remove": remove}
  2514. ), patch(
  2515. "os.path.isdir", return_value=True
  2516. ):
  2517. ret = filestate.tidied(name=name, rmdirs=True)
  2518. exp = {
  2519. "name": name,
  2520. "changes": {
  2521. "removed": [
  2522. os.path.join("test", "test1", "file1"),
  2523. os.path.join("test", "test2", "file2"),
  2524. os.path.join("test", "test2", "test3"),
  2525. os.path.join("test", "file3"),
  2526. os.path.join("test", "test1"),
  2527. os.path.join("test", "test2"),
  2528. ]
  2529. },
  2530. "pchanges": {},
  2531. "result": True,
  2532. "comment": "Removed 6 files or directories from directory {0}".format(name),
  2533. }
  2534. self.assertDictEqual(exp, ret)
  2535. assert remove.call_count == 6
  2536. def test__bad_input(self):
  2537. exp = {
  2538. "name": "test/",
  2539. "changes": {},
  2540. "pchanges": {},
  2541. "result": False,
  2542. "comment": "Specified file test/ is not an absolute path",
  2543. }
  2544. assert filestate.tidied(name="test/") == exp
  2545. exp = {
  2546. "name": "/bad-directory-name/",
  2547. "changes": {},
  2548. "pchanges": {},
  2549. "result": False,
  2550. "comment": "/bad-directory-name/ does not exist or is not a directory.",
  2551. }
  2552. assert filestate.tidied(name="/bad-directory-name/") == exp
  2553. class TestFilePrivateFunctions(TestCase, LoaderModuleMockMixin):
  2554. def setup_loader_modules(self):
  2555. return {filestate: {"__salt__": {"file.stats": filemod.stats}}}
  2556. @destructiveTest
  2557. @skipIf(salt.utils.platform.is_windows(), "File modes do not exist on windows")
  2558. def test__check_directory(self):
  2559. """
  2560. Test the _check_directory function
  2561. Make sure that recursive file permission checks return correctly
  2562. """
  2563. # set file permissions
  2564. # Run _check_directory function
  2565. # Verify that it returns correctly
  2566. # Delete tmp directory structure
  2567. root_tmp_dir = os.path.join(RUNTIME_VARS.TMP, "test__check_dir")
  2568. expected_dir_mode = 0o777
  2569. depth = 3
  2570. try:
  2571. def create_files(tmp_dir):
  2572. for f in range(depth):
  2573. path = os.path.join(tmp_dir, "file_{:03}.txt".format(f))
  2574. with salt.utils.files.fopen(path, "w+"):
  2575. os.chmod(path, expected_dir_mode)
  2576. # Create tmp directory structure
  2577. os.mkdir(root_tmp_dir)
  2578. os.chmod(root_tmp_dir, expected_dir_mode)
  2579. create_files(root_tmp_dir)
  2580. for d in range(depth):
  2581. dir_name = os.path.join(root_tmp_dir, "dir{:03}".format(d))
  2582. os.mkdir(dir_name)
  2583. os.chmod(dir_name, expected_dir_mode)
  2584. create_files(dir_name)
  2585. for s in range(depth):
  2586. sub_dir_name = os.path.join(dir_name, "dir{:03}".format(s))
  2587. os.mkdir(sub_dir_name)
  2588. os.chmod(sub_dir_name, expected_dir_mode)
  2589. create_files(sub_dir_name)
  2590. # Set some bad permissions
  2591. changed_files = {
  2592. os.path.join(root_tmp_dir, "file_000.txt"),
  2593. os.path.join(root_tmp_dir, "dir002", "file_000.txt"),
  2594. os.path.join(root_tmp_dir, "dir000", "dir001", "file_002.txt"),
  2595. os.path.join(root_tmp_dir, "dir001", "dir002"),
  2596. os.path.join(root_tmp_dir, "dir002", "dir000"),
  2597. os.path.join(root_tmp_dir, "dir001"),
  2598. }
  2599. for c in changed_files:
  2600. os.chmod(c, 0o770)
  2601. ret = filestate._check_directory(
  2602. root_tmp_dir,
  2603. dir_mode=oct(expected_dir_mode),
  2604. file_mode=oct(expected_dir_mode),
  2605. recurse=["mode"],
  2606. )
  2607. self.assertSetEqual(changed_files, set(ret[-1].keys()))
  2608. finally:
  2609. # Cleanup
  2610. shutil.rmtree(root_tmp_dir)
  2611. @skipIf(not salt.utils.platform.is_linux(), "Selinux only supported on linux")
  2612. class TestSelinux(TestCase, LoaderModuleMockMixin):
  2613. def setup_loader_modules(self):
  2614. return {
  2615. filestate: {
  2616. "__env__": "base",
  2617. "__salt__": {"file.manage_file": False},
  2618. "__opts__": {"test": False, "cachedir": ""},
  2619. "__instance_id__": "",
  2620. "__low__": {},
  2621. "__utils__": {},
  2622. }
  2623. }
  2624. def test_selinux_change(self):
  2625. file_name = "/tmp/some-test-file"
  2626. check_perms_result = [
  2627. {
  2628. "comment": "The file {0} is set to be changed".format(file_name),
  2629. "changes": {
  2630. "selinux": {
  2631. "New": "User: unconfined_u Type: lost_found_t",
  2632. "Old": "User: system_u Type: user_tmp_t",
  2633. }
  2634. },
  2635. "name": file_name,
  2636. "result": True,
  2637. },
  2638. {"luser": "root", "lmode": "0644", "lgroup": "root"},
  2639. ]
  2640. with patch.object(os.path, "exists", MagicMock(return_value=True)):
  2641. with patch.dict(
  2642. filestate.__salt__,
  2643. {
  2644. "file.source_list": MagicMock(return_value=[file_name, None]),
  2645. "file.check_perms": MagicMock(return_value=check_perms_result),
  2646. },
  2647. ):
  2648. ret = filestate.managed(
  2649. file_name,
  2650. selinux={"seuser": "unconfined_u", "setype": "user_tmp_t"},
  2651. )
  2652. self.assertEqual(True, ret["result"])