1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
# SPDX-License-Identifier: BSD-2-Clause
"""Build a Docker container for the specified Linux distribution."""
from __future__ import annotations
import argparse
import dataclasses
import pathlib
import shlex
import subprocess # noqa: S404
import sys
import tempfile
import typing
from run_sshd_test import util
if typing.TYPE_CHECKING:
import logging
from typing import Final
@dataclasses.dataclass(frozen=True)
class Distro:
"""A description of a single Linux distribution to build a container for."""
name: str
distro: str
version: str
pkg_system: str
@dataclasses.dataclass(frozen=True)
class Config:
"""Runtime configuration for the build_container tool."""
log: logging.Logger
distro: Distro
noop: bool
pull: bool
run_test: bool
utf8_env: dict[str, str]
DISTROS: Final = [
Distro(name="unstable", distro="debian", version="unstable", pkg_system="apt"),
Distro(name="bookworm", distro="debian", version="bookworm", pkg_system="apt"),
Distro(name="noble", distro="ubuntu", version="noble", pkg_system="apt"),
Distro(name="jammy", distro="ubuntu", version="jammy", pkg_system="apt"),
Distro(
name="centos9",
distro="quay.io/centos/centos",
version="stream9",
pkg_system="yum",
),
Distro(name="rocky9", distro="rockylinux/rockylinux", version="9", pkg_system="yum"),
]
def parse_args() -> Config:
"""Parse the command-line arguments."""
parser: Final = argparse.ArgumentParser(prog="build_container")
parser.add_argument(
"-N",
"--noop",
action="store_true",
help="no-operation mode; display what would be done",
)
parser.add_argument(
"-P",
"--pull",
action="store_true",
help="pull a newer base image if available",
)
parser.add_argument(
"-T",
"--run-test",
action="store_true",
help="run the test suite within the container",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="verbose operation; display diagnostic output",
)
parser.add_argument(
"distro",
type=str,
choices=[item.name for item in DISTROS] + ["list"],
help="the Linux distribution to build a container for, or 'list'",
)
args: Final = parser.parse_args()
if args.distro == "list":
for item in DISTROS:
print(f"{item.name}\t{item.distro}:{item.version}")
sys.exit(0)
return Config(
log=util.build_logger(name="build_container", verbose=args.verbose, quiet=False),
distro=next(item for item in DISTROS if item.name == args.distro),
noop=args.noop,
pull=args.pull,
run_test=args.run_test,
utf8_env=util.get_utf8_env(),
)
def get_build_command(cfg: Config, tempd: pathlib.Path) -> list[str]:
"""Construct the `docker build` command to run."""
return (
[
"docker",
"build",
]
+ (["--pull"] if cfg.pull else [])
+ [
"--build-arg",
f"DISTRO={cfg.distro.distro}",
"--build-arg",
f"DISTRO_VERSION={cfg.distro.version}",
"--build-arg",
f"PKG_SYSTEM={cfg.distro.pkg_system}",
"-t",
f"remrun/sshd:{cfg.distro.name}",
"--",
str(tempd),
]
)
def get_test_command(cfg: Config) -> list[str]:
"""Construct the run-docker-test command to run."""
return ["docker/run-docker-test.sh", cfg.distro.name]
def run_command(cfg: Config, cmd: list[str]) -> None:
"""Run a command or, in no-op mode, display it."""
cmd_str: Final = shlex.join(cmd)
if cfg.noop:
print(cmd_str)
return
cfg.log.debug("Running `%(cmd_str)s`", {"cmd_str": cmd_str})
subprocess.check_call(cmd, env=cfg.utf8_env) # noqa: S603
def copy_files(cfg: Config, tempd: pathlib.Path) -> None:
"""Copy the necessary files for building the container."""
flist: Final = (
subprocess.check_output( # noqa: S603
["git", "ls-files", "-z", "docker"], # noqa: S607
encoding="UTF-8",
env=cfg.utf8_env,
)
.strip("\0")
.split("\0")
)
cmd: Final = ["cp", "-p", "-v", "--", *flist, "requirements/install.txt", str(tempd)]
run_command(cfg, cmd)
def main() -> None:
"""Parse command-line options, run 'docker build'."""
cfg: Final = parse_args()
with tempfile.TemporaryDirectory(prefix="remrun-docker-build.") as tempd_obj:
tempd: Final = pathlib.Path(tempd_obj).absolute()
copy_files(cfg, tempd)
run_command(cfg, get_build_command(cfg, tempd))
if cfg.run_test:
run_command(cfg, get_test_command(cfg))
if __name__ == "__main__":
main()
|