Skip to content

Commit 7804b6b

Browse files
committed
tmt: Update bootc-install.py plugin
This contains the latest plugin code from teemtee/tmt#3161. It includes automatically spinning up a podman-machine when podman is not rootful. It also fixes a bug introduced in the latest tmt version related to starting a web server via a systemd service. Signed-off-by: Chris Kyrouac <[email protected]>
1 parent b469332 commit 7804b6b

File tree

1 file changed

+89
-26
lines changed

1 file changed

+89
-26
lines changed

tests/plugins/bootc-install.py

Lines changed: 89 additions & 26 deletions
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:
51+
self._logger.debug(f"Unable to remove podman machine {{PODMAN_MACHINE_NAME}}, 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,46 @@ 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 && \
175189
rm /usr/local -rf && ln -sr /var/usrlocal /usr/local && mkdir -p /var/usrlocal/bin && \
176-
dnf clean all
190+
dnf clean all && \
191+
echo "{{ testcloud_guest }}" >> /opt/testcloud-guest.sh && \
192+
chmod +x /opt/testcloud-guest.sh && \
193+
echo "[Unit]" >> /etc/systemd/system/testcloud.service && \
194+
echo "Description=Testcloud guest integration" >> /etc/systemd/system/testcloud.service && \
195+
echo "After=cloud-init.service" >> /etc/systemd/system/testcloud.service && \
196+
echo "[Service]" >> /etc/systemd/system/testcloud.service && \
197+
echo "ExecStart=/bin/bash /opt/testcloud-guest.sh" >> /etc/systemd/system/testcloud.service && \
198+
echo "[Install]" >> /etc/systemd/system/testcloud.service && \
199+
echo "WantedBy=multi-user.target" >> /etc/systemd/system/testcloud.service && \
200+
systemctl enable testcloud.service
177201
'''
202+
178203
containerfile_parsed = render_template(
179204
containerfile_template,
180-
base_image=base_image)
205+
base_image=base_image,
206+
testcloud_guest=simple_http_start_guest)
181207
(self.workdir / 'Containerfile').write_text(containerfile_parsed)
182208

183209
image_tag = f'localhost/tmtmodified-{self._get_id()}'
184210
tmt.utils.Command(
185211
"podman", "build", f'{self.workdir}',
186212
"-f", f'{self.workdir}/Containerfile',
187213
"-t", image_tag
188-
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
214+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
189215

190216
return image_tag
191217

@@ -197,12 +223,13 @@ def _build_base_image(self, containerfile: str, workdir: str) -> str:
197223
"podman", "build", self._expand_path(workdir),
198224
"-f", self._expand_path(containerfile),
199225
"-t", image_tag
200-
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
226+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
201227
return image_tag
202228

203229
def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None:
204230
""" Build the bootc disk from a container image using bootc image builder """
205231
self._logger.debug("Building bootc disk image")
232+
206233
tmt.utils.Command(
207234
"podman", "run", "--rm", "--privileged",
208235
"-v", f'{CONTAINER_STORAGE_DIR}:{CONTAINER_STORAGE_DIR}',
@@ -211,16 +238,51 @@ def _build_bootc_disk(self, containerimage: str, image_builder: str) -> None:
211238
image_builder, "build",
212239
"--type", "qcow2",
213240
"--local", containerimage
241+
).run(cwd=self.workdir, stream_output=True, logger=self._logger, env=PODMAN_ENV if self._rootless else None)
242+
243+
def _init_podman_machine(self) -> None:
244+
try:
245+
tmt.utils.Command(
246+
"podman", "machine", "rm", "-f", PODMAN_MACHINE_NAME
247+
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
248+
except:
249+
self._logger.debug("Unable to remove existing podman machine (it might not exist)")
250+
251+
self._logger.debug("Initializing podman machine")
252+
tmt.utils.Command(
253+
"podman", "machine", "init", "--rootful",
254+
"--disk-size", PODMAN_MACHINE_DISK_SIZE,
255+
"--memory", PODMAN_MACHINE_MEM,
256+
"--cpus", PODMAN_MACHINE_CPU,
257+
"-v", "/var/tmp/tmt:/var/tmp/tmt",
258+
"-v", "$HOME:$HOME",
259+
PODMAN_MACHINE_NAME
214260
).run(cwd=self.workdir, stream_output=True, logger=self._logger)
215261

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

277+
self._check_if_podman_is_rootless()
278+
220279
data = BootcData.from_plugin(self)
221280
data.image = f"file://{self.workdir}/qcow2/disk.qcow2"
222281
data.show(verbose=self.verbosity_level, logger=self._logger)
223282

283+
if self._rootless:
284+
self._init_podman_machine()
285+
224286
if data.containerimage is not None:
225287
containerimage = data.containerimage
226288
if data.add_deps:
@@ -240,7 +302,8 @@ def go(self, *, logger: Optional[tmt.log.Logger] = None) -> None:
240302
data=data,
241303
name=self.name,
242304
parent=self.step,
243-
containerimage=containerimage)
305+
containerimage=containerimage,
306+
rootless=self._rootless)
244307
self._guest.start()
245308
self._guest.setup()
246309

0 commit comments

Comments
 (0)