Skip to content

Commit

Permalink
tag && untag
Browse files Browse the repository at this point in the history
The 'podman image mount' requires image tag, but from-tarball imports
have no tag (create one).
  • Loading branch information
praiskup committed Dec 11, 2024
1 parent 04afe88 commit 4a660fb
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 23 deletions.
7 changes: 5 additions & 2 deletions mock/py/mockbuild/buildroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ def _fallback(message):
if not self.config["image_skip_pull"]:
podman.retry_image_pull(self.config["image_keep_getting"])
else:
getLog().info("Using local image %s (pull skipped)",
self.chroot_image)
podman.read_image_id()
getLog().info("Using local image %s (%s)",
self.chroot_image, podman.image_id)
podman.tag_image()

if self.is_bootstrap and self.config["hermetic_build"]:
tarball = os.path.join(self.config["offline_local_repository"],
Expand All @@ -294,6 +296,7 @@ def _fallback(message):
raise BootstrapError("Container image architecture check failed")

podman.cp(self.make_chroot_path(), self.config["tar_binary"])
podman.untag()
file_util.unlink_if_exists(os.path.join(self.make_chroot_path(),
"etc/rpm/macros.image-language-conf"))
except _FallbackException as exc:
Expand Down
2 changes: 2 additions & 0 deletions mock/py/mockbuild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import shlex
import socket
import sys
import uuid
import warnings

from templated_dictionary import TemplatedDictionary
Expand Down Expand Up @@ -411,6 +412,7 @@ def setup_default_config_opts():

config_opts["calculatedeps"] = None
config_opts["hermetic_build"] = False
config_opts["mock_run_uuid"] = str(uuid.uuid4())

return config_opts

Expand Down
11 changes: 7 additions & 4 deletions mock/py/mockbuild/plugins/buildroot_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import json
import os

from mockbuild.podman import Podman
from mockbuild.podman import Podman, PodmanError
from mockbuild.installed_packages import query_packages, query_packages_location

requires_api_version = "1.1"
Expand Down Expand Up @@ -101,9 +101,12 @@ def _executor(cmd):
# produce lockfiles even if these are useless for hermetic
# builds).
with self.buildroot.uid_manager.elevated_privileges():
podman = Podman(self.buildroot,
data["config"]["bootstrap_image"])
digest = podman.get_image_digest()
try:
podman = Podman(self.buildroot,
data["config"]["bootstrap_image"])
digest = podman.get_image_digest()
except PodmanError:
digest = "unknown"
data["bootstrap"] = {
"image_digest": digest,
}
Expand Down
83 changes: 66 additions & 17 deletions mock/py/mockbuild/podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(self, buildroot, image):

self.buildroot = buildroot
self.image = image
self.container_id = None
self.image_id = None
getLog().info("Using container image: %s", image)

@traceLog()
Expand All @@ -69,11 +69,33 @@ def pull_image(self):
logger = getLog()
logger.info("Pulling image: %s", self.image)
cmd = [self.podman_binary, "pull", self.image]
out, exit_status = util.do_with_status(cmd, env=self.buildroot.env,
raiseExc=False, returnOutput=1)
if exit_status:
logger.error(out)
return not exit_status

res = subprocess.run(cmd, env=self.buildroot.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=False)
if res.returncode != 0:
logger.error("%s\n%s", res.stdout, res.stderr)
return False

# Record the image id for later use. This is needed for the
# oci-image:tarball images that not necessarily have tags/names.
self.image_id = res.stdout.decode("utf-8").strip()
return True

@property
def _tagged_id(self):
uuid = self.buildroot.config["mock_run_uuid"]
bootstrap = "-bootstrap" if self.buildroot.is_bootstrap else ""
return f"mock{bootstrap}-{uuid}"

def tag_image(self):
"""
Tag the pulled image as mock-{uuid}, or mock-bootstrap-{uuid}.
"""
cmd = ["podman", "tag", self.image_id, self._tagged_id]
getLog().info("Tagging container image as %s", self._tagged_id)
subprocess.run(cmd, env=self.buildroot.env, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=True)

def import_tarball(self, tarball):
"""
Expand All @@ -99,8 +121,8 @@ def mounted_image(self):
chroot directory.
"""
logger = getLog()
cmd_mount = [self.podman_binary, "image", "mount", self.image]
cmd_umount = [self.podman_binary, "image", "umount", self.image]
cmd_mount = [self.podman_binary, "image", "mount", self.image_id]
cmd_umount = [self.podman_binary, "image", "umount", self.image_id]
result = subprocess.run(cmd_mount, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False,
encoding="utf8")
Expand All @@ -109,43 +131,48 @@ def mounted_image(self):
raise PodmanError(message)

mountpoint = result.stdout.strip()
logger.info("mounting %s with podman image mount", self.image)
logger.info("mounting %s with podman image mount", self.image_id)
try:
logger.info("image %s as %s", self.image, mountpoint)
logger.info("image %s as %s", self.image_id, mountpoint)
yield mountpoint
finally:
logger.info("umounting image %s (%s) with podman image umount",
self.image, mountpoint)
self.image_id, mountpoint)
subprocess.run(cmd_umount, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=True)

def get_image_digest(self):
"""
Get the "sha256:..." string for the image we work with.
"""
check = [self.podman_binary, "image", "inspect", self.image,
the_image = self.image
if the_image.startswith("oci-archive:"):
# We can't query digest from tarball directly, but note
# the image needs to be tagged first!
the_image = self._tagged_id
check = [self.podman_binary, "image", "inspect", the_image,
"--format", "{{ .Digest }}"]
result = subprocess.run(check, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False,
encoding="utf8")
if result.returncode:
raise PodmanError(f"Can't get {self.image} podman image digest: {result.stderr}")
raise PodmanError(f"Can't get {the_image} podman image digest: {result.stderr}")
result = result.stdout.strip()
if len(result.splitlines()) != 1:
raise PodmanError(f"The digest of {self.image} image is not a single-line string")
raise PodmanError(f"The digest of {the_image} image is not a single-line string")
return result

def check_native_image_architecture(self):
"""
Check that self.image has been generated for the current
host's architecture.
"""
return podman_check_native_image_architecture(self.image, getLog())
return podman_check_native_image_architecture(self.image_id, getLog())

@traceLog()
def cp(self, destination, tar_cmd):
""" copy content of container to destination directory """
getLog().info("Copy content of container %s to %s", self.image, destination)
getLog().info("Copy content of container %s to %s", self.image_id, destination)

with self.mounted_image() as mount_path:
# pipe-out the temporary mountpoint with the help of tar utility
Expand All @@ -158,5 +185,27 @@ def cp(self, destination, tar_cmd):
tar.communicate()
podman.communicate()

def untag(self):
"""
Remove the additional image ID we created - which means the image itself
is garbage-collected if there's no other tag.
"""
cmd = ["podman", "rmi", self._tagged_id]
getLog().info("Removing image %s", self._tagged_id)
subprocess.run(cmd, env=self.buildroot.env, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=True)

def read_image_id(self):
"""
Given self.image (name), get the image Id.
"""
cmd = ["podman", "image", "inspect", self.image, "--format",
"{{ .Id }}"]
getLog().info("Removing image %s", self.image)
res = subprocess.run(cmd, env=self.buildroot.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
self.image_id = res.stdout.decode("utf-8").strip()

def __repr__(self):
return "Podman({}({}))".format(self.image, self.container_id)
return "Podman({}({}))".format(self.image, self.image_id)

0 comments on commit 4a660fb

Please sign in to comment.