From e5817e24701292010c4bb25747704d23f4ed9514 Mon Sep 17 00:00:00 2001 From: JLCode-tech Date: Wed, 13 May 2026 17:16:18 +1000 Subject: [PATCH 1/3] feat(shared-layer): scope bnk-forge-modules to cloud-agnostic k8s prereqs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combines stages 1 and 2 of the migration to a per-cloud blueprint repo architecture (see MIGRATION.md): - Create k8s/bnk-prerequisites/bnkforge.pack.json (v2alpha1 schema). This was the only "active" shared module missing a pack JSON; the PR #60 catalog-version fix had to source its version from module.json as a fallback. Now sourced from the pack directly. - Add k8s/bnk-cert-issuer to release-2.2-official.json. The module has a working pack and is silently depended on by cert-manager, network-setup, and bnk-prerequisites — but wasn't listed in the catalog, so Forge never generated a transition blueprint for it. Adding it as an active first-class entry. - Add MIGRATION.md at the repo root. Documents the target architecture (per-cloud repos modelled on jgruberf5/bnk-forge-ibm-roks-cluster), what stays here (shared cloud-agnostic k8s layer), what moves out (FLO, CNEInstance, network-setup vendor per-cloud), what retires (bnk-namespaces, far-setup, bnk-gateway-ext), and the phased plan. - Bump VERSION to 2.2-rev.29. Co-Authored-By: Claude --- MIGRATION.md | 88 +++++++++++++ VERSION | 2 +- catalog/releases/release-2.2-official.json | 8 ++ k8s/bnk-prerequisites/bnkforge.pack.json | 142 +++++++++++++++++++++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 MIGRATION.md create mode 100644 k8s/bnk-prerequisites/bnkforge.pack.json 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..e1e5412 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2-rev.28 +2.2-rev.29 diff --git a/catalog/releases/release-2.2-official.json b/catalog/releases/release-2.2-official.json index 9954663..234badb 100644 --- a/catalog/releases/release-2.2-official.json +++ b/catalog/releases/release-2.2-official.json @@ -26,6 +26,14 @@ "deploy_models": ["terraform", "helm", "kubernetes_manifest"] } }, + { + "path": "k8s/bnk-cert-issuer", + "version": "1.0.0", + "state": "active", + "execution": { + "deploy_models": ["kubernetes_manifest"] + } + }, { "path": "k8s/network-setup", "version": "2.0.0", diff --git a/k8s/bnk-prerequisites/bnkforge.pack.json b/k8s/bnk-prerequisites/bnkforge.pack.json new file mode 100644 index 0000000..6c134e6 --- /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. Injected as a project secret.", + "source": "project_secret", + "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." + } + ] + } +} From 98d6b8281b0cde386d1bdda8a1633094c40c0a75 Mon Sep 17 00:00:00 2001 From: JLCode-tech Date: Wed, 13 May 2026 17:19:47 +1000 Subject: [PATCH 2/3] fix(bnk-prerequisites): use 'user' source for cne_pull_secret (CI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validator allows source in {user, module, auto} only. project_secret isn't a valid value — sensitive secrets use source=user + sensitive=true, and Forge lets the deploy form bind it to a project secret. Bump VERSION to 2.2-rev.30. --- VERSION | 2 +- k8s/bnk-prerequisites/bnkforge.pack.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index e1e5412..a2e5879 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2-rev.29 +2.2-rev.30 diff --git a/k8s/bnk-prerequisites/bnkforge.pack.json b/k8s/bnk-prerequisites/bnkforge.pack.json index 6c134e6..0a1385a 100644 --- a/k8s/bnk-prerequisites/bnkforge.pack.json +++ b/k8s/bnk-prerequisites/bnkforge.pack.json @@ -32,8 +32,8 @@ { "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. Injected as a project secret.", - "source": "project_secret", + "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 } ], From 84fd7384500ae7fc5a7f6335bffd2a6bfbaf56ff Mon Sep 17 00:00:00 2001 From: JLCode-tech Date: Wed, 13 May 2026 17:22:43 +1000 Subject: [PATCH 3/3] fix(bnk-cert-issuer): add module.json + per-entry engine override bnk-cert-issuer is a pure-manifest module (no .tf code) rendered by the backend Python engine. The release validator previously required every active entry to match the release-level execution_engine (opentofu), which would have forced a misleading engine value on this module. Changes: - scripts/validate_module_metadata.py: allow per-entry execution.engine override; falls back to release.execution_engine when entry doesn't specify one. Backwards compatible for all 24 existing packs. - catalog/releases/release-2.2-official.json: declare execution.engine = kubernetes on the bnk-cert-issuer entry. - k8s/bnk-cert-issuer/module.json: new file, mirrors the existing pack with engine=kubernetes / deploy_models=[kubernetes_manifest]. - VERSION: 2.2-rev.30 -> 2.2-rev.31. Both pack and release-manifest validators pass locally. Co-Authored-By: Claude --- VERSION | 2 +- catalog/releases/release-2.2-official.json | 1 + k8s/bnk-cert-issuer/module.json | 172 +++++++++++++++++++++ scripts/validate_module_metadata.py | 6 +- 4 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 k8s/bnk-cert-issuer/module.json diff --git a/VERSION b/VERSION index a2e5879..d5cc7bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2-rev.30 +2.2-rev.31 diff --git a/catalog/releases/release-2.2-official.json b/catalog/releases/release-2.2-official.json index 234badb..47173b1 100644 --- a/catalog/releases/release-2.2-official.json +++ b/catalog/releases/release-2.2-official.json @@ -31,6 +31,7 @@ "version": "1.0.0", "state": "active", "execution": { + "engine": "kubernetes", "deploy_models": ["kubernetes_manifest"] } }, 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/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")