#!/usr/bin/env python3 """Scan app-delivery repo directories and output config/regions.json. Discovers all regions by scanning the same directory conventions as the TypeScript scanner (src/lib/scanner.ts). Outputs a JSON file with one entry per region containing regionId, architecture, provider, environment, and regionName. Directory conventions ~~~~~~~~~~~~~~~~~~~~~ repo root is set by ``APP_DELIVERY_REPO_PATH`` (default: ``../app-delivery``). | Architecture | Path pattern | |-------------|-------------------------------------------------------------------------| | dedicated | ``o11y-agent-control-server/envs/--/`` | | nextgen | ``nextgen-regional-control-plane/o11y-agent-control-server/envs/`` | | nextgen (staging) | ``staging/nextgen-regional-control-plane/o11y-agent-control-server/envs/`` | | nextgen (dev) | ``dev/nextgen-regional-control-plane/o11y-agent-control-server/envs/`` | Each directory must contain ``deployment.yaml``, otherwise it is skipped. nextgen vs serverless is distinguished by checking ``config.yaml`` for ``^mode:\\s*serverless``. byoc is distinguished by ``o11yConfig.isByoc: true`` (checked first, mirroring the TypeScript scanner's ``byoc > serverless > nextgen`` precedence). Output format ~~~~~~~~~~~~~ :: { "version": 1, "entries": [ { "regionId": "dedicated:aws:dev:aws-dev-us-east-1", "architecture": "dedicated", "provider": "aws", "environment": "dev", "regionName": "us-east-1" } ] } Usage: make generate-regions """ import json import os import re import sys from pathlib import Path HERE = Path(__file__).resolve().parent PROJECT = HERE.parent REGIONS_PATH = PROJECT / "config" / "regions.json" REPO_ROOT = Path( os.getenv("APP_DELIVERY_REPO_PATH", PROJECT / ".." / "app-delivery") ) DEDICATED_ROOT = REPO_ROOT / "o11y-agent-control-server" / "envs" NEXTGEN_ROOTS = [ ( REPO_ROOT / "nextgen-regional-control-plane" / "o11y-agent-control-server" / "envs", "prod", ), ( REPO_ROOT / "staging" / "nextgen-regional-control-plane" / "o11y-agent-control-server" / "envs", "staging", ), ( REPO_ROOT / "dev" / "nextgen-regional-control-plane" / "o11y-agent-control-server" / "envs", "dev", ), ] SERVERLESS_RE = re.compile(r"^mode:\s*serverless", re.MULTILINE) BYOC_RE = re.compile(r"^\s+isByoc:\s*true\s*$", re.MULTILINE) def is_serverless(dirpath): config = dirpath / "config.yaml" if not config.is_file(): return False return bool(SERVERLESS_RE.search(config.read_text())) def is_byoc(dirpath): """Check ``o11yConfig.isByoc: true`` (strict boolean; quoted "true" does not match).""" config = dirpath / "config.yaml" if not config.is_file(): return False return bool(BYOC_RE.search(config.read_text())) def parse_dedicated_dirname(name): """Parse ``--`` e.g. ``aws-prod-us-east-1``.""" for provider in ("aws", "gcp", "azure"): pfx = f"{provider}-" if not name.startswith(pfx): continue rest = name[len(pfx):] for env in ("dev", "staging", "prod"): epfx = f"{env}-" if rest.startswith(epfx): return dict( provider=provider, environment=env, regionName=rest[len(epfx):], ) return None def parse_nextgen_dirname(name, expected_env): """Parse ``--`` e.g. ``prod-aws-us-west-2-ng``.""" epfx = f"{expected_env}-" if not name.startswith(epfx): return None rest = name[len(epfx):] provider_map = [ ("aws", "aws"), ("alicloud", "alicloud"), ("ali", "alicloud"), ("azure", "azure"), ("gcp", "gcp"), ] for prefix, provider in provider_map: ppfx = f"{prefix}-" if rest.startswith(ppfx): return dict( provider=provider, environment=expected_env, regionName=rest[len(ppfx):], ) return None def discover_dedicated(): if not DEDICATED_ROOT.is_dir(): return [] entries = [] for d in sorted(DEDICATED_ROOT.iterdir()): if not d.is_dir(): continue if not (d / "deployment.yaml").is_file(): continue parsed = parse_dedicated_dirname(d.name) if not parsed: continue entries.append(dict( regionId=f"dedicated:{parsed['provider']}:{parsed['environment']}:{d.name}", architecture="dedicated", **parsed, )) return entries def discover_nextgen(): entries = [] for root, env in NEXTGEN_ROOTS: if not root.is_dir(): continue for d in sorted(root.iterdir()): if not d.is_dir(): continue if not (d / "deployment.yaml").is_file(): continue parsed = parse_nextgen_dirname(d.name, env) if not parsed: continue if is_byoc(d): arch = "byoc" else: arch = "serverless" if is_serverless(d) else "nextgen" entries.append(dict( regionId=f"{arch}:{parsed['provider']}:{parsed['environment']}:{d.name}", architecture=arch, **parsed, )) return entries def main(): if not REPO_ROOT.is_dir(): print(f"error: repo root not found: {REPO_ROOT}", file=sys.stderr) print("Set APP_DELIVERY_REPO_PATH environment variable.", file=sys.stderr) sys.exit(1) entries = discover_dedicated() + discover_nextgen() entries.sort(key=lambda e: e["regionId"]) REGIONS_PATH.write_text( json.dumps(dict(version=1, entries=entries), indent=2) + "\n", ) print(f"discovered {len(entries)} regions → {REGIONS_PATH}") by_arch = {} for e in entries: by_arch.setdefault(e["architecture"], []).append(e) for arch in ("dedicated", "nextgen", "serverless", "byoc"): n = len(by_arch.get(arch, [])) if n: print(f" {arch}: {n}") if __name__ == "__main__": main()