#!/usr/bin/env -S uv run --script # /// script # dependencies = [ # "pyyaml", # ] # /// # vim: filetype=python """ Generate Woodpecker pipelines for rpmbuilder. One pipeline file per distro (build-.yaml for pull_request checks and deploy-.yaml for master pushes). Each pipeline uses a Woodpecker matrix over the packages that target that distro, so every vendor package builds in its own job. Deploy jobs publish to the distro's artifactapi rpm-vendor local repo. The matrix package lists are derived from each rpms/*/metadata.yaml, so this script is the source of truth: run `make pipelines` (or ./tools/gen-pipelines) after adding/removing a package or changing its target distros, and commit the regenerated .woodpecker/*.yaml. """ import glob from pathlib import Path import yaml ROOT = Path(__file__).resolve().parent.parent WOODPECKER = ROOT / ".woodpecker" # Distro -> pipeline settings. `builder` runs buildah/the build tool (the actual # RPM build happens in a nested container using each package's metadata image); # `upload` only needs curl. rpm-vendor- mirrors the artifactapi repos. DISTROS = [ {"key": "almalinux8", "distro": "almalinux/el8", "repo": "rpm-vendor-el8"}, {"key": "almalinux9", "distro": "almalinux/el9", "repo": "rpm-vendor-el9"}, {"key": "fedora42", "distro": "fedora/42", "repo": "rpm-vendor-f42"}, {"key": "fedora43", "distro": "fedora/43", "repo": "rpm-vendor-f43"}, {"key": "fedora44", "distro": "fedora/44", "repo": "rpm-vendor-f44"}, ] BUILDER_IMAGE = "git.unkin.net/unkin/almalinux9-rpmbuilder:latest" UPLOAD_IMAGE = "git.unkin.net/unkin/almalinux9-base:latest" ARTIFACTAPI = "https://artifactapi.k8s.syd1.au.unkin.net" HEADER = "# Generated by tools/gen-pipelines - do not edit by hand.\n" def packages_for(distro: str) -> list[str]: names = [] for mf in sorted(glob.glob(str(ROOT / "rpms" / "*" / "metadata.yaml"))): meta = yaml.safe_load(open(mf)) or {} distros = { repo for build in meta.get("builds", []) for repo in build.get("repository", []) } if distro in distros: names.append(meta.get("name", Path(mf).parent.name)) return sorted(names) def matrix_block(packages: list[str]) -> str: lines = "\n".join(f" - {p}" for p in packages) return f"matrix:\n PACKAGE:\n{lines}\n" def build_step() -> str: return f""" - name: build image: {BUILDER_IMAGE} commands: - mkdir -p /woodpecker/rpms - ln -s /woodpecker/rpms /workspace - ./tools/build build --distro DISTRO --buildah "$PACKAGE" privileged: true backend_options: kubernetes: serviceAccountName: default resources: requests: memory: 512Mi cpu: 1 limits: memory: 2Gi cpu: 2 """ def upload_step(repo: str) -> str: return f""" - name: upload image: {UPLOAD_IMAGE} commands: - | HOST="{ARTIFACTAPI}" REPO="{repo}" for rpm in $(find dist -type f -name '*.rpm'); do FILE=$$(basename "$$rpm") # artifactapi has no HEAD route (405); probe the served Packages path. code=$$(curl -s -o /dev/null -w '%{{http_code}}' "$$HOST/api/v2/remotes/$$REPO/files/Packages/$$FILE" || true) if [ "$$code" = "200" ]; then echo "$$FILE already exists in $$REPO (HTTP $$code); skipping upload" continue fi echo "Uploading $$FILE to $$REPO (existence probe returned $$code)" curl -f -X PUT \\ "$$HOST/api/v2/remotes/$$REPO/files/$$FILE" \\ -H "Content-Type: application/x-rpm" \\ --data-binary @"$$rpm" done depends_on: [build] backend_options: kubernetes: serviceAccountName: default resources: requests: memory: 128Mi cpu: 100m limits: memory: 512Mi cpu: 500m """ def render(distro_cfg: dict, deploy: bool) -> str: packages = packages_for(distro_cfg["distro"]) if deploy: when = "when:\n - event: push\n branch: master\n" else: when = "when:\n - event: pull_request\n" body = HEADER + when + "\n" + matrix_block(packages) + "\nsteps:\n" body += build_step().replace("DISTRO", distro_cfg["distro"]) if deploy: body += "\n" + upload_step(distro_cfg["repo"]) return body def main() -> None: for cfg in DISTROS: (WOODPECKER / f"build-{cfg['key']}.yaml").write_text(render(cfg, deploy=False)) (WOODPECKER / f"deploy-{cfg['key']}.yaml").write_text(render(cfg, deploy=True)) print(f"generated build-{cfg['key']}.yaml deploy-{cfg['key']}.yaml " f"({len(packages_for(cfg['distro']))} packages)") if __name__ == "__main__": main()