test_grains.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. # -*- coding: utf-8 -*-
  2. """
  3. unit tests for the grains state
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import contextlib
  7. # Import Python libs
  8. import os
  9. import salt.modules.grains as grainsmod
  10. import salt.states.grains as grains
  11. # Import salt libs
  12. import salt.utils.files
  13. import salt.utils.stringutils
  14. import salt.utils.yaml
  15. from salt.ext import six
  16. from tests.support.mixins import LoaderModuleMockMixin
  17. from tests.support.mock import MagicMock, patch
  18. # Import Salt Testing libs
  19. from tests.support.runtests import RUNTIME_VARS
  20. from tests.support.unit import TestCase
  21. class GrainsTestCase(TestCase, LoaderModuleMockMixin):
  22. def setup_loader_modules(self):
  23. grains_test_dir = "__salt_test_state_grains"
  24. if not os.path.exists(os.path.join(RUNTIME_VARS.TMP, grains_test_dir)):
  25. os.makedirs(os.path.join(RUNTIME_VARS.TMP, grains_test_dir))
  26. loader_globals = {
  27. "__opts__": {
  28. "test": False,
  29. "conf_file": os.path.join(RUNTIME_VARS.TMP, grains_test_dir, "minion"),
  30. "cachedir": os.path.join(RUNTIME_VARS.TMP, grains_test_dir),
  31. "local": True,
  32. },
  33. "__salt__": {
  34. "cmd.run_all": MagicMock(
  35. return_value={"pid": 5, "retcode": 0, "stderr": "", "stdout": ""}
  36. ),
  37. "grains.get": grainsmod.get,
  38. "grains.set": grainsmod.set,
  39. "grains.setval": grainsmod.setval,
  40. "grains.delval": grainsmod.delval,
  41. "grains.append": grainsmod.append,
  42. "grains.remove": grainsmod.remove,
  43. "saltutil.sync_grains": MagicMock(),
  44. },
  45. }
  46. return {grains: loader_globals, grainsmod: loader_globals}
  47. def assertGrainFileContent(self, grains_string):
  48. if os.path.isdir(grains.__opts__["conf_file"]):
  49. grains_file = os.path.join(grains.__opts__["conf_file"], "grains")
  50. else:
  51. grains_file = os.path.join(
  52. os.path.dirname(grains.__opts__["conf_file"]), "grains"
  53. )
  54. with salt.utils.files.fopen(grains_file, "r") as grf:
  55. grains_data = salt.utils.stringutils.to_unicode(grf.read())
  56. self.assertMultiLineEqual(grains_string, grains_data)
  57. @contextlib.contextmanager
  58. def setGrains(self, grains_data):
  59. with patch.dict(grains.__grains__, grains_data):
  60. with patch.dict(grainsmod.__grains__, grains_data):
  61. if os.path.isdir(grains.__opts__["conf_file"]):
  62. grains_file = os.path.join(grains.__opts__["conf_file"], "grains")
  63. else:
  64. grains_file = os.path.join(
  65. os.path.dirname(grains.__opts__["conf_file"]), "grains"
  66. )
  67. with salt.utils.files.fopen(grains_file, "w+") as grf:
  68. salt.utils.yaml.safe_dump(
  69. grains_data, grf, default_flow_style=False
  70. )
  71. yield
  72. # 'exists' function tests: 2
  73. def test_exists_missing(self):
  74. with self.setGrains({"a": "aval"}):
  75. ret = grains.exists(name="foo")
  76. self.assertEqual(ret["result"], False)
  77. self.assertEqual(ret["comment"], "Grain does not exist")
  78. self.assertEqual(ret["changes"], {})
  79. def test_exists_found(self):
  80. with self.setGrains({"a": "aval", "foo": "bar"}):
  81. # Grain already set
  82. ret = grains.exists(name="foo")
  83. self.assertEqual(ret["result"], True)
  84. self.assertEqual(ret["comment"], "Grain exists")
  85. self.assertEqual(ret["changes"], {})
  86. # 'make_hashable' function tests: 1
  87. def test_make_hashable(self):
  88. with self.setGrains({"cmplx_lst_grain": [{"a": "aval"}, {"foo": "bar"}]}):
  89. hashable_list = {"cmplx_lst_grain": [{"a": "aval"}, {"foo": "bar"}]}
  90. self.assertEqual(
  91. grains.make_hashable(grains.__grains__).issubset(
  92. grains.make_hashable(hashable_list)
  93. ),
  94. True,
  95. )
  96. # 'present' function tests: 12
  97. def test_present_add(self):
  98. # Set a non existing grain
  99. with self.setGrains({"a": "aval"}):
  100. ret = grains.present(name="foo", value="bar")
  101. self.assertEqual(ret["result"], True)
  102. self.assertEqual(ret["changes"], {"foo": "bar"})
  103. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  104. self.assertGrainFileContent("a: aval\nfoo: bar\n")
  105. # Set a non existing nested grain
  106. with self.setGrains({"a": "aval"}):
  107. ret = grains.present(name="foo:is:nested", value="bar")
  108. self.assertEqual(ret["result"], True)
  109. self.assertEqual(ret["changes"], {"foo": {"is": {"nested": "bar"}}})
  110. self.assertEqual(
  111. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "bar"}}}
  112. )
  113. self.assertGrainFileContent(
  114. "a: aval\n" "foo:\n" " is:\n" " nested: bar\n"
  115. )
  116. # Set a non existing nested dict grain
  117. with self.setGrains({"a": "aval"}):
  118. ret = grains.present(name="foo:is:nested", value={"bar": "is a dict"})
  119. self.assertEqual(ret["result"], True)
  120. self.assertEqual(
  121. ret["changes"], {"foo": {"is": {"nested": {"bar": "is a dict"}}}}
  122. )
  123. self.assertEqual(
  124. grains.__grains__,
  125. {"a": "aval", "foo": {"is": {"nested": {"bar": "is a dict"}}}},
  126. )
  127. self.assertGrainFileContent(
  128. "a: aval\n"
  129. + "foo:\n"
  130. + " is:\n"
  131. + " nested:\n"
  132. + " bar: is a dict\n"
  133. )
  134. def test_present_add_key_to_existing(self):
  135. with self.setGrains({"a": "aval", "foo": {"k1": "v1"}}):
  136. # Fails setting a grain to a dict
  137. ret = grains.present(name="foo:k2", value="v2")
  138. self.assertEqual(ret["result"], True)
  139. self.assertEqual(ret["comment"], "Set grain foo:k2 to v2")
  140. self.assertEqual(ret["changes"], {"foo": {"k2": "v2", "k1": "v1"}})
  141. self.assertEqual(
  142. grains.__grains__, {"a": "aval", "foo": {"k1": "v1", "k2": "v2"}}
  143. )
  144. self.assertGrainFileContent(
  145. "a: aval\n" + "foo:\n" + " k1: v1\n" + " k2: v2\n"
  146. )
  147. def test_present_already_set(self):
  148. with self.setGrains({"a": "aval", "foo": "bar"}):
  149. # Grain already set
  150. ret = grains.present(name="foo", value="bar")
  151. self.assertEqual(ret["result"], True)
  152. self.assertEqual(ret["comment"], "Grain is already set")
  153. self.assertEqual(ret["changes"], {})
  154. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  155. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  156. # Nested grain already set
  157. ret = grains.present(name="foo:is:nested", value="bar")
  158. self.assertEqual(ret["result"], True)
  159. self.assertEqual(ret["comment"], "Grain is already set")
  160. self.assertEqual(ret["changes"], {})
  161. self.assertEqual(
  162. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "bar"}}}
  163. )
  164. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  165. # Nested dict grain already set
  166. ret = grains.present(name="foo:is", value={"nested": "bar"})
  167. self.assertEqual(ret["result"], True)
  168. self.assertEqual(ret["comment"], "Grain is already set")
  169. self.assertEqual(ret["changes"], {})
  170. self.assertEqual(
  171. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "bar"}}}
  172. )
  173. def test_present_overwrite(self):
  174. with self.setGrains({"a": "aval", "foo": "bar"}):
  175. # Overwrite an existing grain
  176. ret = grains.present(name="foo", value="newbar")
  177. self.assertEqual(ret["result"], True)
  178. self.assertEqual(ret["changes"], {"foo": "newbar"})
  179. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "newbar"})
  180. self.assertGrainFileContent("a: aval\n" + "foo: newbar\n")
  181. with self.setGrains({"a": "aval", "foo": "bar"}):
  182. # Clear a grain (set to None)
  183. ret = grains.present(name="foo", value=None)
  184. self.assertEqual(ret["result"], True)
  185. self.assertEqual(ret["changes"], {"foo": None})
  186. self.assertEqual(grains.__grains__, {"a": "aval", "foo": None})
  187. self.assertGrainFileContent("a: aval\n" + "foo: null\n")
  188. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  189. # Overwrite an existing nested grain
  190. ret = grains.present(name="foo:is:nested", value="newbar")
  191. self.assertEqual(ret["result"], True)
  192. self.assertEqual(ret["changes"], {"foo": {"is": {"nested": "newbar"}}})
  193. self.assertEqual(
  194. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "newbar"}}}
  195. )
  196. self.assertGrainFileContent(
  197. "a: aval\n" + "foo:\n" + " is:\n" + " nested: newbar\n"
  198. )
  199. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  200. # Clear a nested grain (set to None)
  201. ret = grains.present(name="foo:is:nested", value=None)
  202. self.assertEqual(ret["result"], True)
  203. self.assertEqual(ret["changes"], {"foo": {"is": {"nested": None}}})
  204. self.assertEqual(
  205. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": None}}}
  206. )
  207. self.assertGrainFileContent(
  208. "a: aval\n" + "foo:\n" + " is:\n" + " nested: null\n"
  209. )
  210. def test_present_fail_overwrite(self):
  211. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "val"}}}):
  212. # Overwrite an existing grain
  213. ret = grains.present(name="foo:is", value="newbar")
  214. self.assertEqual(ret["result"], False)
  215. self.assertEqual(ret["changes"], {})
  216. self.assertEqual(
  217. ret["comment"],
  218. "The key 'foo:is' exists but is a dict or a list. Use 'force=True' to overwrite.",
  219. )
  220. self.assertEqual(
  221. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "val"}}}
  222. )
  223. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "val"}}}):
  224. # Clear a grain (set to None)
  225. ret = grains.present(name="foo:is", value=None)
  226. self.assertEqual(ret["result"], False)
  227. self.assertEqual(ret["changes"], {})
  228. self.assertEqual(
  229. ret["comment"],
  230. "The key 'foo:is' exists but is a dict or a list. Use 'force=True' to overwrite.",
  231. )
  232. self.assertEqual(
  233. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "val"}}}
  234. )
  235. def test_present_fails_to_set_dict_or_list(self):
  236. with self.setGrains({"a": "aval", "foo": "bar"}):
  237. # Fails to overwrite a grain to a list
  238. ret = grains.present(name="foo", value=["l1", "l2"])
  239. self.assertEqual(ret["result"], False)
  240. self.assertEqual(
  241. ret["comment"],
  242. "The key 'foo' exists and the "
  243. + "given value is a dict or a list. "
  244. + "Use 'force=True' to overwrite.",
  245. )
  246. self.assertEqual(ret["changes"], {})
  247. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  248. with self.setGrains({"a": "aval", "foo": "bar"}):
  249. # Fails setting a grain to a dict
  250. ret = grains.present(name="foo", value={"k1": "v1"})
  251. self.assertEqual(ret["result"], False)
  252. self.assertEqual(
  253. ret["comment"],
  254. "The key 'foo' exists and the given "
  255. + "value is a dict or a list. Use "
  256. + "'force=True' to overwrite.",
  257. )
  258. self.assertEqual(ret["changes"], {})
  259. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  260. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  261. # Fails to overwrite a nested grain to a list
  262. ret = grains.present(
  263. name="foo,is,nested", value=["l1", "l2"], delimiter=","
  264. )
  265. self.assertEqual(ret["result"], False)
  266. self.assertEqual(ret["changes"], {})
  267. self.assertEqual(
  268. ret["comment"],
  269. "The key 'foo:is:nested' exists and the "
  270. + "given value is a dict or a list. "
  271. + "Use 'force=True' to overwrite.",
  272. )
  273. self.assertEqual(
  274. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "bar"}}}
  275. )
  276. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  277. # Fails setting a nested grain to a dict
  278. ret = grains.present(name="foo:is:nested", value={"k1": "v1"})
  279. self.assertEqual(ret["result"], False)
  280. self.assertEqual(
  281. ret["comment"],
  282. "The key 'foo:is:nested' exists and the "
  283. + "given value is a dict or a list. "
  284. + "Use 'force=True' to overwrite.",
  285. )
  286. self.assertEqual(ret["changes"], {})
  287. self.assertEqual(
  288. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": "bar"}}}
  289. )
  290. def test_present_fail_merge_dict(self):
  291. with self.setGrains({"a": "aval", "foo": {"k1": "v1"}}):
  292. # Fails setting a grain to a dict
  293. ret = grains.present(name="foo", value={"k2": "v2"})
  294. self.assertEqual(ret["result"], False)
  295. self.assertEqual(
  296. ret["comment"],
  297. "The key 'foo' exists but "
  298. + "is a dict or a list. "
  299. + "Use 'force=True' to overwrite.",
  300. )
  301. self.assertEqual(grains.__grains__, {"a": "aval", "foo": {"k1": "v1"}})
  302. self.assertGrainFileContent("a: aval\n" + "foo:\n" + " k1: v1\n")
  303. def test_present_force_to_set_dict_or_list(self):
  304. with self.setGrains({"a": "aval", "foo": "bar"}):
  305. # Force to overwrite a grain to a list
  306. ret = grains.present(name="foo", value=["l1", "l2"], force=True)
  307. self.assertEqual(ret["result"], True)
  308. self.assertEqual(
  309. ret["comment"],
  310. "Set grain foo to ['l1', 'l2']"
  311. if six.PY3
  312. else "Set grain foo to [u'l1', u'l2']",
  313. )
  314. self.assertEqual(ret["changes"], {"foo": ["l1", "l2"]})
  315. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["l1", "l2"]})
  316. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- l1\n" + "- l2\n")
  317. with self.setGrains({"a": "aval", "foo": "bar"}):
  318. # Force setting a grain to a dict
  319. ret = grains.present(name="foo", value={"k1": "v1"}, force=True)
  320. self.assertEqual(ret["result"], True)
  321. self.assertEqual(
  322. ret["comment"],
  323. "Set grain foo to {'k1': 'v1'}"
  324. if six.PY3
  325. else "Set grain foo to {u'k1': u'v1'}",
  326. )
  327. self.assertEqual(ret["changes"], {"foo": {"k1": "v1"}})
  328. self.assertEqual(grains.__grains__, {"a": "aval", "foo": {"k1": "v1"}})
  329. self.assertGrainFileContent("a: aval\n" + "foo:\n" + " k1: v1\n")
  330. with self.setGrains({"a": "aval", "foo": {"is": {"nested": "bar"}}}):
  331. # Force to overwrite a nested grain to a list
  332. ret = grains.present(
  333. name="foo,is,nested", value=["l1", "l2"], delimiter=",", force=True
  334. )
  335. self.assertEqual(ret["result"], True)
  336. self.assertEqual(ret["changes"], {"foo": {"is": {"nested": ["l1", "l2"]}}})
  337. self.assertEqual(
  338. ret["comment"],
  339. "Set grain foo:is:nested to ['l1', 'l2']"
  340. if six.PY3
  341. else "Set grain foo:is:nested to [u'l1', u'l2']",
  342. )
  343. self.assertEqual(
  344. grains.__grains__,
  345. {"a": "aval", "foo": {"is": {"nested": ["l1", "l2"]}}},
  346. )
  347. self.assertGrainFileContent(
  348. "a: aval\n"
  349. + "foo:\n"
  350. + " is:\n"
  351. + " nested:\n"
  352. + " - l1\n"
  353. + " - l2\n"
  354. )
  355. with self.setGrains(
  356. {"a": "aval", "foo": {"is": {"nested": "bar"}, "and": "other"}}
  357. ):
  358. # Force setting a nested grain to a dict
  359. ret = grains.present(name="foo:is:nested", value={"k1": "v1"}, force=True)
  360. self.assertEqual(ret["result"], True)
  361. self.assertEqual(
  362. ret["comment"],
  363. "Set grain foo:is:nested to {'k1': 'v1'}"
  364. if six.PY3
  365. else "Set grain foo:is:nested to {u'k1': u'v1'}",
  366. )
  367. self.assertEqual(
  368. ret["changes"],
  369. {"foo": {"is": {"nested": {"k1": "v1"}}, "and": "other"}},
  370. )
  371. self.assertEqual(
  372. grains.__grains__,
  373. {"a": "aval", "foo": {"is": {"nested": {"k1": "v1"}}, "and": "other"}},
  374. )
  375. self.assertGrainFileContent(
  376. "a: aval\n"
  377. + "foo:\n"
  378. + " and: other\n"
  379. + " is:\n"
  380. + " nested:\n"
  381. + " k1: v1\n"
  382. )
  383. def test_present_fails_to_convert_value_to_key(self):
  384. with self.setGrains({"a": "aval", "foo": "bar"}):
  385. # Fails converting a value to a nested grain key
  386. ret = grains.present(name="foo:is:nested", value={"k1": "v1"})
  387. self.assertEqual(ret["result"], False)
  388. self.assertEqual(
  389. ret["comment"],
  390. "The key 'foo' value is 'bar', "
  391. + "which is different from the provided "
  392. + "key 'is'. Use 'force=True' to overwrite.",
  393. )
  394. self.assertEqual(ret["changes"], {})
  395. def test_present_overwrite_test(self):
  396. with patch.dict(grains.__opts__, {"test": True}):
  397. with self.setGrains({"a": "aval", "foo": "bar"}):
  398. # Overwrite an existing grain
  399. ret = grains.present(name="foo", value="newbar")
  400. self.assertEqual(ret["result"], None)
  401. self.assertEqual(ret["changes"], {"changed": {"foo": "newbar"}})
  402. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  403. self.assertGrainFileContent("a: aval\n" + "foo: bar\n")
  404. def test_present_convert_value_to_key(self):
  405. with self.setGrains({"a": "aval", "foo": "is"}):
  406. # Converts a value to a nested grain key
  407. ret = grains.present(name="foo:is:nested", value={"k1": "v1"})
  408. self.assertEqual(ret["result"], True)
  409. self.assertEqual(
  410. ret["comment"],
  411. "Set grain foo:is:nested to {'k1': 'v1'}"
  412. if six.PY3
  413. else "Set grain foo:is:nested to {u'k1': u'v1'}",
  414. )
  415. self.assertEqual(ret["changes"], {"foo": {"is": {"nested": {"k1": "v1"}}}})
  416. self.assertEqual(
  417. grains.__grains__,
  418. {"a": "aval", "foo": {"is": {"nested": {"k1": "v1"}}}},
  419. )
  420. self.assertGrainFileContent(
  421. "a: aval\n" + "foo:\n" + " is:\n" + " nested:\n" + " k1: v1\n"
  422. )
  423. with self.setGrains({"a": "aval", "foo": ["one", "is", "correct"]}):
  424. # Converts a list element to a nested grain key
  425. ret = grains.present(name="foo:is:nested", value={"k1": "v1"})
  426. self.assertEqual(ret["result"], True)
  427. self.assertEqual(
  428. ret["comment"],
  429. "Set grain foo:is:nested to {'k1': 'v1'}"
  430. if six.PY3
  431. else "Set grain foo:is:nested to {u'k1': u'v1'}",
  432. )
  433. self.assertEqual(
  434. ret["changes"],
  435. {"foo": ["one", {"is": {"nested": {"k1": "v1"}}}, "correct"]},
  436. )
  437. self.assertEqual(
  438. grains.__grains__,
  439. {
  440. "a": "aval",
  441. "foo": ["one", {"is": {"nested": {"k1": "v1"}}}, "correct"],
  442. },
  443. )
  444. self.assertGrainFileContent(
  445. "a: aval\n"
  446. + "foo:\n"
  447. + "- one\n"
  448. + "- is:\n"
  449. + " nested:\n"
  450. + " k1: v1\n"
  451. + "- correct\n"
  452. )
  453. def test_present_unknown_failure(self):
  454. with patch("salt.modules.grains.setval") as mocked_setval:
  455. mocked_setval.return_value = "Failed to set grain foo"
  456. with self.setGrains({"a": "aval", "foo": "bar"}):
  457. # Unknown reason failure
  458. ret = grains.present(name="foo", value="baz")
  459. self.assertEqual(ret["result"], False)
  460. self.assertEqual(ret["comment"], "Failed to set grain foo")
  461. self.assertEqual(ret["changes"], {})
  462. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  463. self.assertGrainFileContent("a: aval\n" + "foo: bar\n")
  464. # 'absent' function tests: 6
  465. def test_absent_already(self):
  466. # Unset a non existent grain
  467. with self.setGrains({"a": "aval"}):
  468. ret = grains.absent(name="foo")
  469. self.assertEqual(ret["result"], True)
  470. self.assertEqual(ret["comment"], "Grain foo does not exist")
  471. self.assertEqual(ret["changes"], {})
  472. self.assertEqual(grains.__grains__, {"a": "aval"})
  473. self.assertGrainFileContent("a: aval\n")
  474. # Unset a non existent nested grain
  475. with self.setGrains({"a": "aval"}):
  476. ret = grains.absent(name="foo:is:nested")
  477. self.assertEqual(ret["result"], True)
  478. self.assertEqual(ret["comment"], "Grain foo:is:nested does not exist")
  479. self.assertEqual(ret["changes"], {})
  480. self.assertEqual(grains.__grains__, {"a": "aval"})
  481. self.assertGrainFileContent("a: aval\n")
  482. def test_absent_unset(self):
  483. # Unset a grain
  484. with self.setGrains({"a": "aval", "foo": "bar"}):
  485. ret = grains.absent(name="foo")
  486. self.assertEqual(ret["result"], True)
  487. self.assertEqual(ret["comment"], "Value for grain foo was set to None")
  488. self.assertEqual(ret["changes"], {"grain": "foo", "value": None})
  489. self.assertEqual(grains.__grains__, {"a": "aval", "foo": None})
  490. self.assertGrainFileContent("a: aval\n" + "foo: null\n")
  491. # Unset grain when its value is False
  492. with self.setGrains({"a": "aval", "foo": False}):
  493. ret = grains.absent(name="foo")
  494. self.assertEqual(ret["result"], True)
  495. self.assertEqual(ret["comment"], "Value for grain foo was set to None")
  496. self.assertEqual(ret["changes"], {"grain": "foo", "value": None})
  497. self.assertEqual(grains.__grains__, {"a": "aval", "foo": None})
  498. self.assertGrainFileContent("a: aval\n" + "foo: null\n")
  499. # Unset a nested grain
  500. with self.setGrains(
  501. {"a": "aval", "foo": ["order", {"is": {"nested": "bar"}}, "correct"]}
  502. ):
  503. ret = grains.absent(name="foo,is,nested", delimiter=",")
  504. self.assertEqual(ret["result"], True)
  505. self.assertEqual(
  506. ret["comment"], "Value for grain foo:is:nested was set to None"
  507. )
  508. self.assertEqual(ret["changes"], {"grain": "foo:is:nested", "value": None})
  509. self.assertEqual(
  510. grains.__grains__,
  511. {"a": "aval", "foo": ["order", {"is": {"nested": None}}, "correct"]},
  512. )
  513. self.assertGrainFileContent(
  514. "a: aval\n"
  515. + "foo:\n"
  516. + "- order\n"
  517. + "- is:\n"
  518. + " nested: null\n"
  519. + "- correct\n"
  520. )
  521. # Unset a nested value don't change anything
  522. with self.setGrains(
  523. {"a": "aval", "foo": ["order", {"is": "nested"}, "correct"]}
  524. ):
  525. ret = grains.absent(name="foo:is:nested")
  526. self.assertEqual(ret["result"], True)
  527. self.assertEqual(ret["comment"], "Grain foo:is:nested does not exist")
  528. self.assertEqual(ret["changes"], {})
  529. self.assertEqual(
  530. grains.__grains__,
  531. {"a": "aval", "foo": ["order", {"is": "nested"}, "correct"]},
  532. )
  533. self.assertGrainFileContent(
  534. "a: aval\n" + "foo:\n" + "- order\n" + "- is: nested\n" + "- correct\n"
  535. )
  536. def test_absent_unset_test(self):
  537. with patch.dict(grains.__opts__, {"test": True}):
  538. with self.setGrains({"a": "aval", "foo": "bar"}):
  539. # Overwrite an existing grain
  540. ret = grains.absent(name="foo")
  541. self.assertEqual(ret["result"], None)
  542. self.assertEqual(ret["changes"], {"grain": "foo", "value": None})
  543. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  544. self.assertGrainFileContent("a: aval\n" + "foo: bar\n")
  545. def test_absent_fails_nested_complex_grain(self):
  546. # Unset a nested complex grain
  547. with self.setGrains(
  548. {"a": "aval", "foo": ["order", {"is": {"nested": "bar"}}, "correct"]}
  549. ):
  550. ret = grains.absent(name="foo:is")
  551. self.assertEqual(ret["result"], False)
  552. self.assertEqual(
  553. ret["comment"],
  554. "The key 'foo:is' exists but is a dict or a list. Use 'force=True' to overwrite.",
  555. )
  556. self.assertEqual(ret["changes"], {})
  557. self.assertEqual(
  558. grains.__grains__,
  559. {"a": "aval", "foo": ["order", {"is": {"nested": "bar"}}, "correct"]},
  560. )
  561. self.assertGrainFileContent(
  562. "a: aval\n"
  563. + "foo:\n"
  564. + "- order\n"
  565. + "- is:\n"
  566. + " nested: bar\n"
  567. + "- correct\n"
  568. )
  569. def test_absent_force_nested_complex_grain(self):
  570. # Unset a nested complex grain
  571. with self.setGrains(
  572. {"a": "aval", "foo": ["order", {"is": {"nested": "bar"}}, "correct"]}
  573. ):
  574. ret = grains.absent(name="foo:is", force=True)
  575. self.assertEqual(ret["result"], True)
  576. self.assertEqual(ret["comment"], "Value for grain foo:is was set to None")
  577. self.assertEqual(ret["changes"], {"grain": "foo:is", "value": None})
  578. self.assertEqual(
  579. grains.__grains__,
  580. {"a": "aval", "foo": ["order", {"is": None}, "correct"]},
  581. )
  582. self.assertGrainFileContent(
  583. "a: aval\n" + "foo:\n" + "- order\n" + "- is: null\n" + "- correct\n"
  584. )
  585. def test_absent_delete(self):
  586. # Delete a grain
  587. with self.setGrains({"a": "aval", "foo": "bar"}):
  588. ret = grains.absent(name="foo", destructive=True)
  589. self.assertEqual(ret["result"], True)
  590. self.assertEqual(ret["comment"], "Grain foo was deleted")
  591. self.assertEqual(ret["changes"], {"deleted": "foo"})
  592. self.assertEqual(grains.__grains__, {"a": "aval"})
  593. self.assertGrainFileContent("a: aval\n")
  594. # Delete a previously unset grain
  595. with self.setGrains({"a": "aval", "foo": None}):
  596. ret = grains.absent(name="foo", destructive=True)
  597. self.assertEqual(ret["result"], True)
  598. self.assertEqual(ret["comment"], "Grain foo was deleted")
  599. self.assertEqual(ret["changes"], {"deleted": "foo"})
  600. self.assertEqual(grains.__grains__, {"a": "aval"})
  601. self.assertGrainFileContent("a: aval\n")
  602. # Delete a nested grain
  603. with self.setGrains(
  604. {
  605. "a": "aval",
  606. "foo": [
  607. "order",
  608. {"is": {"nested": "bar", "other": "value"}},
  609. "correct",
  610. ],
  611. }
  612. ):
  613. ret = grains.absent(name="foo:is:nested", destructive=True)
  614. self.assertEqual(ret["result"], True)
  615. self.assertEqual(ret["comment"], "Grain foo:is:nested was deleted")
  616. self.assertEqual(ret["changes"], {"deleted": "foo:is:nested"})
  617. self.assertEqual(
  618. grains.__grains__,
  619. {"a": "aval", "foo": ["order", {"is": {"other": "value"}}, "correct"]},
  620. )
  621. self.assertGrainFileContent(
  622. "a: aval\n"
  623. + "foo:\n"
  624. + "- order\n"
  625. + "- is:\n"
  626. + " other: value\n"
  627. + "- correct\n"
  628. )
  629. # 'append' function tests: 6
  630. def test_append(self):
  631. # Append to an existing list
  632. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  633. ret = grains.append(name="foo", value="baz")
  634. self.assertEqual(ret["result"], True)
  635. self.assertEqual(ret["comment"], "Value baz was added to grain foo")
  636. self.assertEqual(ret["changes"], {"added": "baz"})
  637. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar", "baz"]})
  638. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n" + "- baz\n")
  639. def test_append_nested(self):
  640. # Append to an existing nested list
  641. with self.setGrains({"a": "aval", "foo": {"list": ["bar"]}}):
  642. ret = grains.append(name="foo,list", value="baz", delimiter=",")
  643. self.assertEqual(ret["result"], True)
  644. self.assertEqual(ret["comment"], "Value baz was added to grain foo:list")
  645. self.assertEqual(ret["changes"], {"added": "baz"})
  646. self.assertEqual(
  647. grains.__grains__, {"a": "aval", "foo": {"list": ["bar", "baz"]}}
  648. )
  649. self.assertGrainFileContent(
  650. "a: aval\n" + "foo:\n" + " list:\n" + " - bar\n" + " - baz\n"
  651. )
  652. def test_append_already(self):
  653. # Append to an existing list
  654. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  655. ret = grains.append(name="foo", value="bar")
  656. self.assertEqual(ret["result"], True)
  657. self.assertEqual(
  658. ret["comment"], "Value bar is already in the list " + "for grain foo"
  659. )
  660. self.assertEqual(ret["changes"], {})
  661. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar"]})
  662. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n")
  663. def test_append_fails_not_a_list(self):
  664. # Fail to append to an existing grain, not a list
  665. with self.setGrains({"a": "aval", "foo": {"bar": "val"}}):
  666. ret = grains.append(name="foo", value="baz")
  667. self.assertEqual(ret["result"], False)
  668. self.assertEqual(ret["comment"], "Grain foo is not a valid list")
  669. self.assertEqual(ret["changes"], {})
  670. self.assertEqual(grains.__grains__, {"a": "aval", "foo": {"bar": "val"}})
  671. def test_append_convert_to_list(self):
  672. # Append to an existing grain, converting to a list
  673. with self.setGrains({"a": "aval", "foo": {"bar": "val"}}):
  674. self.assertGrainFileContent("a: aval\n" + "foo:\n" + " bar: val\n")
  675. ret = grains.append(name="foo", value="baz", convert=True)
  676. self.assertEqual(ret["result"], True)
  677. self.assertEqual(ret["comment"], "Value baz was added to grain foo")
  678. self.assertEqual(ret["changes"], {"added": "baz"})
  679. self.assertEqual(
  680. grains.__grains__, {"a": "aval", "foo": [{"bar": "val"}, "baz"]}
  681. )
  682. self.assertGrainFileContent(
  683. "a: aval\n" + "foo:\n" + "- bar: val\n" + "- baz\n"
  684. )
  685. # Append to an existing grain, converting to a list a multi-value dict
  686. with self.setGrains({"a": "aval", "foo": {"bar": "val", "other": "value"}}):
  687. self.assertGrainFileContent(
  688. "a: aval\n" + "foo:\n" + " bar: val\n" + " other: value\n"
  689. )
  690. ret = grains.append(name="foo", value="baz", convert=True)
  691. self.assertEqual(ret["result"], True)
  692. self.assertEqual(ret["comment"], "Value baz was added to grain foo")
  693. self.assertEqual(ret["changes"], {"added": "baz"})
  694. self.assertEqual(
  695. grains.__grains__,
  696. {"a": "aval", "foo": [{"bar": "val", "other": "value"}, "baz"]},
  697. )
  698. self.assertGrainFileContent(
  699. "a: aval\n" + "foo:\n" + "- bar: val\n" + " other: value\n" + "- baz\n"
  700. )
  701. def test_append_fails_inexistent(self):
  702. # Append to a non existing grain
  703. with self.setGrains({"a": "aval"}):
  704. ret = grains.append(name="foo", value="bar")
  705. self.assertEqual(ret["result"], False)
  706. self.assertEqual(ret["comment"], "Grain foo does not exist")
  707. self.assertEqual(ret["changes"], {})
  708. self.assertEqual(grains.__grains__, {"a": "aval"})
  709. def test_append_convert_to_list_empty(self):
  710. # Append to an existing list
  711. with self.setGrains({"foo": None}):
  712. ret = grains.append(name="foo", value="baz", convert=True)
  713. self.assertEqual(ret["result"], True)
  714. self.assertEqual(ret["comment"], "Value baz was added to grain foo")
  715. self.assertEqual(ret["changes"], {"added": "baz"})
  716. self.assertEqual(grains.__grains__, {"foo": ["baz"]})
  717. self.assertGrainFileContent("foo:\n" + "- baz\n")
  718. # 'list_present' function tests: 7
  719. def test_list_present(self):
  720. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  721. ret = grains.list_present(name="foo", value="baz")
  722. self.assertEqual(ret["result"], True)
  723. self.assertEqual(ret["comment"], "Append value baz to grain foo")
  724. self.assertEqual(ret["changes"], {"new": {"foo": ["bar", "baz"]}})
  725. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar", "baz"]})
  726. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n" + "- baz\n")
  727. def test_list_present_nested(self):
  728. with self.setGrains({"a": "aval", "foo": {"is": {"nested": ["bar"]}}}):
  729. ret = grains.list_present(name="foo,is,nested", value="baz", delimiter=",")
  730. self.assertEqual(ret["result"], True)
  731. self.assertEqual(ret["comment"], "Append value baz to grain foo:is:nested")
  732. self.assertEqual(
  733. ret["changes"], {"new": {"foo": {"is": {"nested": ["bar", "baz"]}}}}
  734. )
  735. self.assertEqual(
  736. grains.__grains__,
  737. {"a": "aval", "foo": {"is": {"nested": ["bar", "baz"]}}},
  738. )
  739. self.assertGrainFileContent(
  740. "a: aval\n"
  741. + "foo:\n"
  742. + " is:\n"
  743. + " nested:\n"
  744. + " - bar\n"
  745. + " - baz\n"
  746. )
  747. def test_list_present_inexistent(self):
  748. with self.setGrains({"a": "aval"}):
  749. ret = grains.list_present(name="foo", value="baz")
  750. self.assertEqual(ret["result"], True)
  751. self.assertEqual(ret["comment"], "Append value baz to grain foo")
  752. self.assertEqual(ret["changes"], {"new": {"foo": ["baz"]}})
  753. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["baz"]})
  754. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- baz\n")
  755. def test_list_present_inexistent_nested(self):
  756. with self.setGrains({"a": "aval"}):
  757. ret = grains.list_present(name="foo:is:nested", value="baz")
  758. self.assertEqual(ret["result"], True)
  759. self.assertEqual(ret["comment"], "Append value baz to grain foo:is:nested")
  760. self.assertEqual(
  761. ret["changes"], {"new": {"foo": {"is": {"nested": ["baz"]}}}}
  762. )
  763. self.assertEqual(
  764. grains.__grains__, {"a": "aval", "foo": {"is": {"nested": ["baz"]}}}
  765. )
  766. self.assertGrainFileContent(
  767. "a: aval\n" + "foo:\n" + " is:\n" + " nested:\n" + " - baz\n"
  768. )
  769. def test_list_present_not_a_list(self):
  770. with self.setGrains({"a": "aval", "foo": "bar"}):
  771. ret = grains.list_present(name="foo", value="baz")
  772. self.assertEqual(ret["result"], False)
  773. self.assertEqual(ret["comment"], "Grain foo is not a valid list")
  774. self.assertEqual(ret["changes"], {})
  775. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  776. self.assertGrainFileContent("a: aval\n" + "foo: bar\n")
  777. def test_list_present_nested_already(self):
  778. with self.setGrains({"a": "aval", "b": {"foo": ["bar"]}}):
  779. ret = grains.list_present(name="b:foo", value="bar")
  780. self.assertEqual(ret["result"], True)
  781. self.assertEqual(ret["comment"], "Value bar is already in grain b:foo")
  782. self.assertEqual(ret["changes"], {})
  783. self.assertEqual(grains.__grains__, {"a": "aval", "b": {"foo": ["bar"]}})
  784. self.assertGrainFileContent("a: aval\n" + "b:\n" + " foo:\n" + " - bar\n")
  785. def test_list_present_already(self):
  786. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  787. ret = grains.list_present(name="foo", value="bar")
  788. self.assertEqual(ret["result"], True)
  789. self.assertEqual(ret["comment"], "Value bar is already in grain foo")
  790. self.assertEqual(ret["changes"], {})
  791. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar"]})
  792. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n")
  793. def test_list_present_unknown_failure(self):
  794. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  795. # Unknown reason failure
  796. with patch.dict(grainsmod.__salt__, {"grains.append": MagicMock()}):
  797. ret = grains.list_present(name="foo", value="baz")
  798. self.assertEqual(ret["result"], False)
  799. self.assertEqual(ret["comment"], "Failed append value baz to grain foo")
  800. self.assertEqual(ret["changes"], {})
  801. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar"]})
  802. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n")
  803. # 'list_absent' function tests: 6
  804. def test_list_absent(self):
  805. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  806. ret = grains.list_absent(name="foo", value="bar")
  807. self.assertEqual(ret["result"], True)
  808. self.assertEqual(ret["comment"], "Value bar was deleted from grain foo")
  809. self.assertEqual(ret["changes"], {"deleted": ["bar"]})
  810. self.assertEqual(grains.__grains__, {"a": "aval", "foo": []})
  811. self.assertGrainFileContent("a: aval\n" + "foo: []\n")
  812. def test_list_absent_nested(self):
  813. with self.setGrains({"a": "aval", "foo": {"list": ["bar"]}}):
  814. ret = grains.list_absent(name="foo:list", value="bar")
  815. self.assertEqual(ret["result"], True)
  816. self.assertEqual(
  817. ret["comment"], "Value bar was deleted from grain foo:list"
  818. )
  819. self.assertEqual(ret["changes"], {"deleted": ["bar"]})
  820. self.assertEqual(grains.__grains__, {"a": "aval", "foo": {"list": []}})
  821. self.assertGrainFileContent("a: aval\n" + "foo:\n" + " list: []\n")
  822. def test_list_absent_inexistent(self):
  823. with self.setGrains({"a": "aval"}):
  824. ret = grains.list_absent(name="foo", value="baz")
  825. self.assertEqual(ret["result"], True)
  826. self.assertEqual(ret["comment"], "Grain foo does not exist")
  827. self.assertEqual(ret["changes"], {})
  828. self.assertEqual(grains.__grains__, {"a": "aval"})
  829. self.assertGrainFileContent("a: aval\n")
  830. def test_list_absent_inexistent_nested(self):
  831. with self.setGrains({"a": "aval"}):
  832. ret = grains.list_absent(name="foo:list", value="baz")
  833. self.assertEqual(ret["result"], True)
  834. self.assertEqual(ret["comment"], "Grain foo:list does not exist")
  835. self.assertEqual(ret["changes"], {})
  836. self.assertEqual(grains.__grains__, {"a": "aval"})
  837. self.assertGrainFileContent("a: aval\n")
  838. def test_list_absent_not_a_list(self):
  839. with self.setGrains({"a": "aval", "foo": "bar"}):
  840. ret = grains.list_absent(name="foo", value="bar")
  841. self.assertEqual(ret["result"], False)
  842. self.assertEqual(ret["comment"], "Grain foo is not a valid list")
  843. self.assertEqual(ret["changes"], {})
  844. self.assertEqual(grains.__grains__, {"a": "aval", "foo": "bar"})
  845. self.assertGrainFileContent("a: aval\n" + "foo: bar\n")
  846. def test_list_absent_already(self):
  847. with self.setGrains({"a": "aval", "foo": ["bar"]}):
  848. ret = grains.list_absent(name="foo", value="baz")
  849. self.assertEqual(ret["result"], True)
  850. self.assertEqual(ret["comment"], "Value baz is absent from grain foo")
  851. self.assertEqual(ret["changes"], {})
  852. self.assertEqual(grains.__grains__, {"a": "aval", "foo": ["bar"]})
  853. self.assertGrainFileContent("a: aval\n" + "foo:\n" + "- bar\n")