Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 316 additions & 0 deletions CATALOG_REPO_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
# BNK Forge Catalog Repo Contract

This document defines the **contract** every `bnk-forge-catalog-*` repository must follow so that:

1. BNK Forge can sync them as Module Sources and/or Blueprint Sources without per-repo special-casing.
2. Customers can clone or import any of them and get a consistent experience.
3. Third-party contributors can author new catalog repos confidently.
4. Vendor-refresh automation (where applicable) works the same in every repo.

If you are creating a new catalog repo — AWS, Azure, GCP, on-prem, or anything else — this file is the canonical reference. Match it.

## Naming

Repository names follow the pattern **`bnk-forge-catalog-<target>`** where `<target>` is a short, hyphen-separated identifier for the deployment target:

| Repo name | Target |
|---|---|
| `bnk-forge-catalog-shared` | This repo — shared cloud-agnostic k8s primitives |
| `bnk-forge-catalog-aws-eks` | AWS EKS |
| `bnk-forge-catalog-azure-aks` | Azure AKS (planned) |
| `bnk-forge-catalog-gcp-gke` | GCP GKE (planned) |
| `bnk-forge-catalog-onprem-k8s` | Generic on-prem Kubernetes (planned) |

External / community catalogs may use their own naming (e.g. `jgruberf5/bnk-forge-ibm-roks-cluster`) — the `bnk-forge-catalog-*` convention applies to JLCode-tech-owned repos.

## Branches and BNK release cadence

Every catalog repo carries one branch per BNK release: `release/2.2`, `release/2.3`, `release/3.0`, etc. The branch matches the BNK product version that the content targets. `main` tracks the most recent release branch.

- New content for the current BNK release lands on the current `release/X.Y` branch.
- A new BNK release cuts a new `release/X.Y` branch, typically branched from the prior release branch and updated forward.
- Customers point Forge's Module Source / Blueprint Source at the branch (or a tag within it) for the BNK version they want.

## Required top-level files

| File | Purpose |
|---|---|
| `README.md` | Repo overview. See "README requirements" below. |
| `.gitignore` | Standard Terraform gitignore (excludes `.terraform/`, `*.tfstate*`, `.terraform.lock.hcl`, etc.). |
| `VENDORED.md` | **Required if the repo vendors modules from another catalog repo.** Documents what is vendored, from where, and the refresh discipline. |
| `VENDORED.pin` | **Required if vendoring.** Machine-readable pin record — auto-generated by `scripts/vendor-refresh.sh`. |
| `scripts/vendor-refresh.sh` | **Required if vendoring.** Portable bash (3.2+ compatible) script that refreshes the vendored modules from a pinned upstream ref. |
| `.github/workflows/vendor-refresh.yml` | **Required if vendoring.** Workflow that runs the script on cron + dispatch and opens a PR if drift detected. |

### README requirements

The root README must include:

1. **One-paragraph summary** of what the repo contains.
2. **Modules table** — every module under `modules/` listed with its path and purpose. Mark "not yet implemented" entries explicitly.
3. **Blueprints table** — every blueprint under `blueprints/` listed with its module chain.
4. **Repo model section** — describe the per-release branching ("This is a long-lived repo with branches per BNK release: `release/2.2`, `release/2.3`, …").
5. **Vendored content** (if applicable) — reference `VENDORED.md` for the discipline; never duplicate the vendor list in README.
6. **Credential template compatibility** — list the credential template variable names this repo's blueprints expect (e.g. `aws_access_key_id`, `aws_region`).
7. **BNK registration outputs** — list the canonical output fields that cluster-create / cluster-register modules emit for Forge auto-registration (`cluster_name`, `cluster_id`, `cluster_endpoint`, `region`, `kubeconfig`).
8. **Forge import instructions** — how to add this repo as a Module Source and Blueprint Source in Forge.

## Directory layout

```
bnk-forge-catalog-<target>/
├── README.md
├── .gitignore
├── VENDORED.md (if vendoring)
├── VENDORED.pin (if vendoring; auto-generated)
├── scripts/
│ └── vendor-refresh.sh (if vendoring)
├── .github/
│ └── workflows/
│ └── vendor-refresh.yml (if vendoring)
├── modules/
│ └── <module-name>/ (one dir per module)
│ ├── bnkforge.pack.json
│ ├── module.json (legacy; required during transition)
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── versions.tf
│ └── README.md
└── blueprints/
└── <blueprint-name>/
├── forge-blueprint.json
└── README.md
```

### Module naming

Modules under `modules/` follow `<target>-<step>` naming:

- `eks-cluster-create`, `eks-cluster-register` (provisioning)
- `eks-cluster-install-cert-manager`, `eks-cluster-install-flo` (install steps)
- `eks-cluster-cneinstall`, `eks-cluster-license` (BNK-specific)

The `<target>` prefix scopes the module name to the deployment target so cross-catalog naming collisions don't happen.

For the shared catalog repo (`bnk-forge-catalog-shared`), modules are organized by family at the top level (`k8s/bnk-prerequisites`, `k8s/cert-manager`, etc.) — they're not deployment-target-scoped because they're cloud-agnostic.

## Module structure

### Required files per module

| File | Purpose |
|---|---|
| `bnkforge.pack.json` | v2alpha1 pack manifest. Forge reads this to populate the module catalog. |
| `module.json` | Legacy metadata. Required during transition while Forge's release-manifest validator still references it. |
| `main.tf`, `variables.tf`, `outputs.tf`, `versions.tf` | Standard Terraform. Skip `.tf` files for pure-manifest modules (engine `kubernetes`). |
| `README.md` | Module-level docs: when to use, inputs, outputs, IAM/cluster permissions required. |
| `manifests/` | YAML manifests for pure-manifest modules (engine `kubernetes`). |
| `scripts/` | Helper scripts invoked by `data.external` blocks in `main.tf`. |
| `examples/` | Usage examples (HCL snippets). Optional but recommended. |

### `bnkforge.pack.json` schema

```json
{
"schema_version": 1,
"module": {
"name": "<human-readable name>",
"path": "<repo-relative path to this dir>",
"version": "<X.Y.Z>",
"category": "<infra|k8s|bnk|app|other>",
"description": "<one-sentence description>",
"provider": "<aws|azure|gcp|ibm|...>", // optional but recommended
"supported_platforms": ["<aws_eks|ibm_roks|any|...>"],
"tags": ["<keyword>", ...]
},
"deployment_pack": {
"engine": "<opentofu|kubernetes|ansible|script>",
"runner_profile": "<opentofu-default|kubernetes-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
}
},
"credentials": {
"required": [
{ "name": "<var-name>", "type": "cloud", "description": "..." }
],
"optional": []
},
"dependencies": {
"required": [
{ "module": "<repo-relative path to upstream module>", "reason": "..." }
],
"optional": []
},
"inputs": {
"required": [
{ "name": "<var-name>", "type": "<string|number|bool|list|map>",
"description": "...", "source": "<user|module|auto>", "sensitive": <bool> }
],
"optional": [
{ "name": "<var-name>", ..., "default": <value> }
]
},
"outputs": {
"key_outputs": [
{ "name": "<output-name>", "type": "<string|...>", "description": "..." }
]
}
}
```

**Validation**: The pack JSON must pass `python3 scripts/validate_pack_manifests.py` in this repo (`bnk-forge-catalog-shared`). The validator is the canonical schema enforcement.

**Forbidden values**: `inputs[*].source` must be one of `user`, `module`, or `auto`. **Do not** use `project_secret` — Forge handles sensitive bindings via the deploy form when `sensitive: true` is set on a `user`-source input.

## Blueprint structure

### `forge-blueprint.json` schema

```json
{
"schema_version": 1,
"blueprint": {
"id": "<unique-id>",
"version": "<X.Y.Z>",
"name": "<human-readable name>",
"description": "<one-paragraph description>"
},
"bnk_version": "<X.Y>", // the BNK release this targets
"compatibility": {
"supported_platform_profiles": ["<aws_eks|...>"],
"required_capabilities": []
},
"category": "<bnk|infrastructure|...>", // 'bnk' for install blueprints, 'infrastructure' for provisioning
"cloud_provider": "<aws|azure|gcp|ibm>",
"icon": "<lucide icon name>",
"color": "<orange|cyan|blue|...>",
"estimated_time": "<5-10 minutes>",
"estimated_cost": "<...>",
"difficulty": "<beginner|intermediate|advanced>",
"maturity": "<preview|beta|reference>",
"tags": ["<keyword>", ...],
"outcomes": [ "<what will exist after apply>", ... ],
"prerequisites": [
{ "type": "kubernetes_cluster", "description": "..." },
{ "type": "credential_template", "name": "<cred-template-var>", "description": "..." },
{ "type": "project_secret", "name": "<secret-var>", "description": "..." }
],
"input_summary": [
{ "label": "Input guidance", "value": "..." }
],
"platform_defaults": {
"<platform-key>": {
"container_platform": "<Generic|OpenShift>",
"storage_class_name": "<cloud-default-storage-class>",
"cloud_provider": "<aws|...>",
"cni_type": "<ipvlan|sriov|...>",
"nad_cni_type": "<ipvlan|...>",
"nad_interface_name": "<eth0|ens3|...>",
"nad_ipvlan_mode": "<l2|l3>",
"cert_manager_namespace": "cert-manager",
"flo_namespace": "<f5-operator|f5-bnk>",
"flo_utils_namespace": "f5-utils"
}
},
"inputs": {
"required": [
{ "name": "<var>", "type": "string", "description": "...",
"sensitive": <bool>,
"source": "<credential_template|project|user>",
"source_field": "<field-in-source>", // when source is credential_template or project
"order": <int> // form ordering, lower = earlier
}
],
"optional": [ ..., "default": <value> ]
},
"modules": [
{
"id": "<step-id>",
"name": "<step-name>",
"description": "<what this step does>",
"module": "modules/<module-dir>",
"version": "<module-version>",
"depends_on": ["<previous-step-id>", ...],
"inputs": {
"<module-input>": "${<blueprint-input>}"
}
}
]
}
```

### Cross-module wiring

Forge auto-wires module-to-module dependencies by **name match across `inputs`/`outputs`** when modules share `depends_on`. The blueprint manifest does **not** use `${module.<id>.outputs.<name>}` syntax — it only references blueprint-level inputs via `${var-name}`. If a module input has the same name as an upstream module's output and the upstream is in `depends_on`, Forge wires it automatically.

### Cloud credential pattern

Two valid patterns:

**Pattern A (IBM-style, cloud-coupled modules)**: every module accepts the cloud credentials directly. Each module does its own cluster lookup via the cloud provider data sources. The blueprint passes credentials to every module's `inputs` block.

**Pattern B (cloud-agnostic modules)**: only the cluster-register / cluster-create module accepts cloud credentials. Downstream modules use Forge-injected `forge_kubeconfig_content` (via `local.forge_kubeconfig` in their TF code — Forge generates `bnk_forge_providers.tf` at deploy time). The blueprint only wires credentials to the cluster module.

Both patterns are valid. The shared `bnk-forge-catalog-shared` modules use **Pattern B** because they're cloud-agnostic by design. Per-cloud-specific install modules (FLO, CNEInstance, License) typically use **Pattern A** because they need cloud APIs for IAM/registry/etc. setup.

A single blueprint can mix both patterns — wire creds to the modules that need them, omit for the vendored cloud-agnostic ones.

## Vendoring rules (if applicable)

If a catalog repo vendors modules from `bnk-forge-catalog-shared` (or any other upstream catalog):

1. **Vendored files are pristine.** Do not hand-edit anything inside a vendored module's directory. The next refresh will clobber local changes.
2. **AWS/Azure/GCP-specific tuning belongs in a wrapper module**, not in the vendored tree. Write a new module (e.g. `eks-cluster-install-flo`) that uses the vendored content as a submodule or reference.
3. **The pin (`VENDORED.pin`) is auto-generated** by `scripts/vendor-refresh.sh`. Do not edit it by hand. To change the upstream ref, run the script with `UPSTREAM_REF=...`.
4. **The refresh script applies deterministic rewrites** to the vendored pack JSONs: rewriting `module.path` and `dependencies.required[].module` to the local module names. The rewrite rules are encoded in the script's `rewrite_jq` block.
5. **The refresh workflow runs three triggers**: `schedule` (weekly cron), `workflow_dispatch` (manual), `repository_dispatch` (event `bnk-forge-modules-released` fired from this repo on push to `release/*`).

See [`VENDORED.md`](https://github.com/JLCode-tech/bnk-forge-catalog-aws-eks/blob/release/2.2/VENDORED.md) in `bnk-forge-catalog-aws-eks` for a working example.

## Validation

Before opening a PR in any catalog repo, run the equivalent of:

```bash
# JSON well-formedness
find . -name "bnkforge.pack.json" -exec jq empty {} \;
find . -name "forge-blueprint.json" -exec jq empty {} \;

# Pack schema (this validator lives in bnk-forge-catalog-shared/scripts/)
python3 scripts/validate_pack_manifests.py

# Terraform (only for non-vendored, non-Forge-injected modules)
tofu fmt -check -recursive
( cd modules/<your-module> && tofu init -backend=false && tofu validate )
```

Vendored TF modules will fail standalone `tofu validate` with "local.forge_kubeconfig not declared" — that's by design, not a bug. Forge injects that local at deploy time.

## Adding a new catalog repo

1. Create `JLCode-tech/bnk-forge-catalog-<target>` as a public repo with an auto-generated README and Terraform `.gitignore`.
2. Push an initial commit on `main`, then branch `release/2.X` (matching the current BNK release).
3. Populate the structure above on the release branch via a series of PRs:
- PR 1: scaffold + cluster-register module + initial blueprint stub
- PR 2: vendor the shared k8s layer + extend blueprint to chain prereqs/cert-manager/cert-issuer (if vendoring)
- PR 3+: per-cloud install modules (FLO, CNEInstance, License) + extend blueprint
4. Once content stabilizes, mark blueprint `maturity: "reference"` and document in this contract's compatibility section above.

## Open questions

- Should `module.json` be deprecated entirely once Forge stops reading it? Yes — but defer until `bnk-forge-v2`'s release-manifest validator no longer requires it.
- Should vendored modules track a tag (e.g. `v2.2.0`) instead of a branch (`release/2.2`)? Branches give us a moving target; tags give immutability. Defer until the first vendor-refresh after a `release/X.Y+1` cut surfaces the tradeoff in practice.
- Should the contract itself be versioned? If yes, this doc gets `version: 1` at the top. Defer until a backwards-incompatible change is actually needed.

---

Last reviewed: 2026-05-13
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.2-rev.31
2.2-rev.32
Loading