Skip to content

Commit af4c07b

Browse files
authored
Merge pull request #858 from ckyrouac/tmt-fix
tmt: Update bootc-install.py plugin
2 parents 17fcc51 + 140eeac commit af4c07b

File tree

3 files changed

+109
-30
lines changed

3 files changed

+109
-30
lines changed

hack/provision-derived.sh

+17-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,23 @@ case "$variant" in
2222
# tmt wants rsync
2323
dnf -y install cloud-init rsync
2424
ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants
25-
# And tmt wants to write to /usr/local/bin
26-
rm /usr/local -rf && ln -sr /var/usrlocal /usr/local && mkdir -p /var/usrlocal/bin
25+
26+
# tmt puts scrips in /var/lib/tmt/scripts, add them to $PATH
27+
touch /etc/environment
28+
echo "export PATH=$PATH:/var/lib/tmt/scripts" >> /etc/environment
29+
30+
# tmt needs a webserver to verify the VM is running
31+
TESTCLOUD_GUEST="python3 -m http.server 10022 || python -m http.server 10022 || /usr/libexec/platform-python -m http.server 10022 || python2 -m SimpleHTTPServer 10022 || python -m SimpleHTTPServer 10022"
32+
echo "$TESTCLOUD_GUEST" >> /opt/testcloud-guest.sh
33+
chmod +x /opt/testcloud-guest.sh
34+
echo "[Unit]" >> /etc/systemd/system/testcloud.service
35+
echo "Description=Testcloud guest integration" >> /etc/systemd/system/testcloud.service
36+
echo "After=cloud-init.service" >> /etc/systemd/system/testcloud.service
37+
echo "[Service]" >> /etc/systemd/system/testcloud.service
38+
echo "ExecStart=/bin/bash /opt/testcloud-guest.sh" >> /etc/systemd/system/testcloud.service
39+
echo "[Install]" >> /etc/systemd/system/testcloud.service
40+
echo "WantedBy=multi-user.target" >> /etc/systemd/system/testcloud.service
41+
systemctl enable testcloud.service
2742
;;
2843
"") echo "No variant"
2944
;;

tests/booted/test-logically-bound-install.nu

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ print "IMAGES:"
66
podman --storage-opt=additionalimagestore=/usr/lib/bootc/storage images # for debugging
77
assert ($images | any {|item| $item.column1 == "quay.io/curl/curl"})
88
assert ($images | any {|item| $item.column1 == "quay.io/curl/curl-base"})
9-
assert ($images | any {|item| $item.column1 == "registry.access.redhat.com/ubi9/podman:latest"}) # this image is signed
9+
assert ($images | any {|item| $item.column1 == "registry.access.redhat.com/ubi9/podman"}) # this image is signed
1010

1111
tap ok

tests/plugins/bootc-install.py

+91-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import dataclasses
22
import os
3-
import uuid
43
from typing import Optional, cast
54

65
import tmt
@@ -15,28 +14,41 @@
1514
from tmt.utils.templates import render_template
1615

1716
DEFAULT_IMAGE_BUILDER = "quay.io/centos-bootc/bootc-image-builder:latest"
18-
CONTAINER_STORAGE_DIR = "/var/lib/containers/storage"
17+
CONTAINER_STORAGE_DIR = tmt.utils.Path("/var/lib/containers/storage")
1918

19+
PODMAN_MACHINE_NAME = 'podman-machine-tmt'
20+
PODMAN_ENV = tmt.utils.Environment.from_dict({"CONTAINER_CONNECTION": f'{PODMAN_MACHINE_NAME}-root'})
21+
PODMAN_MACHINE_CPU = os.getenv('TMT_BOOTC_PODMAN_MACHINE_CPU', '2')
22+
PODMAN_MACHINE_MEM = os.getenv('TMT_BOOTC_PODMAN_MACHINE_MEM', '2048')
23+
PODMAN_MACHINE_DISK_SIZE = os.getenv('TMT_BOOTC_PODMAN_MACHINE_DISK_SIZE', '50')
2024

2125
class GuestBootc(GuestTestcloud):
2226
containerimage: str
27+
_rootless: bool
2328

2429
def __init__(self,
2530
*,
2631
data: tmt.steps.provision.GuestData,
2732
name: Optional[str] = None,
2833
parent: Optional[tmt.utils.Common] = None,
2934
logger: tmt.log.Logger,
30-
containerimage: Optional[str]) -> None:
35+
containerimage: str,
36+
rootless: bool) -> None:
3137
super().__init__(data=data, logger=logger, parent=parent, name=name)
32-
33-
if containerimage:
34-
self.containerimage = containerimage
38+
self.containerimage = containerimage
39+
self._rootless = rootless
3540

3641
def remove(self) -> None:
3742
tmt.utils.Command(
3843
"podman", "rmi", self.containerimage
39-
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
44+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
45+
46+
try:
47+
tmt.utils.Command(
48+
"podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME
49+
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
50+
except Exception:
51+
self._logger.debug("Unable to remove podman machine it might not exist")
4052

4153
super().remove()
4254

@@ -129,21 +141,20 @@ class ProvisionBootc(tmt.steps.provision.ProvisionPlugin[BootcData]):
129141
bootc disk image from the container image, then uses the virtual.testcloud
130142
plugin to create a virtual machine using the bootc disk image.
131143
132-
The bootc disk creation requires running podman as root, this is typically
133-
done by running the command in a rootful podman-machine. The podman-machine
134-
also needs access to ``/var/tmp/tmt``. An example command to initialize the
135-
machine:
144+
The bootc disk creation requires running podman as root. The plugin will
145+
automatically check if the current podman connection is rootless. If it is,
146+
a podman machine will be spun up and used to build the bootc disk. The
147+
podman machine can be configured with the following environment variables:
136148
137-
.. code-block:: shell
138-
139-
podman machine init --rootful --disk-size 200 --memory 8192 \
140-
--cpus 8 -v /var/tmp/tmt:/var/tmp/tmt -v $HOME:$HOME
149+
TMT_BOOTC_PODMAN_MACHINE_CPU='2'
150+
TMT_BOOTC_PODMAN_MACHINE_MEM='2048'
151+
TMT_BOOTC_PODMAN_MACHINE_DISK_SIZE='50'
141152
"""
142153

143154
_data_class = BootcData
144155
_guest_class = GuestTestcloud
145156
_guest = None
146-
_id = str(uuid.uuid4())[:8]
157+
_rootless = True
147158

148159
def _get_id(self) -> str:
149160
# FIXME: cast() - https://github.com/teemtee/tmt/issues/1372
@@ -161,31 +172,47 @@ def _expand_path(self, relative_path: str) -> str:
161172

162173
def _build_derived_image(self, base_image: str) -> str:
163174
""" Build a "derived" container image from the base image with tmt dependencies added """
164-
if not self.workdir:
165-
raise tmt.utils.ProvisionError(
166-
"self.workdir must be defined")
175+
assert self.workdir is not None # narrow type
176+
177+
simple_http_start_guest = \
178+
"""
179+
python3 -m http.server {0} || python -m http.server {0} ||
180+
/usr/libexec/platform-python -m http.server {0} || python2 -m SimpleHTTPServer {0} || python -m SimpleHTTPServer {0}
181+
""".format(10022).replace('\n', ' ')
167182

168183
self._logger.debug("Building modified container image with necessary tmt packages/config")
169184
containerfile_template = '''
170185
FROM {{ base_image }}
171186
172-
RUN \
173-
dnf -y install cloud-init rsync && \
187+
RUN dnf -y install cloud-init rsync && \
174188
ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants && \
175-
rm /usr/local -rf && ln -sr /var/usrlocal /usr/local && mkdir -p /var/usrlocal/bin && \
176-
dnf clean all
189+
touch /etc/environment && \
190+
echo "export PATH=$PATH:/var/lib/tmt/scripts" >> /etc/environment && \
191+
dnf clean all && \
192+
echo "{{ testcloud_guest }}" >> /opt/testcloud-guest.sh && \
193+
chmod +x /opt/testcloud-guest.sh && \
194+
echo "[Unit]" >> /etc/systemd/system/testcloud.service && \
195+
echo "Description=Testcloud guest integration" >> /etc/systemd/system/testcloud.service && \
196+
echo "After=cloud-init.service" >> /etc/systemd/system/testcloud.service && \
197+
echo "[Service]" >> /etc/systemd/system/testcloud.service && \
198+
echo "ExecStart=/bin/bash /opt/testcloud-guest.sh" >> /etc/systemd/system/testcloud.service && \
199+
echo "[Install]" >> /etc/systemd/system/testcloud.service && \
200+
echo "WantedBy=multi-user.target" >> /etc/systemd/system/testcloud.service && \
201+
systemctl enable testcloud.service
177202
'''
203+
178204
containerfile_parsed = render_template(
179205
containerfile_template,
180-
base_image=base_image)
206+
base_image=base_image,
207+
testcloud_guest=simple_http_start_guest)
181208
(self.workdir / 'Containerfile').write_text(containerfile_parsed)
182209

183210
image_tag = f'localhost/tmtmodified-{self._get_id()}'
184211
tmt.utils.Command(
185212
"podman", "build", f'{self.workdir}',
186213
"-f", f'{self.workdir}/Containerfile',
187214
"-t", image_tag
188-
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
215+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
189216

190217
return image_tag
191218

@@ -197,12 +224,13 @@ def _build_base_image(self, containerfile: str, workdir: str) -> str:
197224
"podman", "build", self._expand_path(workdir),
198225
"-f", self._expand_path(containerfile),
199226
"-t", image_tag
200-
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
227+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
201228
return image_tag
202229

203230
def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None:
204231
""" Build the bootc disk from a container image using bootc image builder """
205232
self._logger.debug("Building bootc disk image")
233+
206234
tmt.utils.Command(
207235
"podman", "run", "--rm", "--privileged",
208236
"-v", f'{CONTAINER_STORAGE_DIR}:{CONTAINER_STORAGE_DIR}',
@@ -211,16 +239,51 @@ def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None:
211239
image_builder, "build",
212240
"--type", "qcow2",
213241
"--local", containerimage
242+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
243+
244+
def _init_podman_machine(self) -> None:
245+
try:
246+
tmt.utils.Command(
247+
"podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME
248+
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
249+
except Exception:
250+
self._logger.debug("Unable to remove existing podman machine (it might not exist)")
251+
252+
self._logger.debug("Initializing podman machine")
253+
tmt.utils.Command(
254+
"podman", "machine", "init", "--rootful",
255+
"--disk-size", PODMAN_MACHINE_DISK_SIZE,
256+
"--memory", PODMAN_MACHINE_MEM,
257+
"--cpus", PODMAN_MACHINE_CPU,
258+
"-v", "/var/tmp/tmt:/var/tmp/tmt",
259+
"-v", "$HOME:$HOME",
260+
PODMAN_MACHINE_NAME
214261
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
215262

263+
self._logger.debug("Starting podman machine")
264+
tmt.utils.Command(
265+
"podman", "machine", "start", PODMAN_MACHINE_NAME
266+
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
267+
268+
def _check_if_podman_is_rootless(self) -> None:
269+
output = tmt.utils.Command(
270+
"podman", "info", "--format", "{{.Host.Security.Rootless}}"
271+
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
272+
self._rootless = output.stdout == "true\n"
273+
216274
def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None:
217275
""" Provision the bootc instance """
218276
super().go(logger=logger)
219277

278+
self._check_if_podman_is_rootless()
279+
220280
data = BootcData.from_plugin(self)
221281
data.image = f"file://{self.workdir}/qcow2/disk.qcow2"
222282
data.show(verbose=self.verbosity_level, logger=self._logger)
223283

284+
if self._rootless:
285+
self._init_podman_machine()
286+
224287
if data.containerimage is not None:
225288
containerimage = data.containerimage
226289
if data.add_deps:
@@ -240,7 +303,8 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None:
240303
data=data,
241304
name=self.name,
242305
parent=self.step,
243-
containerimage=containerimage)
306+
containerimage=containerimage,
307+
rootless=self._rootless)
244308
self._guest.start()
245309
self._guest.setup()
246310

0 commit comments

Comments
 (0)