Skip to content

Commit

Permalink
Add option to persist runtime drives
Browse files Browse the repository at this point in the history
  • Loading branch information
septatrix committed Mar 8, 2025
1 parent c8db79c commit b8b95a3
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 24 deletions.
28 changes: 16 additions & 12 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -5288,6 +5291,7 @@ def config_drive_transformer(drives: list[dict[str, Any]], fieldtype: type[Drive
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),
)
)

Expand Down
18 changes: 13 additions & 5 deletions mkosi/qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
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,
Expand Down Expand Up @@ -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)
Expand Down
17 changes: 11 additions & 6 deletions mkosi/resources/man/mkosi.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>:<size>[:<directory>[:<options>[:<file-id>]]]`. `id` specifies
`<id>:<size>[:<directory>[:<options>[:<file-id>[:<persist>]]]]`. `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 `/<directory>/mkosi-drive-<file-id>`
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:**

Expand Down
6 changes: 5 additions & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,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,
Expand Down

0 comments on commit b8b95a3

Please sign in to comment.