From: Michael Ferrari Date: Wed, 7 Aug 2024 09:37:48 +0000 (+0200) Subject: Add executable `mkosi.version` support X-Git-Tag: v25~354^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=04683fb3033450c1f69228ff0b9c00d7d4dc326e;p=thirdparty%2Fmkosi.git Add executable `mkosi.version` support `mkosi.version` is executed during configuration parsing, as opposed to reading the contents of `mkosi.version`. This allows querying the version before the build without needing to manually adjust the version beforehand. This allows using date based versioning by writing a script outputting `date '+%Y-%m-%d'` or using git tag based versioning by outputting `git describe --tags`. --- diff --git a/mkosi/config.py b/mkosi/config.py index 19f0fcf53..2c6a4c92e 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1142,6 +1142,21 @@ def config_parse_minimum_version(value: Optional[str], old: Optional[GenericVers return max(old, new) +def file_run_or_read(file: Path) -> str: + "Run the specified file and capture its output if it's executable, else read file contents" + + if os.access(file, os.X_OK): + return run([file.absolute()], stdout=subprocess.PIPE, env=os.environ).stdout + + content = file.read_text() + + if content.startswith("#!/"): + die(f"{file} starts with a shebang ({content.splitlines()[0]})", + hint="This file should be executable") + + return content + + @dataclasses.dataclass(frozen=True) class KeySource: class Type(StrEnum): @@ -3677,7 +3692,7 @@ class ParseContext: self.config, s.dest, s.parse( - extra.read_text().rstrip("\n") if s.path_read_text else f, + file_run_or_read(extra).rstrip("\n") if s.path_read_text else f, getattr(self.config, s.dest, None) ), ) diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index e67d9951a..500964010 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -620,13 +620,14 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, `ImageVersion=`, `--image-version=` : Configure the image version. This accepts any string, but it is recommended to specify a series of dot separated components. The - version may also be configured in a file `mkosi.version` in which - case it may be conveniently managed via the `bump` verb or the - `--auto-bump` option. When specified the image version is included - in the default output file name, i.e. instead of `image.raw` the - default will be `image_0.1.raw` for version `0.1` of the image, and - similar. The version is also passed via the `$IMAGE_VERSION` to any - build scripts invoked (which may be useful to patch it into + version may also be configured by reading a `mkosi.version` file (in + which case it may be conveniently managed via the `bump` verb or the + `--auto-bump` option) or by reading stdout if it is executable (see + the **Scripts** section below). When specified the image version is + included in the default output file name, i.e. instead of `image.raw` + the default will be `image_0.1.raw` for version `0.1` of the image, + and similar. The version is also passed via the `$IMAGE_VERSION` to + any build scripts invoked (which may be useful to patch it into `/usr/lib/os-release` or similar, in particular the `IMAGE_VERSION=` field of it). @@ -1137,9 +1138,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, `RootPassword=`, `--root-password=`, : Set the system root password. If this option is not used, but a `mkosi.rootpw` file is found in the local - directory, the password is automatically read from it. If the password starts with `hashed:`, it is treated - as an already hashed root password. The root password is also stored in `/usr/lib/credstore` under the - appropriate systemd credential so that it applies even if only `/usr` is shipped in the image. To create + directory, the password is automatically read from it or if the file is executable it is run as a script + and stdout is read instead (see the **Scripts** section below). If the password starts with `hashed:`, it is + treated as an already hashed root password. The root password is also stored in `/usr/lib/credstore` under + the appropriate systemd credential so that it applies even if only `/usr` is shipped in the image. To create an unlocked account without any password use `hashed:` without a hash. `Autologin=`, `--autologin` @@ -2076,6 +2078,19 @@ current working directory. The following scripts are supported: artifacts from `SplitArtifacts=yes` or RPMs built in a build script). Note that this script does not use the tools tree even if one is configured. +* If **`mkosi.version`** exists and is executable, it is run during + configuration parsing and populates `ImageVersion=` with the output on stdout. + This can be used for external version tracking, e.g. with `git describe` or + `date '+%Y-%m-%d'`. Note that this script is executed on the host system + without any sandboxing. + +* If **`mkosi.rootpw`** exists and is executable, it is run during + configuration parsing and populates `RootPassword=` with the output + on stdout. This can be used to randomly generate a password and can + be remembered by outputting it to stderr or by reading `$MKOSI_CONFIG` + in another script (e.g. `mkosi.postoutput`). Note that this script is + executed on the host system without any sandboxing. + If a script uses the `.chroot` extension, mkosi will chroot into the image using `mkosi-chroot` (see below) before executing the script. For example, if `mkosi.postinst.chroot` exists, mkosi will chroot into the diff --git a/tests/test_config.py b/tests/test_config.py index 20db168af..0bdfbe5c3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1275,3 +1275,23 @@ def test_environment(tmp_path: Path) -> None: assert sub.environment["PassThisEnv"] == "abc" assert "TestValue2" not in sub.environment + + +def test_mkosi_version_executable(tmp_path: Path) -> None: + d = tmp_path + + version = d / "mkosi.version" + version.write_text("#!/bin/sh\necho '1.2.3'\n") + + with chdir(d): + with pytest.raises(SystemExit) as error: + _, [config] = parse_config() + + assert error.type is SystemExit + assert error.value.code != 0 + + version.chmod(0o755) + + with chdir(d): + _, [config] = parse_config() + assert config.image_version == "1.2.3"