jitsi-k8s/containers/non-root/build.py

212 lines
7.4 KiB
Python
Raw Normal View History

2024-07-17 11:20:55 +00:00
#!/bin/env python3
import subprocess
from multiprocessing import Process
from enum import Enum
from time import sleep
import yaml
from argparse import ArgumentParser, BooleanOptionalAction
class Action(Enum):
PUSH = "--push"
LOAD = "--load"
def from_str(text: str) -> Enum:
match text:
case "push":
return Action.PUSH
case "load":
return Action.LOAD
class Platform(Enum):
amd64 = "linux/amd64"
arm64 = "linux/arm64"
armv6 = "linux/arm/v6"
armv7 = "linux/arm/v7"
def from_str(text: str) -> Enum:
match text:
case "armv7":
return Platform.armv7
case "arm64":
return Platform.arm64
case "amd64":
return Platform.amd64
case _:
raise Exception(f"unknown platform: {text}")
class PlatformConfig:
def __init__(self, platform: Platform, dockerfile: str, directory: str, args: dict[str, str], tags: list[str]):
self.platform = platform
self.directory = directory
self.dockerfile = dockerfile
self.args = args
self.tags = tags
def run(self, registry: str, library: str, name:str, action: Action, repo: str, verbose: bool) -> int:
command = ["docker", "buildx", "build",
action.value,
"--platform", self.platform.value,
"-f", self.dockerfile]
for tag in self.tags:
command.append("--tag")
command.append(f"{registry}/{library}/{name}:{tag}")
for arg in self.args.items():
command.append("--build-arg")
command.append(f"{arg[0]}={arg[1]}")
command.append(self.directory)
return subprocess.run(command, cwd=repo, stderr=(subprocess.DEVNULL if not verbose else None)).returncode
def __str__(self) -> str:
return f"PlatformConfig{{{self.platform}, {self.directory}, {self.dockerfile}, {self.args}, {self.tags}}}"
def __repr__(self) -> str:
return str(self)
class Builder:
def __init__(self, name: str, repo: str, platforms: list[PlatformConfig], action: Action, registry: str, library: str):
self.name = name
self.repo = repo
self.platforms = platforms
self.action = action
self.registry = registry
self.library = library
def run(self, parallel: bool = False, verbose: bool = False):
if parallel:
try:
processes = list[tuple[str,Process]]()
for platform in self.platforms:
print(f"Building {self.name} for {platform.platform.name}...")
p = Process(target=platform.run, args=(self.registry, self.library, self.name, self.action, self.repo, verbose))
p.start()
processes.append((platform.platform.name,p))
while len(processes):
for p in processes:
if not p[1].is_alive():
if p[1].exitcode == 0:
processes.remove(p)
print(f"Built {self.name} for {p[0]}.")
else:
raise Exception(f"failed to complete {self.name} for {p[0]}")
sleep(0.1)
except KeyboardInterrupt:
for p in processes:
p[1].terminate()
print("ancelled")
exit()
else:
for platform in self.platforms:
print(f"Building {self.name} for {platform.platform.name}...")
if platform.run(self.registry, self.library, self.name, self.action, self.repo, verbose) != 0:
raise Exception(f"failed to complete build for {platform.platform.name}")
print("Done!")
def tag(self, tag: str) -> int:
command = ["docker", "buildx", "imagetools", "create",
"-t", f"{self.registry}/{self.library}/{self.name}:{tag}"]
for platform in self.platforms:
command.append(f"{self.registry}/{self.library}/{self.name}:{platform.tags[0]}")
return subprocess.run(command).returncode
def latest(self) -> int:
return self.tag("latest")
def __str__(self) -> str:
return f"Builder{{{self.name}, {self.repo}, {self.platforms}, {self.action.name}, {self.registry}, {self.library}}}"
def __repr__(self) -> str:
return str(self)
class Tag:
def __init__(self, registry: str, library: str, name: str, tag: str, builds: list[str]):
self.registry = registry
self.library = library
self.name = name
self.tag_name = tag
self.builds = builds
def tag(self) -> int:
command = ["docker", "buildx", "imagetools", "create",
"-t", f"{self.registry}/{self.library}/{self.name}:{self.tag_name}"]
for build in self.builds:
command.append(f"{self.registry}/{self.library}/{self.name}:{build}")
return subprocess.run(command).returncode
def __str__(self) -> str:
return f"Tag{{{self.registry}/{self.library}/{self.name}:{self.tag_name}, {self.builds}}}"
def __repr__(self) -> str:
return str(self)
def parse_yaml(path: str) -> tuple[list[Builder], list[Tag]]:
file = {}
with open(path, "r") as f:
file = yaml.safe_load(f)
registry = file.get("registry", "docker.io")
repos = file.get("repos", [])
builders = list[Builder]()
tags = list[Tag]()
for repo in repos:
name = repo["name"]
library = repo.get("library", "library")
repo_dir = repo.get("repo", "build")
action = repo.get("action", "push")
repo_args = repo.get("args", [])
repo_tags = repo.get("tags", [])
builds = repo.get("builds", [])
platforms = list[PlatformConfig]()
for build in builds:
arch = build.get("arch", "amd64")
dockerfile = build.get("dockerfile", "Dockerfile")
context = build.get("context", ".")
args = build.get("args", [])
build_tags = build.get("tags", [])
platforms.append(PlatformConfig(Platform.from_str(arch), dockerfile, context, { arg["key"]: arg["value"] for arg in repo_args + args }, build_tags))
builders.append(Builder(name, repo_dir, platforms, Action.from_str(action), registry, library))
for tag in repo_tags:
tags.append(Tag(registry, library, name, tag["name"], tag["builds"]))
return (builders, tags)
if __name__ == "__main__":
parser = ArgumentParser(
prog="BuildxManager",
description="configure container builds with yaml"
)
parser.add_argument("-f", action="store", default="build.yml", dest="file")
parser.add_argument("-p", action=BooleanOptionalAction, dest="parallel")
parser.add_argument("-v", action=BooleanOptionalAction, dest="verbose")
parser.add_argument("--dump-only", action=BooleanOptionalAction, dest="dump")
args = parser.parse_args()
builders, tags = parse_yaml(args.file)
if args.dump:
print(builders, tags, sep="\n")
else:
print(f"Performing jobs listed in {args.file}")
for builder in builders:
builder.run(parallel=args.parallel, verbose=args.verbose)
for tag in tags:
tag.tag()