diff --git a/src/soluble/conf.py b/src/soluble/conf.py index 807cd32..1f47c34 100644 --- a/src/soluble/conf.py +++ b/src/soluble/conf.py @@ -44,31 +44,39 @@ }, } +GROUP = "Soluble Options" + CLI_CONFIG = { "bootstrap": { "action": "store_true", "subcommands": ["_global_"], + "group": GROUP, }, "escalate": { "action": "store_true", "subcommands": ["_global_"], + "group": GROUP, }, - "roster_file": {"options": ["-R"]}, + "roster_file": {"options": ["-R"], "group": GROUP}, "ssh_target": { "positional": True, "display_priority": 0, "subcommands": [], "help": "Target for the salt-ssh command. This is typically a minion ID, wildcard, or grain.", + "group": GROUP, }, - "salt_config_dir": {}, + "salt_config_dir": {"group": GROUP}, "salt_bin": { "subcommands": ["_global_"], + "group": GROUP, }, "salt_key_bin": { "subcommands": ["_global_"], + "group": GROUP, }, } +SALT_SSH_GROUP = "Salt-SSH Options" SALT_SSH_OPTIONS = {} for name, opt in all_opts.items(): if name in CLI_CONFIG: @@ -86,7 +94,7 @@ action=opt.action if "store" in opt.action else None, nargs=opt.nargs, options=opt._long_opts + opt._short_opts, - group="Salt-SSH", + group=SALT_SSH_GROUP, ) CLI_CONFIG.update(SALT_SSH_OPTIONS) diff --git a/src/soluble/salt/key.py b/src/soluble/salt/key.py index d3c1da9..0371930 100644 --- a/src/soluble/salt/key.py +++ b/src/soluble/salt/key.py @@ -29,13 +29,9 @@ async def command(hub, name: str, action: Literal["-a", "-d"]): process = await hub.lib.asyncio.create_subprocess_shell( command, - stdout=hub.lib.asyncio.subprocess.PIPE, ) - stdout, _ = await process.communicate() retcode = await process.wait() if retcode != 0: raise ChildProcessError(f"Failed to accept minion keys") - for line in stdout.splitlines(): - hub.log.debug(line) return retcode diff --git a/src/soluble/salt/ssh.py b/src/soluble/salt/ssh.py index 431a2c2..9ca878d 100644 --- a/src/soluble/salt/ssh.py +++ b/src/soluble/salt/ssh.py @@ -1,5 +1,10 @@ async def run_command( - hub, name: str, command: str, *, capture_output: bool = True + hub, + name: str, + command: str, + *, + capture_output: bool = True, + hard_fail: bool = True, ) -> dict[str, object]: """Run a salt-ssh command asynchronously, handle all prompts, and return the output.""" target = hub.soluble.RUN[name].ssh_target @@ -31,7 +36,7 @@ async def run_command( process = await hub.lib.asyncio.create_subprocess_shell( full_command, stdout=stdout, - stderr=hub.lib.asyncio.subprocess.PIPE, + # stderr=hub.lib.asyncio.subprocess.PIPE, ) # Wait for the process to complete and capture stdout at the end @@ -39,7 +44,10 @@ async def run_command( returncode = await process.wait() if returncode != 0: - raise ChildProcessError(f"Command failed: {full_command}") + if hard_fail: + raise ChildProcessError(f"Command failed: {full_command}") + else: + return if not capture_output: return diff --git a/src/soluble/soluble/init.py b/src/soluble/soluble/init.py index 8396a55..60892c7 100644 --- a/src/soluble/soluble/init.py +++ b/src/soluble/soluble/init.py @@ -1,4 +1,5 @@ from soluble.conf import CLI_CONFIG +from soluble.conf import SALT_SSH_GROUP from soluble.conf import SUBCOMMANDS @@ -48,7 +49,7 @@ def cli(hub): # Turn salt-ssh opts into a string for name, opts in CLI_CONFIG.items(): - if opts.get("group", "").lower() != "salt-ssh": + if opts.get("group") != SALT_SSH_GROUP: continue value = kwargs.pop(name, None) @@ -121,7 +122,7 @@ async def setup(hub, name: str): async def run(hub, name: str) -> int: """This is where a soluble plugin runs its primary function""" hub.log.info("Soluble run") - await hub.salt.ssh.run_command(name, f"grains.items", capture_output=False) + await hub.salt.ssh.run_command(name, f"test.ping", capture_output=False) return 0 diff --git a/src/soluble_master/conf.py b/src/soluble_master/conf.py index c68d8bd..9a0a7e0 100644 --- a/src/soluble_master/conf.py +++ b/src/soluble_master/conf.py @@ -5,9 +5,10 @@ "dyne": "soluble", }, } +GROUP = "Soluble Master" CLI_CONFIG = { - "master_config": {"subcommands": ["master"]}, + "master_config": {"subcommands": ["master"], "group": GROUP}, } DYNE = {"soluble": ["soluble"]} diff --git a/src/soluble_minion/conf.py b/src/soluble_minion/conf.py index e1e2fcf..a6fd90c 100644 --- a/src/soluble_minion/conf.py +++ b/src/soluble_minion/conf.py @@ -6,6 +6,8 @@ }, } +GROUP = "Soluble Minion" + CLI_CONFIG = { "minion_config": {"subcommands": ["minion"]}, "salt_command": { @@ -14,6 +16,7 @@ "subcommands": ["minion"], "help": "The salt command to run on the ephemeral nodes", "dyne": "soluble", + "group": GROUP, }, "salt_options": { "positional": True, @@ -22,6 +25,7 @@ "subcommands": ["minion"], "help": "Additional options to be passed to the salt command", "dyne": "soluble", + "group": GROUP, }, } diff --git a/src/soluble_minion/soluble/minion.py b/src/soluble_minion/soluble/minion.py index a509f7e..372dfde 100644 --- a/src/soluble_minion/soluble/minion.py +++ b/src/soluble_minion/soluble/minion.py @@ -64,21 +64,25 @@ async def teardown(hub, name: str): # Stop the Salt minion service hub.log.info("Stopping salt-minion service on target(s)...") await hub.salt.ssh.run_command( - name, "state.single service.disabled name=salt-minion" + name, "state.single service.disabled name=salt-minion", hard_fail=False + ) + await hub.salt.ssh.run_command( + name, "state.single service.dead name=salt-minion", hard_fail=False ) - await hub.salt.ssh.run_command(name, "state.single service.dead name=salt-minion") # Uninstall Salt from the target hub.log.info("Uninstalling Salt from target(s)...") - await hub.salt.ssh.run_command(name, "state.single pkg.removed name=salt-minion") + await hub.salt.ssh.run_command( + name, "state.single pkg.removed name=salt-minion", hard_fail=False + ) # Remove the minion configuration file hub.log.info("Removing minion configuration from target(s)...") await hub.salt.ssh.run_command( - name, "state.single file.absent name=/etc/salt/minion" + name, "state.single file.absent name=/etc/salt/minion", hard_fail=False ) await hub.salt.ssh.run_command( - name, "state.single file.absent name=/etc/salt/minion_id" + name, "state.single file.absent name=/etc/salt/minion_id", hard_fail=False ) hub.log.info("Destroy ephemeral minion key(s) on Salt master...") diff --git a/tests/conftest.py b/tests/conftest.py index 43faad3..45b529e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,11 +9,11 @@ def integration_hub(): hub = pop.hub.Hub() hub.pop.sub.add("tests.helpers", subname="test") hub.pop.sub.add(dyne_name="soluble") - hub.pop.sub.add(python_import="asyncio", sub=hub.lib) hub.pop.sub.add(python_import="asyncssh", sub=hub.lib) hub.pop.sub.add(python_import="docker", sub=hub.lib) hub.pop.sub.add(python_import="pathlib", sub=hub.lib) + hub.pop.sub.add(python_import="pop", sub=hub.lib) hub.pop.sub.add(python_import="pytest", sub=hub.lib) hub.pop.sub.add(python_import="pwd", sub=hub.lib) hub.pop.sub.add(python_import="uuid", sub=hub.lib) @@ -21,10 +21,10 @@ def integration_hub(): hub.pop.sub.add(python_import="socket", sub=hub.lib) hub.pop.sub.add(python_import="tempfile", sub=hub.lib) - with mock.patch("sys.argv", ["soluble"]): - hub.pop.config.load(["soluble"], cli="soluble", parse_cli=False) + hub.pop.config.load(["soluble"], cli="soluble", parse_cli=False) - yield hub + with mock.patch("sys.exit"): + yield hub @pytest.fixture(scope="function", autouse=True) diff --git a/tests/soluble/test_init.py b/tests/soluble/test_init.py index 6129673..92731b4 100644 --- a/tests/soluble/test_init.py +++ b/tests/soluble/test_init.py @@ -2,8 +2,13 @@ def test_help(hub): - with mock.patch("sys.argv", ["soluble"]): - hub.soluble.init.cli() + with mock.patch("sys.argv", ["soluble", "--help"]): + hub.pop.config.load(["soluble"], cli="soluble") + + +def test_sub_help(hub): + with mock.patch("sys.argv", ["soluble", "init", "--help"]): + hub.pop.config.load(["soluble"], cli="soluble") async def test_cli(hub, salt_master): diff --git a/tests/soluble/test_master.py b/tests/soluble/test_master.py index e69de29..20da494 100644 --- a/tests/soluble/test_master.py +++ b/tests/soluble/test_master.py @@ -0,0 +1,10 @@ +from unittest import mock + + +def test_help(hub): + with mock.patch("sys.argv", ["soluble", "master", "--help"]): + hub.pop.config.load(["soluble"], cli="soluble") + + +async def test_cli(hub): + ... diff --git a/tests/soluble/test_minion.py b/tests/soluble/test_minion.py index e69de29..078aeae 100644 --- a/tests/soluble/test_minion.py +++ b/tests/soluble/test_minion.py @@ -0,0 +1,11 @@ +from unittest import mock + + +def test_help(hub): + with mock.patch("sys.argv", ["soluble", "minion", "--help"]): + hub.pop.config.load(["soluble"], cli="soluble") + + +async def test_cli(hub, salt_master): + await hub.test.container.create() + await hub.test.cmd.run("minion", "test.ping")