diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..750586e --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,88 @@ +# BNK-Forge Module Repo — Migration to Per-Cloud Architecture + +This document captures the architectural direction for the BNK-Forge module ecosystem and the role this repository will play once the migration is complete. + +## Target architecture + +The BNK-Forge module ecosystem is moving to a **per-target-platform repo** pattern, modelled on [`jgruberf5/bnk-forge-ibm-roks-cluster`](https://github.com/jgruberf5/bnk-forge-ibm-roks-cluster). Each target platform gets its own repo with: + +- **`modules/`** — single-purpose Terraform modules, one per deployment step (cluster create, install cert-manager, install FLO, deploy CNEInstance, apply license, etc.). Cloud-specific concerns (IAM, registry auth, networking) live here. +- **`blueprints/`** — hand-authored `forge-blueprint.json` manifests that chain those modules together end-to-end. +- The repo registers in Forge as both a Module Source and a Blueprint Source. + +``` +bnk-forge-modules <-- THIS REPO: shared cloud-agnostic k8s layer +├── k8s/bnk-prerequisites/ (namespaces + FAR secrets + manifest) +├── k8s/cert-manager/ (Jetstack Helm install) +└── k8s/bnk-cert-issuer/ (BNK Issuer/ClusterIssuer CRs) + +bnk-forge-ibm-roks-cluster <-- EXISTS (jgruberf5) +bnk-forge-aws-eks-cluster <-- PLANNED +bnk-forge-azure-aks-cluster <-- PLANNED +bnk-forge-gcp-gke-cluster <-- PLANNED +bnk-forge-onprem-k8s <-- PLANNED (any on-prem Kubernetes) +``` + +## What this repository becomes + +After the migration, `bnk-forge-modules` is **the shared cloud-agnostic Kubernetes layer**. It hosts only modules that: + +- Touch the Kubernetes API exclusively (no cloud-provider APIs). +- Use no cloud-specific authentication (no IAM trusted profiles, no IRSA, no GCP service account keys). +- Have the same behavior on EKS, AKS, GKE, ROKS, vanilla on-prem, and kind. + +Every per-cloud and on-prem repo will reference modules from here for their shared prerequisites, rather than vendoring copies. + +### What stays here + +| Module | Purpose | +|---|---| +| `k8s/bnk-prerequisites` | Namespaces, FAR pull secrets, BNK manifest download + version discovery. Foundation module — every downstream module depends on it. | +| `k8s/cert-manager` | Deploys Jetstack cert-manager with BNK-compatible defaults. | +| `k8s/bnk-cert-issuer` | Creates BNK-managed self-signed CA + ClusterIssuer CRs for FLO certificate flows. | + +### What moves out + +| Module | Where it goes | Why | +|---|---|---| +| `bnk/flo` | Vendored per-cloud (each per-cloud repo gets its own copy) | Install model differs by cloud: IBM IAM trusted profile, AWS IRSA, Azure workload identity. BIG-IP CIS controller wiring is cloud-specific. | +| `bnk/cneinstance` | Vendored per-cloud | Chassis configuration tracks the underlying NIC stack (AWS ENA vs IBM SR-IOV vs Azure Accelerated Networking). PR #58 added AWS-specific `F5BnkGateway` chassis logic — exactly the kind of cloud-specific divergence that vendoring contains. | +| `k8s/network-setup` | Vendored per-cloud | Multus + NAD configuration depends on cloud-specific NIC drivers and SR-IOV/DPDK knobs. | + +### What retires + +| Module | Replacement | When | +|---|---|---| +| `k8s/bnk-namespaces` | `k8s/bnk-prerequisites` (superset) | Phase 4 — only `bnk/far-setup` still references it, and that retires too. | +| `bnk/far-setup` | `k8s/bnk-prerequisites` (already absorbs FAR secret setup) | Phase 4 — catalog `reason` field already names bnk-prerequisites as the replacement. | +| `bnk/bnk-gateway-ext` | None | Phase 4 — no consumers anywhere in the repo or templates. | +| `bnk/bnk-vlans`, `bnk/gateway`, `bnk/routes`, `bnk/bnk-netpolicy`, `bnk/bnk-secpolicy`, `bnk/bnk-gatewayclass` | TBD per-module | Phase 4 decision: some may move to per-cloud repos, some may stay shared, some may retire. Driven by whether the underlying CR apply is cloud-specific. | + +## Phased plan + +| Phase | Scope | Status | +|---|---|---| +| 0 | Land in-flight PRs (manifest `version` fix, AWS/EKS chassis, namespace alignment) | Done | +| 1 + 2 | Create missing `bnkforge.pack.json` for `k8s/bnk-prerequisites`; add `k8s/bnk-cert-issuer` to the catalog; document the migration direction (this file) | In progress | +| 3 | Create per-cloud repos: `bnk-forge-aws-eks-cluster`, then Azure, GCP, on-prem. Vendor FLO + CNEInstance + network-setup into each. Hand-author per-cloud blueprints. | Not started | +| 4 | Retire deprecated/legacy modules from this repo once per-cloud repos cover the moved-out modules. Per-module decision for ambiguous legacies. | Not started | +| 5 | Drop the `catalog/release-*.json` auto-generated transition blueprint path entirely; this repo serves only its shared modules. | Not started | + +## Versioning contract + +This repo (and every per-cloud repo) follows the **BNK release cadence**. Current BNK GA is **2.3**. Branches and tags should track BNK release boundaries: + +- `release/2.2` — current branch +- `release/2.3` — when BNK 2.3 content lands +- `release/2.4`, `release/3.x` — as BNK ships them + +The pack JSON schema (`bnkforge.pack.json`) and blueprint manifest schema (`forge-blueprint.json`) are the **stable API** between module repos and Forge. BNK version bumps ship as content changes here, never as Forge code changes. Forge must never branch on a BNK version string. + +## Reference + +- IBM repo (gold-standard pattern): https://github.com/jgruberf5/bnk-forge-ibm-roks-cluster +- Forge app: `bnk-forge-v2` + +--- + +*Last updated: 2026-05-13* diff --git a/VERSION b/VERSION index 9336ede..d5cc7bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2-rev.28 +2.2-rev.31 diff --git a/catalog/releases/release-2.2-official.json b/catalog/releases/release-2.2-official.json index 9954663..47173b1 100644 --- a/catalog/releases/release-2.2-official.json +++ b/catalog/releases/release-2.2-official.json @@ -26,6 +26,15 @@ "deploy_models": ["terraform", "helm", "kubernetes_manifest"] } }, + { + "path": "k8s/bnk-cert-issuer", + "version": "1.0.0", + "state": "active", + "execution": { + "engine": "kubernetes", + "deploy_models": ["kubernetes_manifest"] + } + }, { "path": "k8s/network-setup", "version": "2.0.0", diff --git a/k8s/bnk-cert-issuer/module.json b/k8s/bnk-cert-issuer/module.json new file mode 100644 index 0000000..291841c --- /dev/null +++ b/k8s/bnk-cert-issuer/module.json @@ -0,0 +1,172 @@ +{ + "module": { + "name": "BNK Cert Issuer", + "path": "k8s/bnk-cert-issuer", + "version": "1.0.0", + "layer": "kubernetes", + "category": "security", + "description": "Creates Forge-managed self-signed CA and ClusterIssuer resources for BNK cert-manager flows. Pure-manifest module rendered by the backend Python engine — no Terraform code.", + "cloud_specific": false, + "supported_platforms": ["any"] + }, + "source": { + "kind": "official", + "channel": "release/2.2" + }, + "execution": { + "engine": "kubernetes", + "deploy_models": ["kubernetes_manifest"] + }, + "contract": { + "metadata_version": "module-metadata/v2alpha1" + }, + "dependencies": { + "required": [ + { + "module": "k8s/cert-manager", + "reason": "Cert-manager CRDs must exist before ClusterIssuer/Certificate resources can be created" + }, + { + "module": "k8s/bnk-prerequisites", + "reason": "BNK namespaces must exist" + } + ], + "optional": [] + }, + "inputs": { + "required": [], + "optional": [ + { + "name": "namespace", + "type": "string", + "description": "Namespace where cert-manager resources are managed", + "default": "cert-manager", + "source": "user" + }, + { + "name": "self_signed_cluster_issuer_name", + "type": "string", + "description": "Name of the bootstrap self-signed ClusterIssuer", + "default": "bnk-selfsigned-cluster-issuer", + "source": "user" + }, + { + "name": "ca_certificate_name", + "type": "string", + "description": "Name of the CA Certificate resource", + "default": "bnk-ca", + "source": "user" + }, + { + "name": "ca_secret_name", + "type": "string", + "description": "Secret name that stores the generated CA keypair", + "default": "bnk-ca-secret", + "source": "user" + }, + { + "name": "cluster_issuer_name", + "type": "string", + "description": "Name of the CA-backed ClusterIssuer for BNK components", + "default": "bnk-ca-cluster-issuer", + "source": "user" + }, + { + "name": "instance_namespace", + "type": "string", + "description": "Namespace where CNEInstance and OTEL certificates are created", + "default": "f5-operator", + "source": "module", + "from_module": "k8s/bnk-prerequisites", + "from_output": "operator_namespace" + }, + { + "name": "otel_server_certificate_name", + "type": "string", + "description": "Name of OTEL server certificate resource", + "default": "external-otelsvr", + "source": "user" + }, + { + "name": "otel_server_secret_name", + "type": "string", + "description": "Secret name generated by OTEL server certificate", + "default": "external-otelsvr-secret", + "source": "user" + }, + { + "name": "otel_f5ing_server_certificate_name", + "type": "string", + "description": "Name of F5 ingestion OTEL certificate resource", + "default": "external-f5ingotelsvr", + "source": "user" + }, + { + "name": "otel_f5ing_server_secret_name", + "type": "string", + "description": "Secret name generated by F5 ingestion OTEL certificate", + "default": "external-f5ingotelsvr-secret", + "source": "user" + }, + { + "name": "ca_duration", + "type": "string", + "description": "Requested CA certificate duration", + "default": "87600h", + "source": "user" + }, + { + "name": "ca_renew_before", + "type": "string", + "description": "Renewal window before CA certificate expiry", + "default": "720h", + "source": "user" + }, + { + "name": "otel_duration", + "type": "string", + "description": "Requested OTEL certificate duration", + "default": "8640h", + "source": "user" + }, + { + "name": "otel_renew_before", + "type": "string", + "description": "Renewal window before OTEL certificate expiry", + "default": "720h", + "source": "user" + } + ] + }, + "outputs": { + "key_outputs": [ + { + "name": "cluster_issuer_name", + "type": "string", + "description": "CA-backed ClusterIssuer name for BNK components", + "used_by": ["bnk/cneinstance"], + "sensitive": false + }, + { + "name": "ca_secret_name", + "type": "string", + "description": "Secret name with CA keypair", + "used_by": [], + "sensitive": false + }, + { + "name": "cert_issuer_ready", + "type": "boolean", + "description": "Whether the issuer chain is ready", + "used_by": [], + "sensitive": false + } + ] + }, + "deployment": { + "order": 38, + "estimated_time": "30 seconds", + "requires_user_input": false, + "sensitive_inputs": [] + } +} diff --git a/k8s/bnk-prerequisites/bnkforge.pack.json b/k8s/bnk-prerequisites/bnkforge.pack.json new file mode 100644 index 0000000..0a1385a --- /dev/null +++ b/k8s/bnk-prerequisites/bnkforge.pack.json @@ -0,0 +1,142 @@ +{ + "schema_version": 1, + "module": { + "name": "BNK Prerequisites", + "path": "k8s/bnk-prerequisites", + "version": "1.0.0", + "category": "k8s", + "description": "Creates BNK namespaces, FAR image pull secrets, and downloads the BNK manifest to parse component versions. Foundation module — everything else in the BNK stack depends on it." + }, + "deployment_pack": { + "engine": "opentofu", + "runner_profile": "opentofu-default", + "working_directory": ".", + "entrypoints": { + "module_root": "." + }, + "lifecycle": { + "supports_init": true, + "supports_plan": true, + "supports_apply": true, + "supports_destroy": true, + "supports_refresh": true, + "supports_drift": true + } + }, + "dependencies": { + "required": [], + "optional": [] + }, + "inputs": { + "required": [ + { + "name": "cne_pull_secret", + "type": "string", + "description": "F5 FAR registry credentials. Accepts a bare base64-encoded service account JSON key, or a base64-encoded dockerconfigjson. Format is auto-detected. Bind to a project secret in the deploy form.", + "source": "user", + "sensitive": true + } + ], + "optional": [ + { + "name": "forge_kubeconfig_content", + "type": "string", + "description": "Kubeconfig YAML content. Auto-injected by BNK-Forge from the selected cluster. Set manually for standalone usage.", + "default": "", + "source": "auto", + "sensitive": true + }, + { + "name": "operator_namespace", + "type": "string", + "description": "Namespace for FLO and all BNK components (CNEInstance deploys everything here).", + "default": "f5-operator", + "source": "user" + }, + { + "name": "utils_namespace", + "type": "string", + "description": "Namespace for utility components (e.g. IPAM if deployed separately).", + "default": "f5-utils", + "source": "user" + }, + { + "name": "gateway_namespace", + "type": "string", + "description": "Namespace for Gateway API resources (Gateway, HTTPRoute, etc.).", + "default": "bnk-gw", + "source": "user" + }, + { + "name": "instance_namespace", + "type": "string", + "description": "Namespace where CNEInstance will be created (e.g. f5-bnk for DPU mode). When set and different from operator_namespace, creates an additional namespace + far-secret here. Leave empty to skip.", + "default": "", + "source": "user" + }, + { + "name": "bnk_manifest_version", + "type": "string", + "description": "BNK manifest version to download from FAR (e.g. 2.2.1-3.2226.0-0.0.511). Must start with X.Y.Z- format.", + "default": "2.2.1-3.2226.0-0.0.511", + "source": "user" + }, + { + "name": "cluster_name", + "type": "string", + "description": "Name of the Kubernetes cluster. Auto-wired from the cloud cluster module when available.", + "default": "", + "source": "auto" + } + ] + }, + "outputs": { + "key_outputs": [ + { + "name": "operator_namespace", + "type": "string", + "description": "Operator namespace name (FLO + all BNK components)." + }, + { + "name": "utils_namespace", + "type": "string", + "description": "Utilities namespace name." + }, + { + "name": "gateway_namespace", + "type": "string", + "description": "Gateway namespace name." + }, + { + "name": "far_secret_name", + "type": "string", + "description": "Name of the FAR image pull secret (always 'far-secret')." + }, + { + "name": "flo_version", + "type": "string", + "description": "FLO Helm chart version parsed from the BNK manifest." + }, + { + "name": "manifest_version", + "type": "string", + "description": "BNK manifest version used." + }, + { + "name": "component_versions", + "type": "map", + "description": "All component versions parsed from the BNK manifest." + }, + { + "name": "cert_manager_version", + "type": "string", + "description": "F5 cert-manager version from the manifest (informational — the cert-manager module uses its own version)." + }, + { + "name": "prerequisites_ready", + "type": "boolean", + "description": "Gate output — true when namespaces, secrets, and manifest parsing are all complete." + } + ] + } +} diff --git a/scripts/validate_module_metadata.py b/scripts/validate_module_metadata.py index 116dba8..3018a4d 100644 --- a/scripts/validate_module_metadata.py +++ b/scripts/validate_module_metadata.py @@ -135,9 +135,11 @@ def validate_release_module_entry(entry: dict, module_json: dict, manifest: dict errors.append(f"{module_path}: source.channel must match release.channel ({release.get('channel')})") execution = module_json.get("execution", {}) - if execution.get("engine") != release.get("execution_engine"): + entry_engine = entry.get("execution", {}).get("engine") + expected_engine = entry_engine if entry_engine else release.get("execution_engine") + if execution.get("engine") != expected_engine: errors.append( - f"{module_path}: execution.engine must match release.execution_engine ({release.get('execution_engine')})" + f"{module_path}: execution.engine must match expected ({expected_engine})" ) expected_deploy_models = entry.get("execution", {}).get("deploy_models")