123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- """
- :codeauthor: Pedro Algarvio (pedro@algarvio.me)
- tests.integration.shell.call
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- """
- import copy
- import logging
- import os
- import pprint
- import re
- import shutil
- import sys
- import pytest
- import salt.utils.files
- import salt.utils.json
- import salt.utils.platform
- import salt.utils.yaml
- from tests.support.helpers import PRE_PYTEST_SKIP, PRE_PYTEST_SKIP_REASON, slowTest
- from tests.support.runtests import RUNTIME_VARS
- log = logging.getLogger(__name__)
- pytestmark = pytest.mark.windows_whitelisted
- @slowTest
- def test_fib(salt_call_cli):
- ret = salt_call_cli.run("test.fib", "3")
- assert ret.exitcode == 0
- assert ret.json[0] == 2
- @slowTest
- def test_fib_txt_output(salt_call_cli):
- ret = salt_call_cli.run("--output=txt", "test.fib", "3")
- assert ret.exitcode == 0
- assert ret.json is None
- assert (
- re.match(r"local: \(2, [0-9]{1}\.(([0-9]+)(e-([0-9]+))?)\)\s", ret.stdout)
- is not None
- )
- @slowTest
- @pytest.mark.parametrize("indent", [-1, 0, 1])
- def test_json_out_indent(salt_call_cli, indent):
- ret = salt_call_cli.run("--out=json", "--out-indent={}".format(indent), "test.ping")
- assert ret.exitcode == 0
- assert ret.json is True
- if indent == -1:
- expected_output = '{"local": true}\n'
- elif indent == 0:
- expected_output = '{\n"local": true\n}\n'
- else:
- expected_output = '{\n "local": true\n}\n'
- stdout = ret.stdout
- if salt.utils.platform.is_windows():
- expected_output = expected_output.replace("\n", os.linesep)
- assert ret.stdout == expected_output
- @slowTest
- def test_local_sls_call(salt_call_cli):
- fileroot = os.path.join(RUNTIME_VARS.FILES, "file", "base")
- ret = salt_call_cli.run(
- "--local", "--file-root", fileroot, "state.sls", "saltcalllocal"
- )
- assert ret.exitcode == 0
- state_run_dict = next(iter(ret.json.values()))
- assert state_run_dict["name"] == "test.echo"
- assert state_run_dict["result"] is True
- assert state_run_dict["changes"]["ret"] == "hello"
- @slowTest
- def test_local_salt_call(salt_call_cli):
- """
- This tests to make sure that salt-call does not execute the
- function twice, see https://github.com/saltstack/salt/pull/49552
- """
- with pytest.helpers.temp_file() as filename:
- ret = salt_call_cli.run(
- "--local", "state.single", "file.append", name=filename, text="foo"
- )
- assert ret.exitcode == 0
- state_run_dict = next(iter(ret.json.values()))
- assert state_run_dict["changes"]
- # 2nd sanity check: make sure that "foo" only exists once in the file
- with salt.utils.files.fopen(filename) as fp_:
- contents = fp_.read()
- assert contents.count("foo") == 1, contents
- @slowTest
- @pytest.mark.skip_on_windows(reason=PRE_PYTEST_SKIP_REASON)
- def test_user_delete_kw_output(salt_call_cli):
- ret = salt_call_cli.run("-d", "user.delete", _timeout=120)
- assert ret.exitcode == 0
- expected_output = "salt '*' user.delete name"
- if not salt.utils.platform.is_windows():
- expected_output += " remove=True force=True"
- assert expected_output in ret.stdout
- @slowTest
- def test_salt_documentation_too_many_arguments(salt_call_cli):
- """
- Test to see if passing additional arguments shows an error
- """
- ret = salt_call_cli.run("-d", "virtualenv.create", "/tmp/ve")
- assert ret.exitcode != 0
- assert "You can only get documentation for one method at one time" in ret.stderr
- @slowTest
- def test_issue_6973_state_highstate_exit_code(salt_call_cli):
- """
- If there is no tops/master_tops or state file matches
- for this minion, salt-call should exit non-zero if invoked with
- option --retcode-passthrough
- """
- src = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls")
- dst = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls.bak")
- shutil.move(src, dst)
- expected_comment = "No states found for this minion"
- try:
- ret = salt_call_cli.run("--retcode-passthrough", "state.highstate")
- finally:
- shutil.move(dst, src)
- assert ret.exitcode != 0
- assert expected_comment in ret.stdout
- @slowTest
- @PRE_PYTEST_SKIP
- def test_issue_15074_output_file_append(salt_call_cli):
- with pytest.helpers.temp_file(name="issue-15074") as output_file_append:
- ret = salt_call_cli.run("--output-file", output_file_append, "test.versions")
- assert ret.exitcode == 0
- with salt.utils.files.fopen(output_file_append) as ofa:
- first_run_output = ofa.read()
- assert first_run_output
- ret = salt_call_cli.run(
- "--output-file",
- output_file_append,
- "--output-file-append",
- "test.versions",
- )
- assert ret.exitcode == 0
- with salt.utils.files.fopen(output_file_append) as ofa:
- second_run_output = ofa.read()
- assert second_run_output
- assert second_run_output == first_run_output + first_run_output
- @slowTest
- @PRE_PYTEST_SKIP
- def test_issue_14979_output_file_permissions(salt_call_cli):
- with pytest.helpers.temp_file(name="issue-14979") as output_file:
- with salt.utils.files.set_umask(0o077):
- # Let's create an initial output file with some data
- ret = salt_call_cli.run("--output-file", output_file, "--grains")
- assert ret.exitcode == 0
- try:
- stat1 = os.stat(output_file)
- except OSError:
- pytest.fail("Failed to generate output file {}".format(output_file))
- # Let's change umask
- os.umask(0o777) # pylint: disable=blacklisted-function
- ret = salt_call_cli.run(
- "--output-file", output_file, "--output-file-append", "--grains"
- )
- assert ret.exitcode == 0
- stat2 = os.stat(output_file)
- assert stat1.st_mode == stat2.st_mode
- # Data was appeneded to file
- assert stat1.st_size < stat2.st_size
- # Let's remove the output file
- os.unlink(output_file)
- # Not appending data
- ret = salt_call_cli.run("--output-file", output_file, "--grains")
- assert ret.exitcode == 0
- try:
- stat3 = os.stat(output_file)
- except OSError:
- pytest.fail("Failed to generate output file {}".format(output_file))
- # Mode must have changed since we're creating a new log file
- assert stat1.st_mode != stat3.st_mode
- @slowTest
- @pytest.mark.skip_on_windows(reason="This test does not apply on Win")
- def test_42116_cli_pillar_override(salt_call_cli):
- ret = salt_call_cli.run(
- "state.apply",
- "issue-42116-cli-pillar-override",
- pillar={"myhost": "localhost"},
- )
- state_run_dict = next(iter(ret.json.values()))
- assert state_run_dict["changes"]
- assert (
- state_run_dict["comment"] == 'Command "ping -c 2 localhost" run'
- ), "CLI pillar override not found in pillar data. State Run Dictionary:\n{}".format(
- pprint.pformat(state_run_dict)
- )
- @slowTest
- def test_pillar_items_masterless(salt_minion, salt_call_cli):
- """
- Test to ensure we get expected output
- from pillar.items with salt-call
- """
- TOP = """
- base:
- '{}':
- - generic
- """.format(
- salt_minion.id
- )
- with pytest.helpers.temp_pillar_file("top.sls", TOP):
- ret = salt_call_cli.run("--local", "pillar.items")
- assert ret.exitcode == 0
- assert "knights" in ret.json
- assert sorted(ret.json["knights"]) == sorted(
- ["Lancelot", "Galahad", "Bedevere", "Robin"]
- )
- assert "monty" in ret.json
- assert ret.json["monty"] == "python"
- @slowTest
- def test_masterless_highstate(salt_call_cli):
- """
- test state.highstate in masterless mode
- """
- destpath = os.path.join(RUNTIME_VARS.TMP, "testfile")
- ret = salt_call_cli.run("--local", "state.highstate")
- assert ret.exitcode == 0
- state_run_dict = next(iter(ret.json.values()))
- assert state_run_dict["result"] is True
- assert state_run_dict["__id__"] == destpath
- @slowTest
- @pytest.mark.skip_on_windows
- def test_syslog_file_not_found(salt_minion, salt_call_cli):
- """
- test when log_file is set to a syslog file that does not exist
- """
- old_cwd = os.getcwd()
- with pytest.helpers.temp_directory("log_file_incorrect") as config_dir:
- try:
- os.chdir(config_dir)
- minion_config = copy.deepcopy(salt_minion.config)
- minion_config["log_file"] = "file:///dev/doesnotexist"
- with salt.utils.files.fopen(os.path.join(config_dir, "minion"), "w") as fh_:
- fh_.write(salt.utils.yaml.dump(minion_config, default_flow_style=False))
- ret = salt_call_cli.run(
- "--config-dir", config_dir, "--log-level=debug", "cmd.run", "echo foo",
- )
- if sys.version_info >= (3, 5, 4):
- assert ret.exitcode == 0
- assert (
- "[WARNING ] The log_file does not exist. Logging not setup correctly or syslog service not started."
- in ret.stderr
- )
- assert ret.json == "foo", ret
- else:
- assert ret.exitcode == 2
- assert "Failed to setup the Syslog logging handler" in ret.stderr
- finally:
- os.chdir(old_cwd)
- @slowTest
- @PRE_PYTEST_SKIP
- @pytest.mark.skip_on_windows
- def test_return(salt_call_cli, salt_run_cli):
- command = "echo returnTOmaster"
- ret = salt_call_cli.run("cmd.run", command)
- assert ret.exitcode == 0
- assert ret.json == "returnTOmaster"
- ret = salt_run_cli.run("jobs.list_jobs")
- assert ret.exitcode == 0
- jid = target = None
- for jid, details in ret.json.items():
- if command in details["Arguments"]:
- target = details["Target"]
- break
- ret = salt_run_cli.run("jobs.lookup_jid", jid, _timeout=60)
- assert ret.exitcode == 0
- assert target in ret.json
- assert ret.json[target] == "returnTOmaster"
- @slowTest
- def test_exit_status_unknown_argument(salt_call_cli):
- """
- Ensure correct exit status when an unknown argument is passed to salt CLI.
- """
- ret = salt_call_cli.run("--unknown-argument")
- assert ret.exitcode == salt.defaults.exitcodes.EX_USAGE, ret
- assert "Usage" in ret.stderr
- assert "no such option: --unknown-argument" in ret.stderr
- @slowTest
- def test_exit_status_correct_usage(salt_call_cli):
- """
- Ensure correct exit status when salt CLI starts correctly.
- """
- ret = salt_call_cli.run("test.true")
- assert ret.exitcode == salt.defaults.exitcodes.EX_OK, ret
- @slowTest
- def test_context_retcode_salt_call(salt_call_cli):
- """
- Test that a nonzero retcode set in the context dunder will cause the
- salt CLI to set a nonzero retcode.
- """
- # Test salt-call, making sure to also confirm the behavior of
- # retcode_passthrough.
- ret = salt_call_cli.run("test.retcode", "0")
- assert ret.exitcode == 0, ret
- ret = salt_call_cli.run("test.retcode", "42")
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run("--retcode-passthrough", "test.retcode", "42")
- assert ret.exitcode == 42, ret
- # Test a state run that exits with one or more failures
- ret = salt_call_cli.run("state.single", "test.fail_without_changes", "foo")
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "--retcode-passthrough", "state.single", "test.fail_without_changes", "foo"
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_FAILURE, ret
- # Test a state compiler error
- ret = salt_call_cli.run("state.apply", "thisslsfiledoesnotexist")
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "--retcode-passthrough", "state.apply", "thisslsfiledoesnotexist"
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_COMPILER_ERROR, ret
- @slowTest
- def test_salt_call_error(salt_call_cli):
- """
- Test that we return the expected retcode when a minion function raises
- an exception.
- """
- ret = salt_call_cli.run("test.raise_exception", "TypeError")
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "test.raise_exception", "salt.exceptions.CommandNotFoundError"
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "test.raise_exception", "salt.exceptions.CommandExecutionError"
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "test.raise_exception", "salt.exceptions.SaltInvocationError"
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run(
- "test.raise_exception",
- "OSError",
- "2",
- "No such file or directory",
- "/tmp/foo.txt",
- )
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
- ret = salt_call_cli.run("test.echo", "{foo: bar, result: False}")
- assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
|