#!/usr/bin/python3 import argparse from os import walk from pathlib import Path import urllib.request import shutil import subprocess from datetime import datetime from jinja2 import Template import json LIC_C="ALT-SP-Container" LIC_P="ALT-Container or GPLv3" LIC_SIS="GPLv3" builddir= "./" dockerfile = "Dockerfile" arch_map= {'amd64': "x86_64", '386': "i586", 'arm64': "aarch64", "riscv64": "riscv64", "loong64": "loongarch64" } def parse_args(): registry = "gitea.basealt.ru/alt" arches = ["amd64", "386", "arm64", "loong64", "riscv64"] names=["alt"] latest_tag="p10" #org="alt" branches=["p11", "p10", "Sisyphus"] exclusions='{"p10": ["loong64", "riscv64"], "p11": ["loong64", "riscv64"]}' parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "-b", "--branches", nargs="+", default=branches, choices=branches, help="list of branches", ) parser.add_argument( "-e", "--exclusions", default=exclusions, choices=exclusions, type=json.loads, help="list of branches with exclusion arches which no need built", ) parser.add_argument( "-n", "--names", nargs="+", default=names, choices=names, help="list of names images in templates directory", ) parser.add_argument( "-a", "--arches", nargs="+", default=arches, choices=arches, help="build images for these architectures", ) parser.add_argument( "-r", "--registry", default=registry, ) #parser.add_argument( # "--org", # default=org, #) parser.add_argument( "--no-push", action=argparse.BooleanOptionalAction, default=False, ) parser.add_argument( "--use-local-source", action=argparse.BooleanOptionalAction, default=False, ) parser.add_argument( "-l", "--latest", default=latest_tag, help="tag images in this branch as latest", ) parser.add_argument( "--old-structure", action=argparse.BooleanOptionalAction, default=False, ) parser.add_argument( "--new-structure", action=argparse.BooleanOptionalAction, default=True, ) args = parser.parse_args() return args def run(args): result = subprocess.run( args, capture_output = True, text = True ) if result.stderr is not None: print(result.stderr) return False print(result.stdout) return True def render_templ(path, lic, source): templatepath = Path(path) if templatepath.exists(): template = templatepath.read_text() rendered = Template(template).render( source=source, lic=lic, ) Path(f'{builddir}{dockerfile}').write_text(rendered + "\n") def create_dir(name): try: path = Path(name) path.mkdir(parents=True, exist_ok=True) print(f"Directory '{name}' created successfully.") except FileExistsError: print(f"Directory '{name}' already exists.") except PermissionError: print(f"Permission denied: Unable to create '{name}'.") except Exception as e: print(f"An error occurred: {e}") def main(): args = parse_args() #temp dir to dowdload images from ftp or copy locals tempdir = Path.home().joinpath(".local/share/images") create_dir(tempdir) #directory with templates of base inages walkdir = Path("./base-img") print(f'walk path = {walkdir.as_posix()}') #go walk throw directory with templates for img in walkdir.rglob('*'): #get template file name imgname = img.name.replace(".yaml", "") #if img is file and its file name in templates input list if img.is_file() and imgname in args.names: print(f'start building image {img.name}') for br in args.branches: #set license string for containers label print(f'branch {br}') LIC=LIC_SIS if "p1" in br: LIC=LIC_P elif "c" in br: LIC=LIC_C tag = br.lower() new_tag = datetime.now().strftime("%Y%m%d") manifest = f'{args.registry}/{imgname}' new_manifest = f'{args.registry}/{br}/{imgname}' excl_arches = [] if args.exclusions is not None and args.exclusions.get(br) is not None: excl_arches = args.exclusions.get(br) for a in args.arches: if a in excl_arches: continue print(f'arch {a}') file_arch = arch_map.get(a) if file_arch is None: print(f'not found arch name {a} for file in map {arch_map}') continue source = f"alt-{tag}-rootfs-minimal-{file_arch}.tar.xz" if args.use_local_source: print('use local source mode') #version for cloud image forge with getting source from local filesystem source_path=f"/home/builder/proto/{br}/cloud/{file_arch}" shutil.copy2(source_path + "/" + source, builddir) else: print('use download mode from ftp') sourceurl = f"http://ftp.altlinux.org/pub/distributions/ALTLinux/images/{br}/cloud/{file_arch}/{source}" print(f"source url {sourceurl}") download_path = f'{tempdir.as_posix()}/{source}' print(f"downland path {download_path}") try: urllib.request.urlretrieve(sourceurl, download_path) except Exception as e: print(f"download error: {e}") print(f"skip building {img.name} for {br} and {a}") continue print(f'file is downloaded to {download_path}') run(["cp", download_path, builddir, ]) #delete downlouded from ftp image source tempdir.joinpath(source).unlink() #"--build-arg SOURCE=" + source, #"--build-arg LICENSE=" + LIC, print(f'render file {img.as_posix()}, set license {LIC} and source {source}') render_templ(img.as_posix(), LIC, source) print(f'template {img.name} is rendered to Dockerfile') #build manifest run(["podman", "build", f"--manifest={manifest}:{tag}", f"--platform=linux/{a}", ".", ]) print(f'manifest {manifest}:{tag} for linux/{a} is builded') #delete image source p = Path(f'{builddir}{source}') if p.exists(): p.unlink() #delete dockerfile p = Path(f'{builddir}{dockerfile}') if p.exists(): p.unlink() if args.no_push: print('no push mode') continue print(f'push manifest to registry {args.registry}') if args.old_structure: run(["podman", "manifest", "push", "--all", f"{manifest}:{tag}", f"docker://{manifest}:{tag}", ]) if args.new_structure: run(["podman", "manifest", "push", "--all", f"{manifest}:{tag}", f"docker://{new_manifest}:{new_tag}", ]) run(["podman", "manifest", "push", "--all", f"{manifest}:{tag}", f"docker://{new_manifest}:latest", ]) print(f'manifest {manifest}:{tag} is pushed to registry {args.registry}') if args.latest == br: print(f"push latest tag") if args.old_structure: run(["podman", "manifest", "push", "--all", f"{manifest}:{tag}", f"docker://{manifest}:latest", ]) run(["podman", "manifest", "rm", f"{manifest}:{tag}", ]) if __name__ == "__main__": main()