From: gsegatti Date: Mon, 7 Mar 2022 15:29:06 +0000 (-0800) Subject: Adding "shell" verb to Machine class. X-Git-Tag: v13~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e1b66aec2d46e5b1fa23f482857e463da8bd4c89;p=thirdparty%2Fmkosi.git Adding "shell" verb to Machine class. Summary: Adding support for running Mkosi images from within the Machine class using "shell". - What we do here is skip booting an image whenever using such verb. Then, for running commands, we utilise run_shell_cmdline()'s to leverage systemd-nspawn. - We also add the shell run of the current set of tests to the CI. - We add the "--pipe" option to the cmdline utilised by run_shell_cmdline() in case future scripts use Mkosi from outside a terminal. --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68afa2431..0b2b88f09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,7 @@ jobs: run: | MKOSI_TEST_DEFAULT_VERB=qemu sudo python3 -m pytest -m integration tests MKOSI_TEST_DEFAULT_VERB=boot sudo python3 -m pytest -m integration tests + MKOSI_TEST_DEFAULT_VERB=shell sudo python3 -m pytest -m integration tests integration-test: runs-on: ubuntu-20.04 diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 521335105..322580a1a 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -7426,13 +7426,18 @@ def ensure_networkd(args: MkosiArgs) -> bool: return True -def run_shell_cmdline(args: MkosiArgs) -> List[str]: +def run_shell_cmdline(args: MkosiArgs, pipe: bool = False, commands: Optional[Sequence[str]] = None) -> List[str]: if args.output_format in (OutputFormat.directory, OutputFormat.subvolume): target = f"--directory={args.output}" else: target = f"--image={args.output}" - cmdline = ["systemd-nspawn", target] + cmdline = ["systemd-nspawn", "--quiet", target] + + # Redirecting output correctly when not running directly from the terminal. + console_arg = f"--console={'interactive' if not pipe else 'pipe'}" + if nspawn_knows_arg(console_arg): + cmdline += [console_arg] if args.read_only: cmdline += ["--read-only"] @@ -7458,18 +7463,18 @@ def run_shell_cmdline(args: MkosiArgs) -> List[str]: cmdline += ["--machine", virt_name(args)] - if args.cmdline: + if commands or args.cmdline: # If the verb is 'shell', args.cmdline contains the command to run. # Otherwise, the verb is 'boot', and we assume args.cmdline contains nspawn arguments. if args.verb == Verb.shell: cmdline += ["--"] - cmdline += args.cmdline + cmdline += commands or args.cmdline return cmdline def run_shell(args: MkosiArgs) -> None: - run(run_shell_cmdline(args), stdout=sys.stdout, stderr=sys.stderr) + run(run_shell_cmdline(args, pipe=not sys.stdout.isatty()), stdout=sys.stdout, stderr=sys.stderr) def find_qemu_binary() -> str: @@ -7718,9 +7723,11 @@ def find_address(args: MkosiArgs) -> Tuple[str, str]: def run_command_image(args: MkosiArgs, commands: Sequence[str], timeout: int, check: bool, stdout: _FILE = sys.stdout, stderr: _FILE = sys.stderr) -> CompletedProcess: if args.verb == Verb.qemu: return run_ssh(args, commands, check, stdout, stderr, timeout) - else: + elif args.verb == Verb.boot: cmdline = ["systemd-run", "--quiet", "--wait", "--pipe", "-M", virt_name(args), "/usr/bin/env", *commands] return run(cmdline, check=check, stdout=stdout, stderr=stderr, text=True, timeout=timeout) + else: + return run(run_shell_cmdline(args, pipe=True, commands=commands), check=check, stdout=stdout, stderr=stderr, text=True, timeout=timeout) def run_ssh_cmdline(args: MkosiArgs, commands: Optional[Sequence[str]] = None) -> Sequence[str]: diff --git a/mkosi/machine.py b/mkosi/machine.py index b6f96a99a..a5128a92a 100644 --- a/mkosi/machine.py +++ b/mkosi/machine.py @@ -54,21 +54,22 @@ class Machine: tmp.verb = Verb.boot elif verb == Verb.qemu.name: tmp.verb = Verb.qemu + elif verb == Verb.shell.name: + tmp.verb = Verb.shell else: die("No valid verb was entered.") # Add the arguments in the machine class itself, rather than typing this for every testing function. tmp.force = 1 tmp.autologin = True + tmp.ephemeral = True if tmp.verb == Verb.qemu: tmp.bootable = True tmp.qemu_headless = True tmp.hostonly_initrd = True tmp.netdev = True tmp.ssh = True - elif tmp.verb == Verb.boot: - pass - else: + elif tmp.verb not in (Verb.shell, Verb.boot): die("No valid verb was entered.") self.args = load_args(tmp) @@ -89,7 +90,7 @@ class Machine: def _ensure_booted(self) -> None: # Try to access the serial console which will raise an exception if the machine is not currently booted. - assert self._serial is not None + assert self._serial is not None or self.args.verb == Verb.shell def build(self) -> None: if self.args.verb in MKOSI_COMMANDS_SUDO: @@ -112,6 +113,9 @@ class Machine: return self def boot(self) -> None: + if self.args.verb == Verb.shell: + return + with contextlib.ExitStack() as stack: prepend_to_environ_path(self.args.extra_search_paths) @@ -164,6 +168,9 @@ class MkosiMachineTest(unittest.TestCase): @classmethod def setUpClass(cls) -> None: + if os.getuid() != 0: + raise unittest.SkipTest("Must be invoked as root.") + cls.machine = Machine(cls.args) verb = cls.machine.args.verb @@ -178,6 +185,10 @@ class MkosiMachineTest(unittest.TestCase): cls.machine.build() def setUp(self) -> None: + # Replacing underscores which makes name invalid. + # Necessary for shell otherwise racing conditions to the disk image will happen. + test_name = self.id().split(".")[3] + self.machine.args.hostname = test_name.replace("_", "-") self.machine.boot() def tearDown(self) -> None: diff --git a/tests/test_machine.py b/tests/test_machine.py index 531b494cd..9c2c5f140 100644 --- a/tests/test_machine.py +++ b/tests/test_machine.py @@ -5,6 +5,7 @@ from subprocess import CalledProcessError, TimeoutExpired import pytest +from mkosi.backend import Verb from mkosi.machine import Machine, MkosiMachineTest pytestmark = [ @@ -26,7 +27,7 @@ class MkosiMachineTestCase(MkosiMachineTest): # Check = False to see if stderr and returncode have the expected values result = self.machine.run(["NonExisting", "Command"], check=False) - assert result.returncode in (203, 127) + assert result.returncode in (1, 127, 203) result = self.machine.run(["ls", "-"], check=False) assert result.returncode == 2 @@ -39,6 +40,8 @@ class MkosiMachineTestCase(MkosiMachineTest): def test_before_boot() -> None: m = Machine() + if m.args.verb == Verb.shell: + pytest.skip("Shell never boots the machine.") with pytest.raises(AssertionError): m.run(["ls"]) @@ -46,7 +49,8 @@ def test_before_boot() -> None: def test_after_shutdown() -> None: with Machine() as m: pass - + if m.args.verb == Verb.shell: + pytest.skip("Shell never boots the machine.") with pytest.raises(AssertionError): m.run(["ls"])