Repository: teemtee/tmt (language: python)

Issue:
Create a podman-bootc provisioner
Seems now there is a simple way to run bootable linux containers based on bootc via podman.




Discussion / hints:
Not *directly* via podman; it's confusing. We did create a tool called [podman-bootc](https://github.com/containers/podman-bootc), but is intended to work with podman.

Also xref https://github.com/containers/bootc/pull/593 which ties together something like this (not actually using podman-bootc) and tmt.

Fix patch:
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/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/plans/provision/bootc.fmf b/plans/provision/bootc.fmf
new file mode 100644
index 0000000000..e5fc819c56
--- /dev/null
+++ b/plans/provision/bootc.fmf
@@ -0,0 +1,34 @@
+summary: Bootc virtual machine via testcloud
+
+description: |
+    Verify functionality of the bootc provision plugin.
+
+discover:
+    how: fmf
+    filter: 'tag:provision-bootc'
+
+prepare+:
+  - name: start-libvirtd
+    script: |
+        systemctl start libvirtd
+        systemctl status libvirtd
+
+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
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..e7cafc3fe2
--- /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"
+
+  container-file:
+    type: string
+
+  container-file-workdir:
+    type: string
+
+  container-image:
+    type: string
+
+  add-tmt-dependencies:
+    type: boolean
+
+  image-builder:
+    type: string
+
+required:
+  - how
diff --git a/tmt/steps/provision/bootc.py b/tmt/steps/provision/bootc.py
new file mode 100644
index 0000000000..d1131e8d79
--- /dev/null
+++ b/tmt/steps/provision/bootc.py
@@ -0,0 +1,355 @@
+import dataclasses
+import os
+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
+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
+
+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 <<EOF
+set -euo pipefail
+
+# install dependencies required by tmt
+dnf -y install cloud-init rsync
+ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants
+dnf clean all
+
+# add the scripts in /var/lib/tmt/scripts to the PATH
+touch /etc/environment
+echo "export PATH=$PATH:/var/lib/tmt/scripts" >> /etc/environment
+
+EOF
+"""
+
+
+class GuestBootc(GuestTestcloud):
+    containerimage: str
+    _rootless: bool
+
+    def __init__(self,
+                 *,
+                 data: tmt.steps.provision.GuestData,
+                 name: Optional[str] = None,
+                 parent: Optional[tmt.utils.Common] = None,
+                 logger: tmt.log.Logger,
+                 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,
+            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()
+
+
+@dataclasses.dataclass
+class BootcData(tmt.steps.provision.testcloud.ProvisionTestcloudData):
+    container_file: Optional[str] = field(
+        default=None,
+        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 container-image.
+             """)
+
+    container_file_workdir: str = field(
+        default=".",
+        option=('--container-file-workdir'),
+        metavar='CONTAINER_FILE_WORKDIR',
+        help="""
+             Select working directory for the podman build invocation.
+             """)
+
+    container_image: Optional[str] = field(
+        default=None,
+        option=('--container-image'),
+        metavar='CONTAINER_IMAGE',
+        help="""
+             Select container image to be used to build a bootc disk.
+             This takes priority over Containerfile.
+             """)
+
+    add_tmt_dependencies: bool = field(
+        default=True,
+        is_flag=True,
+        option=('--add-tmt-dependencies/--no-add-tmt-dependencies'),
+        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='IMAGE_BUILDER',
+        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 CentOS Stream 9 bootc image:
+
+    .. code-block:: yaml
+
+        provision:
+            how: bootc
+            container-image: quay.io/centos-bootc/centos-bootc:stream9
+
+    Here's a config example using a Containerfile:
+
+    .. code-block:: yaml
+
+        provision:
+            how: bootc
+            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 already includes tmt
+    dependencies:
+
+    .. code-block:: yaml
+
+        provision:
+            how: bootc
+            add-tmt-dependencies: 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
+    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. 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
+        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("Build 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,
+            env=PODMAN_ENV if self._rootless else None)
+
+        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("Build 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,
+            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("Build 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("Initialize podman machine.")
+        tmt.utils.Command(
+            "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("Start 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()
+
+        # 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.")
+
+        self._guest = GuestBootc(
+            logger=self._logger,
+            data=data,
+            name=self.name,
+            parent=self.step,
+            containerimage=containerimage,
+            rootless=self._rootless)
+        self._guest.start()
+        self._guest.setup()
+
+    def guest(self) -> Optional[tmt.steps.provision.Guest]:
+        return self._guest


Test patch:
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 @@
+1
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,1 @@
+FROM quay.io/centos-bootc/centos-bootc:stream9
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/main.fmf b/tests/provision/bootc/main.fmf
new file mode 100644
index 0000000000..a1d0ca3fd4
--- /dev/null
+++ b/tests/provision/bootc/main.fmf
@@ -0,0 +1,13 @@
+summary: Make sure that bootc provision method works
+tag+:
+  - provision-only
+  - 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
diff --git a/tests/provision/bootc/test.sh b/tests/provision/bootc/test.sh
new file mode 100755
index 0000000000..b856de7ac4
--- /dev/null
+++ b/tests/provision/bootc/test.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+. /usr/share/beakerlib/beakerlib.sh || exit 1
+
+IMAGE_NEEDS_DEPS="localhost/tmt-bootc-needs-deps"
+IMAGE_INCLUDES_DEPS="localhost/tmt-bootc-includes-deps"
+
+TESTCLOUD_IMAGE="/var/tmp/tmt/testcloud/images/disk.qcow2"
+
+
+rlJournalStart
+    rlPhaseStartSetup
+        # Use /var/tmp/tmt so the temp directories are accessible
+        # in the podman machine mount
+        rlRun "mkdir -p /var/tmp/tmt"
+        rlRun "run=\$(mktemp -d --tmpdir=/var/tmp/tmt)" 0 "Create run directory"
+        rlRun "pushd data"
+    rlPhaseEnd
+
+    rlPhaseStartTest "Image that needs dependencies"
+        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 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 "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 "tmt -vvv run --scratch -i $run plan --name /plans/containerfile/includes-deps"
+        rlRun "rm -rf $TESTCLOUD_IMAGE"
+    rlPhaseEnd
+
+    rlPhaseStartCleanup
+        rlRun "popd"
+        rlRun "rm -r $run" 0 "Remove run directory"
+        rlRun "podman rmi $IMAGE_INCLUDES_DEPS" 0,1
+        rlRun "podman rmi $IMAGE_NEEDS_DEPS" 0,1
+    rlPhaseEnd
+rlJournalEnd