From: Septatrix <24257556+Septatrix@users.noreply.github.com> Date: Sat, 8 Mar 2025 13:21:34 +0000 (+0100) Subject: Add option to persist runtime drives X-Git-Tag: v26~324 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=64495fdaaef1b72b503627d9fd58ab720da6ed21;p=thirdparty%2Fmkosi.git Add option to persist runtime drives --- diff --git a/mkosi/config.py b/mkosi/config.py index 002b3e41b..7ebb3daa0 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -156,6 +156,7 @@ class Drive: directory: Optional[Path] options: Optional[str] file_id: str + persist: bool # We use negative numbers for specifying special constants @@ -1366,27 +1367,29 @@ def parse_profile(value: str) -> str: def parse_drive(value: str) -> Drive: - parts = value.split(":", maxsplit=4) - if not parts or not parts[0]: + parts = value.split(":") + + if len(parts) > 6: + die(f"Too many components in drive '{value}") + + if len(parts) < 1: die(f"No ID specified for drive '{value}'") if len(parts) < 2: die(f"Missing size in drive '{value}") - if len(parts) > 5: - die(f"Too many components in drive '{value}") - id = parts[0] if not is_valid_filename(id): die(f"Unsupported path character in drive id '{id}'") - size = parse_bytes(parts[1]) - - directory = parse_path(parts[2]) if len(parts) > 2 and parts[2] else None - options = parts[3] if len(parts) > 3 and parts[3] else None - file_id = parts[4] if len(parts) > 4 and parts[4] else id - - return Drive(id=id, size=size, directory=directory, options=options, file_id=file_id) + return Drive( + id=id, + size=parse_bytes(parts[1]), + directory=parse_path(p) if len(parts) > 2 and (p := parts[2]) else None, + options=p if len(parts) > 3 and (p := parts[3]) else None, + file_id=p if len(parts) > 4 and (p := parts[4]) else id, + persist=parse_boolean(p) if len(parts) > 5 and (p := parts[5]) else False, + ) def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Optional[int]: @@ -5288,6 +5291,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ directory=Path(d["Directory"]) if d.get("Directory") else None, options=d.get("Options"), file_id=d.get("FileId", d["Id"]), + persist=d.get("Persist", False), ) ) diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 1c2c7857a..ab2144178 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -24,7 +24,7 @@ import textwrap import uuid from collections.abc import Iterator, Sequence from pathlib import Path -from typing import Optional +from typing import IO, Optional from mkosi.config import ( Args, @@ -795,10 +795,18 @@ def apply_runtime_size(config: Config, image: Path) -> None: @contextlib.contextmanager def finalize_drive(config: Config, drive: Drive) -> Iterator[Path]: - with tempfile.NamedTemporaryFile( - dir=drive.directory or "/var/tmp", - prefix=f"mkosi-drive-{drive.id}", - ) as file: + with contextlib.ExitStack() as stack: + file: IO[bytes] + if drive.persist: + path = Path(drive.directory or "/var/tmp") / f"mkosi-drive-{drive.id}" + file = path.open("a+b") + else: + file = stack.enter_context( + tempfile.NamedTemporaryFile( + dir=drive.directory or "/var/tmp", + prefix=f"mkosi-drive-{drive.id}", + ) + ) maybe_make_nocow(Path(file.name)) file.truncate(round_up(drive.size, resource.getpagesize())) yield Path(file.name) diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 17230a24c..d46d3a0ae 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1791,18 +1791,23 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, `Drives=`, `--drive=` : Add a drive. Takes a colon-delimited string of format - `:[:[:[:]]]`. `id` specifies + `:[:[:[:[:]]]]`. `id` specifies the ID assigned to the drive. This can be used as the `drive=` property in various **qemu** devices. `size` specifies the size of the drive. This takes a size in bytes. Additionally, the suffixes `K`, `M` and `G` can be used to specify a size in kilobytes, megabytes and gigabytes respectively. `directory` optionally specifies the directory - in which to create the file backing the drive. `options` optionally - specifies extra comma-delimited properties which are passed verbatim + in which to create the file backing the drive. If unset, the file will be created under `/var/tmp`. + `options` optionally specifies extra comma-delimited properties which are passed verbatim to **qemu**'s `-blockdev` option. `file-id` specifies the ID of the file - backing the drive. Drives with the same file ID will share the - backing file. The directory and size of the file will be determined - from the first drive with a given file ID. + backing the drive. If unset, this defaults to the drive ID. + Drives with the same file ID will share the backing file. + The directory and size of the file will be determined from the first drive with a given file ID. + `persist` takes a boolean value and determines whether the drive will be persisted across **qemu** invocations. + Enabling persistence also prevents suffixing the filename with a random string. + The file backing the drive will always be available under `//mkosi-drive-` + You can skip values by setting them to the empty string, specifying e.g. `myfs:1G::::yes` + will create a persistent drive under `/var/tmp/mkosi-drive-myfs`. **Example usage:** diff --git a/tests/test_json.py b/tests/test_json.py index 1207459a3..ff5d4bf4d 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -156,6 +156,7 @@ def test_config() -> None: "FileId": "red", "Id": "abc", "Options": "abc,qed", + "Persist": false, "Size": 200 }, { @@ -163,6 +164,15 @@ def test_config() -> None: "FileId": "wcd", "Id": "abc", "Options": "", + "Persist": false, + "Size": 200 + }, + { + "Directory": null, + "FileId": "bla", + "Id": "abc", + "Options": "", + "Persist": true, "Size": 200 } ], @@ -470,7 +480,11 @@ def test_config() -> None: credentials={"credkey": "credval"}, dependencies=["dep1"], distribution=Distribution.fedora, - drives=[Drive("abc", 200, Path("/foo/bar"), "abc,qed", "red"), Drive("abc", 200, None, "", "wcd")], + drives=[ + Drive("abc", 200, Path("/foo/bar"), "abc,qed", "red", False), + Drive("abc", 200, None, "", "wcd", False), + Drive("abc", 200, None, "", "bla", True), + ], environment_files=[], environment={"foo": "foo", "BAR": "BAR", "Qux": "Qux"}, ephemeral=True,