]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Adding "shell" verb to Machine class.
authorgsegatti <gabrielsegatti2@gmail.com>
Mon, 7 Mar 2022 15:29:06 +0000 (07:29 -0800)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 9 Mar 2022 16:09:11 +0000 (16:09 +0000)
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.

.github/workflows/ci.yml
mkosi/__init__.py
mkosi/machine.py
tests/test_machine.py

index 68afa24312d776b3b013c0ec28e6dfd82e5e33c2..0b2b88f093e1b0d8c4d5fd9f034b0e7f180e0df7 100644 (file)
@@ -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
index 5213351055c32373e6b1aab58c2a5120485ff118..322580a1a5db10db8859a6057c0e708aea8adf51 100644 (file)
@@ -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]:
index b6f96a99afa064dcfd142e38d581411b3d20aac5..a5128a92ad26c74df9b32aec02fa789f6e8037e5 100644 (file)
@@ -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:
index 531b494cd3eed524405f57f32dd3662b3d636c1e..9c2c5f140fe1761f8b9d0783a10ae82502022923 100644 (file)
@@ -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"])