diff --git a/Makefile b/Makefile index cba4a090..e31c81f7 100644 --- a/Makefile +++ b/Makefile @@ -205,6 +205,10 @@ airgap-mirror: ## Mirror content to disconnected registry (requires MIRROR_REGIS airgap-post-install: ## Post-install bootstrap (after airgap-mirror + labctl apply-mirror-resources) @scripts/airgap-post-install.sh +.PHONY: airgap-repoint-subs +airgap-repoint-subs: ## Re-run the default-catalog subscription repoint (safety net; safe any time, e.g. after MCH deploys) + @scripts/airgap-post-install.sh --repoint-subs + diff --git a/airgap/imageset-config-4.22.yaml b/airgap/imageset-config-4.22.yaml index 9bd9794a..07620e89 100644 --- a/airgap/imageset-config-4.22.yaml +++ b/airgap/imageset-config-4.22.yaml @@ -1,6 +1,7 @@ --- # ImageSetConfiguration for CoCo Validated Pattern airgap deployment -# Target: MIRROR_REGISTRY_HOST:8443 (mirror-registry — sole airgap target, Phase 25.2) +# Target: a single mirror registry (org-prefixed target for Quay, e.g. +# quay.example:443/mirror — MIRROR_REGISTRY includes the org path) # OCP: 4.22.17 # Topology: baremetal (full operator set from values-baremetal.yaml) # @@ -10,7 +11,7 @@ # Usage: # oc-mirror -c airgap/imageset-config-4.22.yaml \ # --workspace file://$HOME/oc-mirror-workspace \ -# docker://MIRROR_REGISTRY_HOST:8443 --v2 +# docker:// --v2 apiVersion: mirror.openshift.io/v2alpha1 kind: ImageSetConfiguration @@ -113,7 +114,8 @@ mirror: # NFD operand — worker/master/gc DaemonSets use this tag, NOT the operator digest. - name: registry.redhat.io/openshift4/ose-node-feature-discovery-rhel9:v4.22 # Vault — Red Hat certified UBI image from registry.connect.redhat.com - - name: registry.connect.redhat.com/hashicorp/vault:1.21.4-ubi + # (hashicorp-vault chart 0.1.11 deploys vault:2.1.2-ubi by default) + - name: registry.connect.redhat.com/hashicorp/vault:2.1.2-ubi # Kyverno (Helm chart, not OLM — images from ghcr.io) - name: ghcr.io/kyverno/kyverno:v1.17.2 - name: ghcr.io/kyverno/kyvernopre:v1.17.2 @@ -125,7 +127,7 @@ mirror: # --- VP Helm OCI charts --- - name: quay.io/validatedpatterns/clustergroup:0.9.58 - name: quay.io/validatedpatterns/acm:0.1.27 - # values pin ~0.1.10 resolves the newest 0.1.x — mirror that, not the floor + # values pin 0.1.11 exactly (chart 0.1.11 deploys vault:2.1.2-ubi) - name: quay.io/validatedpatterns/hashicorp-vault:0.1.11 - name: quay.io/validatedpatterns/openshift-external-secrets:0.0.4 - name: quay.io/validatedpatterns/trustee:0.10.2 diff --git a/airgap/itms-manual-mirrors.yaml b/airgap/itms-manual-mirrors.yaml index b3097324..18b2ef85 100644 --- a/airgap/itms-manual-mirrors.yaml +++ b/airgap/itms-manual-mirrors.yaml @@ -1,3 +1,6 @@ +# Org-prefixed mirror targets: MIRROR_REGISTRY must include the org path +# (e.g. quay.example:443/mirror) — the same value scripts/airgap-post-install.sh +# requires — or every tag-based operand pull 404s. apiVersion: config.openshift.io/v1 kind: ImageTagMirrorSet metadata: @@ -14,3 +17,7 @@ spec: - MIRROR_REGISTRY_HOST:8443/hashicorp # NOTE: quay.io/community-operator-pipeline-prod removed — oc-mirror 4.22 # generates ITMS for it in itms-oc-mirror.yaml automatically. + # Kyverno chart images: the chart pulls from reg.kyverno.io, not ghcr.io. + - source: reg.kyverno.io + mirrors: + - MIRROR_REGISTRY_HOST:8443 diff --git a/charts/coco-supported/hello-openshift/templates/_helpers.tpl b/charts/coco-supported/hello-openshift/templates/_helpers.tpl index 162bbf9d..741215cc 100644 --- a/charts/coco-supported/hello-openshift/templates/_helpers.tpl +++ b/charts/coco-supported/hello-openshift/templates/_helpers.tpl @@ -56,3 +56,34 @@ Determine runtime class name from global config. {{- define "hello-openshift.runtimeClassName" -}} {{- .Values.global.coco.runtimeClassName -}} {{- end }} + +{{/* +Render a workload container image for the CoCo test workloads. + +CoCo guest VMs pull their images directly — there is no IDMS/ITMS inside the +CVM, so a registry.redhat.io/ubi9/... reference cannot be redirected at pull +time. In airgap mode (global.airgap: true) the image is rewritten against +global.mirrorRegistry using the oc-mirror source-path convention: the registry +hostname is stripped and the mirror root prefixed, e.g. + + registry.redhat.io/ubi9/httpd-24@sha256:... + → quay.example:443/mirror/ubi9/httpd-24@sha256:... + +The mirror root must include the org path for Quay targets (it is the same +value oc-mirror was pointed at). Rendering fails fast when airgap mode is on +and the mirror is unset or still a placeholder — a missing value here would +otherwise surface as ImagePullBackOff inside the CVM minutes later. +*/}} +{{- define "hello-openshift.image" -}} +{{- $image := .image -}} +{{- if .root.Values.global.airgap -}} +{{- $mirror := .root.Values.global.mirrorRegistry | default "" -}} +{{- if or (not $mirror) (contains "MIRROR_REGISTRY_HOST" $mirror) -}} +{{- fail "global.mirrorRegistry must be set to the mirror root (e.g. quay.example:443/mirror) when global.airgap is true" -}} +{{- end -}} +{{- $path := regexReplaceAll "^[a-zA-Z0-9.-]+(:[0-9]+)?/(.*)$" $image "${2}" -}} +{{- printf "%s/%s" $mirror $path -}} +{{- else -}} +{{- $image -}} +{{- end -}} +{{- end -}} diff --git a/charts/coco-supported/hello-openshift/templates/insecure-policy-deployment.yaml b/charts/coco-supported/hello-openshift/templates/insecure-policy-deployment.yaml index 6bee4414..6d36083c 100644 --- a/charts/coco-supported/hello-openshift/templates/insecure-policy-deployment.yaml +++ b/charts/coco-supported/hello-openshift/templates/insecure-policy-deployment.yaml @@ -23,7 +23,7 @@ spec: runtimeClassName: {{ include "hello-openshift.runtimeClassName" . }} containers: - name: hello-openshift - image: {{ .Values.image }} + image: {{ include "hello-openshift.image" (dict "root" $ "image" .Values.image) }} ports: - containerPort: 8080 volumeMounts: diff --git a/charts/coco-supported/hello-openshift/templates/secure-deployment.yaml b/charts/coco-supported/hello-openshift/templates/secure-deployment.yaml index b36026b6..8a263ee0 100644 --- a/charts/coco-supported/hello-openshift/templates/secure-deployment.yaml +++ b/charts/coco-supported/hello-openshift/templates/secure-deployment.yaml @@ -25,7 +25,7 @@ spec: runtimeClassName: {{ include "hello-openshift.runtimeClassName" . }} containers: - name: hello-openshift - image: {{ .Values.image }} + image: {{ include "hello-openshift.image" (dict "root" $ "image" .Values.image) }} ports: - containerPort: 8080 volumeMounts: diff --git a/charts/coco-supported/hello-openshift/templates/standard-deployment.yaml b/charts/coco-supported/hello-openshift/templates/standard-deployment.yaml index 561e1c28..0d0db831 100644 --- a/charts/coco-supported/hello-openshift/templates/standard-deployment.yaml +++ b/charts/coco-supported/hello-openshift/templates/standard-deployment.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: hello-openshift - image: {{ .Values.image }} + image: {{ include "hello-openshift.image" (dict "root" $ "image" .Values.image) }} ports: - containerPort: 8080 volumeMounts: diff --git a/charts/coco-supported/hello-openshift/values.yaml b/charts/coco-supported/hello-openshift/values.yaml index 9205e61a..13f673c2 100644 --- a/charts/coco-supported/hello-openshift/values.yaml +++ b/charts/coco-supported/hello-openshift/values.yaml @@ -11,3 +11,9 @@ runtimeClassName: "" # Global values used by this chart (overridden by values-global.yaml) global: clusterPlatform: "" # Cluster platform: "Azure" or "AWS" - determines runtime class + # Airgap mode comes from values-global.yaml (global.airgap); the default here + # is connected so standalone chart rendering uses upstream registries. + airgap: false + # Mirror root (org-prefixed for Quay targets) — required when airgap is true. + # The workload charts rewrite their images against it; see _helpers.tpl. + mirrorRegistry: "" diff --git a/charts/coco-supported/kbs-access-curl/templates/_helpers.tpl b/charts/coco-supported/kbs-access-curl/templates/_helpers.tpl new file mode 100644 index 00000000..23185a7c --- /dev/null +++ b/charts/coco-supported/kbs-access-curl/templates/_helpers.tpl @@ -0,0 +1,30 @@ +{{/* +Render a workload container image for the CoCo test workloads. + +CoCo guest VMs pull their images directly — there is no IDMS/ITMS inside the +CVM, so a registry.redhat.io/ubi9/... reference cannot be redirected at pull +time. In airgap mode (global.airgap: true) the image is rewritten against +global.mirrorRegistry using the oc-mirror source-path convention: the registry +hostname is stripped and the mirror root prefixed, e.g. + + registry.redhat.io/ubi9/httpd-24@sha256:... + → quay.example:443/mirror/ubi9/httpd-24@sha256:... + +The mirror root must include the org path for Quay targets (it is the same +value oc-mirror was pointed at). Rendering fails fast when airgap mode is on +and the mirror is unset or still a placeholder — a missing value here would +otherwise surface as ImagePullBackOff inside the CVM minutes later. +*/}} +{{- define "kbs-access-curl.image" -}} +{{- $image := .image -}} +{{- if .root.Values.global.airgap -}} +{{- $mirror := .root.Values.global.mirrorRegistry | default "" -}} +{{- if or (not $mirror) (contains "MIRROR_REGISTRY_HOST" $mirror) -}} +{{- fail "global.mirrorRegistry must be set to the mirror root (e.g. quay.example:443/mirror) when global.airgap is true" -}} +{{- end -}} +{{- $path := regexReplaceAll "^[a-zA-Z0-9.-]+(:[0-9]+)?/(.*)$" $image "${2}" -}} +{{- printf "%s/%s" $mirror $path -}} +{{- else -}} +{{- $image -}} +{{- end -}} +{{- end -}} diff --git a/charts/coco-supported/kbs-access-curl/templates/deployment.yaml b/charts/coco-supported/kbs-access-curl/templates/deployment.yaml index 48beeb75..a6f99026 100644 --- a/charts/coco-supported/kbs-access-curl/templates/deployment.yaml +++ b/charts/coco-supported/kbs-access-curl/templates/deployment.yaml @@ -37,7 +37,7 @@ spec: runAsUser: 0 containers: - name: httpd - image: {{ .Values.image }} + image: {{ include "kbs-access-curl.image" (dict "root" $ "image" .Values.image) }} ports: - containerPort: 8080 securityContext: @@ -50,7 +50,7 @@ spec: readOnly: true initContainers: - name: curl - image: {{ .Values.initImage }} + image: {{ include "kbs-access-curl.image" (dict "root" $ "image" .Values.initImage) }} command: ['sh', '-c', 'curl -s http://127.0.0.1:8006/cdh/resource/default/kbsres1/key3 > /var/www/html/secret.txt'] securityContext: privileged: true diff --git a/charts/coco-supported/kbs-access-curl/values.yaml b/charts/coco-supported/kbs-access-curl/values.yaml index 82fda729..88ca2919 100644 --- a/charts/coco-supported/kbs-access-curl/values.yaml +++ b/charts/coco-supported/kbs-access-curl/values.yaml @@ -9,3 +9,9 @@ defaultMemory: "" runtimeClassName: "" global: clusterPlatform: "" # Cluster platform: "Azure", "AWS", etc. - determines runtime class + # Airgap mode comes from values-global.yaml (global.airgap); the default here + # is connected so standalone chart rendering uses upstream registries. + airgap: false + # Mirror root (org-prefixed for Quay targets) — required when airgap is true. + # The workload charts rewrite their images against it; see _helpers.tpl. + mirrorRegistry: "" diff --git a/charts/coco-supported/kbs-access-sealed/templates/_helpers.tpl b/charts/coco-supported/kbs-access-sealed/templates/_helpers.tpl new file mode 100644 index 00000000..61e2ab98 --- /dev/null +++ b/charts/coco-supported/kbs-access-sealed/templates/_helpers.tpl @@ -0,0 +1,30 @@ +{{/* +Render a workload container image for the CoCo test workloads. + +CoCo guest VMs pull their images directly — there is no IDMS/ITMS inside the +CVM, so a registry.redhat.io/ubi9/... reference cannot be redirected at pull +time. In airgap mode (global.airgap: true) the image is rewritten against +global.mirrorRegistry using the oc-mirror source-path convention: the registry +hostname is stripped and the mirror root prefixed, e.g. + + registry.redhat.io/ubi9/httpd-24@sha256:... + → quay.example:443/mirror/ubi9/httpd-24@sha256:... + +The mirror root must include the org path for Quay targets (it is the same +value oc-mirror was pointed at). Rendering fails fast when airgap mode is on +and the mirror is unset or still a placeholder — a missing value here would +otherwise surface as ImagePullBackOff inside the CVM minutes later. +*/}} +{{- define "kbs-access-sealed.image" -}} +{{- $image := .image -}} +{{- if .root.Values.global.airgap -}} +{{- $mirror := .root.Values.global.mirrorRegistry | default "" -}} +{{- if or (not $mirror) (contains "MIRROR_REGISTRY_HOST" $mirror) -}} +{{- fail "global.mirrorRegistry must be set to the mirror root (e.g. quay.example:443/mirror) when global.airgap is true" -}} +{{- end -}} +{{- $path := regexReplaceAll "^[a-zA-Z0-9.-]+(:[0-9]+)?/(.*)$" $image "${2}" -}} +{{- printf "%s/%s" $mirror $path -}} +{{- else -}} +{{- $image -}} +{{- end -}} +{{- end -}} diff --git a/charts/coco-supported/kbs-access-sealed/templates/deployment.yaml b/charts/coco-supported/kbs-access-sealed/templates/deployment.yaml index ad2503fc..b80989c4 100644 --- a/charts/coco-supported/kbs-access-sealed/templates/deployment.yaml +++ b/charts/coco-supported/kbs-access-sealed/templates/deployment.yaml @@ -24,7 +24,7 @@ spec: runtimeClassName: {{ .Values.global.coco.runtimeClassName }} containers: - name: httpd - image: {{ .Values.image }} + image: {{ include "kbs-access-sealed.image" (dict "root" $ "image" .Values.image) }} ports: - containerPort: 8080 protocol: TCP diff --git a/charts/coco-supported/kbs-access-sealed/values.yaml b/charts/coco-supported/kbs-access-sealed/values.yaml index 7e06d347..2586ccfc 100644 --- a/charts/coco-supported/kbs-access-sealed/values.yaml +++ b/charts/coco-supported/kbs-access-sealed/values.yaml @@ -2,3 +2,12 @@ # Override via values-global.yaml or values-baremetal.yaml image: registry.redhat.io/ubi9/httpd-24@sha256:68a91ff691092f455fea682330c499588747231c16516cd4f35aff821e6847f2 + +global: + clusterPlatform: "" # Cluster platform: "Azure", "AWS", etc. - determines runtime class + # Airgap mode comes from values-global.yaml (global.airgap); the default here + # is connected so standalone chart rendering uses upstream registries. + airgap: false + # Mirror root (org-prefixed for Quay targets) — required when airgap is true. + # The workload charts rewrite their images against it; see _helpers.tpl. + mirrorRegistry: "" diff --git a/overrides/values-baremetal-airgap.yaml b/overrides/values-baremetal-airgap.yaml index 32c5a34e..2425d484 100644 --- a/overrides/values-baremetal-airgap.yaml +++ b/overrides/values-baremetal-airgap.yaml @@ -3,9 +3,12 @@ # The connected default (global.airgap: false) loads the inert # values-baremetal-connected.yaml instead. # -# SCOPE: this overlay applies when ArgoCD renders the clustergroup chart — -# i.e. to the clusterGroup OLM subscriptions (ESO, CNV, LVMS, trustee, -# cert-manager, intel-*, ACM, NFD) and the chart values below. +# SCOPE — this overlay reaches the ROOT clustergroup render: the +# patterns-operator resolves clusterGroup.sharedValueFiles itself when it +# builds the root Application (internal/controller/argo.go, +# commonApplicationSourceHelm → getSharedValueFiles), in addition to the +# chart forwarding them to child Applications. So the clusterGroup +# subscriptions and the app overrides below are all in scope. # # It does NOT reach the bootstrap operators: the openshift-gitops-operator # and patterns-operator subscriptions are created by the patterns-operator @@ -13,12 +16,24 @@ # be set directly in values-global.yaml: # main.gitops.operatorSource: cs-redhat-operator-index-v4-22 # main.patternsOperator.source: cs-community-operator-index-v4-22 +# +# LIST WARNING: Helm replaces lists when merging — an `overrides:` list here +# would drop any entry added to the same app in values-baremetal.yaml. This +# overlay therefore sets ONLY scalars (map values). Mode-dependent list +# entries (e.g. acm.mce_operator.source) are tpl-conditionals in the base +# file instead. global: - # Mirrored catalog for the Argo-rendered OLM subscriptions (oc-mirror output) + # Mirrored catalog for the Argo-rendered OLM subscriptions (oc-mirror output). + # The names track the operator index tags in airgap/imageset-config-.yaml + # (e.g. redhat/redhat-operator-index:v4.22 → cs-redhat-operator-index-v4-22 for + # global.clusterVersion 4.22); scripts/airgap-post-install.sh step 3e fails fast + # if any catalog referenced here is missing from the cluster. catalogSource: cs-redhat-operator-index-v4-22 catalogSourceNamespace: openshift-marketplace -# Trustee: flip platform collateral to the offline file backend. -# Collect with `make collect-dcap-collateral` before deploy. -kbs: - tdx: - collateralService: "file:///opt/confidential-containers/attestation-service/tdx/platform_collaterals.json" +# Connected default: the certified-operators catalog, disabled in airgap. +clusterGroup: + subscriptions: + intel-device-plugins: + source: cs-certified-operator-index-v4-22 + intel-tdx-dcap: + source: cs-certified-operator-index-v4-22 diff --git a/scripts/airgap-post-install.sh b/scripts/airgap-post-install.sh index 6db90b48..3b7152e8 100755 --- a/scripts/airgap-post-install.sh +++ b/scripts/airgap-post-install.sh @@ -6,7 +6,11 @@ # # Required env: # KUBECONFIG — path to cluster kubeconfig -# MIRROR_REGISTRY — registry host:port (e.g. MIRROR_REGISTRY_HOST:8443) +# MIRROR_REGISTRY — mirror root INCLUDING the org path for Quay targets +# (e.g. quay.example:443/mirror). Direct references +# (workload image rewrites, ITMS mirrors, chart pulls) +# all carry the org path; a bare host:port gets 401/404 +# on Quay. # # Optional env: # EXTRA_CA_CERT — path to CA cert file for the mirror registry @@ -46,6 +50,7 @@ case "${1:-}" in --sync-repos-only) MODE="sync"; shift ;; --deploy-pattern) MODE="deploy"; shift ;; --fix-manifest-lists) MODE="fix-manifests"; shift ;; + --repoint-subs) MODE="repoint"; shift ;; esac # ─── Step 1: Validate prerequisites ───────────────────────────── @@ -71,7 +76,8 @@ validate_prereqs() { fi if [[ -z "$MIRROR_REGISTRY" ]]; then - error "MIRROR_REGISTRY is not set (e.g. MIRROR_REGISTRY_HOST:8443)" + error "MIRROR_REGISTRY is not set (mirror root INCLUDING the org path," + error "e.g. quay.example:443/mirror — a bare host:port gets 401/404 on Quay)" exit 1 fi @@ -346,6 +352,193 @@ PYEOF # The old inline ITMS used registry_base with /mirror/ prefix which was # incorrect for single-registry (mirror-registry) architecture. +# ─── Step 3d: Repoint default-catalog subscriptions to the mirror catalog ─ +# Safety net for subscriptions created at runtime by operators (e.g. MCH +# creating the multicluster-engine subscription): they reference the DEFAULT +# catalog sources, which are disabled in airgap, and never resolve. +# +# The pattern's own subscriptions are declarative — sourceless ones follow +# global.catalogSource, the intel ones and the MCE source are set by +# overrides/values-baremetal-airgap.yaml and the acm override in +# values-baremetal.yaml — so ArgoCD-owned subscriptions are skipped here: +# ArgoCD self-heal would revert an out-of-band patch on the next sync. +# +# Idempotent and safe to re-run at any time (e.g. after MCH deploys): +# make airgap-repoint-subs +repoint_default_catalog_subscriptions() { + step "3d" "Repoint default-catalog subscriptions to mirrored catalogs" + + # Derive the oc-mirror catalog suffix from global.clusterVersion + # (4.22 → v4-22), matching the operator index tags in the imageset. + local cluster_version catalog_suffix + cluster_version=$(python3 -c " +import yaml +with open('${PATTERN_DIR}/values-global.yaml') as f: + d = yaml.safe_load(f) +print(d.get('global', {}).get('clusterVersion', '')) +" 2>/dev/null || true) + if [[ -n "${cluster_version}" ]]; then + catalog_suffix="v${cluster_version//./-}" + info "Catalog suffix -${catalog_suffix} (from global.clusterVersion ${cluster_version})" + else + # Fallback: derive from the CatalogSources oc-mirror actually created. + catalog_suffix=$(oc get catalogsources -n openshift-marketplace \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null \ + | grep -oE 'index-v[0-9]+-[0-9]+' | head -1 | sed 's/.*index-//' || true) + if [[ -z "${catalog_suffix}" ]]; then + error "Cannot derive the catalog suffix: global.clusterVersion is empty" + error "and no cs-*-index-v* CatalogSources exist in openshift-marketplace." + exit 1 + fi + warn "Catalog suffix -${catalog_suffix} derived from live CatalogSources (clusterVersion empty)" + fi + + local map=" +redhat-operators:cs-redhat-operator-index-${catalog_suffix} +certified-operators:cs-certified-operator-index-${catalog_suffix} +community-operators:cs-community-operator-index-${catalog_suffix} +" + local repointed=0 skipped=0 missing_catalog=0 + while IFS= read -r line; do + [[ -z "$line" ]] && continue + local def="${def:-}" mirror="${mirror:-}" + def="${line%%:*}" + mirror="${line##*:}" + + # Only repoint towards a CatalogSource that actually exists — + # patching to a missing source leaves the subscription unresolvable + # and is worse than leaving it on the disabled default. + if ! oc get catalogsource "$mirror" -n openshift-marketplace >/dev/null 2>&1; then + error " target CatalogSource $mirror not found — $def subscriptions left untouched" + missing_catalog=$((missing_catalog + 1)) + continue + fi + + # One "namespace name" pair per line — read line by line, NOT + # for-loop word splitting (which would feed each token to both + # ns and name and patch the wrong objects). + local subs + subs=$(oc get subscriptions.operators.coreos.com -A -o json 2>/dev/null | + python3 -c " +import json, sys +d = json.load(sys.stdin) +for s in d.get('items', []): + if s['spec'].get('source') == '$def': + print(s['metadata']['namespace'] + ' ' + s['metadata']['name']) +" 2>/dev/null) || subs="" + + while IFS=' ' read -r ns name; do + [[ -z "${ns:-}" ]] && continue + # Skip ArgoCD-owned subscriptions: declarative via the overlay, + # and self-heal reverts out-of-band patches on them. + if oc get subscription.operators.coreos.com "$name" -n "$ns" \ + -o jsonpath='{.metadata.labels.argocd\.argoproj\.io/instance}' 2>/dev/null | grep -q .; then + info " skipping $ns/$name (ArgoCD-owned — declarative via the airgap overlay)" + skipped=$((skipped + 1)) + continue + fi + info " repointing $ns/$name: $def -> $mirror" + if oc patch subscriptions.operators.coreos.com "$name" -n "$ns" --type merge \ + -p "{\"spec\":{\"source\":\"$mirror\",\"sourceNamespace\":\"openshift-marketplace\"}}"; then + repointed=$((repointed + 1)) + else + error " failed to repoint $ns/$name" + fi + done <<< "$subs" + done <<< "$map" + + info "Repointed: $repointed, skipped (ArgoCD-owned): $skipped, missing catalogs: $missing_catalog" + + # Final check — anything still on a disabled default catalog will never + # resolve. Fail loudly instead of leaving it for a stuck operator. + local stragglers + stragglers=$(oc get subscriptions.operators.coreos.com -A -o json 2>/dev/null | + python3 -c " +import json, sys +d = json.load(sys.stdin) +for s in d.get('items', []): + if s['spec'].get('source') in ('redhat-operators', 'certified-operators', 'community-operators'): + print(s['metadata']['namespace'] + '/' + s['metadata']['name'] + ' -> ' + s['spec']['source']) +" 2>/dev/null) || stragglers="" + if [[ -n "$stragglers" ]]; then + error "Subscriptions still on disabled default catalogs:" + while IFS= read -r s; do + [[ -z "$s" ]] && continue + error " $s" + done <<< "$stragglers" + error "ArgoCD-owned entries are declarative (airgap overlay); others need the repoint fixed." + exit 1 + fi + info "No subscriptions remain on disabled default catalogs" +} + +# ─── Step 3e: Validate the airgap overlay's catalog references ─── +# The overlay names mirrored CatalogSources literally (they track the +# imageset's operator index tags). Fail fast — before the pattern deploys — +# if any of them is missing, and check the mirror registry consistency while +# we are at it: a placeholder or mismatched global.mirrorRegistry would only +# surface as ImagePullBackOff inside the CVMs much later. +validate_airgap_overlay() { + step "3e" "Validate airgap overlay catalog references" + + local overlay="${PATTERN_DIR}/overrides/values-baremetal-airgap.yaml" + if [[ ! -f "$overlay" ]]; then + warn "No airgap overlay at $overlay — skipping validation" + return + fi + + # Every cs-* catalog named in the overlay must exist and be READY. + local catalogs + catalogs=$(python3 -c " +import re, sys +text = open('${overlay}').read() +for m in sorted(set(re.findall(r'cs-[a-z-]+-index-v[0-9-]+', text))): + print(m) +" 2>/dev/null) || catalogs="" + local missing=0 + while IFS= read -r cs; do + [[ -z "$cs" ]] && continue + local state + state=$(oc get catalogsource "$cs" -n openshift-marketplace \ + -o jsonpath='{.status.connectionState.lastObservedState}' 2>/dev/null) || state="" + if [[ "$state" == "READY" ]]; then + info " $cs: READY" + else + error " $cs: missing or not READY (state: '${state:-absent}')" + missing=$((missing + 1)) + fi + done <<< "$catalogs" + if [[ $missing -gt 0 ]]; then + error "The airgap overlay references catalogs that are not READY on this cluster." + error "Check that global.clusterVersion matches the mirrored operator index tags." + exit 1 + fi + + # Mirror registry consistency: global.mirrorRegistry must be a real, + # non-placeholder value and match $MIRROR_REGISTRY (org path included). + local mirror yaml_mirror + mirror="${MIRROR_REGISTRY%%/}" + yaml_mirror=$(python3 -c " +import yaml +with open('${PATTERN_DIR}/values-global.yaml') as f: + d = yaml.safe_load(f) +print(d.get('global', {}).get('mirrorRegistry', '') or '') +" 2>/dev/null) || yaml_mirror="" + if [[ -z "$yaml_mirror" || "$yaml_mirror" == *"MIRROR_REGISTRY_HOST"* ]]; then + error "global.mirrorRegistry is empty or a placeholder in values-global.yaml." + error "Set it to the mirror root INCLUDING the org path (e.g. quay.example:443/mirror)" + error "— the workload charts rewrite their images against it in airgap mode." + exit 1 + fi + if [[ "$yaml_mirror" != "$mirror" ]]; then + error "global.mirrorRegistry ('$yaml_mirror') != MIRROR_REGISTRY ('$mirror')." + error "They must be the same value (org path included) or workloads will pull" + error "from a different registry than the one the cluster mirrors." + exit 1 + fi + info "global.mirrorRegistry matches MIRROR_REGISTRY ($mirror)" +} + # ─── Step 5: Mirror OCI Helm charts and utility images ─────────── mirror_oci_charts() { step 5 "Mirror OCI Helm charts and utility images" @@ -712,12 +905,23 @@ with open('${PATTERN_DIR}/values-global.yaml') as f: " 2>/dev/null || echo "gitops-1.22") local gitops_source + # operatorSource may be empty in connected mode — fall back to a + # clusterVersion-derived mirrored catalog name (same convention as the + # overlay and step 3d), never a hardcoded minor. + local cluster_version default_source + cluster_version=$(python3 -c " +import yaml +with open('${PATTERN_DIR}/values-global.yaml') as f: + d = yaml.safe_load(f) + print(d.get('global', {}).get('clusterVersion', '')) +" 2>/dev/null || true) + default_source="cs-redhat-operator-index-v${cluster_version//./-}" gitops_source=$(python3 -c " import yaml with open('${PATTERN_DIR}/values-global.yaml') as f: d = yaml.safe_load(f) - print(d.get('main',{}).get('gitops',{}).get('operatorSource','cs-redhat-operator-index-v4-21')) -" 2>/dev/null || echo "cs-redhat-operator-index-v4-21") + print(d.get('main',{}).get('gitops',{}).get('operatorSource','') or '${default_source}') +" 2>/dev/null || echo "$default_source") info "GitOps channel: $gitops_channel (from values-global.yaml)" info "GitOps source: $gitops_source" @@ -732,8 +936,27 @@ data: gitops.channel: "${gitops_channel}" gitops.catalogSource: "${gitops_source}" gitops.sourceNamespace: "openshift-marketplace" + # App-of-apps sync-wave gating (Application health check) — see + # argoproj/argo-cd#3781; without it the root app applies all waves ungated. + # The connected path sets this declaratively via values-global.yaml + # (main.gitops.applicationHealthCheckEnabled) once pattern-install renders + # it (validatedpatterns/pattern-install-chart#52); this bootstrap writes it + # directly because the operator subscription predates any chart render. + gitops.applicationHealthCheckEnabled: "true" EOF info "patterns-operator-config ConfigMap created" + + # Post-apply assertion: the operator only restores the Application health + # check when the key is present at its first reconcile — fail loudly here + # rather than let the root app apply all waves ungated. + local key + key=$(oc get configmap patterns-operator-config -n openshift-operators \ + -o jsonpath='{.data.gitops\.applicationHealthCheckEnabled}' 2>/dev/null || true) + if [[ "$key" != "true" ]]; then + error "gitops.applicationHealthCheckEnabled is not \"true\" on patterns-operator-config" + exit 1 + fi + info "Application health check key verified on patterns-operator-config" } # ─── Step 11: Deploy Pattern CR directly ───────────────────────── @@ -847,6 +1070,13 @@ case "$MODE" in fix_manifest_lists exit 0 ;; + repoint) + # Standalone safety net — safe to run any time after the bootstrap, + # e.g. once MCH has created its runtime subscriptions. + validate_prereqs + repoint_default_catalog_subscriptions + exit 0 + ;; esac echo "╔══════════════════════════════════════════════════════════════╗" @@ -859,6 +1089,8 @@ disable_default_catalogs create_catalog_sources apply_ocmirror_resources normalise_mirror_source_policy +repoint_default_catalog_subscriptions +validate_airgap_overlay mirror_oci_charts fix_manifest_lists add_argocd_ca diff --git a/scripts/verify/render-gate.sh b/scripts/verify/render-gate.sh new file mode 100755 index 00000000..cc207e01 --- /dev/null +++ b/scripts/verify/render-gate.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +# Render gate for the CoCo pattern values chain — both connectivity modes. +# +# Reproduces, without a cluster, the exact values chain the patterns-operator +# assembles for the ROOT clustergroup Application (values-global.yaml + +# values-.yaml + the sharedValueFiles it resolves for the platform/ +# storage/version/hardware/airgap axes), then asserts: +# +# airgap mode +# - intel subscriptions render on the mirrored certified catalog +# - sourceless subscriptions render on the mirrored redhat catalog +# - the acm mce_operator override renders the mirrored catalog +# - the trustee collateral override renders the file:// backend +# connected mode +# - intel subscriptions stay on certified-operators +# - sourceless subscriptions stay on redhat-operators (chart default) +# - the acm mce_operator override renders redhat-operators (chart default) +# - the trustee collateral override renders the Intel PCS URL +# workload charts (each, standalone) +# - connected: upstream registry.redhat.io images unchanged +# - airgap: images rewritten against global.mirrorRegistry +# - airgap without a mirror: rendering FAILS (fail-fast, not a silent +# ImagePullBackOff in the CVM) +# +# Usage: scripts/verify/render-gate.sh [mirror-root] +# mirror-root defaults to quay.example:443/mirror (placeholder is fine — +# nothing is pulled; only the rendered strings are asserted). +# +# Requires: helm >= 3.14 and network access to quay.io for the chart pull. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PATTERN_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +CLUSTERGROUP_VERSION="0.9.58" +GROUP="baremetal" +PLATFORM="None" # SNO: no values- file is present +MIRROR_ROOT="${1:-quay.example:443/mirror}" +FAILS=0 + +pass() { echo -e "PASS: $*"; } +fail() { echo -e "FAIL: $*"; FAILS=$((FAILS + 1)); } + +# ── Root render: the operator's value chain ────────────────────────── +helm pull oci://quay.io/validatedpatterns/clustergroup \ + --version "$CLUSTERGROUP_VERSION" --untar --untardir "$WORK" >/dev/null +CHART="${WORK}/clustergroup" + +render_root() { # $1 = airgap|connected + local mode="$1" + helm template cg "${CHART}" \ + -f "${PATTERN_DIR}/values-global.yaml" \ + -f "${PATTERN_DIR}/values-${GROUP}.yaml" \ + -f "${PATTERN_DIR}/overrides/values-storage-lvm.yaml" \ + -f "${PATTERN_DIR}/overrides/values-4.22.yaml" \ + -f "${PATTERN_DIR}/overrides/values-hw-intel-tdx.yaml" \ + -f "${PATTERN_DIR}/overrides/values-${GROUP}-${mode}.yaml" \ + --set global.repoURL=https://example.com/coco-pattern.git \ + --set global.targetRevision=render-gate \ + --set global.airgap="$([ "$mode" = airgap ] && echo true || echo false)" \ + --set-string "global.mirrorRegistry=$([ "$mode" = airgap ] && echo "$MIRROR_ROOT" || echo '')" \ + 2>/dev/null +} + +sub_source() { # $1 = rendered manifest, $2 = subscription name + echo "$1" | awk -v wanted="$2" ' + /^ name: / { cur = $2 } + /^ source: / { if (cur == wanted) { print $2; exit } }' +} + +acm_override() { # $1 = rendered manifest + echo "$1" | grep -A1 'name: acm.mce_operator.source' | grep 'value:' | head -1 | sed "s/.*value: //; s/'//g; s/\"//g" +} + +collateral_override() { # $1 = rendered manifest + echo "$1" | grep -A1 'name: kbs.tdx.collateralService' | grep 'value:' | head -1 | sed "s/.*value: //; s/'//g; s/\"//g" +} + +# ── Airgap assertions ──────────────────────────────────────────────── +echo "── airgap mode (global.airgap=true, mirror=${MIRROR_ROOT})" +AIRGAP="$(render_root airgap)" || { fail "airgap root render errored"; } + +for sub in intel-device-plugins-operator intel-tdx-dcap-operator; do + src="$(sub_source "$AIRGAP" "$sub")" + [[ "$src" == "cs-certified-operator-index-v4-22" ]] \ + && pass "subscription $sub → $src" \ + || fail "subscription $sub → '${src}' (expected cs-certified-operator-index-v4-22)" +done + +for sub in openshift-external-secrets-operator trustee-operator sandboxed-containers-operator; do + src="$(sub_source "$AIRGAP" "$sub")" + [[ "$src" == "cs-redhat-operator-index-v4-22" ]] \ + && pass "subscription $sub → $src" \ + || fail "subscription $sub → '${src}' (expected cs-redhat-operator-index-v4-22)" +done + +val="$(acm_override "$AIRGAP")" +[[ "$val" == "cs-redhat-operator-index-v4-22" ]] \ + && pass "acm.mce_operator.source → $val" \ + || fail "acm.mce_operator.source → '${val}' (expected cs-redhat-operator-index-v4-22)" + +val="$(collateral_override "$AIRGAP")" +[[ "$val" == file:///opt/confidential-containers/attestation-service/tdx/platform_collaterals.json ]] \ + && pass "kbs.tdx.collateralService → file:// backend" \ + || fail "kbs.tdx.collateralService → '${val}' (expected the file:// backend)" + +# ── Connected assertions ───────────────────────────────────────────── +echo "── connected mode (global.airgap=false)" +CONNECTED="$(render_root connected)" || { fail "connected root render errored"; } + +for sub in intel-device-plugins-operator intel-tdx-dcap-operator; do + src="$(sub_source "$CONNECTED" "$sub")" + [[ "$src" == "certified-operators" ]] \ + && pass "subscription $sub → $src" \ + || fail "subscription $sub → '${src}' (expected certified-operators)" +done + +for sub in openshift-external-secrets-operator trustee-operator sandboxed-containers-operator; do + src="$(sub_source "$CONNECTED" "$sub")" + [[ "$src" == "redhat-operators" ]] \ + && pass "subscription $sub → $src" \ + || fail "subscription $sub → '${src}' (expected redhat-operators)" +done + +val="$(acm_override "$CONNECTED")" +[[ "$val" == "redhat-operators" ]] \ + && pass "acm.mce_operator.source → $val (chart default)" \ + || fail "acm.mce_operator.source → '${val}' (expected redhat-operators)" + +val="$(collateral_override "$CONNECTED")" +[[ "$val" == "https://api.trustedservices.intel.com/sgx/certification/v4/" ]] \ + && pass "kbs.tdx.collateralService → Intel PCS" \ + || fail "kbs.tdx.collateralService → '${val}' (expected Intel PCS URL)" + +# ── RBAC bootstrap bridge (patterns-operator#887) ─────────────────── +echo "── RBAC bridge" +for mode in airgap connected; do + out="$(render_root "$mode")" || { fail "$mode root render errored"; continue; } + # All documents mentioning the bridge name (the CRB itself, plus the + # helm-values ConfigMap which embeds the values indented). The real CRB is + # distinguished by an UNINDENTED "kind: ClusterRoleBinding" line. + block="$(echo "$out" | awk -v RS='---' '/coco-pattern-argocd-cluster-admin-bootstrap/')" + if [[ -z "$block" ]]; then + fail "$mode: RBAC bridge CRB missing from root render" + continue + fi + ok=1 + echo "$block" | grep -q "^kind: ClusterRoleBinding" || ok=0 + echo "$block" | grep -q "cluster-admin$" || ok=0 + echo "$block" | grep -q "vp-gitops-argocd-application-controller" || ok=0 + echo "$block" | grep -q 'sync-wave: "-1"' || ok=0 + if [[ $ok -eq 1 ]]; then + pass "$mode: RBAC bridge CRB at wave -1 for the controller SA" + else + fail "$mode: RBAC bridge CRB malformed (kind/role/subject/wave)" + fi +done + +# ── Workload charts ────────────────────────────────────────────────── +echo "── workload charts" +for chart in hello-openshift kbs-access-curl kbs-access-sealed; do + out="$(helm template wg "${PATTERN_DIR}/charts/coco-supported/${chart}" \ + --set global.coco.runtimeClassName=kata-cc 2>/dev/null)" \ + || { fail "$chart connected render errored"; continue; } + if echo "$out" | grep -q "registry.redhat.io/ubi9/httpd-24@sha256:68a91ff"; then + pass "$chart connected: upstream image unchanged" + else + fail "$chart connected: image not the upstream registry.redhat.io reference" + fi + + out="$(helm template wg "${PATTERN_DIR}/charts/coco-supported/${chart}" \ + --set global.coco.runtimeClassName=kata-cc --set global.airgap=true \ + --set-string "global.mirrorRegistry=${MIRROR_ROOT}" 2>/dev/null)" \ + || { fail "$chart airgap render errored"; continue; } + if echo "$out" | grep -q "${MIRROR_ROOT}/ubi9/httpd-24@sha256:68a91ff" \ + && ! echo "$out" | grep -q "registry.redhat.io/ubi9"; then + pass "$chart airgap: image rewritten to ${MIRROR_ROOT}/ubi9/..." + else + fail "$chart airgap: image not rewritten to the mirror root" + fi +done + +# initImage (registry.access.redhat.com source path) +out="$(helm template x "${PATTERN_DIR}/charts/coco-supported/kbs-access-curl" \ + --set global.coco.runtimeClassName=kata-cc --set global.airgap=true \ + --set-string "global.mirrorRegistry=${MIRROR_ROOT}" 2>/dev/null)" \ + && echo "$out" | grep -q "${MIRROR_ROOT}/ubi9/ubi:latest" \ + && pass "kbs-access-curl airgap: initImage rewritten (registry.access path)" \ + || fail "kbs-access-curl airgap: initImage not rewritten" + +# Fail-fast: airgap without a mirror must not render +if helm template x "${PATTERN_DIR}/charts/coco-supported/hello-openshift" \ + --set global.coco.runtimeClassName=kata-cc --set global.airgap=true \ + >/dev/null 2>&1; then + fail "airgap without global.mirrorRegistry rendered (should fail fast)" +else + pass "airgap without global.mirrorRegistry fails fast" +fi + +# ── Verdict ────────────────────────────────────────────────────────── +echo "" +if [[ $FAILS -eq 0 ]]; then + echo "RENDER GATE: PASS (0 failures)" +else + echo "RENDER GATE: FAIL (${FAILS} failures)" + exit 1 +fi diff --git a/values-baremetal.yaml b/values-baremetal.yaml index 271361b6..8a996299 100644 --- a/values-baremetal.yaml +++ b/values-baremetal.yaml @@ -82,8 +82,7 @@ clusterGroup: namespace: openshift-operators # These Intel operators live in the certified-operators catalog, not redhat-operators source: certified-operators - # Required for airgap -- point at your mirrored catalog source instead - # source: cs-certified-operator-index-v4-22 + # Airgap: repointed by overrides/values-baremetal-airgap.yaml channel: stable annotations: argocd.argoproj.io/sync-wave: "10" @@ -92,8 +91,7 @@ clusterGroup: namespace: intel-dcap-operator-system # These Intel operators live in the certified-operators catalog, not redhat-operators source: certified-operators - # Required for airgap -- point at your mirrored catalog source instead - # source: cs-certified-operator-index-v4-22 + # Airgap: repointed by overrides/values-baremetal-airgap.yaml channel: alpha annotations: argocd.argoproj.io/sync-wave: "10" @@ -127,6 +125,33 @@ clusterGroup: - '/overrides/values-baremetal-{{ if $.Values.global.airgap }}airgap{{ else }}connected{{ end }}.yaml' extraObjects: + # Bridge for the first-sync ordering race (patterns-operator#887): ArgoCD + # applies wave-0 resources by kind, so the imperative ServiceAccounts are + # attempted before the cluster-admin ClusterRoleBinding that authorises the + # controller to create them. Ungated installs self-heal on the next + # auto-sync; with sync-wave gating the first operation parks on wave-0 + # health and deadlocks. This binding applies at wave -1, before any wave-0 + # kind, which the controller can create from the start (the operator's + # instance role already grants rbac.authorization.k8s.io: */*). + # Distinct name on purpose — matching the clustergroup chart's own + # vp-gitops-cluster-admin-rolebinding would collide in the manifest. + # TODO: remove once patterns-operator creates the binding (or the + # clustergroup chart moves its own to wave -1) — see patterns-operator#887. + argocd-cluster-admin-bootstrap: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: coco-pattern-argocd-cluster-admin-bootstrap + annotations: + argocd.argoproj.io/sync-wave: "-1" + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + subjects: + - kind: ServiceAccount + name: vp-gitops-argocd-application-controller + namespace: vp-gitops wait-for-vault-unsealed: apiVersion: batch/v1 kind: Job @@ -168,18 +193,30 @@ clusterGroup: project: hub chart: acm chartVersion: 0.1.* - # Required for airgap -- set to your mirrored catalog source name - # overrides: - # - name: acm.mce_operator.source - # value: "cs-redhat-operator-index-v4-22" + # Wave 15: after the wave-10 ACM operator subscription (MCH CRD), + # before trustee at 20. + annotations: + argocd.argoproj.io/sync-wave: "15" + # The MCH operator creates the multicluster-engine subscription at + # runtime from the mce-subscription-spec annotation (rendered from this + # value), so it must be born on the right catalog. Connected: the + # default redhat-operators. Airgap: the mirrored catalog (the default + # catalogs are disabled). Kept as a single tpl-conditional list entry — + # an overlay list would replace this one wholesale. + overrides: + - name: acm.mce_operator.source + value: '{{ if $.Values.global.airgap }}cs-redhat-operator-index-v4-22{{ else }}redhat-operators{{ end }}' vault: name: vault namespace: vault project: vault chart: hashicorp-vault - # Require 0.1.10 or newer within the 0.1.x release series. - chartVersion: "~0.1.10" + # Exact pin: chart 0.1.11 deploys vault:2.1.2-ubi — the version the + # imageset mirrors. A range (~0.1.10) would resolve to whatever is + # newest at deploy time and desync from the mirrored vault image + # (0.1.10 deploys 1.21.4-ubi). + chartVersion: 0.1.11 annotations: argocd.argoproj.io/sync-wave: "0" @@ -219,13 +256,11 @@ clusterGroup: # Inject mirror-registry CA into KBS trust store (required for airgap image pulls) - name: kbs.registryCa.enabled value: "true" - # TDX DCAP collateral — connected (default): the trustee AS fetches - # platform collateral (TCB info, QE identity, CRLs) directly from - # Intel PCS; these endpoints are public and need no API key. - # Airgap: overrides/values-baremetal-airgap.yaml flips this to the - # file:// backend (collateral collected via make collect-dcap-collateral). + # TDX DCAP collateral — connected: Intel PCS (public, no API key). + # Airgap: the offline file backend (make collect-dcap-collateral). + # The chart tpl-renders override values — global.airgap selects. - name: kbs.tdx.collateralService - value: "https://api.trustedservices.intel.com/sgx/certification/v4/" + value: "{{ if $.Values.global.airgap }}file:///opt/confidential-containers/attestation-service/tdx/platform_collaterals.json{{ else }}https://api.trustedservices.intel.com/sgx/certification/v4/{{ end }}" - name: kbs.tdx.collateralMountPath value: "/opt/confidential-containers/attestation-service/tdx" - name: kbs.tdx.collateralFileName @@ -343,11 +378,9 @@ clusterGroup: path: charts/coco-supported/hello-openshift annotations: argocd.argoproj.io/sync-wave: "30" - # Required for airgap — CoCo guest VMs pull images directly, no IDMS/ITMS in the CVM. - # Uncomment and set MIRROR_REGISTRY_HOST to your mirror registry. - # overrides: - # - name: image - # value: "MIRROR_REGISTRY_HOST:8443/ubi9/httpd-24@sha256:68a91ff691092f455fea682330c499588747231c16516cd4f35aff821e6847f2" + # Airgap: the chart rewrites its image against global.mirrorRegistry + # (see charts/coco-supported//templates/_helpers.tpl) — no + # overrides needed here in either mode. syncPolicy: automated: prune: true @@ -359,12 +392,9 @@ clusterGroup: path: charts/coco-supported/kbs-access-curl annotations: argocd.argoproj.io/sync-wave: "30" - # Required for airgap — CoCo guest VMs pull images directly, no IDMS/ITMS in the CVM. - # overrides: - # - name: image - # value: "MIRROR_REGISTRY_HOST:8443/ubi9/httpd-24@sha256:68a91ff691092f455fea682330c499588747231c16516cd4f35aff821e6847f2" - # - name: initImage - # value: "MIRROR_REGISTRY_HOST:8443/ubi9/ubi:latest" + # Airgap: the chart rewrites its image against global.mirrorRegistry + # (see charts/coco-supported//templates/_helpers.tpl) — no + # overrides needed here in either mode. syncPolicy: automated: prune: true @@ -376,10 +406,9 @@ clusterGroup: path: charts/coco-supported/kbs-access-sealed annotations: argocd.argoproj.io/sync-wave: "30" - # Required for airgap — CoCo guest VMs pull images directly, no IDMS/ITMS in the CVM. - # overrides: - # - name: image - # value: "MIRROR_REGISTRY_HOST:8443/ubi9/httpd-24@sha256:68a91ff691092f455fea682330c499588747231c16516cd4f35aff821e6847f2" + # Airgap: the chart rewrites its image against global.mirrorRegistry + # (see charts/coco-supported//templates/_helpers.tpl) — no + # overrides needed here in either mode. syncPolicy: automated: prune: true diff --git a/values-global.yaml b/values-global.yaml index 31e69af1..b3e76623 100644 --- a/values-global.yaml +++ b/values-global.yaml @@ -9,9 +9,17 @@ global: # Airgap / disconnected master switch. false (default, connected): # Intel DCAP zero-touch platform registration (vault/ESO PCS API key), # trustee fetches platform collateral directly from Intel PCS. - # true (disconnected): set by overrides/values-baremetal-airgap.yaml — - # manual PCK provisioning (scripts/dcap-pck.py) and file-backed collateral. + # true (disconnected): loads the airgap overlay (repoints subscriptions + # and workload images at the mirror); manual PCK provisioning + # (scripts/dcap-pck.py) and file-backed collateral. airgap: false + # Mirror root for the workload image rewrite — set on the disconnected + # deploy branch (config-only delta), the same value oc-mirror was pointed + # at. MUST include the org path for Quay targets (e.g. + # quay.example:443/mirror): the CoCo guest VMs pull images directly and + # every reference carries it. Empty is fine while connected; the workload + # charts fail rendering if this is empty (or a placeholder) in airgap mode. + mirrorRegistry: "" secretStore: # Warning: This must be present even if it is set to none. backend: vault # none, vault, kubernetes @@ -65,11 +73,21 @@ main: # operatorSource: cs-redhat-operator-index-v4-22 # Keep this channel aligned with airgap/imageset-config-4.22.yaml. channel: gitops-1.22 + # App-of-apps sync-wave gating. ArgoCD 1.8 dropped the Application + # health check (argoproj/argo-cd#3781); the patterns-operator can restore + # it but defaults it OFF. Without it the root Application treats child + # Applications as healthy and applies ALL waves at once, so wave ordering + # (e.g. workloads after trustee) is not enforced. Rendered into the + # patterns-operator-config ConfigMap by pattern-install >= 0.0.19 + # (validatedpatterns/pattern-install-chart#53); the disconnected bootstrap + # writes it directly (scripts/airgap-post-install.sh step 10). + applicationHealthCheckEnabled: "true" multiSourceConfig: enabled: true clusterGroupChartVersion: 0.9.58 # VP OCI Helm charts are mirrored to mirror-registry by oc-mirror additionalImages. # Use mirror-registry directly — no Quay dependency for airgap deploys. + # REQUIRED in airgap: no IDMS redirect in the ArgoCD repo-server. helmRepoUrl: quay.io/validatedpatterns # Common secret store configuration used across multiple charts.