#!/usr/bin/env python3 """Render thin ci-fleet-publish-based pipelines for the fleet family (oleks/ci-scripts#16): the 10 identical-fleet repos in manifest.yaml's `repos:` list and the 5 thin-parity repos in `thin_repos:`. This is a PREP/PLANNING script (per team-lead direction on #16/#17): it renders into a local --out dir for diffing against the current generated `.woodpecker.yaml` files. It does NOT write into ~/projects/building/ and is not wired into generate.py's main() — that wiring (and the actual repo migration) happens once the diffs are reviewed and the tag-scheme question is settled. Behavior-preservation approach: every piece of shell logic that generate.py's render()/render_thin() used to inline (resolv.conf, nix.conf, attic lifecycle, redis/sccache env, token remap, doctor gate) either maps to a PLUGIN_* setting the plugin already implements (nameserver, cores, max_jobs, cache), or gets folded into the `run:` command as an explicit shell one-liner/block — the plugin's `publish` flow just execs `bash -lc "$PLUGIN_RUN"`, so anything that was a `commands:` line before can still be a line in `run:`. Usage: python3 generator/render_thin_fleet.py --out /path/to/scratch """ import argparse import pathlib import yaml HERE = pathlib.Path(__file__).parent.parent PLUGIN_IMAGE = "git.oleks.space/oleks/ci-fleet-publish:v1" GENERATED_HEADER = ( "# GENERATED by oleks/ci-scripts — do not hand-edit.\n" "# Source: oleks/ci-scripts manifest.yaml + generator/render_thin_fleet.py" " (oleks/ci-scripts#16).\n" "# Re-render with: python3 generator/render_thin_fleet.py --out \n" "\n" ) NAMESERVER = "169.254.20.10" def render_identical(repo: dict) -> str: """The 10 `repos:` entries (oleks/ci-scripts#1 family).""" attic = repo.get("attic", True) redis_cache = repo.get("redis_cache", False) cores = repo.get("cores") max_jobs = repo.get("max_jobs", False) run_lines = [] if redis_cache: run_lines += [ "export SCCACHE_REDIS_ENDPOINT=tcp://redis.default.svc.cluster.local:6379", "export SCCACHE_REDIS_PASSWORD=$REDIS_PASSWORD", "export CCACHE_REMOTE_STORAGE=redis://redis.default.svc.cluster.local:6379", "export CCACHE_REMOTE_ONLY=1", ] run_lines.append("PUBLISH=1 nix run .#publish") run_block = "\n".join(f" {line}" for line in run_lines) settings_lines = [ f' run: |\n{run_block}', f' cache: "{str(attic).lower()}"', f' nameserver: "{NAMESERVER}"', ] if max_jobs: settings_lines.append(' max_jobs: "true"') if cores: settings_lines.append(f' cores: "{cores}"') settings_block = "\n".join(settings_lines) env_lines = [" GITEA_CLONE_TOKEN:", " from_secret: gitea_clone_token"] if attic: env_lines += [" ATTIC_TOKEN:", " from_secret: attic_token"] if redis_cache: env_lines += [" REDIS_PASSWORD:", " from_secret: redis_password"] env_block = "\n".join(env_lines) return f"""{GENERATED_HEADER}labels: arch: {repo["arch"]} when: - event: tag ref: "refs/tags/v*" steps: - name: publish image: {PLUGIN_IMAGE} environment: {env_block} settings: {settings_block} backend_options: kubernetes: resources: requests: memory: {repo["memory_request"]} limits: memory: {repo["memory_limit"]} """ def render_thin_parity(repo: dict) -> str: """The 5 `thin_repos:` entries (oleks/ci-scripts#10 family).""" doctor = repo.get("doctor", False) token_direct = repo.get("token_direct", True) publish_style = repo.get("publish_style", "publish1") tags_full = repo.get("tags_full", False) gitea_clone_token = repo.get("gitea_clone_token", True) run_lines = [] if doctor: run_lines.append( "nix run git+https://git.oleks.space/oleks/parity-lib#pipeline-doctor -- . --strict" ) if not token_direct: run_lines.append('export REGISTRY_TOKEN="$CI_REGISTRY_TOKEN"') if publish_style == "publish1": run_lines.append("PUBLISH=1 nix run .#publish") else: run_lines.append("nix run .#publish-s390x -- --publish") run_block = "\n".join(f" {line}" for line in run_lines) settings_lines = [ f' run: |\n{run_block}', ' cache: "false"', # oleks/ci-scripts#10: this family never used # the inline attic accelerator (dead ATTIC_TOKEN in some repos) — # cache: false preserves that, doesn't silently turn caching on. f' nameserver: "{NAMESERVER}"', ] settings_block = "\n".join(settings_lines) env_lines = [] if gitea_clone_token: env_lines += [" GITEA_CLONE_TOKEN:", " from_secret: gitea_clone_token"] if token_direct: env_lines += [" REGISTRY_TOKEN:", " from_secret: registry_token"] else: env_lines += [" CI_REGISTRY_TOKEN:", " from_secret: registry_token"] env_block = "\n".join(env_lines) if env_lines else " {}" # oleks/ci-scripts#10 dead_secrets (ATTIC_TOKEN/REDIS_PASSWORD declared # but never referenced) are deliberately DROPPED here, not carried # forward — they were unused in the underlying command either way. clone_note = "" if tags_full: clone_note = ( "\n# NOTE: original PLUGIN_TAGS=true/PLUGIN_DEPTH=0 (full clone incl. tags)\n" "# is a `clone:` block override the thin contract doesn't have a settings\n" "# equivalent for yet — needs resolving before this repo migrates (see\n" "# migration report).\n" ) return f"""{GENERATED_HEADER}labels: arch: {repo["arch"]} when: - event: tag ref: "refs/tags/v*" {clone_note} steps: - name: publish image: {PLUGIN_IMAGE} environment: {env_block} settings: {settings_block} backend_options: kubernetes: resources: requests: memory: {repo["memory_request"]} limits: memory: {repo["memory_limit"]} """ def main(): ap = argparse.ArgumentParser() ap.add_argument("--out", type=pathlib.Path, required=True) args = ap.parse_args() args.out.mkdir(parents=True, exist_ok=True) manifest = yaml.safe_load((HERE / "manifest.yaml").read_text()) for repo in manifest["repos"]: name = pathlib.Path(repo["dir"]).name target = args.out / f"{name}.woodpecker.yaml" target.write_text(render_identical(repo)) print(f"wrote {target}") for repo in manifest.get("thin_repos", []): name = pathlib.Path(repo["dir"]).name target = args.out / f"{name}.woodpecker.yaml" target.write_text(render_thin_parity(repo)) print(f"wrote {target}") if __name__ == "__main__": main()