Files
ci-scripts/generator/render_thin_fleet.py
2026-07-05 23:14:10 +03:00

202 lines
6.8 KiB
Python

#!/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 <repo>\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()