Skip to main content
Glama
docker_image_build.py9.49 kB
#!/usr/bin/env python3 """ Invokes a `docker image build`. """ import argparse import subprocess import json import sys from enum import Enum, EnumMeta from typing import Any, Dict, List, Union # A slightly more Rust-y feeling enum # Thanks to: https://stackoverflow.com/a/65225753 class MetaEnum(EnumMeta): def __contains__(self: type[Any], member: object) -> bool: try: self(member) except ValueError: return False return True class BaseEnum(Enum, metaclass=MetaEnum): pass class PlatformArch(BaseEnum): Aarch64 = "aarch64" X86_64 = "x86_64" class PlatformOS(BaseEnum): Darwin = "darwin" Linux = "linux" Windows = "windows" class ImageArch(BaseEnum): Amd64 = "amd64" Arm64v8 = "arm64v8" VARIANT = "container" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--artifact-out-file", required=True, help="Path to write the image artifact file", ) parser.add_argument( "--git-info-json", required=True, help="Path to the Git metadata JSON file", ) parser.add_argument( "--build-metadata-out-file", required=True, help="Path to write the build metadata JSON file", ) parser.add_argument( "--name", required=True, help="Name of the image to build, from the full name {org}/{name}", ) parser.add_argument( "--org", required=True, help="Org of the image to build, from the full name {org}/{name}", ) parser.add_argument( "--label-metadata-out-file", required=True, help="Path to write the label metadata JSON file", ) parser.add_argument( "--tag-metadata-out-file", required=True, help="Path to write the tag metadata JSON file", ) parser.add_argument( "--docker-context-dir", required=True, help="Path to Docker context directory", ) parser.add_argument( "--build-arg", action="append", help="Build arg options passed to `docker build`", ) parser.add_argument( "--arch", required=True, choices=[arch.value for arch in PlatformArch], help="Target architecture", ) parser.add_argument( "--os", required=True, choices=[os.value for os in PlatformOS], help="Target operating system", ) parser.add_argument( "--author", required=True, help="Image author to be used in image metadata", ) parser.add_argument( "--source-url", required=True, help="Source code URL to be used in image metadata", ) parser.add_argument( "--license", required=True, help="Image license to be used in image metadata", ) return parser.parse_args() def main() -> int: args = parse_args() git_info = load_git_info(args.git_info_json) # argparse validates these are valid enum values arch = PlatformArch(args.arch) os = PlatformOS(args.os) image_arch = map_platform_to_image_arch(os, arch) label_metadata = compute_label_metadata( git_info, image_arch, args.org, args.name, args.author, args.source_url, args.license, ) tag_metadata = compute_tag_metadata(label_metadata, image_arch) build_image( args.docker_context_dir, label_metadata, args.build_arg or [], tag_metadata, ) write_json(args.label_metadata_out_file, label_metadata) write_json(args.tag_metadata_out_file, tag_metadata) write_artifact(args.artifact_out_file, tag_metadata) b3sum = compute_b3sum(args.artifact_out_file) build_metadata = compute_build_metadata(label_metadata, image_arch, b3sum) write_json(args.build_metadata_out_file, build_metadata) return 0 def write_json(output: str, metadata: Union[Dict[str, str], List[str]]): with open(output, "w") as file: json.dump(metadata, file, sort_keys=True) def load_git_info(git_info_file: str) -> Dict[str, str | int | bool]: with open(git_info_file) as file: return json.load(file) def build_image( cwd: str, metadata: Dict[str, str | str], build_args: List[str], tags: List[str], ): cmd = [ "docker", "image", "build", ] for key, value in metadata.items(): cmd.append("--label") cmd.append(f"{key}={value}") for build_arg in build_args: cmd.append("--build-arg") # If the build_arg already contains '=', pass it as-is # otherwise, pass just the key (for empty values). # This allows docker to look up env vars from the host if '=' in build_arg and not build_arg.endswith('='): cmd.append(build_arg) else: cmd.append(build_arg.rstrip('=')) for tag in tags: cmd.append("--tag") cmd.append(tag) cmd.append("--file") cmd.append("Dockerfile") cmd.append(".") print("--- Build image with: {}".format(" ".join(cmd))) subprocess.run(cmd, cwd=cwd).check_returncode() def write_artifact(output: str, tag_metadata: List[str]): cmd = [ "docker", "save", "--output", output, ] cmd.extend(tag_metadata) print("--- Creating image archive with: {}".format(" ".join(cmd))) subprocess.run(cmd).check_returncode() def compute_label_metadata( git_info: Dict[str, str | int | bool], image_arch: ImageArch, image_org: str, image_name: str, author: str, source_url: str, license: str, ) -> Dict[str, str]: full_image_name = f"{image_org}/{image_name}" created = git_info.get("committer_date_strict_iso8601") revision = git_info.get("commit_hash") canonical_version = git_info.get("canonical_version") commit_url = "{}/commit/{}".format( source_url.removesuffix(".git"), revision, ) image_url = ("https://hub.docker.com/r/{}/" + "tags?page=1&ordering=last_updated&name={}-{}").format( full_image_name, canonical_version, image_arch.value, ) metadata = { "name": full_image_name, "maintainer": author, "org.opencontainers.image.version": canonical_version, "org.opencontainers.image.authors": author, "org.opencontainers.image.licenses": license, "org.opencontainers.image.source": source_url, "org.opencontainers.image.revision": revision, "org.opencontainers.image.created": created, "com.systeminit.image.architecture": image_arch.value, "com.systeminit.image.image_url": image_url, "com.systeminit.image.commit_url": commit_url, } return metadata def compute_tag_metadata( label_metadata: Dict[str, str], image_arch: ImageArch, ) -> List[str]: metadata = [ "{}:{}-{}".format( label_metadata.get("name"), label_metadata.get("org.opencontainers.image.version"), image_arch.value, ), ] return metadata def compute_b3sum(artifact_file: str) -> str: cmd = [ "b3sum", "--no-names", artifact_file, ] result = subprocess.run(cmd, capture_output=True) # Print out stderr from process if it failed if result.returncode != 0: sys.stderr.write(result.stderr.decode("ascii")) result.check_returncode() b3sum = result.stdout.decode("ascii").rstrip() return b3sum def compute_build_metadata_v2( git_info: Dict[str, str | int | bool], family: str, platform_arch: PlatformArch, platform_os: PlatformOS, b3sum: str, ) -> Dict[str, str]: metadata = { "family": family, "variant": VARIANT, "version": git_info.get("canonical_version"), "arch": platform_arch.value, "os": platform_os.value, "commit": git_info.get("commit_hash"), "branch": git_info.get("branch"), "b3sum": b3sum, } return metadata def map_platform_to_image_arch( os: PlatformOS, arch: PlatformArch, ) -> ImageArch: if os != PlatformOS.Linux: raise ValueError(f"Unsupported platform operation system: {os.value}") # Map to image arch names image_arch_mapping = { PlatformArch.X86_64: ImageArch.Amd64, PlatformArch.Aarch64: ImageArch.Arm64v8, } if arch not in image_arch_mapping: raise ValueError(f"Unsupported platform architecture: {arch.value}") return image_arch_mapping[arch] def compute_build_metadata( label_metadata: Dict[str, str], image_arch: ImageArch, b3sum: str, ) -> Dict[str, str]: metadata = { "kind": "docker_image", "name": "{}--{}--{}.tar".format( label_metadata.get("name", "UNKNOWN_NAME").replace("/", "--"), label_metadata.get("org.opencontainers.image.version"), image_arch.value, ), "version": label_metadata.get("org.opencontainers.image.version"), "architecture": image_arch.value, "commit": label_metadata.get("org.opencontainers.image.revision"), "b3sum": b3sum, } return metadata if __name__ == "__main__": sys.exit(main())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server