feat(catalog-seed): add bp-cnpg-pair Blueprint + wordpress-tenant active-hot-standby mode (Refs TBD-E8b, TBD-B31) (#1717)

Wave 28-B discovery: the bp-cnpg-pair Catalyst-curated Blueprint chart
(platform/cnpg-pair/ @ 0.1.1) was missing from the catalog-seed
template added by PR #1697. The chart is published at
oci://ghcr.io/openova-io/bp-cnpg-pair, but operators had no way to see
it in /api/v1/catalog on a fresh Sovereign — only the 13 entries from
PR #1697 rendered.

This PR seeds bp-cnpg-pair alongside its bp-cnpg companion in
templates/catalog-seed/blueprints.yaml. Render goes from 13 -> 14
Blueprint CRs on a freshly-handed-over Sovereign.

Also wires the canonical `database.mode` enum knob on bp-wordpress-
tenant (singleton | active-hot-standby), aligning the operator-facing
interface with the new bp-cnpg-pair Blueprint:

  - chart/values.yaml: new `database.mode` (empty default for back-compat).
  - chart/templates/_helpers.tpl: new `bp-wordpress-tenant.dbMode` helper
    with resolution precedence (enum wins; legacy
    `pg.activeHotStandby.enabled` boolean folds as alias for chart
    0.3.x overlays).
  - chart/templates/cnpg-cluster.yaml: reads the resolved enum via the
    helper instead of the raw boolean. Output is bit-for-bit identical
    when overlays don't set the new knob (back-compat smoke verified:
    legacy boolean still renders 2 Cluster CRs).
  - blueprint.yaml: configSchema exposes `database.mode` so the
    marketplace voucher -> org wizard (D29) can present a
    "Postgres topology" picker instead of a boolean.
  - Chart.yaml: version bump 0.3.0 -> 0.3.1.

Status:
  - chart render: helm lint clean on both charts; 4 invariants pass
    (singleton/mode=ahs/legacy-bool/mode-overrides-bool).
  - runtime D31: chart-rendered as of PR #1562; full prov-time
    runtime verification remains deferred (gated on next Sovereign
    fresh-prov per docs/SESSION-2026-05-17-CONVERGENCE.md).

Refs TBD-E8b, TBD-B31.

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
This commit is contained in:
e3mrah 2026-05-18 19:08:05 +04:00 committed by GitHub
parent a033eb7b15
commit 6685bd7441
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 256 additions and 6 deletions

View File

@ -6,7 +6,7 @@ metadata:
catalyst.openova.io/category: tenant-app
catalyst.openova.io/section: pts-7-sme-tenant
spec:
version: 0.2.0
version: 0.3.1
card:
title: WordPress Tenant
summary: |
@ -99,6 +99,23 @@ spec:
database:
type: object
properties:
mode:
type: string
enum: ["", "singleton", "active-hot-standby"]
default: ""
description: |
Canonical operator-facing DB topology selector (TBD-E8b).
- "singleton" (DEFAULT) — single in-vcluster CNPG Cluster CR.
- "active-hot-standby" (D31) — primary + replica CNPG Cluster CRs across
two regions, WAL streaming over Cilium
ClusterMesh (mirrors bp-cnpg-pair shape).
When set, this enum overrides the legacy
`pg.activeHotStandby.enabled` boolean. Empty string falls
through to the boolean for back-compat with chart 0.3.x
overlays. The marketplace voucher → org wizard (D29)
writes this field directly.
cnpgClusterName:
type: string
default: wordpress-db

View File

@ -42,7 +42,19 @@ name: bp-wordpress-tenant
# - New tests/active-hot-standby-render.sh render-gate asserts default
# render emits 1 Cluster and enabled render emits 2 Cluster CRs with
# the right nodeSelectors + replica.source + externalCluster.host.
version: 0.3.0
# 0.3.1 (TBD-E8b, 2026-05-18): canonical `database.mode` enum.
# - New `database.mode` knob (singleton | active-hot-standby). When
# set, overrides the legacy `pg.activeHotStandby.enabled` boolean
# (kept as back-compat alias for chart 0.3.0 overlays). Resolution
# precedence centralised in _helpers.tpl `bp-wordpress-tenant.dbMode`.
# - templates/cnpg-cluster.yaml now reads the resolved enum via the
# helper; pre-existing renders bit-for-bit identical when overlays
# don't set the new knob (back-compat smoke).
# - blueprint.yaml configSchema exposes `database.mode` so the
# marketplace voucher → org wizard (D29) can present a
# "Postgres topology" picker instead of a boolean.
# - Companion to catalog-seed bp-cnpg-pair Blueprint CR (Refs TBD-E8b).
version: 0.3.1
appVersion: "6"
description: |
Catalyst Blueprint scratch chart for in-vcluster WordPress, one

View File

@ -125,6 +125,57 @@ Mirror bp-cnpg-pair's naming + validation pattern (see
platform/cnpg-pair/chart/templates/_helpers.tpl).
*/}}
{{/*
D31 / TBD-E8b: canonical operator-facing DB topology selector.
Resolves the chart's DB topology to one of:
- "singleton" (default — single in-vcluster CNPG Cluster CR)
- "active-hot-standby" (D31 — primary + replica CNPG Cluster CRs,
WAL streaming over Cilium ClusterMesh; mirrors
bp-cnpg-pair shape — see platform/cnpg-pair/)
Precedence (operator-supplied `database.mode` wins; legacy
`pg.activeHotStandby.enabled` boolean folds as alias for clusters whose
orchestrator overlays haven't been re-rendered with the canonical
enum):
1. `database.mode` is "active-hot-standby" → active-hot-standby
2. `database.mode` is "singleton" → singleton
3. `database.mode` is empty/unset:
- `pg.activeHotStandby.enabled=true` → active-hot-standby
- otherwise → singleton
The enum form (`database.mode`) is the canonical interface customers
see in the marketplace voucher → org wizard (D29). The boolean form
(`pg.activeHotStandby.enabled`) remains a back-compat alias and will
be removed in chart 0.4.0.
*/}}
{{- define "bp-wordpress-tenant.dbMode" -}}
{{- $mode := .Values.database.mode | default "" -}}
{{- if eq $mode "active-hot-standby" -}}
active-hot-standby
{{- else if eq $mode "singleton" -}}
singleton
{{- else if .Values.pg.activeHotStandby.enabled -}}
active-hot-standby
{{- else -}}
singleton
{{- end -}}
{{- end -}}
{{/*
D31 / TBD-E8b: boolean convenience predicate — returns "true" when the
resolved `dbMode` is "active-hot-standby". Used by cnpg-cluster.yaml
in place of the legacy `.Values.pg.activeHotStandby.enabled` boolean
so the canonical enum drives template rendering. Empty (falsy) when
mode resolves to "singleton".
*/}}
{{- define "bp-wordpress-tenant.dbModeActiveHotStandby" -}}
{{- if eq (include "bp-wordpress-tenant.dbMode" .) "active-hot-standby" -}}
true
{{- end -}}
{{- end -}}
{{/*
D31: replica Cluster CR name. Suffix `-replica` matches bp-cnpg-pair.
Truncated to 63 chars per the K8s resource-name limit.

View File

@ -1,13 +1,17 @@
{{- /*
─── CNPG Postgres for WordPress ─────────────────────────────────────────
Two render modes, controlled by `pg.activeHotStandby.enabled`:
Two render modes, selected by the canonical operator-facing enum
`database.mode` (singleton | active-hot-standby). Legacy boolean
`pg.activeHotStandby.enabled` is folded in by _helpers.tpl's
`bp-wordpress-tenant.dbMode` for back-compat with chart 0.3.x
orchestrator overlays (see TBD-E8b).
1. SINGLE-CLUSTER mode (default):
1. SINGLE-CLUSTER mode (database.mode=singleton, DEFAULT):
Renders ONE Cluster.postgresql.cnpg.io named `<cnpgClusterName>` in
the tenant namespace. Mirrors bp-gitea / bp-harbor patterns.
2. ACTIVE-HOT-STANDBY mode (Sovereign DoD D31):
2. ACTIVE-HOT-STANDBY mode (database.mode=active-hot-standby, DoD D31):
Renders TWO Cluster CRs mirroring the bp-cnpg-pair pattern (see
platform/cnpg-pair/chart/templates/{primary,replica}-cluster.yaml):
- PRIMARY `<cnpgClusterName>` in `primaryRegion`
@ -42,7 +46,13 @@ namespaces (issue #584; bp-gitea precedent).
*/ -}}
{{- if and .Values.wordpress.enabled .Values.database.cluster.enabled }}
{{- if .Capabilities.APIVersions.Has "postgresql.cnpg.io/v1" }}
{{- $haEnabled := .Values.pg.activeHotStandby.enabled -}}
{{- /* D31 / TBD-E8b: read the canonical `database.mode` enum (with
back-compat fold from the legacy `pg.activeHotStandby.enabled`
boolean). When mode resolves to "active-hot-standby" this template
renders TWO Cluster CRs (primary + replica) mirroring bp-cnpg-pair;
"singleton" (default) renders ONE Cluster CR. See _helpers.tpl
`bp-wordpress-tenant.dbMode` for the resolution precedence. */ -}}
{{- $haEnabled := eq (include "bp-wordpress-tenant.dbMode" .) "active-hot-standby" -}}
{{- if $haEnabled }}
{{- include "bp-wordpress-tenant.validateActiveHotStandbyRegions" . }}
{{- end }}

View File

@ -122,6 +122,29 @@ wordpress:
# platform/gitea/chart/templates/database-secret-sync-job.yaml for
# the rationale on Reflector vs. post-install Job.
database:
# ─── D31 / TBD-E8b — canonical DB topology selector ──────────────────
# Operator-facing enum: "singleton" (default) | "active-hot-standby".
# When set, this is the canonical interface and overrides the legacy
# boolean `pg.activeHotStandby.enabled` (kept as a back-compat alias
# for chart 0.3.x orchestrator overlays — removal slated for 0.4.0).
#
# The marketplace voucher → org wizard (D29) writes this field
# directly when the customer ticks the "cross-region HA database"
# checkbox; the catalog-seed Blueprint `bp-cnpg-pair` is the
# standalone equivalent customers can install side-by-side for any
# non-WordPress tenant-app.
#
# When mode=active-hot-standby:
# - `pg.activeHotStandby.{primaryRegion,replicaRegion}` MUST be set
# AND differ (templates/_helpers.tpl validateActiveHotStandbyRegions
# fail-fasts at render time).
# - bp-cnpg MUST be installed in BOTH regions (bootstrap-kit does
# this in every region — verified on t132).
# - DoD D11 (Cilium ClusterMesh via LoadBalancer) MUST be reconciled.
#
# Empty default → fall through to the legacy boolean for back-compat.
mode: ""
# CNPG Cluster name & topology — operator-overridable via cluster
# overlay. Defaults to a tenant-isolated cluster sized for SME usage.
cnpgClusterName: "wordpress-db"

View File

@ -59,6 +59,7 @@ Files
blueprint-bp-prometheus.yaml — Tenant metrics scraping
blueprint-bp-keycloak.yaml — Per-tenant Identity Provider
blueprint-bp-cnpg.yaml — CloudNative-PG Postgres operator
blueprint-bp-cnpg-pair.yaml — Active-hotstandby CNPG cluster-pair (D31 DR, Refs TBD-E8b)
blueprint-bp-redis.yaml — In-memory key/value store
blueprint-bp-clickhouse.yaml — Column-oriented analytics DB
blueprint-bp-opensearch.yaml — Search + log analytics

View File

@ -137,6 +137,142 @@ spec:
type: string
default: 10Gi
---
# bp-cnpg-pair — D31 active-hot-standby CNPG cluster-pair (companion to
# bp-cnpg). Catalyst-curated Blueprint chart published at
# oci://ghcr.io/openova-io/bp-cnpg-pair (Chart.yaml: platform/cnpg-pair/
# chart/Chart.yaml @ 0.1.1). Renders TWO postgresql.cnpg.io/v1.Cluster
# CRs (primary + replica) pinned to two regions, WAL streaming over
# Cilium ClusterMesh per ADR-0001 §9 (never public TLS). Per Wave 28-B
# discovery this Blueprint was missing from the catalog-seed even
# though the chart is published and the wordpress-tenant chart's
# `pg.activeHotStandby.enabled` knob already renders the same shape
# inline (DoD D31). Seeding here lets operators install the pair
# standalone (companion DB for any tenant-app) AND lets the install
# flow render a stable reference target when wordpress-tenant.db.mode
# = active-hot-standby. Refs TBD-E8b, TBD-B31.
apiVersion: catalyst.openova.io/v1
kind: Blueprint
metadata:
name: bp-cnpg-pair
labels:
catalyst.openova.io/managed-by: catalog-seed
catalyst.openova.io/origin: openova-public
catalyst.openova.io/curation: openova-public
catalyst.openova.io/category: database
catalyst.openova.io/section: pts-9-disaster-recovery
catalyst.openova.io/companion: bp-cnpg
app.kubernetes.io/managed-by: {{ $managedBy }}
app.kubernetes.io/instance: {{ $instance }}
spec:
version: "0.1.1"
visibility: listed
card:
title: "CNPG Cluster-Pair (Active-Hotstandby DR)"
category: database
family: postgres
summary: "Active-hot-standby CNPG cluster-pair across two regions. WAL streams over Cilium ClusterMesh for cross-region DR."
description: "Catalyst-curated Blueprint that renders a primary postgresql.cnpg.io/v1.Cluster CR in region A + a replica Cluster CR in region B configured as a CNPG replica cluster (replica.enabled=true + externalCluster) streaming WAL over a Cilium ClusterMesh-shared Service. Failover-readiness probe Pod flips Ready when WAL lag < threshold so Continuum K-Cont-2 can promote on operator-triggered switchover."
icon: cnpg-pair.svg
license: "Apache-2.0"
tags: [database, postgres, ha, dr, disaster-recovery, multi-region, active-hotstandby, clustermesh, cnpg]
docs: "https://cloudnative-pg.io/documentation/"
placementSchema:
# The cluster-pair IS the placement — primary + replica are the
# two regions. CNPG's replication model is asynchronous-streaming
# primary + 1 replica region; multi-replica-region is out of scope.
modes: [active-hotstandby]
default: active-hotstandby
minRegions: 2
maxRegions: 2
manifests:
chart: bp-cnpg-pair
source:
kind: HelmRepository
type: oci
url: oci://ghcr.io/openova-io
chart: bp-cnpg-pair
version: 0.1.1
configSchema:
type: object
required: [primary, replica, image]
properties:
primary:
type: object
required: [region]
properties:
region:
type: string
description: "Region key for the primary CNPG Cluster (e.g. hz-fsn-rtz-prod). Must differ from replica.region."
instances:
type: integer
default: 3
minimum: 3
maximum: 5
storage:
type: object
properties:
size:
type: string
default: "100Gi"
storageClass:
type: string
default: hcloud-volumes
replica:
type: object
required: [region]
properties:
region:
type: string
description: "Region key for the replica CNPG Cluster. Must differ from primary.region."
instances:
type: integer
default: 3
minimum: 3
maximum: 5
storage:
type: object
properties:
size:
type: string
default: "100Gi"
storageClass:
type: string
default: hcloud-volumes
walStreaming:
type: object
properties:
targetLagSeconds:
type: integer
default: 30
minimum: 1
maximum: 600
timeoutSeconds:
type: integer
default: 60
minimum: 5
maximum: 600
clusterMesh:
type: object
properties:
enabled:
type: boolean
default: true
description: "Cilium ClusterMesh transport for WAL. Disabling is FORENSIC ONLY — public-TLS replication is unsupported per ADR-0001 §9."
affinity:
type: string
default: local
description: "ClusterMesh affinity hint (local|remote|none)."
image:
type: object
required: [tag]
properties:
repository:
type: string
default: ghcr.io/cloudnative-pg/postgresql
tag:
type: string
description: "REQUIRED — empty triggers a template-time fail-fast. Example: '16.3-23' or 'sha256:<digest>'."
---
apiVersion: catalyst.openova.io/v1
kind: Blueprint
metadata: