Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions elements/flatcar/flatcar-usr.bst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
kind: manual
description: |
Import the Flatcar LTS /usr as the Bluefin Server OS payload base.
Extracts ./usr from upstream Flatcar container rootfs, strips Flatcar's
update and provisioning stack in favor of native Bluefin equivalents
(systemd-sysupdate, systemd-creds, Kured), and removes /usr/lib/modules
to leave kernel module management exclusively to flatcar-kernel.bst.

(@):
- include/arch.yml
- include/flatcar.yml

build-depends:
- base/base-stack.bst
- freedesktop-sdk.bst:components/tar.bst
- freedesktop-sdk.bst:components/gzip.bst
- freedesktop-sdk.bst:components/findutils.bst

variables:
strip-binaries: ""

sources:
- kind: remote
url: flatcar:stable/%{flatcar-board}/%{flatcar-version}/flatcar-container.tar.gz
ref: c533ff576e128d11ce240e01f818c358d75b9ff118b2dd9199fbf4684547a32c

config:
install-commands:
- |
set -euo pipefail

mkdir -p "%{install-root}"

# Extract the complete Flatcar /usr into the element install root.
tar -xzf flatcar-container.tar.gz -C "%{install-root}" ./usr

# Remove kernel modules; elements/flatcar/flatcar-kernel.bst extracts and owns
# /usr/lib/modules/%{flatcar-kver} (uncompressing .ko.xz modules and running depmod)
# to prevent filesystem overlap collisions when composed together in os-stack.bst.
rm -rf "%{install-root}/usr/lib/modules"

# Remove Flatcar's update and provisioning stack with explicit rm lines.
# Each removal names its Bluefin replacement. No wildcard sweeps are used
# so that an upstream unit rename fails loudly instead of silently regressing.
# Note: /usr/lib/flatcar/bootengine.img is deliberately preserved here;
# stage 1 initramfs loop-mounts it from /usr as the stage 2 initramfs.

# update_engine: replaced by Bluefin's systemd-sysupdate (os-sysupdate.bst) for A/B image updates
rm "%{install-root}/usr/bin/update_engine"

# update_engine_client: replaced by Bluefin's systemd-sysupdate CLI and systemctl
rm "%{install-root}/usr/bin/update_engine_client"

# update_engine_stub: replaced by Bluefin's native systemd-sysupdate mechanism
rm "%{install-root}/usr/bin/update_engine_stub"

# locksmithd: replaced by Bluefin's Kured reboot coordination (os-kured-hook.bst) via /run/reboot-required
rm "%{install-root}/usr/lib/locksmith/locksmithd"

# locksmithd.service: replaced by Bluefin's Kured reboot coordination (os-kured-hook.bst)
rm "%{install-root}/usr/lib/systemd/system/locksmithd.service"
rm "%{install-root}/usr/lib/systemd/system/multi-user.target.wants/locksmithd.service"

# ignition: replaced by Bluefin's systemd-creds first-boot credential provisioning (os-creds-prov.bst)
rm "%{install-root}/usr/bin/ignition"

# ignition-delete-config.service: replaced by Bluefin's systemd-creds credential provisioning
rm "%{install-root}/usr/lib/systemd/system/ignition-delete-config.service"
rm "%{install-root}/usr/lib/systemd/system/sysinit.target.wants/ignition-delete-config.service"

# coreos-cloudinit: replaced by Bluefin's systemd-creds first-boot credential provisioning (os-creds-prov.bst)
rm "%{install-root}/usr/bin/coreos-cloudinit"

# flatcar-update: replaced by Bluefin's systemd-sysupdate (os-sysupdate.bst) and os-justfile
rm "%{install-root}/usr/bin/flatcar-update"

# download_sysext: replaced by Bluefin's declarative sysext delivery via systemd-sysext
rm "%{install-root}/usr/bin/download_sysext"

# ensure-sysext.service: replaced by Bluefin's systemd-sysext.service and sysupdate integration
rm "%{install-root}/usr/lib/systemd/system/ensure-sysext.service"
rm "%{install-root}/usr/lib/systemd/system/sysinit.target.wants/ensure-sysext.service"
248 changes: 248 additions & 0 deletions tests/unit/test_flatcar_usr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
"""Contract tests for elements/flatcar/flatcar-usr.bst.

Verifies that the Flatcar /usr base import:
- Uses kind: manual with prebuilt binaries unstripped (strip-binaries: "").
- Imports flatcar-container.tar.gz pinned by sha256 via the flatcar: alias.
- Removes /usr/lib/modules to avoid filesystem collisions with flatcar-kernel.bst.
- Explicitly removes Flatcar's update and provisioning stack (update_engine,
locksmithd, ignition, coreos-cloudinit, flatcar-update, download_sysext,
ensure-sysext) with individual rm commands documented with their Bluefin replacements.
- Preserves usr/lib/flatcar/bootengine.img for two-stage kernel boot.
- Preserves verified OS components (systemd 257, glibc, bash, sshd, crictl, etc.).
"""

from __future__ import annotations

import re
import subprocess
from pathlib import Path

import yaml

REPO_ROOT = Path(__file__).resolve().parents[2]
FLATCAR_USR_ELEMENT = REPO_ROOT / "elements" / "flatcar" / "flatcar-usr.bst"
FLATCAR_YML = REPO_ROOT / "include" / "flatcar.yml"

REMOVED_BINARIES = [
"usr/bin/update_engine",
"usr/bin/update_engine_client",
"usr/bin/update_engine_stub",
"usr/lib/locksmith/locksmithd",
"usr/bin/ignition",
"usr/bin/coreos-cloudinit",
"usr/bin/flatcar-update",
"usr/bin/download_sysext",
]

REMOVED_UNITS = [
"usr/lib/systemd/system/locksmithd.service",
"usr/lib/systemd/system/ignition-delete-config.service",
"usr/lib/systemd/system/ensure-sysext.service",
]

REMOVED_COMPONENTS = [
"update_engine",
"update_engine_client",
"update_engine_stub",
"locksmithd",
"locksmithd.service",
"ignition",
"ignition-delete-config.service",
"coreos-cloudinit",
"flatcar-update",
"download_sysext",
"ensure-sysext.service",
]


def _load_element_data() -> dict:
content = FLATCAR_USR_ELEMENT.read_text(encoding="utf-8")
return yaml.safe_load(content)


def test_flatcar_usr_element_structure() -> None:
"""Verify flatcar-usr.bst declares the expected BuildStream structure."""
assert FLATCAR_USR_ELEMENT.is_file(), f"{FLATCAR_USR_ELEMENT} must exist"
data = _load_element_data()

assert data.get("kind") == "manual", "flatcar-usr.bst must be kind: manual"

# Must include arch.yml and flatcar.yml
includes = data.get("(@)", [])
assert "include/arch.yml" in includes
assert "include/flatcar.yml" in includes

# Must declare build-depends for sandbox execution
build_deps = data.get("build-depends", [])
assert "base/base-stack.bst" in build_deps, "Must depend on base/base-stack.bst"
assert "freedesktop-sdk.bst:components/tar.bst" in build_deps
assert "freedesktop-sdk.bst:components/gzip.bst" in build_deps
assert "freedesktop-sdk.bst:components/findutils.bst" in build_deps

# Prebuilt binaries must not be stripped
variables = data.get("variables", {})
assert variables.get("strip-binaries") == "", (
"variables.strip-binaries must be empty string to prevent stripping"
)

# Exactly one remote source for flatcar-container.tar.gz pinned by sha256
sources = data.get("sources", [])
assert len(sources) == 1, "Must declare exactly one source"
src = sources[0]
assert src.get("kind") == "remote"
assert src.get("url") == "flatcar:stable/%{flatcar-board}/%{flatcar-version}/flatcar-container.tar.gz"
assert len(src.get("ref", "")) == 64, "Source ref must be a valid sha256 hash"


def test_flatcar_usr_explicit_removals_and_replacements() -> None:
"""Verify explicit rm lines and Bluefin replacement documentation."""
content = FLATCAR_USR_ELEMENT.read_text(encoding="utf-8")

for component in REMOVED_COMPONENTS:
assert component in content, (
f"flatcar-usr.bst must explicitly name {component} in its removal commands"
)

# Assert no wildcard sweeps in removal section
install_commands = _load_element_data().get("config", {}).get("install-commands", [])
script = "\n".join(install_commands)
for line in script.splitlines():
clean = line.strip()
if clean.startswith("rm "):
assert "*" not in clean, (
f"Wildcard removal forbidden to ensure rename failures: {line}"
)

# Assert each removed component is commented with replacement
for component in [
"update_engine",
"locksmithd",
"ignition",
"coreos-cloudinit",
"flatcar-update",
"download_sysext",
"ensure-sysext",
]:
pattern = rf"#\s*{component}.*?:.*?replaced by"
assert re.search(pattern, content, re.IGNORECASE), (
f"flatcar-usr.bst must document replacement for {component}"
)


def test_flatcar_usr_preserves_bootengine_img() -> None:
"""Verify usr/lib/flatcar/bootengine.img is preserved and never deleted."""
content = FLATCAR_USR_ELEMENT.read_text(encoding="utf-8")

assert "bootengine.img" in content, (
"flatcar-usr.bst must explicitly document preservation of bootengine.img"
)

install_commands = _load_element_data().get("config", {}).get("install-commands", [])
script = "\n".join(install_commands)
for line in script.splitlines():
if "bootengine" in line:
assert not line.strip().startswith("rm"), (
f"bootengine.img must not be removed: {line}"
)


def test_flatcar_usr_removes_modules() -> None:
"""Verify script logic removes /usr/lib/modules to avoid collision with flatcar-kernel.bst."""
content = FLATCAR_USR_ELEMENT.read_text(encoding="utf-8")
assert 'rm -rf "%{install-root}/usr/lib/modules"' in content, (
"Script must remove /usr/lib/modules to prevent collision with flatcar-kernel.bst"
)


def test_flatcar_usr_contract_execution(tmp_path: Path) -> None:
"""Simulate execution of flatcar-usr.bst install-commands against a staged /usr.

Verifies:
1. Removed binaries are absent.
2. Removed units are absent.
3. bootengine.img is preserved.
4. Module directory /usr/lib/modules is removed to avoid collision with flatcar-kernel.bst.
5. Retained files (bash, sshd, systemd, crictl) remain untouched.
"""
flatcar_yml_data = yaml.safe_load(FLATCAR_YML.read_text(encoding="utf-8"))
kver = flatcar_yml_data["variables"]["flatcar-kver"]

install_root = tmp_path / "install-root"
usr = install_root / "usr"

# Populate mock files that flatcar-container.tar.gz delivers
bin_dir = usr / "bin"
bin_dir.mkdir(parents=True)
for b in [
"bash", "crictl", "sshd", "update_engine", "update_engine_client",
"update_engine_stub", "ignition", "coreos-cloudinit", "flatcar-update",
"download_sysext"
]:
(bin_dir / b).write_text(f"mock-{b}", encoding="utf-8")

locksmith_dir = usr / "lib" / "locksmith"
locksmith_dir.mkdir(parents=True)
(locksmith_dir / "locksmithd").write_text("mock-locksmithd", encoding="utf-8")

systemd_dir = usr / "lib" / "systemd" / "system"
systemd_dir.mkdir(parents=True)
(systemd_dir / "locksmithd.service").write_text("mock-unit", encoding="utf-8")
(systemd_dir / "ignition-delete-config.service").write_text("mock-unit", encoding="utf-8")
(systemd_dir / "ensure-sysext.service").write_text("mock-unit", encoding="utf-8")

wants_dir1 = systemd_dir / "multi-user.target.wants"
wants_dir1.mkdir(parents=True)
(wants_dir1 / "locksmithd.service").symlink_to("../locksmithd.service")

wants_dir2 = systemd_dir / "sysinit.target.wants"
wants_dir2.mkdir(parents=True)
(wants_dir2 / "ignition-delete-config.service").symlink_to("../ignition-delete-config.service")
(wants_dir2 / "ensure-sysext.service").symlink_to("../ensure-sysext.service")

flatcar_dir = usr / "lib" / "flatcar"
flatcar_dir.mkdir(parents=True)
bootengine = flatcar_dir / "bootengine.img"
bootengine.write_bytes(b"mock-bootengine-squashfs")

# Module directory: usr/lib/modules/<kver>/
mod_dir = usr / "lib" / "modules" / kver
(mod_dir / "kernel" / "drivers").mkdir(parents=True)
(mod_dir / "kernel" / "drivers" / "driver.ko").write_bytes(b"mock-driver")
(mod_dir / "modules.dep").write_text("mock modules.dep", encoding="utf-8")

# Extract script from flatcar-usr.bst and adapt variables
install_commands = _load_element_data().get("config", {}).get("install-commands", [])
script = "\n".join(install_commands)

# Replace BuildStream variables
script = script.replace("%{install-root}", str(install_root))
script = script.replace("%{flatcar-kver}", kver)

# In test environment, the mock files are already populated, so bypass tar extraction
script = re.sub(r"tar\s+-xzf\s+flatcar-container\.tar\.gz\s+-C\s+.*", "# tar extract bypassed", script)

# Run the script in bash
result = subprocess.run(["bash", "-c", script], capture_output=True, text=True)
assert result.returncode == 0, f"Script failed:\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"

# 1. Assert removed binaries are absent
for rel_path in REMOVED_BINARIES:
target = install_root / rel_path
assert not target.exists(), f"Removed binary {rel_path} must be absent from output"

# 2. Assert removed units are absent
for rel_path in REMOVED_UNITS:
target = install_root / rel_path
assert not target.exists(), f"Removed unit {rel_path} must be absent from output"

# 3. Assert module tree is completely removed to avoid collision with flatcar-kernel.bst
assert not (usr / "lib" / "modules").exists(), (
"/usr/lib/modules must be removed to avoid collision with flatcar-kernel.bst"
)

# 4. Assert usr/lib/flatcar/bootengine.img exists in output
assert bootengine.is_file(), "usr/lib/flatcar/bootengine.img must exist in element output"

# 5. Assert kept binaries remain
for kept in ["bash", "crictl", "sshd"]:
assert (bin_dir / kept).is_file(), f"Kept binary {kept} must remain in output"
Loading