diff --git a/.github/scripts/check-release-version.py b/.github/scripts/check-release-version.py index dd9ed25..04101bb 100755 --- a/.github/scripts/check-release-version.py +++ b/.github/scripts/check-release-version.py @@ -1,29 +1,30 @@ #!/usr/bin/env python3 """Enforce the release-version invariant for Bluefin Server. -project.conf declares: +The two release version axes are each declared in exactly one place: - variables: - release-version: "X.Y.Z" # must match the FSDK point release + project.conf installer-version: "X.Y.Z" # FSDK point release + include/flatcar.yml flatcar-version: "X.Y.Z" # Flatcar LTS release -That value names every published *OS* release asset -(`bluefin-server-ddi-.raw.zst`, `bluefin-server-.efi`, -`bluefin-server-installer-.raw.zst`) and is the version -systemd-sysupdate extracts from those filenames via `@v`. +The installer-version axis names the offline installer disk image and PXE boot +inputs (bluefin-server-installer-.raw.zst, bluefin-server-pxe-*), which +compose their userspace from freedesktop-sdk. -The k0s sysext is deliberately *not* on this axis: it is an -independently-pinned third-party payload versioned from `include/k0s.yml` -and enforced separately by `.github/scripts/check-k0s-version.py`. +The flatcar-version axis names the OS payload release assets +(bluefin-server-ddi-.raw.zst, bluefin-server-.efi) and is the version +systemd-sysupdate extracts from those filenames via `@v`. project.conf pulls +include/flatcar.yml in via its `(@)` list, so %{flatcar-version} resolves +project-wide without being restated. -The release *tag* is derived independently by the Justfile -(`fsdk_version`), which greps the point release out of the pinned -`elements/freedesktop-sdk.bst` junction ref. Renovate bumps that ref -automatically; nothing bumps `release-version`. When the two drift, CI -publishes a new tag containing assets that still carry the old version -string, so `systemd-sysupdate` sees no version change and the fleet -silently stops updating. +The k0s sysext is on its own axis: an independently-pinned third-party +payload versioned from `include/k0s.yml` and enforced separately by +`.github/scripts/check-k0s-version.py`. -This script fails closed on that drift. +This script validates both axes against their pins: + * project.conf installer-version against elements/freedesktop-sdk.bst + * include/flatcar.yml flatcar-version is well-formed + +This script fails closed on any drift. """ import re @@ -33,11 +34,15 @@ ROOT = Path(__file__).resolve().parents[2] PROJECT_CONF = ROOT / "project.conf" FSDK_JUNCTION = ROOT / "elements" / "freedesktop-sdk.bst" +FLATCAR_PIN = ROOT / "include" / "flatcar.yml" -RELEASE_VERSION_RE = re.compile( - r"^\s*release-version:\s*[\"']?([0-9]+\.[0-9]+\.[0-9]+)[\"']?\s*$", re.MULTILINE +INSTALLER_VERSION_RE = re.compile( + r"^\s*installer-version:\s*[\"']?([0-9]+\.[0-9]+\.[0-9]+)[\"']?\s*$", re.MULTILINE ) FSDK_REF_RE = re.compile(r"freedesktop-sdk-([0-9]+\.[0-9]+\.[0-9]+)") +FLATCAR_PIN_RE = re.compile( + r"^\s*flatcar-version:\s*[\"']?([0-9]+\.[0-9]+\.[0-9]+)[\"']?\s*$", re.MULTILINE +) def read(path): @@ -49,39 +54,48 @@ def read(path): def main(): conf = read(PROJECT_CONF) junction = read(FSDK_JUNCTION) + flatcar = read(FLATCAR_PIN) - conf_match = RELEASE_VERSION_RE.search(conf) - if not conf_match: + installer_match = INSTALLER_VERSION_RE.search(conf) + if not installer_match: sys.exit( - "ERROR: project.conf does not declare a " - "'release-version: X.Y.Z' variable." + "ERROR: project.conf does not declare an " + "'installer-version: X.Y.Z' variable." ) - declared = conf_match.group(1) + installer_declared = installer_match.group(1) - ref_match = FSDK_REF_RE.search(junction) - if not ref_match: + fsdk_match = FSDK_REF_RE.search(junction) + if not fsdk_match: sys.exit( "ERROR: elements/freedesktop-sdk.bst has no " "'freedesktop-sdk-X.Y.Z' point release in its ref." ) - pinned = ref_match.group(1) + fsdk_pinned = fsdk_match.group(1) + + flatcar_pin_match = FLATCAR_PIN_RE.search(flatcar) + if not flatcar_pin_match: + sys.exit( + "ERROR: include/flatcar.yml does not declare a " + "'flatcar-version: X.Y.Z' variable." + ) + flatcar_pinned = flatcar_pin_match.group(1) - if declared != pinned: + if installer_declared != fsdk_pinned: sys.exit( - "ERROR: release-version drift.\n" - f" project.conf release-version : {declared}\n" - f" elements/freedesktop-sdk.bst pinned ref: {pinned}\n" + "ERROR: installer-version drift.\n" + f" project.conf installer-version : {installer_declared}\n" + f" elements/freedesktop-sdk.bst pinned ref: {fsdk_pinned}\n" "\n" - "The release tag is derived from the junction ref while asset\n" - "filenames are derived from release-version. While these differ,\n" - "a new GitHub Release publishes assets still named with the old\n" - "version, systemd-sysupdate reads the old version from '@v', and\n" - "deployed hosts never see an update.\n" + "The installer release tag and installer assets are derived from the\n" + "junction ref while project.conf declares installer-version.\n" "\n" - f"Fix: set release-version to \"{pinned}\" in project.conf." + f"Fix: set installer-version to \"{fsdk_pinned}\" in project.conf." ) - print(f"OK: release-version {declared} matches the pinned FSDK point release.") + print( + f"OK: installer-version {installer_declared} matches pinned FSDK point release.\n" + f"OK: flatcar-version {flatcar_pinned} declared in include/flatcar.yml." + ) if __name__ == "__main__": diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95cf310..3f992c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,11 +13,11 @@ repos: language: system files: ^\.github/workflows/.*\.(yml|yaml)$ - id: check-release-version - name: release-version matches pinned FSDK point release + name: release versions match pinned FSDK and Flatcar releases entry: python .github/scripts/check-release-version.py language: system pass_filenames: false - files: ^(project\.conf|elements/freedesktop-sdk\.bst|\.github/scripts/check-release-version\.py)$ + files: ^(project\.conf|elements/freedesktop-sdk\.bst|include/flatcar\.yml|\.github/scripts/check-release-version\.py)$ - id: check-k0s-version name: k0s version derived from include/k0s.yml, not restated entry: python .github/scripts/check-k0s-version.py diff --git a/docs/skills/avoid-over-engineering.md b/docs/skills/avoid-over-engineering.md index ff1702e..7b25c18 100644 --- a/docs/skills/avoid-over-engineering.md +++ b/docs/skills/avoid-over-engineering.md @@ -67,8 +67,9 @@ existing repo convention already does the job. ## Verification - [ ] `just validate` passes before and after the change. -- [ ] No hardcoded version duplicates remain; `release-version` in `project.conf` - is the single source of truth. +- [ ] No hardcoded version duplicates remain; `installer-version` in `project.conf` + and `flatcar-version` in `include/flatcar.yml` define the installer and OS + payload axes. - [ ] Removed build dependencies are not used by any command in the element. - [ ] For `manual`/`script` elements, the sandbox still has `/bin/sh` and any coreutils the commands need after a dep cut. - [ ] For `script` elements, build the element with `just bst build `; transitive tools (`dracut`, `ukify`, etc.) may fail silently if their own runtime deps are missing from the sandbox. diff --git a/docs/skills/ddi-installer-build.md b/docs/skills/ddi-installer-build.md index 0916d10..d0973b6 100644 --- a/docs/skills/ddi-installer-build.md +++ b/docs/skills/ddi-installer-build.md @@ -91,7 +91,8 @@ The release process is driven by `.github/workflows/build.yml`: - CI builds the DDI payload, installer, target UKI, k0s sysext, and standalone PXE boot inputs (`bluefin-server-pxe-vmlinuz-*`, `bluefin-server-pxe-initrd-*.cpio.gz`). - CI uploads the versioned release assets to the corresponding - `installer-v` GitHub Release. + `installer-v` GitHub Release. The release tag tracks the + installer axis, while the DDI and UKI inside it track the Flatcar payload axis (`flatcar-version`). - CI also produces a combined `dist/release/SHA256SUMS` manifest and signs it to create `SHA256SUMS.gpg` for `systemd-sysupdate` verification. The PXE inputs are included in this manifest, per `docs/skills/ddi-installer.md`. diff --git a/docs/skills/index.md b/docs/skills/index.md index 02c5b0a..d04fe69 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -43,4 +43,4 @@ This is the lazy-load routing table for agent skills. Keep this file in memory w - **Publish registry:** factory OCI registry at `:30500` (or configured by operator). - **Cluster build workflow:** `bluefin-server-build-pipeline` in the downstream factory CI repository. - **Cluster boot-test workflow:** `bluefin-server-boot-test` in the downstream factory CI repository. -- **Version scheme:** FSDK-derived only; no separate application version axis. +- **Version scheme:** two axes — `installer-version` (FSDK-derived, `project.conf`) and `flatcar-version` (OS payload, `include/flatcar.yml`); no application version axis. diff --git a/elements/bluefin-server/os-release-flatcar.bst b/elements/bluefin-server/os-release-flatcar.bst index e21ed19..cc1c29d 100644 --- a/elements/bluefin-server/os-release-flatcar.bst +++ b/elements/bluefin-server/os-release-flatcar.bst @@ -18,8 +18,8 @@ config: install-commands: - mkdir -p "%{install-root}/usr/lib" - | - # %{release-version} is declared in project.conf and enforced against - # the junction ref by check-release-version.py — no re-parsing needed. + # %{flatcar-version} comes from include/flatcar.yml, the single source of + # truth for the Flatcar axis — no re-parsing needed. FLATCAR_VERSION="%{flatcar-version}" cat < "%{install-root}/usr/lib/os-release" @@ -28,13 +28,13 @@ config: ID_LIKE=fedora VERSION="${FLATCAR_VERSION}-fsdk" VERSION_ID=${FLATCAR_VERSION} - PRETTY_NAME="Bluefin Server %{release-version}" + PRETTY_NAME="Bluefin Server %{flatcar-version}" ANSI_COLOR="0;38;5;208" HOME_URL="https://github.com/projectbluefin/server" BUG_REPORT_URL="https://github.com/projectbluefin/server/issues" FLATCAR_BOARD="%{flatcar-board}" CPE_NAME="cpe:/o:flatcar-linux:flatcar_linux:${FLATCAR_VERSION}" - IMAGE_VERSION=%{release-version} + IMAGE_VERSION=%{flatcar-version} EOF - | mkdir -p "%{install-root}/etc" diff --git a/elements/oci/bluefin-server-ddi.bst b/elements/oci/bluefin-server-ddi.bst index 9f80157..3b7ecf4 100644 --- a/elements/oci/bluefin-server-ddi.bst +++ b/elements/oci/bluefin-server-ddi.bst @@ -19,7 +19,7 @@ build-depends: location: /layer variables: - ddi-version: "%{release-version}" + ddi-version: "%{flatcar-version}" config: commands: diff --git a/elements/oci/bluefin-server-installer.bst b/elements/oci/bluefin-server-installer.bst index 44d9f30..8321051 100644 --- a/elements/oci/bluefin-server-installer.bst +++ b/elements/oci/bluefin-server-installer.bst @@ -62,9 +62,6 @@ build-depends: config: location: /k0s -variables: - installer-version: "%{release-version}" - config: commands: # ── Step 1a: Prepare rootfs directories and mount points ──────────── @@ -443,7 +440,7 @@ config: zstd --rm -T0 -19 -q "${FNAME}" -o "${FNAME}.zst" # Also export the target UKI (.efi) for GitHub Releases and systemd-sysupdate - cp /layer/usr/lib/bluefin-server/bluefin-server.efi "bluefin-server-%{installer-version}.efi" + cp /layer/usr/lib/bluefin-server/bluefin-server.efi "bluefin-server-%{flatcar-version}.efi" # Export raw PXE inputs alongside the UKI. PXE users supply their own # kernel command line, so these remain separate from the fixed-cmdline UKI. INSTALLER_KVER="$(basename "$(find /layer/usr/lib/modules -mindepth 1 -maxdepth 1 -type d | head -n1)")" @@ -451,7 +448,7 @@ config: cp /installer.cpio.gz "bluefin-server-pxe-initrd-%{installer-version}.cpio.gz" sha256sum --binary "${FNAME}.zst" > SHA256SUMS - sha256sum --binary "bluefin-server-%{installer-version}.efi" >> SHA256SUMS + sha256sum --binary "bluefin-server-%{flatcar-version}.efi" >> SHA256SUMS sha256sum --binary "bluefin-server-pxe-vmlinuz-%{installer-version}" >> SHA256SUMS sha256sum --binary "bluefin-server-pxe-initrd-%{installer-version}.cpio.gz" >> SHA256SUMS ls -lh diff --git a/project.conf b/project.conf index f369f67..d395f19 100644 --- a/project.conf +++ b/project.conf @@ -9,6 +9,10 @@ element-path: elements (@): - gnome-build-meta.bst:freedesktop-sdk.bst:include/runtime.yml - include/aliases.yml + # OS payload axis (Flatcar): single source of truth for %{flatcar-version} + # and %{flatcar-kver}. Included here so both resolve project-wide without + # being restated. Enforced by .github/scripts/check-release-version.py. + - include/flatcar.yml options: arch: @@ -23,10 +27,10 @@ sandbox: build-arch: "%{arch}" variables: - # Single source of truth for asset versioning; must match the FSDK point - # release pinned in elements/freedesktop-sdk.bst. Enforced by + # Installer axis (FSDK): must match the point release pinned in + # elements/freedesktop-sdk.bst. Enforced by # .github/scripts/check-release-version.py. - release-version: "26.08.0" + installer-version: "26.08.0" # Pull-only: read from the shared GNOME + Bluefin BuildStream CAS caches. artifacts: diff --git a/tests/unit/test_release_version.py b/tests/unit/test_release_version.py index 4f61fc4..ed010d8 100644 --- a/tests/unit/test_release_version.py +++ b/tests/unit/test_release_version.py @@ -1,10 +1,13 @@ """Unit coverage for .github/scripts/check-release-version.py. -The script is the only gate protecting against release-version drift between -``project.conf`` and the pinned ``elements/freedesktop-sdk.bst`` junction ref. -When it regresses, CI publishes release assets carrying a stale version string -and systemd-sysupdate stops seeing updates, so every branch of ``read()``, -``main()`` and both module-level regexes is exercised here. +The script is the gate protecting against version drift on the two release +axes: the installer axis declared in ``project.conf`` and pinned in +``elements/freedesktop-sdk.bst``, and the Flatcar OS payload axis declared in +``include/flatcar.yml``. + +When either regresses, CI publishes release assets carrying stale version +strings and systemd-sysupdate or operators see version mismatches, so every +branch of ``read()``, ``main()`` and all module-level regexes is exercised here. """ import importlib.util @@ -29,25 +32,40 @@ def _load_module(): def checker(tmp_path): """Fresh module instance with its path constants rooted at tmp_path. - ``ROOT``/``PROJECT_CONF``/``FSDK_JUNCTION`` are module-level globals derived - from the script's own location, so each test gets a re-imported copy pointed - at an isolated tree. + ``ROOT``/``PROJECT_CONF``/``FSDK_JUNCTION``/``FLATCAR_PIN`` are module-level + globals derived from the script's own location, so each test gets a + re-imported copy pointed at an isolated tree. """ module = _load_module() module.ROOT = tmp_path module.PROJECT_CONF = tmp_path / "project.conf" module.FSDK_JUNCTION = tmp_path / "elements" / "freedesktop-sdk.bst" - module.FSDK_JUNCTION.parent.mkdir(parents=True) + module.FLATCAR_PIN = tmp_path / "include" / "flatcar.yml" + module.FSDK_JUNCTION.parent.mkdir(parents=True, exist_ok=True) + module.FLATCAR_PIN.parent.mkdir(parents=True, exist_ok=True) yield module sys.modules.pop("check_release_version", None) -def _write(checker, declared="26.08.0", pinned="26.08.0"): +def _write( + checker, + installer_declared="26.08.0", + fsdk_pinned="26.08.0", + flatcar_pinned="4593.2.5", +): checker.PROJECT_CONF.write_text( - f'variables:\n release-version: "{declared}"\n', encoding="utf-8" + f'variables:\n installer-version: "{installer_declared}"\n', + encoding="utf-8", ) checker.FSDK_JUNCTION.write_text( - f"junction:\n ref: freedesktop-sdk-{pinned}-0-gdb97cce\n", encoding="utf-8" + f"junction:\n ref: freedesktop-sdk-{fsdk_pinned}-0-gdb97cce\n", + encoding="utf-8", + ) + checker.FLATCAR_PIN.write_text( + "variables:\n" + f' flatcar-version: "{flatcar_pinned}"\n' + ' flatcar-kver: "6.12.102-flatcar"\n', + encoding="utf-8", ) @@ -73,22 +91,22 @@ def test_read_exits_when_path_is_a_directory(checker, tmp_path): assert "expected file not found" in str(excinfo.value) -# --- RELEASE_VERSION_RE --------------------------------------------------- +# --- INSTALLER_VERSION_RE ------------------------------------------------- @pytest.mark.parametrize( "line", [ - ' release-version: "26.08.0"', - " release-version: '26.08.0'", - " release-version: 26.08.0", - "release-version: 26.08.0", - "\trelease-version: 26.08.0", - ' release-version: "26.08.0" ', + ' installer-version: "26.08.0"', + " installer-version: '26.08.0'", + " installer-version: 26.08.0", + "installer-version: 26.08.0", + "\tinstaller-version: 26.08.0", + ' installer-version: "26.08.0" ', ], ) -def test_release_version_re_accepts_supported_spellings(checker, line): - match = checker.RELEASE_VERSION_RE.search(f"variables:\n{line}\n") +def test_installer_version_re_accepts_supported_spellings(checker, line): + match = checker.INSTALLER_VERSION_RE.search(f"variables:\n{line}\n") assert match is not None assert match.group(1) == "26.08.0" @@ -96,14 +114,14 @@ def test_release_version_re_accepts_supported_spellings(checker, line): @pytest.mark.parametrize( "line", [ - " release-version: 26.08", - " release-version:", - " other-release-version-thing: 1.2.3", - " # release-version: 1.2.3", + " installer-version: 26.08", + " installer-version:", + " other-installer-version-thing: 1.2.3", + " # installer-version: 1.2.3", ], ) -def test_release_version_re_rejects_malformed_declarations(checker, line): - assert checker.RELEASE_VERSION_RE.search(f"variables:\n{line}\n") is None +def test_installer_version_re_rejects_malformed_declarations(checker, line): + assert checker.INSTALLER_VERSION_RE.search(f"variables:\n{line}\n") is None # --- FSDK_REF_RE ---------------------------------------------------------- @@ -120,17 +138,37 @@ def test_fsdk_ref_re_ignores_two_component_track_glob(checker): assert checker.FSDK_REF_RE.search(" track: freedesktop-sdk-26.08*\n") is None +# --- FLATCAR_PIN_RE ------------------------------------------------------- + + +def test_flatcar_pin_re_extracts_version_from_pin(checker): + match = checker.FLATCAR_PIN_RE.search(' flatcar-version: "4593.2.5"\n') + assert match.group(1) == "4593.2.5" + + +def test_flatcar_pin_re_rejects_malformed(checker): + assert checker.FLATCAR_PIN_RE.search(" flatcar-version: 4593.2\n") is None + + # --- main() --------------------------------------------------------------- def test_main_passes_when_versions_match(checker, capsys): - _write(checker, declared="26.08.0", pinned="26.08.0") + _write( + checker, + installer_declared="26.08.0", + fsdk_pinned="26.08.0", + flatcar_pinned="4593.2.5", + ) checker.main() - assert "OK: release-version 26.08.0" in capsys.readouterr().out + out = capsys.readouterr().out + assert "OK: installer-version 26.08.0" in out + assert "OK: flatcar-version 4593.2.5" in out def test_main_exits_when_project_conf_missing(checker): checker.FSDK_JUNCTION.write_text("ref: freedesktop-sdk-26.08.0\n", encoding="utf-8") + checker.FLATCAR_PIN.write_text('flatcar-version: "4593.2.5"\n', encoding="utf-8") with pytest.raises(SystemExit) as excinfo: checker.main() assert "expected file not found" in str(excinfo.value) @@ -138,50 +176,77 @@ def test_main_exits_when_project_conf_missing(checker): def test_main_exits_when_junction_missing(checker): checker.PROJECT_CONF.write_text( - 'variables:\n release-version: "26.08.0"\n', encoding="utf-8" + 'variables:\n installer-version: "26.08.0"\n', + encoding="utf-8", ) + checker.FLATCAR_PIN.write_text('flatcar-version: "4593.2.5"\n', encoding="utf-8") with pytest.raises(SystemExit) as excinfo: checker.main() assert "expected file not found" in str(excinfo.value) -def test_main_exits_when_release_version_not_declared(checker): +def test_main_exits_when_flatcar_pin_missing(checker): + checker.PROJECT_CONF.write_text( + 'variables:\n installer-version: "26.08.0"\n', + encoding="utf-8", + ) + checker.FSDK_JUNCTION.write_text("ref: freedesktop-sdk-26.08.0\n", encoding="utf-8") + with pytest.raises(SystemExit) as excinfo: + checker.main() + assert "expected file not found" in str(excinfo.value) + + +def test_main_exits_when_installer_version_not_declared(checker): checker.PROJECT_CONF.write_text("variables:\n other: 1\n", encoding="utf-8") checker.FSDK_JUNCTION.write_text("ref: freedesktop-sdk-26.08.0\n", encoding="utf-8") + checker.FLATCAR_PIN.write_text('flatcar-version: "4593.2.5"\n', encoding="utf-8") with pytest.raises(SystemExit) as excinfo: checker.main() - assert "does not declare a" in str(excinfo.value) + assert "does not declare an 'installer-version" in str(excinfo.value) def test_main_exits_when_junction_has_no_point_release(checker): checker.PROJECT_CONF.write_text( - 'variables:\n release-version: "26.08.0"\n', encoding="utf-8" + 'variables:\n installer-version: "26.08.0"\n', + encoding="utf-8", ) checker.FSDK_JUNCTION.write_text( "junction:\n track: freedesktop-sdk-26.08*\n", encoding="utf-8" ) + checker.FLATCAR_PIN.write_text('flatcar-version: "4593.2.5"\n', encoding="utf-8") + with pytest.raises(SystemExit) as excinfo: + checker.main() + assert "no 'freedesktop-sdk-X.Y.Z' point release" in str(excinfo.value) + + +def test_main_exits_when_flatcar_pin_has_no_version(checker): + checker.PROJECT_CONF.write_text( + 'variables:\n installer-version: "26.08.0"\n', + encoding="utf-8", + ) + checker.FSDK_JUNCTION.write_text("ref: freedesktop-sdk-26.08.0\n", encoding="utf-8") + checker.FLATCAR_PIN.write_text("variables:\n other: 1\n", encoding="utf-8") with pytest.raises(SystemExit) as excinfo: checker.main() - assert "no" in str(excinfo.value) - assert "point release" in str(excinfo.value) + assert "does not declare a 'flatcar-version: X.Y.Z'" in str(excinfo.value) -def test_main_exits_on_drift_and_names_both_versions(checker): - _write(checker, declared="26.08.0", pinned="26.08.1") +def test_main_exits_on_installer_drift_and_names_both_versions(checker): + _write(checker, installer_declared="26.08.0", fsdk_pinned="26.08.1") with pytest.raises(SystemExit) as excinfo: checker.main() message = str(excinfo.value) - assert "release-version drift" in message + assert "installer-version drift" in message assert "26.08.0" in message assert "26.08.1" in message - assert 'set release-version to "26.08.1"' in message + assert 'set installer-version to "26.08.1"' in message -def test_main_drift_is_detected_across_minor_lines(checker): - _write(checker, declared="25.08.0", pinned="26.08.0") +def test_main_installer_drift_is_detected_across_minor_lines(checker): + _write(checker, installer_declared="25.08.0", fsdk_pinned="26.08.0") with pytest.raises(SystemExit) as excinfo: checker.main() - assert "release-version drift" in str(excinfo.value) + assert "installer-version drift" in str(excinfo.value) # --- live repository invariant ------------------------------------------- @@ -194,4 +259,6 @@ def test_real_repository_has_no_release_version_drift(capsys): module.main() finally: sys.modules.pop("check_release_version", None) - assert "OK: release-version" in capsys.readouterr().out + out = capsys.readouterr().out + assert "OK: installer-version" in out + assert "OK: flatcar-version" in out