From f217e9a536de8929eb665f1cce0ba80453181704 Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Wed, 21 Aug 2024 10:40:20 -0400 Subject: [PATCH 01/12] plugins: Add bootc provision plugin This creates a new provision plugin that is built on top of the existing TestCloud (virtual) plugin. It adds new parameters to pass a Containerfile or container image. The plugin will then build a container image (if necessary) then build a bootc disk image from the container image using bootc image builder. Currently, bootc requires podman to be run as root when building a disk image. This is typically handled by running a podman machine as root. An additional parameter "add-deps" toggles building a derived container image with the tmt test requirements. Signed-off-by: Chris Kyrouac --- plans/provision/bootc.fmf | 34 +++ tests/provision/bootc/data/.fmf/version | 1 + .../data/containerfile_includes_deps.fmf | 13 + .../bootc/data/containerfile_needs_deps.fmf | 13 + .../bootc/data/image_includes_deps.fmf | 12 + .../provision/bootc/data/image_needs_deps.fmf | 12 + .../bootc/data/includes_deps.containerfile | 6 + .../bootc/data/needs_deps.containerfile | 1 + tests/provision/bootc/main.fmf | 6 + tests/provision/bootc/test.sh | 63 +++++ tmt/steps/provision/bootc.py | 242 ++++++++++++++++++ 11 files changed, 403 insertions(+) create mode 100644 plans/provision/bootc.fmf create mode 100644 tests/provision/bootc/data/.fmf/version create mode 100644 tests/provision/bootc/data/containerfile_includes_deps.fmf create mode 100644 tests/provision/bootc/data/containerfile_needs_deps.fmf create mode 100644 tests/provision/bootc/data/image_includes_deps.fmf create mode 100644 tests/provision/bootc/data/image_needs_deps.fmf create mode 100644 tests/provision/bootc/data/includes_deps.containerfile create mode 100644 tests/provision/bootc/data/needs_deps.containerfile create mode 100644 tests/provision/bootc/main.fmf create mode 100755 tests/provision/bootc/test.sh create mode 100644 tmt/steps/provision/bootc.py diff --git a/plans/provision/bootc.fmf b/plans/provision/bootc.fmf new file mode 100644 index 0000000000..15885d415e --- /dev/null +++ b/plans/provision/bootc.fmf @@ -0,0 +1,34 @@ +summary: Bootc virtual machine via testcloud + +description: | + bootc tests + +discover: + how: fmf + filter: 'tag:provision-bootc' + + +environment: + PROVISION_HOW: virtual + +adjust+: + - enabled: true + when: how == provision + + - provision: + hardware: + virtualization: + is-supported: true + memory: ">= 4 GB" + when: trigger == commit + + - prepare+: + - name: Disable IPv6 + how: shell + script: + - sysctl -w net.ipv6.conf.all.disable_ipv6=1 + - sysctl -w net.ipv6.conf.default.disable_ipv6=1 + because: Disable IPv6 in CI to avoid IPv6 connections that are disabled in CI + when: trigger == commit + +enabled: true diff --git a/tests/provision/bootc/data/.fmf/version b/tests/provision/bootc/data/.fmf/version new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/provision/bootc/data/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/tests/provision/bootc/data/containerfile_includes_deps.fmf b/tests/provision/bootc/data/containerfile_includes_deps.fmf new file mode 100644 index 0000000000..2e5e9bcef7 --- /dev/null +++ b/tests/provision/bootc/data/containerfile_includes_deps.fmf @@ -0,0 +1,13 @@ +execute: + how: tmt +discover: + how: shell + tests: + - name: booted image + test: bootc status && bootc status | grep localhost/tmtbase +provision: + how: bootc + add-deps: false + containerfile: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/includes_deps.containerfile" + containerfile-workdir: . + disk: 20 diff --git a/tests/provision/bootc/data/containerfile_needs_deps.fmf b/tests/provision/bootc/data/containerfile_needs_deps.fmf new file mode 100644 index 0000000000..d188cc82f1 --- /dev/null +++ b/tests/provision/bootc/data/containerfile_needs_deps.fmf @@ -0,0 +1,13 @@ +execute: + how: tmt +discover: + how: shell + tests: + - name: booted image + test: bootc status | grep localhost/tmtmodified +provision: + how: bootc + add-deps: true + containerfile: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/needs_deps.containerfile" + containerfile-workdir: . + disk: 20 diff --git a/tests/provision/bootc/data/image_includes_deps.fmf b/tests/provision/bootc/data/image_includes_deps.fmf new file mode 100644 index 0000000000..981b946709 --- /dev/null +++ b/tests/provision/bootc/data/image_includes_deps.fmf @@ -0,0 +1,12 @@ +execute: + how: tmt +discover: + how: shell + tests: + - name: booted image + test: bootc status | grep localhost/tmt-bootc-includes-deps +provision: + how: bootc + add-deps: false + containerimage: localhost/tmt-bootc-includes-deps + disk: 20 diff --git a/tests/provision/bootc/data/image_needs_deps.fmf b/tests/provision/bootc/data/image_needs_deps.fmf new file mode 100644 index 0000000000..af992ba9ba --- /dev/null +++ b/tests/provision/bootc/data/image_needs_deps.fmf @@ -0,0 +1,12 @@ +execute: + how: tmt +discover: + how: shell + tests: + - name: booted image + test: bootc status | grep localhost/tmtmodified +provision: + how: bootc + add-deps: true + containerimage: localhost/tmt-bootc-needs-deps + disk: 20 diff --git a/tests/provision/bootc/data/includes_deps.containerfile b/tests/provision/bootc/data/includes_deps.containerfile new file mode 100644 index 0000000000..76f6694cba --- /dev/null +++ b/tests/provision/bootc/data/includes_deps.containerfile @@ -0,0 +1,6 @@ +FROM quay.io/centos-bootc/centos-bootc:stream9 + +RUN dnf -y install cloud-init rsync && \ + ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants && \ + rm /usr/local -rf && ln -sr /var/usrlocal /usr/local && mkdir -p /var/usrlocal/bin && \ + dnf clean all diff --git a/tests/provision/bootc/data/needs_deps.containerfile b/tests/provision/bootc/data/needs_deps.containerfile new file mode 100644 index 0000000000..1498df8767 --- /dev/null +++ b/tests/provision/bootc/data/needs_deps.containerfile @@ -0,0 +1 @@ +FROM quay.io/centos-bootc/centos-bootc:stream9 diff --git a/tests/provision/bootc/main.fmf b/tests/provision/bootc/main.fmf new file mode 100644 index 0000000000..82b71bbcb4 --- /dev/null +++ b/tests/provision/bootc/main.fmf @@ -0,0 +1,6 @@ +summary: Make sure that bootc provision method works +tag+: + - provision-only + - provision-bootc +require: + - tmt+provision-virtual diff --git a/tests/provision/bootc/test.sh b/tests/provision/bootc/test.sh new file mode 100755 index 0000000000..9ad8556446 --- /dev/null +++ b/tests/provision/bootc/test.sh @@ -0,0 +1,63 @@ +#!/bin/bash +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +IMAGE_NEEDS_DEPS="localhost/tmt-bootc-needs-deps" +IMAGE_NEEDS_DEPS_PLAN="$(pwd)/data/image_needs_deps.fmf" +IMAGE_INCLUDES_DEPS="localhost/tmt-bootc-includes-deps" +IMAGE_INCLUDES_DEPS_PLAN="$(pwd)/data/image_includes_deps.fmf" + +CONTAINERFILE_NEEDS_DEPS="$(pwd)/data/needs_deps.containerfile" +CONTAINERFILE_NEEDS_DEPS_PLAN="$(pwd)/data/containerfile_needs_deps.fmf" +CONTAINERFILE_INCLUDES_DEPS="$(pwd)/data/includes_deps.containerfile" +CONTAINERFILE_INCLUDES_DEPS_PLAN="$(pwd)/data/containerfile_includes_deps.fmf" + + +rlJournalStart + rlPhaseStartSetup + # cleanup previous runs + test -d /var/tmp/tmt/testcloud && rlRun "rm -rf /var/tmp/tmt/testcloud" + + # use /var/tmp/tmt so the temp directories are accessible + # in the podman machine mount + rlRun "tmp=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create tmp directory" + rlRun "run=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create run directory" + rlRun "pushd $tmp" + rlRun "set -o pipefail" + rlRun "tmt init" + rlPhaseEnd + + rlPhaseStartTest "Image that needs dependencies" + rlRun "podman build . -f $CONTAINERFILE_NEEDS_DEPS -t $IMAGE_NEEDS_DEPS" + rlRun "cp $IMAGE_NEEDS_DEPS_PLAN ." + rlRun "tmt -vvvvv run -i $run" + rlPhaseEnd + + rlPhaseStartTest "Image that already includes dependencies" + rlRun "podman build . -f $CONTAINERFILE_INCLUDES_DEPS -t $IMAGE_INCLUDES_DEPS" + rlRun "cp $IMAGE_INCLUDES_DEPS_PLAN ." + rlRun "tmt -vvvvv run -i $run" + rlPhaseEnd + + rlPhaseStartTest "Containerfile that needs dependencies" + rlRun "cp $CONTAINERFILE_NEEDS_DEPS_PLAN ." + rlRun "cp $CONTAINERFILE_NEEDS_DEPS $run" + rlRun "tmt -vvvvv run --environment TMT_BOOTC_CONTAINERFILE_RUNDIR=$run -i $run" + rlPhaseEnd + + rlPhaseStartTest "Containerfile that already includes dependencies" + rlRun "cp $CONTAINERFILE_INCLUDES_DEPS_PLAN ." + rlRun "cp $CONTAINERFILE_INCLUDES_DEPS $run" + rlRun "tmt -vvvvv run --environment TMT_BOOTC_CONTAINERFILE_RUNDIR=$run -i $run" + rlPhaseEnd + + rlPhaseStartCleanup + rlRun "popd" + rlRun "rm -r $tmp" 0 "Remove tmp directory" + rlRun "rm -r $run" 0 "Remove run directory" + + rlRun "podman rmi $IMAGE_INCLUDES_DEPS" 0,1 + rlRun "podman rmi $IMAGE_NEEDS_DEPS" 0,1 + + test -d /var/tmp/tmt/testcloud && rlRun "rm -rf /var/tmp/tmt/testcloud" + rlPhaseEnd +rlJournalEnd diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py new file mode 100644 index 0000000000..e0791b4f7a --- /dev/null +++ b/tmt/steps/provision/bootc.py @@ -0,0 +1,242 @@ +import dataclasses +import os +from typing import Optional, cast + +import tmt +import tmt.base +import tmt.log +import tmt.steps +import tmt.steps.provision +import tmt.steps.provision.testcloud +import tmt.utils +from tmt.steps.provision.testcloud import GuestTestcloud +from tmt.utils import field +from tmt.utils.templates import render_template + +DEFAULT_IMAGE_BUILDER = "quay.io/centos-bootc/bootc-image-builder:latest" +CONTAINER_STORAGE_DIR = tmt.utils.Path("/var/lib/containers/storage") + + +class GuestBootc(GuestTestcloud): + containerimage: str + + def __init__(self, + *, + data: tmt.steps.provision.GuestData, + name: Optional[str] = None, + parent: Optional[tmt.utils.Common] = None, + logger: tmt.log.Logger, + containerimage: str) -> None: + super().__init__(data=data, logger=logger, parent=parent, name=name) + self.containerimage = containerimage + + def remove(self) -> None: + tmt.utils.Command( + "podman", "rmi", self.containerimage + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + + super().remove() + + +@dataclasses.dataclass +class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData): + containerfile: Optional[str] = field( + default=None, + option='--containerfile', + metavar='CONTAINERFILE', + help=""" + Select container file to be used to build a container image + that is then used by bootc image builder to create a disk image. + + Cannot be used with containerimage. + """) + + containerfile_workdir: str = field( + default=".", + option=('--containerfile-workdir'), + metavar='CONTAINERFILE_WORKDIR', + help=""" + Select working directory for the podman build invocation. + """) + + containerimage: Optional[str] = field( + default=None, + option=('--containerimage'), + metavar='CONTAINERIMAGE', + help=""" + Select container image to be used to build a bootc disk. + This takes priority over containerfile. + """) + + add_deps: bool = field( + default=True, + is_flag=True, + option=('--add-deps'), + help=""" + Add tmt dependencies to the supplied container image or image built + from the supplied Containerfile. + This will cause a derived image to be built from the supplied image. + """) + + image_builder: str = field( + default=DEFAULT_IMAGE_BUILDER, + option=('--image-builder'), + metavar='IMAGEBUILDER', + help=""" + The full repo:tag url of the bootc image builder image to use for + building the bootc disk image. + """) + + +@tmt.steps.provides_method('bootc') +class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): + """ + Provision a local virtual machine using a bootc container image + + Minimal config which uses the Fedora bootc image: + + .. code-block:: yaml + + provision: + how: bootc + containerimage: quay.io/fedora/fedora-bootc:40 + + Here's a config example using a containerfile: + + .. code-block:: yaml + + provision: + how: bootc + containerfile: "./my-custom-image.containerfile" + containerfile-workdir: . + image_builder: quay.io/centos-bootc/bootc-image-builder:stream9 + disk: 100 + + Another config example using an image that includes tmt dependencies: + + .. code-block:: yaml + + provision: + how: bootc + add_deps: false + containerimage: localhost/my-image-with-deps + + This plugin is an extension of the virtual.testcloud plugin. + Essentially, it takes a container image as input, builds a + bootc disk image from the container image, then uses the virtual.testcloud + plugin to create a virtual machine using the bootc disk image. + + The bootc disk creation requires running podman as root, this is typically + done by running the command in a rootful podman-machine. The podman-machine + also needs access to ``/var/tmp/tmt``. An example command to initialize the + machine: + + .. code-block:: shell + + podman machine init --rootful --disk-size 200 --memory 8192 \ + --cpus 8 -v /var/tmp/tmt:/var/tmp/tmt -v $HOME:$HOME + """ + + _data_class = BootcData + _guest_class = GuestTestcloud + _guest = None + + def _get_id(self) -> str: + # FIXME: cast() - https://github.com/teemtee/tmt/issues/1372 + parent = cast(tmt.steps.provision.Provision, self.parent) + assert parent.plan is not None + assert parent.plan.my_run is not None + assert parent.plan.my_run.unique_id is not None + return parent.plan.my_run.unique_id + + def _expand_path(self, relative_path: str) -> str: + """ Expand the path to the full path relative to the current working dir """ + if relative_path.startswith("/"): + return relative_path + return f"{os.getcwd()}/{relative_path}" + + def _build_derived_image(self, base_image: str) -> str: + """ Build a "derived" container image from the base image with tmt dependencies added """ + assert self.workdir is not None # narrow type + + self._logger.debug("Building modified container image with necessary tmt packages/config") + containerfile_template = ''' + FROM {{ base_image }} + + RUN \ + dnf -y install cloud-init rsync && \ + ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants && \ + rm /usr/local -rf && ln -sr /var/usrlocal /usr/local && mkdir -p /var/usrlocal/bin && \ + dnf clean all + ''' + containerfile_parsed = render_template( + containerfile_template, + base_image=base_image) + (self.workdir / 'Containerfile').write_text(containerfile_parsed) + + image_tag = f'localhost/tmtmodified-{self._get_id()}' + tmt.utils.Command( + "podman", "build", f'{self.workdir}', + "-f", f'{self.workdir}/Containerfile', + "-t", image_tag + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + + return image_tag + + def _build_base_image(self, containerfile: str, workdir: str) -> str: + """ Build the "base" or user supplied container image """ + image_tag = f'localhost/tmtbase-{self._get_id()}' + self._logger.debug("Building container image") + tmt.utils.Command( + "podman", "build", self._expand_path(workdir), + "-f", self._expand_path(containerfile), + "-t", image_tag + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + return image_tag + + def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None: + """ Build the bootc disk from a container image using bootc image builder """ + self._logger.debug("Building bootc disk image") + tmt.utils.Command( + "podman", "run", "--rm", "--privileged", + "-v", f'{CONTAINER_STORAGE_DIR}:{CONTAINER_STORAGE_DIR}', + "--security-opt", "label=type:unconfined_t", + "-v", f"{self.workdir}:/output", + image_builder, "build", + "--type", "qcow2", + "--local", containerimage + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + + def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: + """ Provision the bootc instance """ + super().go(logger=logger) + + data = BootcData.from_plugin(self) + data.image = f"file://{self.workdir}/qcow2/disk.qcow2" + data.show(verbose=self.verbosity_level, logger=self._logger) + + if data.containerimage is not None: + containerimage = data.containerimage + if data.add_deps: + containerimage = self._build_derived_image(data.containerimage) + self._build_bootc_disk(containerimage, data.image_builder) + elif data.containerfile is not None: + containerimage = self._build_base_image(data.containerfile, data.containerfile_workdir) + if data.add_deps: + containerimage = self._build_derived_image(containerimage) + self._build_bootc_disk(containerimage, data.image_builder) + else: + raise tmt.utils.ProvisionError( + "Either containerfile or containerimage must be specified.") + + self._guest = GuestBootc( + logger=self._logger, + data=data, + name=self.name, + parent=self.step, + containerimage=containerimage) + self._guest.start() + self._guest.setup() + + def guest(self) -> Optional[tmt.steps.provision.Guest]: + return self._guest From 611596625179431d460733d5dd154da8cd9e2c0b Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Fri, 25 Oct 2024 11:43:46 -0400 Subject: [PATCH 02/12] bootc: Add schema for bootc plugin Signed-off-by: Chris Kyrouac --- pyproject.toml | 2 + tmt.spec | 11 +++++ tmt/schemas/provision/bootc.yaml | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 tmt/schemas/provision/bootc.yaml diff --git a/pyproject.toml b/pyproject.toml index 0f125de355..8785f609f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ provision-virtual = [ "testcloud>=0.11.3", ] provision-container = [] +provision-bootc = [] report-junit = [ # Required to support XML parsing and checking the XSD schemas. "lxml>=4.6.5", @@ -75,6 +76,7 @@ all = [ "tmt[test-convert]", "tmt[export-polarion]", "tmt[provision-container]", + "tmt[provision-bootc]", "tmt[provision-virtual]", "tmt[provision-beaker]", "tmt[report-junit]", diff --git a/tmt.spec b/tmt.spec index 3f70fd53e1..6728990135 100644 --- a/tmt.spec +++ b/tmt.spec @@ -90,6 +90,16 @@ Recommends: qemu-system-x86-core %description -n tmt+provision-virtual %_metapackage_description +%package -n tmt+provision-bootc +Summary: Dependencies required for tmt bootc machine provisioner +Provides: tmt-provision-bootc == %{version}-%{release} +Requires: tmt == %{version}-%{release} +Requires: tmt+provision-virtual == %{version}-%{release} +Requires: podman +Recommends: podman-machine + +%description -n tmt+provision-bootc %_metapackage_description + %package -n tmt+provision-beaker Summary: Dependencies required for tmt beaker provisioner Provides: tmt-provision-beaker == %{version}-%{release} @@ -152,6 +162,7 @@ install -pm 644 %{name}/steps/provision/mrack/mrack* %{buildroot}/etc/%{name}/ %files -n tmt+provision-container -f %{_pyproject_ghost_distinfo} %files -n tmt+provision-virtual -f %{_pyproject_ghost_distinfo} +%files -n tmt+provision-bootc -f %{_pyproject_ghost_distinfo} %files -n tmt+test-convert -f %{_pyproject_ghost_distinfo} %files -n tmt+provision-beaker -f %{_pyproject_ghost_distinfo} %config(noreplace) %{_sysconfdir}/%{name}/mrack* diff --git a/tmt/schemas/provision/bootc.yaml b/tmt/schemas/provision/bootc.yaml new file mode 100644 index 0000000000..a9fae5f055 --- /dev/null +++ b/tmt/schemas/provision/bootc.yaml @@ -0,0 +1,71 @@ +--- + +# +# JSON Schema definition for `bootc` provision plugin +# +# https://tmt.readthedocs.io/en/stable/spec/plans.html#bootc +# + +$id: /schemas/provision/bootc +$schema: https://json-schema.org/draft-07/schema + +type: object +additionalProperties: false + +properties: + + how: + type: string + enum: + - bootc + + name: + type: string + + image: + type: string + + user: + type: string + + become: + type: boolean + + key: + $ref: "/schemas/common#/definitions/one_or_more_strings" + + memory: + type: integer + + disk: + type: integer + + connection: + type: string + enum: + - session + - system + + arch: + $ref: "/schemas/common#/definitions/arch" + + role: + $ref: "/schemas/common#/definitions/role" + + containerfile: + type: string + + containerfile-workdir: + type: string + + containerimage: + type: string + + add-deps: + type: boolean + + image-builder: + type: string + +required: + - how From bbcd26435509f05f0e50d7481be2590bf3327d8b Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Fri, 25 Oct 2024 11:20:47 -0400 Subject: [PATCH 03/12] bootc: Automatically start podman-machine When the podman connection is rootless=True, this will automatically start a new rootful podman-machine to be used for the bootc disk creation. Signed-off-by: Chris Kyrouac --- tmt/steps/provision/bootc.py | 164 ++++++++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 29 deletions(-) diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py index e0791b4f7a..eee3c7a180 100644 --- a/tmt/steps/provision/bootc.py +++ b/tmt/steps/provision/bootc.py @@ -1,9 +1,10 @@ import dataclasses import os -from typing import Optional, cast +from typing import TYPE_CHECKING, Optional, cast import tmt import tmt.base +import tmt.hardware import tmt.log import tmt.steps import tmt.steps.provision @@ -13,12 +14,44 @@ from tmt.utils import field from tmt.utils.templates import render_template +if TYPE_CHECKING: + from tmt.hardware import Size + +DEFAULT_TMP_PATH = "/var/tmp/tmt" # noqa: S108 + DEFAULT_IMAGE_BUILDER = "quay.io/centos-bootc/bootc-image-builder:latest" CONTAINER_STORAGE_DIR = tmt.utils.Path("/var/lib/containers/storage") +PODMAN_MACHINE_NAME = 'podman-machine-tmt' +PODMAN_ENV = tmt.utils.Environment.from_dict( + {"CONTAINER_CONNECTION": f'{PODMAN_MACHINE_NAME}-root'}) + +DEFAULT_PODMAN_MACHINE_CPU = 2 +DEFAULT_PODMAN_MACHINE_MEM: 'Size' = tmt.hardware.UNITS('2048 MB') +DEFAULT_PODMAN_MACHINE_DISK_SIZE: 'Size' = tmt.hardware.UNITS('50 GB') + +CONTAINER_TEMPLATE = """ +FROM {{ base_image }} + +RUN <> /etc/environment + +EOF +""" + class GuestBootc(GuestTestcloud): containerimage: str + _rootless: bool def __init__(self, *, @@ -26,14 +59,29 @@ def __init__(self, name: Optional[str] = None, parent: Optional[tmt.utils.Common] = None, logger: tmt.log.Logger, - containerimage: str) -> None: + containerimage: str, + rootless: bool) -> None: super().__init__(data=data, logger=logger, parent=parent, name=name) self.containerimage = containerimage + self._rootless = rootless def remove(self) -> None: tmt.utils.Command( - "podman", "rmi", self.containerimage - ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + "podman", + "rmi", + self.containerimage).run( + cwd=self.workdir, + stream_output=True, + logger=self._logger, + env=PODMAN_ENV if self._rootless else None) + + try: + tmt.utils.Command( + "podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + except BaseException: + self._logger.debug( + "Unable to remove podman machine {PODMAN_MACHINE_NAME}, it might not exist") super().remove() @@ -126,20 +174,15 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): bootc disk image from the container image, then uses the virtual.testcloud plugin to create a virtual machine using the bootc disk image. - The bootc disk creation requires running podman as root, this is typically - done by running the command in a rootful podman-machine. The podman-machine - also needs access to ``/var/tmp/tmt``. An example command to initialize the - machine: - - .. code-block:: shell - - podman machine init --rootful --disk-size 200 --memory 8192 \ - --cpus 8 -v /var/tmp/tmt:/var/tmp/tmt -v $HOME:$HOME + The bootc disk creation requires running podman as root. The plugin will + automatically check if the current podman connection is rootless. If it is, + a podman machine will be spun up and used to build the bootc disk. """ _data_class = BootcData _guest_class = GuestTestcloud _guest = None + _rootless = True def _get_id(self) -> str: # FIXME: cast() - https://github.com/teemtee/tmt/issues/1372 @@ -176,10 +219,17 @@ def _build_derived_image(self, base_image: str) -> str: image_tag = f'localhost/tmtmodified-{self._get_id()}' tmt.utils.Command( - "podman", "build", f'{self.workdir}', - "-f", f'{self.workdir}/Containerfile', - "-t", image_tag - ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + "podman", + "build", + f'{self.workdir}', + "-f", + f'{self.workdir}/Containerfile', + "-t", + image_tag).run( + cwd=self.workdir, + stream_output=True, + logger=self._logger, + env=PODMAN_ENV if self._rootless else None) return image_tag @@ -188,33 +238,88 @@ def _build_base_image(self, containerfile: str, workdir: str) -> str: image_tag = f'localhost/tmtbase-{self._get_id()}' self._logger.debug("Building container image") tmt.utils.Command( - "podman", "build", self._expand_path(workdir), - "-f", self._expand_path(containerfile), - "-t", image_tag - ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + "podman", + "build", + self._expand_path(workdir), + "-f", + self._expand_path(containerfile), + "-t", + image_tag).run( + cwd=self.workdir, + stream_output=True, + logger=self._logger, + env=PODMAN_ENV if self._rootless else None) return image_tag def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None: """ Build the bootc disk from a container image using bootc image builder """ self._logger.debug("Building bootc disk image") + + tmt.utils.Command( + "podman", + "run", + "--rm", + "--privileged", + "-v", + f'{CONTAINER_STORAGE_DIR}:{CONTAINER_STORAGE_DIR}', + "--security-opt", + "label=type:unconfined_t", + "-v", + f"{self.workdir}:/output", + image_builder, + "build", + "--type", + "qcow2", + "--local", + containerimage).run( + cwd=self.workdir, + stream_output=True, + logger=self._logger, + env=PODMAN_ENV if self._rootless else None) + + def _init_podman_machine(self) -> None: + try: + tmt.utils.Command( + "podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + except BaseException: + self._logger.debug("Unable to remove existing podman machine (it might not exist)") + + self._logger.debug("Initializing podman machine") tmt.utils.Command( - "podman", "run", "--rm", "--privileged", - "-v", f'{CONTAINER_STORAGE_DIR}:{CONTAINER_STORAGE_DIR}', - "--security-opt", "label=type:unconfined_t", - "-v", f"{self.workdir}:/output", - image_builder, "build", - "--type", "qcow2", - "--local", containerimage + "podman", "machine", "init", "--rootful", + "--disk-size", f"{DEFAULT_PODMAN_MACHINE_DISK_SIZE.magnitude}", + "--memory", f"{DEFAULT_PODMAN_MACHINE_MEM.magnitude}", + "--cpus", f"{DEFAULT_PODMAN_MACHINE_CPU}", + "-v", f"{DEFAULT_TMP_PATH}:{DEFAULT_TMP_PATH}", + "-v", "$HOME:$HOME", + PODMAN_MACHINE_NAME ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + self._logger.debug("Starting podman machine") + tmt.utils.Command( + "podman", "machine", "start", PODMAN_MACHINE_NAME + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + + def _check_if_podman_is_rootless(self) -> None: + output = tmt.utils.Command( + "podman", "info", "--format", "{{.Host.Security.Rootless}}" + ).run(cwd=self.workdir, stream_output=True, logger=self._logger) + self._rootless = output.stdout == "true\n" + def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: """ Provision the bootc instance """ super().go(logger=logger) + self._check_if_podman_is_rootless() + data = BootcData.from_plugin(self) data.image = f"file://{self.workdir}/qcow2/disk.qcow2" data.show(verbose=self.verbosity_level, logger=self._logger) + if self._rootless: + self._init_podman_machine() + if data.containerimage is not None: containerimage = data.containerimage if data.add_deps: @@ -234,7 +339,8 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: data=data, name=self.name, parent=self.step, - containerimage=containerimage) + containerimage=containerimage, + rootless=self._rootless) self._guest.start() self._guest.setup() From 9384d13feb2f0e11c6be2ae445f5b81088bc64f6 Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Tue, 5 Nov 2024 10:59:19 -0500 Subject: [PATCH 04/12] bootc: Make temp directory in test setup Ensures the /var/tmp/tmt directory exists before creating the temp directories. This directory might not exist in the CI environment. It usually exists when running locally. Signed-off-by: Chris Kyrouac --- tests/provision/bootc/test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/provision/bootc/test.sh b/tests/provision/bootc/test.sh index 9ad8556446..c140962a67 100755 --- a/tests/provision/bootc/test.sh +++ b/tests/provision/bootc/test.sh @@ -19,6 +19,7 @@ rlJournalStart # use /var/tmp/tmt so the temp directories are accessible # in the podman machine mount + rlRun "mkdir -p /var/tmp/tmt" rlRun "tmp=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create tmp directory" rlRun "run=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create run directory" rlRun "pushd $tmp" From 4fe280c308bc69c13354a1589dfbeadcc943326a Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Mon, 11 Nov 2024 11:38:41 -0500 Subject: [PATCH 05/12] bootc: Start libvirtd during prepare step of tests Signed-off-by: Chris Kyrouac --- plans/provision/bootc.fmf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plans/provision/bootc.fmf b/plans/provision/bootc.fmf index 15885d415e..6944f36b18 100644 --- a/plans/provision/bootc.fmf +++ b/plans/provision/bootc.fmf @@ -7,6 +7,11 @@ discover: how: fmf filter: 'tag:provision-bootc' +prepare+: + - name: start-libvirtd + script: | + systemctl start libvirtd + systemctl status libvirtd environment: PROVISION_HOW: virtual From a4c331c629788e93b037f37ef1ecc6159c94c22a Mon Sep 17 00:00:00 2001 From: Chris Kyrouac Date: Mon, 18 Nov 2024 15:44:12 -0500 Subject: [PATCH 06/12] bootc: Rename variables to use - as separator Signed-off-by: Chris Kyrouac --- .../data/containerfile_includes_deps.fmf | 6 +- .../bootc/data/containerfile_needs_deps.fmf | 6 +- .../bootc/data/image_includes_deps.fmf | 4 +- .../provision/bootc/data/image_needs_deps.fmf | 4 +- tmt/schemas/provision/bootc.yaml | 8 +-- tmt/steps/provision/bootc.py | 55 ++++++++++--------- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/tests/provision/bootc/data/containerfile_includes_deps.fmf b/tests/provision/bootc/data/containerfile_includes_deps.fmf index 2e5e9bcef7..754b88386b 100644 --- a/tests/provision/bootc/data/containerfile_includes_deps.fmf +++ b/tests/provision/bootc/data/containerfile_includes_deps.fmf @@ -7,7 +7,7 @@ discover: test: bootc status && bootc status | grep localhost/tmtbase provision: how: bootc - add-deps: false - containerfile: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/includes_deps.containerfile" - containerfile-workdir: . + add-tmt-dependencies: false + container-file: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/includes_deps.containerfile" + container-file-workdir: . disk: 20 diff --git a/tests/provision/bootc/data/containerfile_needs_deps.fmf b/tests/provision/bootc/data/containerfile_needs_deps.fmf index d188cc82f1..5270d54e20 100644 --- a/tests/provision/bootc/data/containerfile_needs_deps.fmf +++ b/tests/provision/bootc/data/containerfile_needs_deps.fmf @@ -7,7 +7,7 @@ discover: test: bootc status | grep localhost/tmtmodified provision: how: bootc - add-deps: true - containerfile: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/needs_deps.containerfile" - containerfile-workdir: . + add-tmt-dependencies: true + container-file: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/needs_deps.containerfile" + container-file-workdir: . disk: 20 diff --git a/tests/provision/bootc/data/image_includes_deps.fmf b/tests/provision/bootc/data/image_includes_deps.fmf index 981b946709..e856e0c2ef 100644 --- a/tests/provision/bootc/data/image_includes_deps.fmf +++ b/tests/provision/bootc/data/image_includes_deps.fmf @@ -7,6 +7,6 @@ discover: test: bootc status | grep localhost/tmt-bootc-includes-deps provision: how: bootc - add-deps: false - containerimage: localhost/tmt-bootc-includes-deps + add-tmt-dependencies: false + container-image: localhost/tmt-bootc-includes-deps disk: 20 diff --git a/tests/provision/bootc/data/image_needs_deps.fmf b/tests/provision/bootc/data/image_needs_deps.fmf index af992ba9ba..a56fcfad98 100644 --- a/tests/provision/bootc/data/image_needs_deps.fmf +++ b/tests/provision/bootc/data/image_needs_deps.fmf @@ -7,6 +7,6 @@ discover: test: bootc status | grep localhost/tmtmodified provision: how: bootc - add-deps: true - containerimage: localhost/tmt-bootc-needs-deps + add-tmt-dependencies: true + container-image: localhost/tmt-bootc-needs-deps disk: 20 diff --git a/tmt/schemas/provision/bootc.yaml b/tmt/schemas/provision/bootc.yaml index a9fae5f055..e7cafc3fe2 100644 --- a/tmt/schemas/provision/bootc.yaml +++ b/tmt/schemas/provision/bootc.yaml @@ -52,16 +52,16 @@ properties: role: $ref: "/schemas/common#/definitions/role" - containerfile: + container-file: type: string - containerfile-workdir: + container-file-workdir: type: string - containerimage: + container-image: type: string - add-deps: + add-tmt-dependencies: type: boolean image-builder: diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py index eee3c7a180..10725e25c8 100644 --- a/tmt/steps/provision/bootc.py +++ b/tmt/steps/provision/bootc.py @@ -81,45 +81,45 @@ def remove(self) -> None: ).run(cwd=self.workdir, stream_output=True, logger=self._logger) except BaseException: self._logger.debug( - "Unable to remove podman machine {PODMAN_MACHINE_NAME}, it might not exist") + "Unable to remove podman machine '{PODMAN_MACHINE_NAME}', it might not exist.") super().remove() @dataclasses.dataclass class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData): - containerfile: Optional[str] = field( + container_file: Optional[str] = field( default=None, - option='--containerfile', - metavar='CONTAINERFILE', + option='--container-file', + metavar='CONTAINER_FILE', help=""" Select container file to be used to build a container image that is then used by bootc image builder to create a disk image. - Cannot be used with containerimage. + Cannot be used with container-image. """) - containerfile_workdir: str = field( + container_file_workdir: str = field( default=".", - option=('--containerfile-workdir'), - metavar='CONTAINERFILE_WORKDIR', + option=('--container-file-workdir'), + metavar='CONTAINER_FILE_WORKDIR', help=""" Select working directory for the podman build invocation. """) - containerimage: Optional[str] = field( + container_image: Optional[str] = field( default=None, - option=('--containerimage'), - metavar='CONTAINERIMAGE', + option=('--container-image'), + metavar='CONTAINER_IMAGE', help=""" Select container image to be used to build a bootc disk. This takes priority over containerfile. """) - add_deps: bool = field( + add_tmt_dependencies: bool = field( default=True, is_flag=True, - option=('--add-deps'), + option=('--add-tmt-dependencies'), help=""" Add tmt dependencies to the supplied container image or image built from the supplied Containerfile. @@ -129,7 +129,7 @@ class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData): image_builder: str = field( default=DEFAULT_IMAGE_BUILDER, option=('--image-builder'), - metavar='IMAGEBUILDER', + metavar='IMAGE_BUILDER', help=""" The full repo:tag url of the bootc image builder image to use for building the bootc disk image. @@ -155,9 +155,9 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): provision: how: bootc - containerfile: "./my-custom-image.containerfile" - containerfile-workdir: . - image_builder: quay.io/centos-bootc/bootc-image-builder:stream9 + container-file: "./my-custom-image.containerfile" + container-file-workdir: . + image-builder: quay.io/centos-bootc/bootc-image-builder:stream9 disk: 100 Another config example using an image that includes tmt dependencies: @@ -166,8 +166,8 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): provision: how: bootc - add_deps: false - containerimage: localhost/my-image-with-deps + add-deps: false + container-image: localhost/my-image-with-deps This plugin is an extension of the virtual.testcloud plugin. Essentially, it takes a container image as input, builds a @@ -320,19 +320,20 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: if self._rootless: self._init_podman_machine() - if data.containerimage is not None: - containerimage = data.containerimage - if data.add_deps: - containerimage = self._build_derived_image(data.containerimage) + if data.container_image is not None: + containerimage = data.container_image + if data.add_tmt_dependencies: + containerimage = self._build_derived_image(data.container_image) self._build_bootc_disk(containerimage, data.image_builder) - elif data.containerfile is not None: - containerimage = self._build_base_image(data.containerfile, data.containerfile_workdir) - if data.add_deps: + elif data.container_file is not None: + containerimage = self._build_base_image( + data.container_file, data.container_file_workdir) + if data.add_tmt_dependencies: containerimage = self._build_derived_image(containerimage) self._build_bootc_disk(containerimage, data.image_builder) else: raise tmt.utils.ProvisionError( - "Either containerfile or containerimage must be specified.") + "Either container-file or container-image must be specified.") self._guest = GuestBootc( logger=self._logger, From 40c2974821bf1e09f482052ab0974bfe7a5cdd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Fri, 15 Nov 2024 13:43:08 +0100 Subject: [PATCH 07/12] Add a short release note, mark avc failure as expected --- docs/releases.rst | 6 ++++++ tests/provision/bootc/main.fmf | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/releases.rst b/docs/releases.rst index e5b5075d88..821a2a2b57 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -20,6 +20,12 @@ environment files are found. The ``tmt try`` command now supports the new :ref:`/stories/cli/try/option/arch` option. +As a tech preview, a new :ref:`/plugins/provision/bootc` provision +plugin has been implemented. It takes a container image as input, +builds a bootc disk image from the container image, then uses the +:ref:`/plugins/provision/virtual.testcloud` plugin to create a +virtual machine using the bootc disk image. + tmt-1.38.0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/provision/bootc/main.fmf b/tests/provision/bootc/main.fmf index 82b71bbcb4..c3567096dd 100644 --- a/tests/provision/bootc/main.fmf +++ b/tests/provision/bootc/main.fmf @@ -4,3 +4,9 @@ tag+: - provision-bootc require: - tmt+provision-virtual + +# As for now there is an expected AVC failure: +# https://github.com/osbuild/bootc-image-builder/issues/645 +check: + - how: avc + result: xfail From e830fb11404cd7c96073ef189e72959fea4fe205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Tue, 19 Nov 2024 23:02:09 +0100 Subject: [PATCH 08/12] Run the bootc plan under the `provision` packit job --- .packit.yaml | 2 +- plans/provision/bootc.fmf | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.packit.yaml b/.packit.yaml index 5908b020a5..d9e84677be 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -93,7 +93,7 @@ jobs: tf_extra_params: test: tmt: - name: /plans/provision/virtual + name: /plans/provision/(bootc|virtual) environments: - tmt: context: diff --git a/plans/provision/bootc.fmf b/plans/provision/bootc.fmf index 6944f36b18..e5fc819c56 100644 --- a/plans/provision/bootc.fmf +++ b/plans/provision/bootc.fmf @@ -1,7 +1,7 @@ summary: Bootc virtual machine via testcloud description: | - bootc tests + Verify functionality of the bootc provision plugin. discover: how: fmf @@ -13,9 +13,6 @@ prepare+: systemctl start libvirtd systemctl status libvirtd -environment: - PROVISION_HOW: virtual - adjust+: - enabled: true when: how == provision @@ -35,5 +32,3 @@ adjust+: - sysctl -w net.ipv6.conf.default.disable_ipv6=1 because: Disable IPv6 in CI to avoid IPv6 connections that are disabled in CI when: trigger == commit - -enabled: true From 2963385ce287e08ed551b05664917f42a5bd09a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Wed, 20 Nov 2024 19:27:42 +0100 Subject: [PATCH 09/12] Simplify and fix tests, several minor adjustments Make sure that all test cases are actually executed. Run tests directly in the `data` directory. Simplify plans using inheritance. Plus some minor adjustments. --- .../data/containerfile_includes_deps.fmf | 13 ------ .../bootc/data/containerfile_needs_deps.fmf | 13 ------ .../bootc/data/image_includes_deps.fmf | 12 ----- .../provision/bootc/data/image_needs_deps.fmf | 12 ----- ...tainerfile => includes-deps.containerfile} | 0 ...containerfile => needs-deps.containerfile} | 0 tests/provision/bootc/data/plans.fmf | 45 +++++++++++++++++++ tests/provision/bootc/data/test.fmf | 2 + tests/provision/bootc/test.sh | 43 ++++++------------ tmt/steps/provision/bootc.py | 30 ++++++++----- 10 files changed, 78 insertions(+), 92 deletions(-) delete mode 100644 tests/provision/bootc/data/containerfile_includes_deps.fmf delete mode 100644 tests/provision/bootc/data/containerfile_needs_deps.fmf delete mode 100644 tests/provision/bootc/data/image_includes_deps.fmf delete mode 100644 tests/provision/bootc/data/image_needs_deps.fmf rename tests/provision/bootc/data/{includes_deps.containerfile => includes-deps.containerfile} (100%) rename tests/provision/bootc/data/{needs_deps.containerfile => needs-deps.containerfile} (100%) create mode 100644 tests/provision/bootc/data/plans.fmf create mode 100644 tests/provision/bootc/data/test.fmf diff --git a/tests/provision/bootc/data/containerfile_includes_deps.fmf b/tests/provision/bootc/data/containerfile_includes_deps.fmf deleted file mode 100644 index 754b88386b..0000000000 --- a/tests/provision/bootc/data/containerfile_includes_deps.fmf +++ /dev/null @@ -1,13 +0,0 @@ -execute: - how: tmt -discover: - how: shell - tests: - - name: booted image - test: bootc status && bootc status | grep localhost/tmtbase -provision: - how: bootc - add-tmt-dependencies: false - container-file: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/includes_deps.containerfile" - container-file-workdir: . - disk: 20 diff --git a/tests/provision/bootc/data/containerfile_needs_deps.fmf b/tests/provision/bootc/data/containerfile_needs_deps.fmf deleted file mode 100644 index 5270d54e20..0000000000 --- a/tests/provision/bootc/data/containerfile_needs_deps.fmf +++ /dev/null @@ -1,13 +0,0 @@ -execute: - how: tmt -discover: - how: shell - tests: - - name: booted image - test: bootc status | grep localhost/tmtmodified -provision: - how: bootc - add-tmt-dependencies: true - container-file: "$TMT_BOOTC_CONTAINERFILE_RUNDIR/needs_deps.containerfile" - container-file-workdir: . - disk: 20 diff --git a/tests/provision/bootc/data/image_includes_deps.fmf b/tests/provision/bootc/data/image_includes_deps.fmf deleted file mode 100644 index e856e0c2ef..0000000000 --- a/tests/provision/bootc/data/image_includes_deps.fmf +++ /dev/null @@ -1,12 +0,0 @@ -execute: - how: tmt -discover: - how: shell - tests: - - name: booted image - test: bootc status | grep localhost/tmt-bootc-includes-deps -provision: - how: bootc - add-tmt-dependencies: false - container-image: localhost/tmt-bootc-includes-deps - disk: 20 diff --git a/tests/provision/bootc/data/image_needs_deps.fmf b/tests/provision/bootc/data/image_needs_deps.fmf deleted file mode 100644 index a56fcfad98..0000000000 --- a/tests/provision/bootc/data/image_needs_deps.fmf +++ /dev/null @@ -1,12 +0,0 @@ -execute: - how: tmt -discover: - how: shell - tests: - - name: booted image - test: bootc status | grep localhost/tmtmodified -provision: - how: bootc - add-tmt-dependencies: true - container-image: localhost/tmt-bootc-needs-deps - disk: 20 diff --git a/tests/provision/bootc/data/includes_deps.containerfile b/tests/provision/bootc/data/includes-deps.containerfile similarity index 100% rename from tests/provision/bootc/data/includes_deps.containerfile rename to tests/provision/bootc/data/includes-deps.containerfile diff --git a/tests/provision/bootc/data/needs_deps.containerfile b/tests/provision/bootc/data/needs-deps.containerfile similarity index 100% rename from tests/provision/bootc/data/needs_deps.containerfile rename to tests/provision/bootc/data/needs-deps.containerfile diff --git a/tests/provision/bootc/data/plans.fmf b/tests/provision/bootc/data/plans.fmf new file mode 100644 index 0000000000..f5e1560a2f --- /dev/null +++ b/tests/provision/bootc/data/plans.fmf @@ -0,0 +1,45 @@ +discover: + how: fmf +provision: + how: bootc + disk: 20 +execute: + how: tmt + + +/image: + + /needs-deps: + summary: "Image that needs dependencies" + provision+: + add-tmt-dependencies: true + container-image: localhost/tmt-bootc-needs-deps + environment: + PATTERN: localhost/tmtmodified + + /includes-deps: + summary: "Image that already includes dependencies" + provision+: + add-tmt-dependencies: false + container-image: localhost/tmt-bootc-includes-deps + environment: + PATTERN: localhost/tmt-bootc-includes-deps + +/containerfile: + + /needs-deps: + summary: "Containerfile that needs dependencies" + provision+: + add-tmt-dependencies: true + container-file: needs-deps.containerfile + environment: + PATTERN: localhost/tmtmodified + + /includes-deps: + summary: "Containerfile that already includes dependencies" + provision: + how: bootc + add-tmt-dependencies: false + container-file: includes-deps.containerfile + environment: + PATTERN: localhost/tmtbase diff --git a/tests/provision/bootc/data/test.fmf b/tests/provision/bootc/data/test.fmf new file mode 100644 index 0000000000..62cb056140 --- /dev/null +++ b/tests/provision/bootc/data/test.fmf @@ -0,0 +1,2 @@ +summary: Check that booted image matches expected pattern +test: "bootc status && bootc status | grep $PATTERN" diff --git a/tests/provision/bootc/test.sh b/tests/provision/bootc/test.sh index c140962a67..b856de7ac4 100755 --- a/tests/provision/bootc/test.sh +++ b/tests/provision/bootc/test.sh @@ -2,63 +2,46 @@ . /usr/share/beakerlib/beakerlib.sh || exit 1 IMAGE_NEEDS_DEPS="localhost/tmt-bootc-needs-deps" -IMAGE_NEEDS_DEPS_PLAN="$(pwd)/data/image_needs_deps.fmf" IMAGE_INCLUDES_DEPS="localhost/tmt-bootc-includes-deps" -IMAGE_INCLUDES_DEPS_PLAN="$(pwd)/data/image_includes_deps.fmf" -CONTAINERFILE_NEEDS_DEPS="$(pwd)/data/needs_deps.containerfile" -CONTAINERFILE_NEEDS_DEPS_PLAN="$(pwd)/data/containerfile_needs_deps.fmf" -CONTAINERFILE_INCLUDES_DEPS="$(pwd)/data/includes_deps.containerfile" -CONTAINERFILE_INCLUDES_DEPS_PLAN="$(pwd)/data/containerfile_includes_deps.fmf" +TESTCLOUD_IMAGE="/var/tmp/tmt/testcloud/images/disk.qcow2" rlJournalStart rlPhaseStartSetup - # cleanup previous runs - test -d /var/tmp/tmt/testcloud && rlRun "rm -rf /var/tmp/tmt/testcloud" - - # use /var/tmp/tmt so the temp directories are accessible + # Use /var/tmp/tmt so the temp directories are accessible # in the podman machine mount rlRun "mkdir -p /var/tmp/tmt" - rlRun "tmp=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create tmp directory" rlRun "run=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create run directory" - rlRun "pushd $tmp" - rlRun "set -o pipefail" - rlRun "tmt init" + rlRun "pushd data" rlPhaseEnd rlPhaseStartTest "Image that needs dependencies" - rlRun "podman build . -f $CONTAINERFILE_NEEDS_DEPS -t $IMAGE_NEEDS_DEPS" - rlRun "cp $IMAGE_NEEDS_DEPS_PLAN ." - rlRun "tmt -vvvvv run -i $run" + rlRun "podman build . -f needs-deps.containerfile -t $IMAGE_NEEDS_DEPS" + rlRun "tmt -vvv run --scratch -i $run plan --name /plans/image/needs-deps" + rlRun "rm -rf $TESTCLOUD_IMAGE" rlPhaseEnd rlPhaseStartTest "Image that already includes dependencies" - rlRun "podman build . -f $CONTAINERFILE_INCLUDES_DEPS -t $IMAGE_INCLUDES_DEPS" - rlRun "cp $IMAGE_INCLUDES_DEPS_PLAN ." - rlRun "tmt -vvvvv run -i $run" + rlRun "podman build . -f includes-deps.containerfile -t $IMAGE_INCLUDES_DEPS" + rlRun "tmt -vvv run --scratch -i $run plan --name /plans/image/includes-deps" + rlRun "rm -rf $TESTCLOUD_IMAGE" rlPhaseEnd rlPhaseStartTest "Containerfile that needs dependencies" - rlRun "cp $CONTAINERFILE_NEEDS_DEPS_PLAN ." - rlRun "cp $CONTAINERFILE_NEEDS_DEPS $run" - rlRun "tmt -vvvvv run --environment TMT_BOOTC_CONTAINERFILE_RUNDIR=$run -i $run" + rlRun "tmt -vvv run --scratch -i $run plan --name /plans/containerfile/needs-deps" + rlRun "rm -rf $TESTCLOUD_IMAGE" rlPhaseEnd rlPhaseStartTest "Containerfile that already includes dependencies" - rlRun "cp $CONTAINERFILE_INCLUDES_DEPS_PLAN ." - rlRun "cp $CONTAINERFILE_INCLUDES_DEPS $run" - rlRun "tmt -vvvvv run --environment TMT_BOOTC_CONTAINERFILE_RUNDIR=$run -i $run" + rlRun "tmt -vvv run --scratch -i $run plan --name /plans/containerfile/includes-deps" + rlRun "rm -rf $TESTCLOUD_IMAGE" rlPhaseEnd rlPhaseStartCleanup rlRun "popd" - rlRun "rm -r $tmp" 0 "Remove tmp directory" rlRun "rm -r $run" 0 "Remove run directory" - rlRun "podman rmi $IMAGE_INCLUDES_DEPS" 0,1 rlRun "podman rmi $IMAGE_NEEDS_DEPS" 0,1 - - test -d /var/tmp/tmt/testcloud && rlRun "rm -rf /var/tmp/tmt/testcloud" rlPhaseEnd rlJournalEnd diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py index 10725e25c8..dbe3ae50d1 100644 --- a/tmt/steps/provision/bootc.py +++ b/tmt/steps/provision/bootc.py @@ -113,7 +113,7 @@ class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData): metavar='CONTAINER_IMAGE', help=""" Select container image to be used to build a bootc disk. - This takes priority over containerfile. + This takes priority over container-file. """) add_tmt_dependencies: bool = field( @@ -141,13 +141,13 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): """ Provision a local virtual machine using a bootc container image - Minimal config which uses the Fedora bootc image: + Minimal config which uses the CentOS Stream 9 bootc image: .. code-block:: yaml provision: how: bootc - containerimage: quay.io/fedora/fedora-bootc:40 + container-image: quay.io/centos-bootc/centos-bootc:stream9 Here's a config example using a containerfile: @@ -160,13 +160,14 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): image-builder: quay.io/centos-bootc/bootc-image-builder:stream9 disk: 100 - Another config example using an image that includes tmt dependencies: + Another config example using an image that already includes tmt + dependencies: .. code-block:: yaml provision: how: bootc - add-deps: false + add-tmt-dependencies: false container-image: localhost/my-image-with-deps This plugin is an extension of the virtual.testcloud plugin. @@ -202,7 +203,7 @@ def _build_derived_image(self, base_image: str) -> str: """ Build a "derived" container image from the base image with tmt dependencies added """ assert self.workdir is not None # narrow type - self._logger.debug("Building modified container image with necessary tmt packages/config") + self._logger.debug("Build modified container image with necessary tmt packages/config.") containerfile_template = ''' FROM {{ base_image }} @@ -236,7 +237,7 @@ def _build_derived_image(self, base_image: str) -> str: def _build_base_image(self, containerfile: str, workdir: str) -> str: """ Build the "base" or user supplied container image """ image_tag = f'localhost/tmtbase-{self._get_id()}' - self._logger.debug("Building container image") + self._logger.debug("Build container image.") tmt.utils.Command( "podman", "build", @@ -253,7 +254,7 @@ def _build_base_image(self, containerfile: str, workdir: str) -> str: def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None: """ Build the bootc disk from a container image using bootc image builder """ - self._logger.debug("Building bootc disk image") + self._logger.debug("Build bootc disk image.") tmt.utils.Command( "podman", @@ -283,9 +284,9 @@ def _init_podman_machine(self) -> None: "podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME ).run(cwd=self.workdir, stream_output=True, logger=self._logger) except BaseException: - self._logger.debug("Unable to remove existing podman machine (it might not exist)") + self._logger.debug("Unable to remove existing podman machine (it might not exist).") - self._logger.debug("Initializing podman machine") + self._logger.debug("Initialize podman machine.") tmt.utils.Command( "podman", "machine", "init", "--rootful", "--disk-size", f"{DEFAULT_PODMAN_MACHINE_DISK_SIZE.magnitude}", @@ -296,7 +297,7 @@ def _init_podman_machine(self) -> None: PODMAN_MACHINE_NAME ).run(cwd=self.workdir, stream_output=True, logger=self._logger) - self._logger.debug("Starting podman machine") + self._logger.debug("Start podman machine.") tmt.utils.Command( "podman", "machine", "start", PODMAN_MACHINE_NAME ).run(cwd=self.workdir, stream_output=True, logger=self._logger) @@ -320,20 +321,25 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None: if self._rootless: self._init_podman_machine() + # Use provided container image if data.container_image is not None: containerimage = data.container_image if data.add_tmt_dependencies: containerimage = self._build_derived_image(data.container_image) self._build_bootc_disk(containerimage, data.image_builder) + + # Build image according to the container file elif data.container_file is not None: containerimage = self._build_base_image( data.container_file, data.container_file_workdir) if data.add_tmt_dependencies: containerimage = self._build_derived_image(containerimage) self._build_bootc_disk(containerimage, data.image_builder) + + # Image of file have to provided else: raise tmt.utils.ProvisionError( - "Either container-file or container-image must be specified.") + "Either 'container-file' or 'container-image' must be specified.") self._guest = GuestBootc( logger=self._logger, From 51ad275121e54aec4f537b8a5ecd1402f47213a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Wed, 20 Nov 2024 21:15:20 +0100 Subject: [PATCH 10/12] Disable `avc` check, extend the `duration` a bit --- tests/provision/bootc/main.fmf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/provision/bootc/main.fmf b/tests/provision/bootc/main.fmf index c3567096dd..f5d0e40876 100644 --- a/tests/provision/bootc/main.fmf +++ b/tests/provision/bootc/main.fmf @@ -4,9 +4,10 @@ tag+: - provision-bootc require: - tmt+provision-virtual +duration: 40m # As for now there is an expected AVC failure: # https://github.com/osbuild/bootc-image-builder/issues/645 check: - how: avc - result: xfail + enabled: false From e111bab408ef3c10a8d376d0e175801e1e7b182a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Thu, 21 Nov 2024 11:19:31 +0100 Subject: [PATCH 11/12] Fix the `avc` check `xfail` to actually work Let's go the other way round: Enable the check by default, allow individual tests to set it according to their needs by a simple definition and disable it globally when not initiated by packit. --- tests/provision/bootc/main.fmf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/provision/bootc/main.fmf b/tests/provision/bootc/main.fmf index f5d0e40876..a1d0ca3fd4 100644 --- a/tests/provision/bootc/main.fmf +++ b/tests/provision/bootc/main.fmf @@ -10,4 +10,4 @@ duration: 40m # https://github.com/osbuild/bootc-image-builder/issues/645 check: - how: avc - enabled: false + result: xfail From 21ae57de865f5088e5d4da19f8bde086fdbd0279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pl=C3=ADchal?= Date: Thu, 21 Nov 2024 14:18:03 +0100 Subject: [PATCH 12/12] Add the `--no-add-tmt-dependencies` option as well This is needed to actually allow disabling the flag from the command ine as it is `True` by default. --- tmt/steps/provision/bootc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py index dbe3ae50d1..d1131e8d79 100644 --- a/tmt/steps/provision/bootc.py +++ b/tmt/steps/provision/bootc.py @@ -113,13 +113,13 @@ class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData): metavar='CONTAINER_IMAGE', help=""" Select container image to be used to build a bootc disk. - This takes priority over container-file. + This takes priority over Containerfile. """) add_tmt_dependencies: bool = field( default=True, is_flag=True, - option=('--add-tmt-dependencies'), + option=('--add-tmt-dependencies/--no-add-tmt-dependencies'), help=""" Add tmt dependencies to the supplied container image or image built from the supplied Containerfile. @@ -149,7 +149,7 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]): how: bootc container-image: quay.io/centos-bootc/centos-bootc:stream9 - Here's a config example using a containerfile: + Here's a config example using a Containerfile: .. code-block:: yaml