include build manager script
This commit is contained in:
parent
80013c3401
commit
2eabd832d9
|
@ -0,0 +1,211 @@
|
|||
#!/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()
|
Loading…
Reference in New Issue