openova/platform/self-sovereign-cutover/chart/templates/09-cutover-status-configmap.yaml
hatiyildiz 9774d7ba53 feat(self-sovereign-cutover): add step 10 — pivot vCluster HelmReleases to Sovereign Harbor (Refs #2034)
The chart's own comment at platform/bp-mgmt-vcluster/chart/values.yaml:77-79
promised "post-handover, the per-Sovereign overlay rewrites to
`harbor.<sovereign-fqdn>/proxy-ghcr/...`" — but the rewrite step never
existed anywhere in the cutover sequence. As a result, every Sovereign
post-handover keeps pulling vCluster control-plane images from
`harbor.openova.io` indefinitely, a direct violation of Principle #11
(no tether to harbor.openova.io after handover). Caught by the TBD-V24
tether audit on 2026-05-20.

Why step 04 (containerd registries.yaml pivot) doesn't catch it:
registries.yaml.v2 only mirrors the 7 canonical UPSTREAMS (ghcr.io,
docker.io, registry.k8s.io, gcr.io, quay.io, xpkg.upbound.io,
public.ecr.aws). The host `harbor.openova.io` is treated as a literal
endpoint, not an upstream, so containerd routes those image pulls
direct to mothership Harbor regardless of mirror config.

This step adds:
- Phase 1: live `kubectl patch helmrelease` against each of
  {bp-mgmt-vcluster, bp-rtz-vcluster, bp-dmz-vcluster} in flux-system,
  patching BOTH `spec.values.<role>Vcluster.image.repository`
  (umbrella) AND `spec.values.vcluster.controlPlane.statefulSet.image.
  {registry,repository}` (loft-sh subchart). Topology-aware: secondaries
  skip MGMT (not present), primary skips RTZ (not present). Idempotent:
  re-runs no-op when already pivoted.
- Phase 2: git push to local Gitea injecting the same override blocks
  into clusters/_template/bootstrap-kit/{54,58,59}-bp-*-vcluster.yaml
  so the bootstrap-kit Kustomization doesn't revert the live patch on
  next reconcile (same pattern as step 06 Phase 2 + Phase 2.5).

Coordination with chart 0.1.34 (TBD-V25, PR #2036, already merged):
totalSteps bumped from "9" → "10" in 09-cutover-status-configmap.yaml.
Contract test (tests/cutover-contract.sh) asserts shift from 9 → 10
step ConfigMaps and from 8 → 9 job-mode ConfigMaps. New Case 21
verifies Step 10's wrapper + subchart patches are wired correctly.

RBAC: ClusterRole gains helm.toolkit.fluxcd.io.helmreleases
{update,patch}. Step-06 Phase-1.6 (the openova-catalog HR patch shipped
in chart 0.1.31) was silently relying on this verb already — chart
0.1.31's RBAC change was missed, so this bump ALSO closes a latent
permission gap that would have surfaced on any cluster where the prior
patch attempt happened to require it.

Operator note: existing actively-running vCluster Pods do NOT churn on
this step — they're already running with images pulled at startup. The
patch ensures the NEXT image-pull (chart bump, Pod restart, region
add) routes through the Sovereign-local Harbor.

Refs #2034 (NOT Closes — operator-walk on fresh prov + screenshot
required per CLAUDE.md §4 anti-theater discipline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 04:18:04 +02:00

94 lines
3.4 KiB
YAML

{{- /*
Status ConfigMap — initial state for the cutover state machine.
Keys consumed by the catalyst-api cutover endpoint (issue #792,
products/catalyst/bootstrap/api/internal/handler/cutover.go):
cutoverComplete "true" | "false"
cutoverStartedAt RFC3339, set by /start
cutoverFinishedAt RFC3339, set when last step succeeds
currentStep <stepName> | "" when idle
currentStepIndex "0" .. "N"
totalSteps "N"
progressPercent "0" .. "100"
failedStep <stepName> | "" when no failure
lastError operator-actionable string | ""
step.<stepName>.startedAt
step.<stepName>.finishedAt
step.<stepName>.result "success" | "failed" | "skipped"
step.<stepName>.jobName Job/DaemonSet name (audit pointer)
Plus this chart's own:
registriesYamlActive "v1" | "v2" | "rollback"
Read by the registry-pivot DaemonSet's
reconcile loop; written by catalyst-api
between step 03 success and step 05.
helm.sh/resource-policy: keep ensures Helm leaves this ConfigMap in
place on chart uninstall so a re-install doesn't lose state mid-cutover.
*/ -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.status.configMapName }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "bp-self-sovereign-cutover.labels" . | nindent 4 }}
app.kubernetes.io/component: cutover-status
annotations:
helm.sh/resource-policy: keep
data:
cutoverComplete: "false"
cutoverStartedAt: ""
cutoverFinishedAt: ""
currentStep: ""
currentStepIndex: "0"
totalSteps: "10"
progressPercent: "0"
failedStep: ""
lastError: ""
registriesYamlActive: "v1"
# Per-step keys are populated lazily by catalyst-api as steps run.
# Initial empty placeholders included so a UI rendering this ConfigMap
# before /start has been called has something to bind to.
step.gitea-mirror.startedAt: ""
step.gitea-mirror.finishedAt: ""
step.gitea-mirror.result: ""
step.gitea-mirror.jobName: ""
step.harbor-projects.startedAt: ""
step.harbor-projects.finishedAt: ""
step.harbor-projects.result: ""
step.harbor-projects.jobName: ""
step.harbor-prewarm.startedAt: ""
step.harbor-prewarm.finishedAt: ""
step.harbor-prewarm.result: ""
step.harbor-prewarm.jobName: ""
step.registry-pivot.startedAt: ""
step.registry-pivot.finishedAt: ""
step.registry-pivot.result: ""
step.registry-pivot.jobName: ""
step.flux-gitrepository-patch.startedAt: ""
step.flux-gitrepository-patch.finishedAt: ""
step.flux-gitrepository-patch.result: ""
step.flux-gitrepository-patch.jobName: ""
step.helmrepository-patches.startedAt: ""
step.helmrepository-patches.finishedAt: ""
step.helmrepository-patches.result: ""
step.helmrepository-patches.jobName: ""
step.catalyst-api-env-patch.startedAt: ""
step.catalyst-api-env-patch.finishedAt: ""
step.catalyst-api-env-patch.result: ""
step.catalyst-api-env-patch.jobName: ""
step.egress-block-test.startedAt: ""
step.egress-block-test.finishedAt: ""
step.egress-block-test.result: ""
step.egress-block-test.jobName: ""
step.gitea-token-mint.startedAt: ""
step.gitea-token-mint.finishedAt: ""
step.gitea-token-mint.result: ""
step.gitea-token-mint.jobName: ""
step.vcluster-registry-pivot.startedAt: ""
step.vcluster-registry-pivot.finishedAt: ""
step.vcluster-registry-pivot.result: ""
step.vcluster-registry-pivot.jobName: ""