Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions sceptre/hooks/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,24 @@ def run(self):
"""
envs = self.stack.connection_manager.create_session_environment_variables()

if isinstance(self.argument, str) and self.argument != "":
command_to_run = self.argument
shell = None

elif (
isinstance(self.argument, dict)
and set(self.argument) == {"run", "shell"}
and isinstance(self.argument["run"], str)
and isinstance(self.argument["shell"], str)
and self.argument["run"] != ""
and self.argument["shell"] != ""
):
command_to_run = self.argument["run"]
shell = self.argument["shell"]
command_to_run = None
shell = None

if isinstance(self.argument, dict):
command_to_run = self.argument.get("run")
shell = self.argument.get("shell")
else:
command_to_run = self.argument

if not isinstance(command_to_run, str) or command_to_run == "":
raise InvalidHookArgumentTypeError(
"A cmd hook requires either a string argument or an object with "
"`run` and `shell` keys with string values. "
f"a `run` key and a string value. You gave `{self.argument!r}`."
)

if shell is not None and not isinstance(shell, str) or shell == "":
raise InvalidHookArgumentTypeError(
"A cmd hook requires a `shell` key with a non-empty string. "
f"You gave `{self.argument!r}`."
)

Expand Down
60 changes: 35 additions & 25 deletions tests/test_hooks/test_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
from sceptre.hooks.cmd import Cmd
from sceptre.stack import Stack

ERROR_MESSAGE = (
r"^A cmd hook requires either a string argument or an object with `run` and "
r"`shell` keys with string values\. You gave `{0}`\.$"
RUN_ERROR_MESSAGE = (
r"^A cmd hook requires either a string argument or an object with a `run` "
r"key and a string value\. You gave `{0}`\.$"
)

SHELL_ERROR_MESSAGE = (
r"^A cmd hook requires a `shell` key with a non-empty string\. You gave `{0}`\.$"
)


Expand All @@ -32,57 +36,53 @@ def stack():


def test_null_input_raises_exception(stack):
message = ERROR_MESSAGE.format(r"None")
message = RUN_ERROR_MESSAGE.format(r"None")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd(None, stack).run()


def test_empty_string_raises_exception(stack):
message = ERROR_MESSAGE.format(r"''")
message = RUN_ERROR_MESSAGE.format(r"''")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd("", stack).run()


def test_list_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\['echo', 'hello'\]")
message = RUN_ERROR_MESSAGE.format(r"\['echo', 'hello'\]")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd(["echo", "hello"], stack).run()


def test_dict_without_shell_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': 'echo hello'\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": "echo hello"}, stack).run()


def test_dict_without_run_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'shell': '/bin/bash'\}")
message = RUN_ERROR_MESSAGE.format(r"\{'shell': '/bin/bash'\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"shell": "/bin/bash"}, stack).run()


def test_dict_with_list_run_raises_exception(stack):
message = ERROR_MESSAGE.format(
message = RUN_ERROR_MESSAGE.format(
r"\{'run': \['echo', 'hello'\], 'shell': '/bin/bash'\}"
)
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": ["echo", "hello"], "shell": "/bin/bash"}, stack).run()


def test_dict_with_empty_run_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': '', 'shell': '/bin/bash'\}")
message = RUN_ERROR_MESSAGE.format(r"\{'run': '', 'shell': '/bin/bash'\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": "", "shell": "/bin/bash"}, stack).run()


def test_dict_with_null_run_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': None, 'shell': '/bin/bash'\}")
message = RUN_ERROR_MESSAGE.format(r"\{'run': None, 'shell': '/bin/bash'\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": None, "shell": "/bin/bash"}, stack).run()


def test_dict_with_list_shell_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': 'echo hello', 'shell': \['/bin/bash'\]\}")
message = SHELL_ERROR_MESSAGE.format(
r"\{'run': 'echo hello', 'shell': \['/bin/bash'\]\}"
)
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": "echo hello", "shell": ["/bin/bash"]}, stack).run()

Expand All @@ -101,21 +101,15 @@ def test_dict_with_non_executable_shell_raises_exception(stack):


def test_dict_with_empty_shell_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': 'echo hello', 'shell': ''\}")
message = SHELL_ERROR_MESSAGE.format(r"\{'run': 'echo hello', 'shell': ''\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": "echo hello", "shell": ""}, stack).run()


def test_dict_with_null_shell_raises_exception(stack):
message = ERROR_MESSAGE.format(r"\{'run': 'echo hello', 'shell': None\}")
with pytest.raises(InvalidHookArgumentTypeError, match=message):
Cmd({"run": "echo hello", "shell": None}, stack).run()


def test_input_exception_reprs_input(stack):
import datetime

exception_message = ERROR_MESSAGE.format(r"datetime.date\(2023, 8, 31\)")
exception_message = RUN_ERROR_MESSAGE.format(r"datetime.date\(2023, 8, 31\)")
with pytest.raises(InvalidHookArgumentTypeError, match=exception_message):
Cmd(datetime.date(2023, 8, 31), stack).run()

Expand Down Expand Up @@ -151,6 +145,22 @@ def test_default_shell_is_sh(stack, capfd):
assert cap.err.strip() == ""


def test_dict_without_shell_uses_default_shell(stack, capfd):
Cmd("echo $0", stack).run()
cap1 = capfd.readouterr()
Cmd({"run": "echo $0"}, stack).run()
cap2 = capfd.readouterr()
assert cap1 == cap2


def test_dict_with_null_shell_uses_default_shell(stack, capfd):
Cmd("echo $0", stack).run()
cap1 = capfd.readouterr()
Cmd({"run": "echo $0", "shell": None}, stack).run()
cap2 = capfd.readouterr()
assert cap1 == cap2


def test_shell_parameter_sets_the_shell(stack, capfd):
# Determine the local path to bash (it's not always /bin/bash)
Cmd("echo $0", stack).run()
Expand Down