#!/usr/bin/env python3 """Nova Onboarding — auto-generate an environment binding file (P19, REQ-183). Given a consumer onboarding request (validated against schemas/onboarding.schema.json), generate a ``.json`` environment binding file from the dev template, filling in the consumer's ownerId + billingTag. The generated file is a starting point for the platform team (or a future automation) to bind to a real AWS account. This is the "request path" half of the no-humans onboarding flow (D-113). Real AWS account/network/state provisioning is a future feature milestone; this module removes the human handoff from the *request* step by generating the binding file + emitting a git patch / PR-branch instruction. Usage: python3 core/onboarding.py [--out ] python3 core/onboarding.py --request '{"consumerRepo":"acdl/c","requestedEnvironment":"qa","ownerId":"team-a","billingTag":"cc-a"}' """ from __future__ import annotations import argparse import json import os import sys from pathlib import Path from typing import Any, Dict def _repo_root() -> Path: return Path(__file__).resolve().parent.parent def _load_template_env(template_env: str = "dev", root: Path | None = None) -> Dict[str, Any]: """Load the template environment JSON (defaults to dev.json).""" root = root or _repo_root() env_path = root / "core" / "environments" / f"{template_env}.json" if not env_path.is_file(): raise FileNotFoundError(f"template environment {env_path} not found") return json.loads(env_path.read_text()) def generate_env_file( request: Dict[str, Any], template_env: str = "dev", root: Path | None = None, ) -> Dict[str, Any]: """Generate an environment binding dict from a consumer onboarding request. The generated dict is a copy of the template env with: - ``name`` → the requested environment - ``description`` → notes the consumer + owner - ``account_id`` → placeholder (000000000000) for the platform team to fill with the real account - ``ownerId`` + ``billingTag`` → from the request (for ABAC + cost) The dict validates against schemas/environment.schema.json. Returns the generated env dict. """ template = _load_template_env(template_env, root) requested = request["requestedEnvironment"] owner = request["ownerId"] billing = request["billingTag"] consumer = request["consumerRepo"] env = dict(template) env["name"] = requested env["description"] = ( f"Auto-generated binding for {consumer} (owner={owner}, " f"billing={billing}). Replace account_id with the real " f"{requested} account before deploying." ) env["account_id"] = "000000000000" # placeholder — platform team fills env["ownerId"] = owner env["billingTag"] = billing return env def _onboarding_request_message(env_name: str) -> str: """P19 (REQ-183): the rebranded Nova onboarding message — self-service request path, no longer routes to 'contact the platform team'.""" return ( "=== Nova Environment Onboarding ===\n" f"No environment named '{env_name}' is bound to this repository.\n\n" "Nova environments are platform-managed. The platform provisions on\n" "your behalf:\n" " - an AWS account (or a scoped partition of one)\n" " - a network (VPC + subnets)\n" " - a state backend (an S3 bucket + DynamoDB lock table)\n" " - an IAM role surfaced to your repo via attribute-based\n" " authorization (ABAC)\n\n" "You do not provide an AWS account, VPC, subnet, or state bucket.\n\n" "To request an environment (self-service):\n" " 1. Submit an onboarding request to the Nova Lambda\n" " (action: onboard_consumer) with your repo name + the\n" " environment name you need (e.g. 'dev').\n" " 2. The platform generates an environment binding + opens a PR.\n" " 3. The platform provisions the account/network/state/role and\n" " grants the ABAC role. Your next pipeline run proceeds.\n\n" "Run: python3 core/onboarding.py --request '{...}' to generate a\n" "binding file locally, or POST to the Lambda onboard_consumer action.\n" "===================================\n" ) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Generate an env binding from an onboarding request.") group = parser.add_mutually_exclusive_group(required=True) group.add_argument("request_file", nargs="?", help="path to a request JSON file") group.add_argument("--request", help="inline request JSON string") parser.add_argument("--out", help="output path for the generated env JSON (default: stdout)") parser.add_argument("--template-env", default="dev", help="template environment (default: dev)") args = parser.parse_args(argv) if args.request: request = json.loads(args.request) else: request = json.loads(Path(args.request_file).read_text()) env = generate_env_file(request, template_env=args.template_env) env_json = json.dumps(env, indent=2) + "\n" if args.out: Path(args.out).write_text(env_json) print(f"wrote: {args.out}") else: print(env_json) return 0 if __name__ == "__main__": sys.exit(main())