diff --git a/imagecraft/models/volume.py b/imagecraft/models/volume.py index 54ab97bf..a432e5e6 100644 --- a/imagecraft/models/volume.py +++ b/imagecraft/models/volume.py @@ -161,6 +161,9 @@ class Role(str, enum.Enum): SYSTEM_BOOT = "system-boot" """The partition stores the image's boot assets.""" + SYSTEM_SEED = "system-seed" + """The partition stores the image's initial seed data used during first boot.""" + class StructureItem(CraftBaseModel): """Structure item of the image.""" diff --git a/imagecraft/plugins/_setup.py b/imagecraft/plugins/_setup.py index ff0c2aab..cdeb0e13 100644 --- a/imagecraft/plugins/_setup.py +++ b/imagecraft/plugins/_setup.py @@ -18,6 +18,8 @@ from craft_parts.plugins.plugins import PluginType from .mmdebstrap_plugin import MmdebstrapPlugin +from .snap_preseed_plugin import SnapPreseedPlugin +from .uc_prepare_plugin import UcPreparePlugin def get_app_plugins() -> dict[str, PluginType]: @@ -25,7 +27,11 @@ def get_app_plugins() -> dict[str, PluginType]: :returns: A dict mapping plugin names to plugins """ - return {"mmdebstrap": MmdebstrapPlugin} + return { + "mmdebstrap": MmdebstrapPlugin, + "snap-preseed": SnapPreseedPlugin, + "uc-prepare": UcPreparePlugin, + } def setup_plugins() -> None: diff --git a/imagecraft/plugins/snap_preseed_plugin.py b/imagecraft/plugins/snap_preseed_plugin.py new file mode 100644 index 00000000..2de23eb4 --- /dev/null +++ b/imagecraft/plugins/snap_preseed_plugin.py @@ -0,0 +1,93 @@ +# This file is part of imagecraft. +# +# Copyright 2026 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . + +"""The snap-preseed plugin.""" + +import shlex +from typing import Literal, cast + +from craft_parts.plugins import Plugin, PluginProperties +from typing_extensions import override + + +class SnapPreseedPluginProperties(PluginProperties, frozen=True): + """Properties for the 'snap-preseed' plugin.""" + + plugin: Literal["snap-preseed"] = "snap-preseed" + + snap_preseed_snaps: list[str] + snap_preseed_channel: str | None = None + snap_preseed_model_assert: str = "" + snap_preseed_validation: Literal["ignore", "enforce"] = "ignore" + snap_preseed_assertions: list[str] = [] + snap_preseed_revisions: str | None = None + + +class SnapPreseedPlugin(Plugin): + """Prepare snaps for Ubuntu Classic images using 'snap prepare-image'.""" + + properties_class = SnapPreseedPluginProperties + + @override + def get_build_snaps(self) -> set[str]: + """Return a set of required snaps to install in the build environment.""" + return set() + + @override + def get_build_packages(self) -> set[str]: + """Return a set of required packages to install in the build environment.""" + return set() + + @override + def get_build_environment(self) -> dict[str, str]: + """Return a dictionary with the environment to use in the build step.""" + return {} + + @override + def get_build_commands(self) -> list[str]: + """Return a list of commands to run during the build step.""" + options = cast(SnapPreseedPluginProperties, self._options) + cmd = [ + "snap", + "prepare-image", + "--classic", + f"--arch={self._part_info.target_arch}", + f"--validation={options.snap_preseed_validation}", + ] + if options.snap_preseed_channel: + cmd.append(f"--channel={options.snap_preseed_channel}") + + if options.snap_preseed_revisions: + cmd.append(f"--revisions={options.snap_preseed_revisions}") + + cmd.extend( + f"--assert={assertion}" for assertion in options.snap_preseed_assertions + ) + + cmd.extend( + f"--snap={self._resolve_snap(snap)}" for snap in options.snap_preseed_snaps + ) + + cmd.append(options.snap_preseed_model_assert) + cmd.append(str(self._part_info.part_install_dir)) + return [shlex.join(cmd)] + + def _resolve_snap(self, snap: str) -> str: + snap = snap.strip() + if "/" in snap and not snap.endswith(".snap"): + name, channel = snap.split("/", 1) + snap = f"{name}={channel}" + return snap diff --git a/imagecraft/plugins/uc_prepare_plugin.py b/imagecraft/plugins/uc_prepare_plugin.py new file mode 100644 index 00000000..4cd15022 --- /dev/null +++ b/imagecraft/plugins/uc_prepare_plugin.py @@ -0,0 +1,135 @@ +# This file is part of imagecraft. +# +# Copyright 2026 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . + +"""The uc-prepare plugin.""" + +from typing import Literal, cast + +from craft_parts.plugins import Plugin, PluginProperties +from pydantic import model_validator +from typing_extensions import Self, override + + +class UcPreparePluginProperties(PluginProperties, frozen=True): + """Properties for the uc-prepare plugin.""" + + plugin: Literal["uc-prepare"] = "uc-prepare" + + uc_prepare_model_assert: str + uc_prepare_snaps: list[str] = [] + uc_prepare_channel: str | None = None + uc_prepare_validation: Literal["ignore", "enforce"] = "ignore" + uc_prepare_assertions: list[str] = [] + uc_prepare_revisions: str | None = None + uc_prepare_preseed: bool = False + uc_prepare_preseed_sign_key: str | None = None + uc_prepare_apparmor_features_dir: str | None = None + uc_prepare_sysfs_overlay: str | None = None + + @model_validator(mode="after") + def sign_key_requires_preseed(self) -> Self: + """preseed-sign-key requires preseed to be enabled.""" + if self.uc_prepare_preseed_sign_key and not self.uc_prepare_preseed: + raise ValueError( + "uc-prepare-preseed-sign-key cannot be used without uc-prepare-preseed" + ) + return self + + @model_validator(mode="after") + def sysfs_overlay_requires_preseed(self) -> Self: + """sysfs-overlay requires preseed to be enabled.""" + if self.uc_prepare_sysfs_overlay and not self.uc_prepare_preseed: + raise ValueError( + "uc-prepare-sysfs-overlay cannot be used without uc-prepare-preseed" + ) + return self + + +class UcPreparePlugin(Plugin): + """Prepare snaps for Ubuntu Core using 'snap prepare-image'.""" + + properties_class = UcPreparePluginProperties + + @override + def get_build_snaps(self) -> set[str]: + """Return a set of required snaps to install in the build environment.""" + return set() + + @override + def get_build_packages(self) -> set[str]: + """Return a set of required packages to install in the build environment.""" + options = cast(UcPreparePluginProperties, self._options) + if ( + options.uc_prepare_preseed + and self._part_info.host_arch != self._part_info.target_arch + ): + return {"qemu-user-static"} + return set() + + @override + def get_build_environment(self) -> dict[str, str]: + """Return a dictionary with the environment to use in the build step.""" + return {} + + @override + def get_build_commands(self) -> list[str]: + """Return a list of commands to run during the build step.""" + options = cast(UcPreparePluginProperties, self._options) + + cmd = ["snap", "prepare-image"] + + if options.uc_prepare_preseed: + cmd.append("--preseed") + + if options.uc_prepare_preseed_sign_key: + cmd.append(f"--preseed-sign-key={options.uc_prepare_preseed_sign_key}") + + if options.uc_prepare_apparmor_features_dir: + cmd.append( + f"--apparmor-features-dir={options.uc_prepare_apparmor_features_dir}" + ) + + if options.uc_prepare_sysfs_overlay: + cmd.append(f"--sysfs-overlay={options.uc_prepare_sysfs_overlay}") + + cmd.append(f"--validation={options.uc_prepare_validation}") + + if options.uc_prepare_channel: + cmd.append(f"--channel={options.uc_prepare_channel}") + + if options.uc_prepare_revisions: + cmd.append(f"--revisions={options.uc_prepare_revisions}") + + cmd.extend( + f"--assert={assertion}" for assertion in options.uc_prepare_assertions + ) + + cmd.extend( + f"--snap={self._resolve_snap(snap)}" for snap in options.uc_prepare_snaps + ) + + cmd.append( + f"{options.uc_prepare_model_assert} {self._part_info.part_install_dir}" + ) + + return [" ".join(cmd)] + + def _resolve_snap(self, snap: str) -> str: + snap = snap.strip() + if "/" in snap and not snap.endswith(".snap"): + name, channel = snap.split("/", 1) + snap = f"{name}={channel}" + return snap diff --git a/tests/spread/plugins/snap-preseed/imagecraft.yaml b/tests/spread/plugins/snap-preseed/imagecraft.yaml new file mode 100644 index 00000000..ebd8198e --- /dev/null +++ b/tests/spread/plugins/snap-preseed/imagecraft.yaml @@ -0,0 +1,33 @@ +name: snap-preseed-test +version: "0.1" +summary: Test snap-preseed plugin +description: Test snap-preseed plugin +base: bare +build-base: ubuntu@24.04 + +platforms: + amd64: + +volumes: + disk: + schema: gpt + structure: + - name: rootfs + role: system-data + type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 + filesystem: ext4 + filesystem-label: writable + size: 2G + +filesystems: + default: + - mount: / + device: (volume/disk/rootfs) + +parts: + snaps: + plugin: snap-preseed + snap-preseed-snaps: + - hello-world/latest/stable + organize: + "var/*": (overlay)/var/ diff --git a/tests/spread/plugins/snap-preseed/task.yaml b/tests/spread/plugins/snap-preseed/task.yaml new file mode 100644 index 00000000..e80ef227 --- /dev/null +++ b/tests/spread/plugins/snap-preseed/task.yaml @@ -0,0 +1,15 @@ +summary: Test snap-preseed plugin + +systems: [ubuntu-24.04-64] + +execute: | + imagecraft pack --verbose --destructive-mode + + test -f disk.img + + test -d prime/var/lib/snapd/seed/ + test -f prime/var/lib/snapd/seed/snaps/hello-world_*.snap + +restore: | + imagecraft clean --destructive-mode + rm -rf disk.img || true diff --git a/tests/spread/plugins/uc-prepare/imagecraft.yaml b/tests/spread/plugins/uc-prepare/imagecraft.yaml new file mode 100644 index 00000000..676723cb --- /dev/null +++ b/tests/spread/plugins/uc-prepare/imagecraft.yaml @@ -0,0 +1,36 @@ +name: uc-prepare-test +version: "1.0" +summary: Test uc-prepare plugin +description: Test uc-prepare plugin +base: bare +build-base: ubuntu@24.04 + +platforms: + amd64: + +filesystems: + default: + - mount: / + device: (volume/disk/ubuntu-seed) + +parts: + uc-seed: + plugin: uc-prepare + source: . + uc-prepare-model-assert: model.assert + organize: + "system-seed/*": (volume/disk/ubuntu-seed)/ + prime: + - -kernel + - -gadget + - -resolved-content + +volumes: + disk: + schema: gpt + structure: + - name: ubuntu-seed + role: system-seed + type: C12A7328-F81F-11D2-BA4B-00A0C93EC93B + filesystem: vfat + size: 1500M diff --git a/tests/spread/plugins/uc-prepare/model.assert b/tests/spread/plugins/uc-prepare/model.assert new file mode 100755 index 00000000..411b8f85 --- /dev/null +++ b/tests/spread/plugins/uc-prepare/model.assert @@ -0,0 +1,48 @@ +type: model +authority-id: canonical +series: 16 +brand-id: canonical +model: ubuntu-core-24-amd64 +architecture: amd64 +base: core24 +grade: signed +snaps: + - + default-channel: 24/stable + id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH + name: pc + type: gadget + - + default-channel: 24/stable + id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza + name: pc-kernel + type: kernel + - + default-channel: latest/stable + id: dwTAh7MZZ01zyriOZErqd1JynQLiOGvM + name: core24 + type: base + - + default-channel: latest/stable + id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4 + name: snapd + type: snapd + - + default-channel: 24/stable + id: ASctKBEHzVt3f1pbZLoekCvcigRjtuqw + name: console-conf + presence: optional + type: app +timestamp: 2024-04-19T08:42:32+00:00 +sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn + +AcLBXAQAAQoABgUCZiLKhgAKCRDgT5vottzAEmmwD/9xNRSiN7yoPMsERG2aSOLr+2ZTJddBhdj3 +bEuS6ifCb1K/t7CpNhmJt1iMasdcUl7q1DJCUr/uunLgoVKODxE0Vx4k3UCtoEH2v2Mj6oqjPdAh +d90gsEBP0nVhf+LjoS34zF3O7ScrJU69mjUPhcDx0mgOslSY5aHbFTIKsT9InJ3tKgg41+RFxvwZ +V1Ksf4ITkuy9ap7CjqhczFyNMSMn0cmF9OKK39jvmtfpXRVmjTDCoF0mLXmJ+RDyKFbjMuQU1O3S +opv6gJPE//hWj9Hn5S3DIVgjcpWWOqGk+wREFGgMs/g9aT5DoLMp/FvcMVPH8sZNLiZF3wrVJCjZ +Tm+fD3SGBlht5GJGKWL5eAlGRVOUNh0cmX1WTYuZKw+OOnNuQyPykfkEFE2OKwSQwXP+ZICuGIcX +KlO56GB8OLs8hdQQrOZH8ysO5qM3Ee0Iq/keJiQZIYIv63ODLn4cPkTa2Es33alVf59xiFvUFAfd +2hWOAFDbxSJSETNmCReSMNXR01U8YTKpQMB9CA5tlX7RlrHCeLrPld8ytSCLUf29ofsJE3yavgTK +2wt5eeX6M5okmq2TR78iH6xbX3qeK8J6Ld1ElhGIikivnXMPyA5BApdQa6aoHtHVx9jHRlgWNOZs +X5uwovE8pMriAGI4l+AWYFG6SeVyY90qqlAmzaybsw== diff --git a/tests/spread/plugins/uc-prepare/task.yaml b/tests/spread/plugins/uc-prepare/task.yaml new file mode 100644 index 00000000..e2dc8be1 --- /dev/null +++ b/tests/spread/plugins/uc-prepare/task.yaml @@ -0,0 +1,17 @@ +summary: Test uc-prepare plugin + +systems: [ubuntu-24.04-64] + +execute: | + imagecraft pack --verbose --destructive-mode + + test -f disk.img + + test -f prime/snaps/pc_*.snap + test -f prime/snaps/pc-kernel_*.snap + test -f prime/snaps/core24_*.snap + test -f prime/snaps/snapd_*.snap + +restore: | + imagecraft clean --destructive-mode + rm -rf disk.img || true diff --git a/tests/unit/models/test_volume.py b/tests/unit/models/test_volume.py index cc2eee3f..b94ef49f 100644 --- a/tests/unit/models/test_volume.py +++ b/tests/unit/models/test_volume.py @@ -140,7 +140,7 @@ def test_volume_valid(): }, ), ( - "1 validation error for Volume\nstructure.0.role\n Input should be 'system-data' or 'system-boot'", + "1 validation error for Volume\nstructure.0.role\n Input should be 'system-data', 'system-boot' or 'system-seed'", ValidationError, { "schema": "gpt", diff --git a/tests/unit/plugins/test_snap_preseed_plugin.py b/tests/unit/plugins/test_snap_preseed_plugin.py new file mode 100644 index 00000000..93c98375 --- /dev/null +++ b/tests/unit/plugins/test_snap_preseed_plugin.py @@ -0,0 +1,102 @@ +# This file is part of imagecraft. +# +# Copyright 2026 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import pytest +from craft_parts import PartInfo, ProjectInfo +from craft_parts.parts import Part +from imagecraft.plugins.snap_preseed_plugin import ( + SnapPreseedPlugin, + SnapPreseedPluginProperties, +) +from pydantic import ValidationError + + +@pytest.fixture +def part_info(new_dir): + project_info = ProjectInfo(application_name="test_snap_preseed", cache_dir=new_dir) + return PartInfo(project_info=project_info, part=Part("my-part", {})) + + +def test_missing_snaps_key(): + with pytest.raises(ValidationError, match="snap-preseed-snaps"): + SnapPreseedPluginProperties.unmarshal({}) + + +@pytest.fixture +def cmd_prefix(part_info): + return f"snap prepare-image --classic --arch={part_info.target_arch} --validation=ignore" + + +def test_get_build_commands(part_info, cmd_prefix): + properties = SnapPreseedPluginProperties.unmarshal( + {"snap-preseed-snaps": ["core24", "hello-world/latest/stable"]} + ) + + plugin = SnapPreseedPlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"{cmd_prefix} --snap=core24 --snap=hello-world=latest/stable '' {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_model_assertion(part_info, cmd_prefix): + properties = SnapPreseedPluginProperties.unmarshal( + { + "snap-preseed-snaps": ["core24"], + "snap-preseed-model-assert": "model.assert", + } + ) + + plugin = SnapPreseedPlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"{cmd_prefix} --snap=core24 model.assert {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_assertions(part_info, cmd_prefix): + properties = SnapPreseedPluginProperties.unmarshal( + { + "snap-preseed-snaps": ["core24"], + "snap-preseed-assertions": ["system-user.assert", "account.assert"], + } + ) + + plugin = SnapPreseedPlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"{cmd_prefix} --assert=system-user.assert --assert=account.assert --snap=core24 '' {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_revisions(part_info, cmd_prefix): + properties = SnapPreseedPluginProperties.unmarshal( + { + "snap-preseed-snaps": ["core24"], + "snap-preseed-revisions": "./revisions.txt", + } + ) + + plugin = SnapPreseedPlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"{cmd_prefix} --revisions=./revisions.txt --snap=core24 '' {part_info.part_install_dir}" + ) diff --git a/tests/unit/plugins/test_uc_prepare_plugin.py b/tests/unit/plugins/test_uc_prepare_plugin.py new file mode 100644 index 00000000..fda7baf1 --- /dev/null +++ b/tests/unit/plugins/test_uc_prepare_plugin.py @@ -0,0 +1,165 @@ +# This file is part of imagecraft. +# +# Copyright 2026 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import pytest +from craft_parts import PartInfo, ProjectInfo +from craft_parts.parts import Part +from imagecraft.plugins.uc_prepare_plugin import ( + UcPreparePlugin, + UcPreparePluginProperties, +) +from pydantic import ValidationError + + +@pytest.fixture +def part_info(new_dir): + project_info = ProjectInfo(application_name="test_uc_prepare", cache_dir=new_dir) + return PartInfo(project_info=project_info, part=Part("my-part", {})) + + +def test_missing_model_assertion(): + with pytest.raises(ValidationError, match="uc-prepare-model-assert"): + UcPreparePluginProperties.unmarshal({}) + + +def test_preseed_sign_key_without_preseed(): + with pytest.raises(ValueError, match="cannot be used without uc-prepare-preseed"): + UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-preseed-sign-key": "sign-key", + } + ) + + +def test_sysfs_overlay_without_preseed(): + with pytest.raises(ValueError, match="cannot be used without uc-prepare-preseed"): + UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-sysfs-overlay": "./sysfs", + } + ) + + +def test_get_build_packages_without_preseed(part_info): + properties = UcPreparePluginProperties.unmarshal( + {"uc-prepare-model-assert": "model.assert"} + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert plugin.get_build_packages() == set() + + +def test_get_build_packages_with_preseed_same_arch(part_info, mocker): + mocker.patch.object(part_info, "host_arch", "amd64") + mocker.patch.object(part_info, "target_arch", "amd64") + properties = UcPreparePluginProperties.unmarshal( + {"uc-prepare-model-assert": "model.assert", "uc-prepare-preseed": True} + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert plugin.get_build_packages() == set() + + +def test_get_build_packages_with_preseed_different_arch(part_info, mocker): + mocker.patch.object(part_info, "host_arch", "amd64") + mocker.patch.object(part_info, "target_arch", "arm64") + properties = UcPreparePluginProperties.unmarshal( + {"uc-prepare-model-assert": "model.assert", "uc-prepare-preseed": True} + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert plugin.get_build_packages() == {"qemu-user-static"} + + +def test_get_build_commands(part_info): + properties = UcPreparePluginProperties.unmarshal( + {"uc-prepare-model-assert": "model.assert"} + ) + + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"snap prepare-image --validation=ignore model.assert {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_preseed(part_info): + properties = UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-preseed": True, + } + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"snap prepare-image --preseed --validation=ignore model.assert {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_preseed_sign_key(part_info): + properties = UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-preseed": True, + "uc-prepare-preseed-sign-key": "sign-key", + } + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"snap prepare-image --preseed --preseed-sign-key=sign-key --validation=ignore model.assert {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_apparmor_dir(part_info): + apparmor_features_dir = "/sys/kernel/security/somewhere" + properties = UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-preseed": True, + "uc-prepare-apparmor-features-dir": apparmor_features_dir, + } + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"snap prepare-image --preseed --apparmor-features-dir={apparmor_features_dir} --validation=ignore model.assert {part_info.part_install_dir}" + ) + + +def test_get_build_commands_with_sysfs_overlay(part_info): + sysfs_overlay = "./sysfs-overlay" + properties = UcPreparePluginProperties.unmarshal( + { + "uc-prepare-model-assert": "model.assert", + "uc-prepare-preseed": True, + "uc-prepare-sysfs-overlay": sysfs_overlay, + } + ) + plugin = UcPreparePlugin(properties=properties, part_info=part_info) + + assert ( + plugin.get_build_commands()[0] + == f"snap prepare-image --preseed --sysfs-overlay={sysfs_overlay} --validation=ignore model.assert {part_info.part_install_dir}" + )