test_salt_call.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. """
  2. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  3. tests.integration.shell.call
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. """
  6. import copy
  7. import logging
  8. import os
  9. import pprint
  10. import re
  11. import shutil
  12. import sys
  13. import pytest
  14. import salt.utils.files
  15. import salt.utils.json
  16. import salt.utils.platform
  17. import salt.utils.yaml
  18. from tests.support.helpers import PRE_PYTEST_SKIP, PRE_PYTEST_SKIP_REASON, slowTest
  19. from tests.support.runtests import RUNTIME_VARS
  20. log = logging.getLogger(__name__)
  21. pytestmark = pytest.mark.windows_whitelisted
  22. @slowTest
  23. def test_fib(salt_call_cli):
  24. ret = salt_call_cli.run("test.fib", "3")
  25. assert ret.exitcode == 0
  26. assert ret.json[0] == 2
  27. @slowTest
  28. def test_fib_txt_output(salt_call_cli):
  29. ret = salt_call_cli.run("--output=txt", "test.fib", "3")
  30. assert ret.exitcode == 0
  31. assert ret.json is None
  32. assert (
  33. re.match(r"local: \(2, [0-9]{1}\.(([0-9]+)(e-([0-9]+))?)\)\s", ret.stdout)
  34. is not None
  35. )
  36. @slowTest
  37. @pytest.mark.parametrize("indent", [-1, 0, 1])
  38. def test_json_out_indent(salt_call_cli, indent):
  39. ret = salt_call_cli.run("--out=json", "--out-indent={}".format(indent), "test.ping")
  40. assert ret.exitcode == 0
  41. assert ret.json is True
  42. if indent == -1:
  43. expected_output = '{"local": true}\n'
  44. elif indent == 0:
  45. expected_output = '{\n"local": true\n}\n'
  46. else:
  47. expected_output = '{\n "local": true\n}\n'
  48. stdout = ret.stdout
  49. if salt.utils.platform.is_windows():
  50. expected_output = expected_output.replace("\n", os.linesep)
  51. assert ret.stdout == expected_output
  52. @slowTest
  53. def test_local_sls_call(salt_call_cli):
  54. fileroot = os.path.join(RUNTIME_VARS.FILES, "file", "base")
  55. ret = salt_call_cli.run(
  56. "--local", "--file-root", fileroot, "state.sls", "saltcalllocal"
  57. )
  58. assert ret.exitcode == 0
  59. state_run_dict = next(iter(ret.json.values()))
  60. assert state_run_dict["name"] == "test.echo"
  61. assert state_run_dict["result"] is True
  62. assert state_run_dict["changes"]["ret"] == "hello"
  63. @slowTest
  64. def test_local_salt_call(salt_call_cli):
  65. """
  66. This tests to make sure that salt-call does not execute the
  67. function twice, see https://github.com/saltstack/salt/pull/49552
  68. """
  69. with pytest.helpers.temp_file() as filename:
  70. ret = salt_call_cli.run(
  71. "--local", "state.single", "file.append", name=filename, text="foo"
  72. )
  73. assert ret.exitcode == 0
  74. state_run_dict = next(iter(ret.json.values()))
  75. assert state_run_dict["changes"]
  76. # 2nd sanity check: make sure that "foo" only exists once in the file
  77. with salt.utils.files.fopen(filename) as fp_:
  78. contents = fp_.read()
  79. assert contents.count("foo") == 1, contents
  80. @slowTest
  81. @pytest.mark.skip_on_windows(reason=PRE_PYTEST_SKIP_REASON)
  82. def test_user_delete_kw_output(salt_call_cli):
  83. ret = salt_call_cli.run("-d", "user.delete", _timeout=120)
  84. assert ret.exitcode == 0
  85. expected_output = "salt '*' user.delete name"
  86. if not salt.utils.platform.is_windows():
  87. expected_output += " remove=True force=True"
  88. assert expected_output in ret.stdout
  89. @slowTest
  90. def test_salt_documentation_too_many_arguments(salt_call_cli):
  91. """
  92. Test to see if passing additional arguments shows an error
  93. """
  94. ret = salt_call_cli.run("-d", "virtualenv.create", "/tmp/ve")
  95. assert ret.exitcode != 0
  96. assert "You can only get documentation for one method at one time" in ret.stderr
  97. @slowTest
  98. def test_issue_6973_state_highstate_exit_code(salt_call_cli):
  99. """
  100. If there is no tops/master_tops or state file matches
  101. for this minion, salt-call should exit non-zero if invoked with
  102. option --retcode-passthrough
  103. """
  104. src = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls")
  105. dst = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls.bak")
  106. shutil.move(src, dst)
  107. expected_comment = "No states found for this minion"
  108. try:
  109. ret = salt_call_cli.run("--retcode-passthrough", "state.highstate")
  110. finally:
  111. shutil.move(dst, src)
  112. assert ret.exitcode != 0
  113. assert expected_comment in ret.stdout
  114. @slowTest
  115. @PRE_PYTEST_SKIP
  116. def test_issue_15074_output_file_append(salt_call_cli):
  117. with pytest.helpers.temp_file(name="issue-15074") as output_file_append:
  118. ret = salt_call_cli.run("--output-file", output_file_append, "test.versions")
  119. assert ret.exitcode == 0
  120. with salt.utils.files.fopen(output_file_append) as ofa:
  121. first_run_output = ofa.read()
  122. assert first_run_output
  123. ret = salt_call_cli.run(
  124. "--output-file",
  125. output_file_append,
  126. "--output-file-append",
  127. "test.versions",
  128. )
  129. assert ret.exitcode == 0
  130. with salt.utils.files.fopen(output_file_append) as ofa:
  131. second_run_output = ofa.read()
  132. assert second_run_output
  133. assert second_run_output == first_run_output + first_run_output
  134. @slowTest
  135. @PRE_PYTEST_SKIP
  136. def test_issue_14979_output_file_permissions(salt_call_cli):
  137. with pytest.helpers.temp_file(name="issue-14979") as output_file:
  138. with salt.utils.files.set_umask(0o077):
  139. # Let's create an initial output file with some data
  140. ret = salt_call_cli.run("--output-file", output_file, "--grains")
  141. assert ret.exitcode == 0
  142. try:
  143. stat1 = os.stat(output_file)
  144. except OSError:
  145. pytest.fail("Failed to generate output file {}".format(output_file))
  146. # Let's change umask
  147. os.umask(0o777) # pylint: disable=blacklisted-function
  148. ret = salt_call_cli.run(
  149. "--output-file", output_file, "--output-file-append", "--grains"
  150. )
  151. assert ret.exitcode == 0
  152. stat2 = os.stat(output_file)
  153. assert stat1.st_mode == stat2.st_mode
  154. # Data was appeneded to file
  155. assert stat1.st_size < stat2.st_size
  156. # Let's remove the output file
  157. os.unlink(output_file)
  158. # Not appending data
  159. ret = salt_call_cli.run("--output-file", output_file, "--grains")
  160. assert ret.exitcode == 0
  161. try:
  162. stat3 = os.stat(output_file)
  163. except OSError:
  164. pytest.fail("Failed to generate output file {}".format(output_file))
  165. # Mode must have changed since we're creating a new log file
  166. assert stat1.st_mode != stat3.st_mode
  167. @slowTest
  168. @pytest.mark.skip_on_windows(reason="This test does not apply on Win")
  169. def test_42116_cli_pillar_override(salt_call_cli):
  170. ret = salt_call_cli.run(
  171. "state.apply",
  172. "issue-42116-cli-pillar-override",
  173. pillar={"myhost": "localhost"},
  174. )
  175. state_run_dict = next(iter(ret.json.values()))
  176. assert state_run_dict["changes"]
  177. assert (
  178. state_run_dict["comment"] == 'Command "ping -c 2 localhost" run'
  179. ), "CLI pillar override not found in pillar data. State Run Dictionary:\n{}".format(
  180. pprint.pformat(state_run_dict)
  181. )
  182. @slowTest
  183. def test_pillar_items_masterless(salt_minion, salt_call_cli):
  184. """
  185. Test to ensure we get expected output
  186. from pillar.items with salt-call
  187. """
  188. TOP = """
  189. base:
  190. '{}':
  191. - generic
  192. """.format(
  193. salt_minion.id
  194. )
  195. with pytest.helpers.temp_pillar_file("top.sls", TOP):
  196. ret = salt_call_cli.run("--local", "pillar.items")
  197. assert ret.exitcode == 0
  198. assert "knights" in ret.json
  199. assert sorted(ret.json["knights"]) == sorted(
  200. ["Lancelot", "Galahad", "Bedevere", "Robin"]
  201. )
  202. assert "monty" in ret.json
  203. assert ret.json["monty"] == "python"
  204. @slowTest
  205. def test_masterless_highstate(salt_call_cli):
  206. """
  207. test state.highstate in masterless mode
  208. """
  209. destpath = os.path.join(RUNTIME_VARS.TMP, "testfile")
  210. ret = salt_call_cli.run("--local", "state.highstate")
  211. assert ret.exitcode == 0
  212. state_run_dict = next(iter(ret.json.values()))
  213. assert state_run_dict["result"] is True
  214. assert state_run_dict["__id__"] == destpath
  215. @slowTest
  216. @pytest.mark.skip_on_windows
  217. def test_syslog_file_not_found(salt_minion, salt_call_cli):
  218. """
  219. test when log_file is set to a syslog file that does not exist
  220. """
  221. old_cwd = os.getcwd()
  222. with pytest.helpers.temp_directory("log_file_incorrect") as config_dir:
  223. try:
  224. os.chdir(config_dir)
  225. minion_config = copy.deepcopy(salt_minion.config)
  226. minion_config["log_file"] = "file:///dev/doesnotexist"
  227. with salt.utils.files.fopen(os.path.join(config_dir, "minion"), "w") as fh_:
  228. fh_.write(salt.utils.yaml.dump(minion_config, default_flow_style=False))
  229. ret = salt_call_cli.run(
  230. "--config-dir", config_dir, "--log-level=debug", "cmd.run", "echo foo",
  231. )
  232. if sys.version_info >= (3, 5, 4):
  233. assert ret.exitcode == 0
  234. assert (
  235. "[WARNING ] The log_file does not exist. Logging not setup correctly or syslog service not started."
  236. in ret.stderr
  237. )
  238. assert ret.json == "foo", ret
  239. else:
  240. assert ret.exitcode == 2
  241. assert "Failed to setup the Syslog logging handler" in ret.stderr
  242. finally:
  243. os.chdir(old_cwd)
  244. @slowTest
  245. @PRE_PYTEST_SKIP
  246. @pytest.mark.skip_on_windows
  247. def test_return(salt_call_cli, salt_run_cli):
  248. command = "echo returnTOmaster"
  249. ret = salt_call_cli.run("cmd.run", command)
  250. assert ret.exitcode == 0
  251. assert ret.json == "returnTOmaster"
  252. ret = salt_run_cli.run("jobs.list_jobs")
  253. assert ret.exitcode == 0
  254. jid = target = None
  255. for jid, details in ret.json.items():
  256. if command in details["Arguments"]:
  257. target = details["Target"]
  258. break
  259. ret = salt_run_cli.run("jobs.lookup_jid", jid, _timeout=60)
  260. assert ret.exitcode == 0
  261. assert target in ret.json
  262. assert ret.json[target] == "returnTOmaster"
  263. @slowTest
  264. def test_exit_status_unknown_argument(salt_call_cli):
  265. """
  266. Ensure correct exit status when an unknown argument is passed to salt CLI.
  267. """
  268. ret = salt_call_cli.run("--unknown-argument")
  269. assert ret.exitcode == salt.defaults.exitcodes.EX_USAGE, ret
  270. assert "Usage" in ret.stderr
  271. assert "no such option: --unknown-argument" in ret.stderr
  272. @slowTest
  273. def test_exit_status_correct_usage(salt_call_cli):
  274. """
  275. Ensure correct exit status when salt CLI starts correctly.
  276. """
  277. ret = salt_call_cli.run("test.true")
  278. assert ret.exitcode == salt.defaults.exitcodes.EX_OK, ret
  279. @slowTest
  280. def test_context_retcode_salt_call(salt_call_cli):
  281. """
  282. Test that a nonzero retcode set in the context dunder will cause the
  283. salt CLI to set a nonzero retcode.
  284. """
  285. # Test salt-call, making sure to also confirm the behavior of
  286. # retcode_passthrough.
  287. ret = salt_call_cli.run("test.retcode", "0")
  288. assert ret.exitcode == 0, ret
  289. ret = salt_call_cli.run("test.retcode", "42")
  290. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  291. ret = salt_call_cli.run("--retcode-passthrough", "test.retcode", "42")
  292. assert ret.exitcode == 42, ret
  293. # Test a state run that exits with one or more failures
  294. ret = salt_call_cli.run("state.single", "test.fail_without_changes", "foo")
  295. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  296. ret = salt_call_cli.run(
  297. "--retcode-passthrough", "state.single", "test.fail_without_changes", "foo"
  298. )
  299. assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_FAILURE, ret
  300. # Test a state compiler error
  301. ret = salt_call_cli.run("state.apply", "thisslsfiledoesnotexist")
  302. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  303. ret = salt_call_cli.run(
  304. "--retcode-passthrough", "state.apply", "thisslsfiledoesnotexist"
  305. )
  306. assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_COMPILER_ERROR, ret
  307. @slowTest
  308. def test_salt_call_error(salt_call_cli):
  309. """
  310. Test that we return the expected retcode when a minion function raises
  311. an exception.
  312. """
  313. ret = salt_call_cli.run("test.raise_exception", "TypeError")
  314. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  315. ret = salt_call_cli.run(
  316. "test.raise_exception", "salt.exceptions.CommandNotFoundError"
  317. )
  318. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  319. ret = salt_call_cli.run(
  320. "test.raise_exception", "salt.exceptions.CommandExecutionError"
  321. )
  322. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  323. ret = salt_call_cli.run(
  324. "test.raise_exception", "salt.exceptions.SaltInvocationError"
  325. )
  326. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  327. ret = salt_call_cli.run(
  328. "test.raise_exception",
  329. "OSError",
  330. "2",
  331. "No such file or directory",
  332. "/tmp/foo.txt",
  333. )
  334. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  335. ret = salt_call_cli.run("test.echo", "{foo: bar, result: False}")
  336. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret