Build container image using repro-build

Invoke the `repro-build` script when building a container image, instead
of the underlying Docker/Podman commands. The `repro-build` script
handles the underlying complexity to call Docker/Podman in a manner that
makes the image reproducible.

Moreover, mirror some arguments from the `repro-build` script, so that
consumers of `build-image.py` can pass them to it.

Important: the resulting image will be in .tar format, not .tar.gz,
starting from this commit. This means that our tests will be broken for
the next few commits.

Fixes #1074
This commit is contained in:
Alex Pyrgiotis 2025-02-26 18:28:27 +02:00
parent 94fad78f94
commit 69234507c4
No known key found for this signature in database
GPG key ID: B6C15EBA0357C9AA

View file

@ -1,5 +1,4 @@
import argparse import argparse
import gzip
import platform import platform
import secrets import secrets
import subprocess import subprocess
@ -13,8 +12,6 @@ if platform.system() in ["Darwin", "Windows"]:
elif platform.system() == "Linux": elif platform.system() == "Linux":
CONTAINER_RUNTIME = "podman" CONTAINER_RUNTIME = "podman"
ARCH = platform.machine()
def str2bool(v): def str2bool(v):
if isinstance(v, bool): if isinstance(v, bool):
@ -50,6 +47,16 @@ def determine_git_tag():
) )
def determine_debian_archive_date():
"""Get the date of the Debian archive from Dockerfile.env."""
for env in Path("Dockerfile.env").read_text().split("\n"):
if env.startswith("DEBIAN_ARCHIVE_DATE"):
return env.split("=")[1]
raise Exception(
"Could not find 'DEBIAN_ARCHIVE_DATE' build argument in Dockerfile.env"
)
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
@ -59,16 +66,15 @@ def main():
help=f"The container runtime for building the image (default: {CONTAINER_RUNTIME})", help=f"The container runtime for building the image (default: {CONTAINER_RUNTIME})",
) )
parser.add_argument( parser.add_argument(
"--no-save", "--platform",
action="store_true", default=None,
help="Do not save the container image as a tarball in share/container.tar.gz", help=f"The platform for building the image (default: current platform)",
) )
parser.add_argument( parser.add_argument(
"--compress-level", "--output",
type=int, "-o",
choices=range(0, 10), default=str(Path("share") / "container.tar"),
default=9, help="Path to store the container image",
help="The Gzip compression level, from 0 (lowest) to 9 (highest, default)",
) )
parser.add_argument( parser.add_argument(
"--use-cache", "--use-cache",
@ -83,63 +89,62 @@ def main():
default=None, default=None,
help="Provide a custom tag for the image (for development only)", help="Provide a custom tag for the image (for development only)",
) )
parser.add_argument(
"--debian-archive-date",
"-d",
default=determine_debian_archive_date(),
help="Use a specific Debian snapshot archive, by its date (default %(default)s)",
)
parser.add_argument(
"--dry",
default=False,
action="store_true",
help="Do not run any commands, just print what would happen",
)
args = parser.parse_args() args = parser.parse_args()
tarball_path = Path("share") / "container.tar.gz" tag = args.tag or f"{args.debian_archive_date}-{determine_git_tag()}"
image_id_path = Path("share") / "image-id.txt" image_name_tagged = f"{IMAGE_NAME}:{tag}"
print(f"Building for architecture '{ARCH}'")
tag = args.tag or determine_git_tag()
image_name_tagged = IMAGE_NAME + ":" + tag
print(f"Will tag the container image as '{image_name_tagged}'") print(f"Will tag the container image as '{image_name_tagged}'")
image_id_path = Path("share") / "image-id.txt"
if not args.dry:
with open(image_id_path, "w") as f: with open(image_id_path, "w") as f:
f.write(tag) f.write(tag)
# Build the container image, and tag it with the calculated tag # Build the container image, and tag it with the calculated tag
print("Building container image") print("Building container image")
cache_args = [] if args.use_cache else ["--no-cache"] cache_args = [] if args.use_cache else ["--no-cache"]
platform_args = [] if not args.platform else ["--platform", args.platform]
rootless_args = [] if args.runtime == "docker" else ["--rootless"]
rootless_args = []
dry_args = [] if not args.dry else ["--dry"]
subprocess.run( subprocess.run(
[ [
args.runtime, "./dev_scripts/repro-build.py",
"build", "build",
BUILD_CONTEXT, "--runtime",
args.runtime,
"--build-arg",
f"DEBIAN_ARCHIVE_DATE={args.debian_archive_date}",
"--datetime",
args.debian_archive_date,
*dry_args,
*cache_args, *cache_args,
"-f", *platform_args,
"Dockerfile", *rootless_args,
"--tag", "--tag",
image_name_tagged, image_name_tagged,
"--output",
args.output,
"-f",
"Dockerfile",
BUILD_CONTEXT,
], ],
check=True, check=True,
) )
if not args.no_save:
print("Saving container image")
cmd = subprocess.Popen(
[
CONTAINER_RUNTIME,
"save",
image_name_tagged,
],
stdout=subprocess.PIPE,
)
print("Compressing container image")
chunk_size = 4 << 20
with gzip.open(
tarball_path,
"wb",
compresslevel=args.compress_level,
) as gzip_f:
while True:
chunk = cmd.stdout.read(chunk_size)
if len(chunk) > 0:
gzip_f.write(chunk)
else:
break
cmd.wait(5)
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())