Skip to content

Commit 19faccc

Browse files
committed
feat: unified registry configuration with multi-registry support
Refactor supply-chain and qtodo charts to use a single, option-agnostic registry configuration instead of separate per-registry blocks. Registry options (configure one in values-hub.yaml): - Option 1: Built-in Quay Registry - Option 2: BYO/External Registry (quay.io, ghcr.io, etc.) - Option 3: Embedded OCP Image Registry Key changes: Supply-chain chart: * Unified registry.* parameters (domain, org, user, vaultPath, passwordVaultKey) * Use tpl function to resolve template expressions in registry.domain values passed as --set parameters from the validated patterns framework * Embedded OCP registry automation (registry.embeddedOCP.ensureImageNamespaceRBAC): - Auto-create image namespace matching registry.org - Grant pipeline SA system:image-builder via RoleBinding - Enable default route on OCP image registry via Kubernetes API (curl-based Job using ServiceAccount token, no oc CLI dependency) * ArgoCD hook annotations on the route-enabler Job (Sync + HookSucceeded) * Rename qtodo-registry-pass to qtodo-quay-pass for clarity Qtodo chart: * Unified app.images.main.registry.* parameters * Use tpl function in registry-external-secret.yaml for domain resolution ztvp-certificates chart: * Node-level image pull trust for kubelet (imagePullTrust.*) * Create ConfigMap with ingress CA per registry hostname in openshift-config * Patch image.config.openshift.io/cluster additionalTrustedCA * RBAC for patching image.config.openshift.io resources Documentation: * Comprehensive supply-chain.md with configuration steps for all three registry options, vault paths, and example overrides * Updated values-secret.yaml.template with registry credential examples Signed-off-by: Min Zhang <minzhang@redhat.com>
1 parent 9274825 commit 19faccc

14 files changed

Lines changed: 442 additions & 139 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ super-linter-output
1515
github_conf
1616

1717
# Editor and IDE specific files
18+
.cursor/
1819
.cursorrules
1920
.vscode/

charts/qtodo/templates/registry-external-secret.yaml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,14 @@ spec:
1818
.dockerconfigjson: |
1919
{
2020
"auths": {
21-
"{{ required "app.images.main.registry.domain is required when registry.auth is enabled" .Values.app.images.main.registry.domain }}": {
21+
"{{ tpl (required "app.images.main.registry.domain is required when registry.auth is enabled" .Values.app.images.main.registry.domain) $ }}": {
2222
"auth": "{{ `{{ printf "%s:%s" "` }}{{ .Values.app.images.main.registry.user }}{{ `" .password | b64enc }}` }}"
2323
}
2424
}
2525
}
2626
data:
2727
- secretKey: password
2828
remoteRef:
29-
{{- if .Values.app.images.main.registry.builtinQuay.enabled }}
30-
key: {{ .Values.app.images.main.registry.builtinQuay.vaultPath }}
31-
property: {{ .Values.app.images.main.registry.builtinQuay.passwordVaultKey }}
32-
{{- else if .Values.app.images.main.registry.externalRegistry.enabled }}
33-
key: {{ .Values.app.images.main.registry.externalRegistry.vaultPath }}
34-
property: {{ .Values.app.images.main.registry.externalRegistry.passwordVaultKey }}
35-
{{- end }}
36-
{{- end }}
29+
key: {{ required "app.images.main.registry.vaultPath is required when registry.auth is enabled" .Values.app.images.main.registry.vaultPath }}
30+
property: {{ required "app.images.main.registry.passwordVaultKey is required when registry.auth is enabled" .Values.app.images.main.registry.passwordVaultKey }}
31+
{{- end }}

charts/qtodo/values.yaml

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,14 @@ app:
1515
# Modified to Always to force a pull so we can test changes to the container image without requiring manual deletion of images or restarts of argo
1616
pullPolicy: Always
1717
registry:
18-
# auth: controls whether to create registry auth secret
19-
# Set to true when using private registry (built-in Quay or external)
18+
# Set to true to create registry auth secret for image pulls
2019
auth: false
2120
secretName: qtodo-registry-auth
2221
user: registry-user
2322
# domain: registry.example.com # REQUIRED when auth is enabled
24-
25-
# Built-in Quay registry (optional)
26-
# When enabled, uses auto-generated credentials from Vault
27-
builtinQuay:
28-
enabled: false
29-
vaultPath: secret/data/hub/infra/quay/quay-users
30-
passwordVaultKey: quay-user-password
31-
32-
# External/BYO registry (optional)
33-
# When enabled, uses user-provided credentials from Vault
34-
externalRegistry:
35-
enabled: false
36-
vaultPath: secret/data/hub/infra/registry/registry-user
37-
passwordVaultKey: registry-password
23+
# Vault path and key for registry password (set for your scenario)
24+
vaultPath: ""
25+
passwordVaultKey: ""
3826
spiffeHelper:
3927
name: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9
4028
version: v0.10.0

charts/supply-chain/templates/pipeline-qtodo.yaml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
{{- /* Determine registry domain: auto-construct for built-in Quay, require for external */ -}}
2-
{{- $registryDomain := "" -}}
3-
{{- if .Values.registry.domain -}}
4-
{{- $registryDomain = .Values.registry.domain -}}
5-
{{- else if .Values.quay.enabled -}}
6-
{{- $registryDomain = printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain -}}
7-
{{- else -}}
8-
{{- fail "registry.domain is required for external registry" -}}
9-
{{- end -}}
101
---
112
apiVersion: tekton.dev/v1beta1
123
kind: Pipeline
@@ -34,7 +25,7 @@ spec:
3425
- name: image-target
3526
type: string
3627
description: qtodo image push destination (e.g. quay.io/ztvp/qtodo:latest)
37-
default: {{ $registryDomain }}/{{ .Values.registry.org }}/{{ .Values.registry.repo }}:{{ .Values.qtodo.tag }}
28+
default: {{ tpl (required "registry.domain is required" .Values.registry.domain) $ }}/{{ .Values.registry.org }}/{{ .Values.registry.repo }}:{{ .Values.qtodo.tag }}
3829
- name: image-tls-verify
3930
type: string
4031
description: Whether to verify TLS when pushing to the OCI registry

charts/supply-chain/templates/quay/quay-user-job.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ spec:
1919
command: ["python3", "/app/create_user.py"]
2020
env:
2121
- name: QUAY_HOST
22-
value: {{ .Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }}
22+
value: {{ tpl (.Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain)) $ }}
2323
- name: QUAY_ADMIN_USER
2424
value: {{ .Values.registry.user }}
2525
- name: QUAY_ADMIN_PASSWORD

charts/supply-chain/templates/rbac/registry-image-namespace.yaml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,95 @@ subjects:
2525
- kind: ServiceAccount
2626
name: pipeline
2727
namespace: {{ .Values.global.namespace }}
28+
---
29+
# Enable the default route on the embedded OCP image registry so that
30+
# the pipeline can push and external clients can pull images via the route.
31+
# Uses a Job because the imageregistry config is a cluster-singleton managed
32+
# by the image-registry operator; declarative ownership would conflict.
33+
apiVersion: v1
34+
kind: ServiceAccount
35+
metadata:
36+
name: registry-route-enabler
37+
namespace: {{ .Values.global.namespace }}
38+
annotations:
39+
argocd.argoproj.io/sync-wave: "0"
40+
---
41+
apiVersion: rbac.authorization.k8s.io/v1
42+
kind: ClusterRole
43+
metadata:
44+
name: {{ .Values.global.namespace }}-registry-route-enabler
45+
annotations:
46+
argocd.argoproj.io/sync-wave: "0"
47+
rules:
48+
- apiGroups: ["imageregistry.operator.openshift.io"]
49+
resources: ["configs"]
50+
verbs: ["get", "patch"]
51+
---
52+
apiVersion: rbac.authorization.k8s.io/v1
53+
kind: ClusterRoleBinding
54+
metadata:
55+
name: {{ .Values.global.namespace }}-registry-route-enabler
56+
annotations:
57+
argocd.argoproj.io/sync-wave: "0"
58+
roleRef:
59+
apiGroup: rbac.authorization.k8s.io
60+
kind: ClusterRole
61+
name: {{ .Values.global.namespace }}-registry-route-enabler
62+
subjects:
63+
- kind: ServiceAccount
64+
name: registry-route-enabler
65+
namespace: {{ .Values.global.namespace }}
66+
---
67+
apiVersion: batch/v1
68+
kind: Job
69+
metadata:
70+
name: enable-registry-default-route
71+
namespace: {{ .Values.global.namespace }}
72+
annotations:
73+
argocd.argoproj.io/sync-wave: "1"
74+
argocd.argoproj.io/hook: Sync
75+
argocd.argoproj.io/hook-delete-policy: HookSucceeded
76+
spec:
77+
backoffLimit: 3
78+
template:
79+
spec:
80+
serviceAccountName: registry-route-enabler
81+
restartPolicy: Never
82+
containers:
83+
- name: enable-route
84+
image: registry.access.redhat.com/ubi9/ubi:9.7-1764794285
85+
command:
86+
- /bin/sh
87+
- -ce
88+
- |
89+
APISERVER="https://kubernetes.default.svc"
90+
TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
91+
CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
92+
RESOURCE_URL="${APISERVER}/apis/imageregistry.operator.openshift.io/v1/configs/cluster"
93+
AUTH_HEADER="Authorization: Bearer ${TOKEN}"
94+
95+
echo "Checking current defaultRoute status..."
96+
BODY=$(curl -sS --cacert "${CACERT}" -H "${AUTH_HEADER}" "${RESOURCE_URL}")
97+
rc=$?; if [ $rc -ne 0 ]; then echo "ERROR: GET failed (curl rc=${rc})"; exit 1; fi
98+
99+
# Parse defaultRoute from JSON without jq/grep dependency
100+
case "${BODY}" in
101+
*'"defaultRoute":true'*) echo "Default route already enabled, nothing to do."; exit 0 ;;
102+
esac
103+
104+
echo "Enabling default route on embedded OCP image registry..."
105+
RESP=$(curl -sS -w "\n%{http_code}" --cacert "${CACERT}" \
106+
-H "${AUTH_HEADER}" \
107+
-H "Content-Type: application/merge-patch+json" \
108+
-X PATCH -d '{"spec":{"defaultRoute":true}}' \
109+
"${RESOURCE_URL}")
110+
HTTP_CODE=$(echo "${RESP}" | tail -1)
111+
112+
if [ "${HTTP_CODE}" -ge 200 ] 2>/dev/null && [ "${HTTP_CODE}" -lt 300 ] 2>/dev/null; then
113+
echo "Default route enabled successfully (HTTP ${HTTP_CODE})."
114+
else
115+
echo "ERROR: PATCH failed (HTTP ${HTTP_CODE})."
116+
echo "${RESP}" | head -5
117+
exit 1
118+
fi
28119
{{- end }}

charts/supply-chain/templates/secrets/qtodo-quay-pass.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Purpose: Provides password for the Quay user provisioner job to create/update users in built-in Quay
44
Used by: quay-user-job.yaml (CronJob that provisions Quay users)
55
Only created when: quay.enabled=true (built-in Quay registry)
6-
Not used for: BYO/external registry (use qtodo-registry-auth.yaml instead)
6+
Uses unified registry.vaultPath and registry.passwordVaultKey
77
*/}}
88
{{- if eq .Values.quay.enabled true }}
99
---
@@ -26,6 +26,6 @@ spec:
2626
data:
2727
- secretKey: password
2828
remoteRef:
29-
key: {{ .Values.quay.vaultPath }}
30-
property: {{ .Values.quay.passwordVaultKey }}
31-
{{- end }}
29+
key: {{ .Values.registry.vaultPath }}
30+
property: {{ .Values.registry.passwordVaultKey }}
31+
{{- end }}

charts/supply-chain/templates/secrets/qtodo-registry-auth.yaml

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,38 @@
22
Pipeline Registry Auth Secret
33
Purpose: Provides dockerconfigjson for pipeline to push/pull images
44
Used by: Tekton pipeline tasks (build-image, sign-image, verify-image)
5-
Created when: quay.enabled=true OR externalRegistry.enabled=true
6-
Vault path: Automatically selects based on which registry is enabled
7-
- Built-in Quay: quay.vaultPath (auto-generated credentials)
8-
- BYO Registry: externalRegistry.vaultPath (user-provided credentials)
9-
Registry domain:
10-
- Built-in Quay: auto-constructed as quay-registry-quay-quay-enterprise.<hubClusterDomain>
11-
- BYO Registry: must be explicitly set via registry.domain
5+
Created when: registry.enabled=true
6+
Registry-agnostic: works for built-in Quay, BYO (quay.io, ghcr.io), or embedded OCP.
7+
Set registry.domain, registry.vaultPath, and registry.passwordVaultKey for your scenario.
128
*/}}
13-
{{- if or .Values.quay.enabled .Values.externalRegistry.enabled }}
14-
{{- /* Determine registry domain: auto-construct for built-in Quay, require for external */ -}}
15-
{{- $registryDomain := "" -}}
16-
{{- if .Values.registry.domain -}}
17-
{{- $registryDomain = .Values.registry.domain -}}
18-
{{- else if .Values.quay.enabled -}}
19-
{{- $registryDomain = printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain -}}
20-
{{- else -}}
21-
{{- fail "registry.domain is required for external registry" -}}
22-
{{- end -}}
9+
{{- if .Values.registry.enabled }}
2310
---
2411
apiVersion: "external-secrets.io/v1beta1"
2512
kind: ExternalSecret
2613
metadata:
27-
name: qtodo-registry-auth
14+
name: {{ .Values.registry.authSecretName }}
2815
namespace: {{ .Release.Namespace | default .Values.global.namespace }}
2916
spec:
3017
refreshInterval: 15s
3118
secretStoreRef:
3219
name: {{ .Values.global.secretStore.name }}
3320
kind: {{ .Values.global.secretStore.kind }}
3421
target:
35-
name: qtodo-registry-auth
22+
name: {{ .Values.registry.authSecretName }}
3623
template:
3724
type: kubernetes.io/dockerconfigjson
3825
data:
3926
.dockerconfigjson: |
4027
{
4128
"auths": {
42-
"{{ $registryDomain }}": {
29+
"{{ tpl (required "registry.domain is required when registry.enabled=true" .Values.registry.domain) $ }}": {
4330
"auth": "{{ `{{ printf "%s:%s" "` }}{{ .Values.registry.user }}{{ `" .password | b64enc }}` }}"
4431
}
4532
}
4633
}
4734
data:
4835
- secretKey: password
4936
remoteRef:
50-
{{- if .Values.quay.enabled }}
51-
key: {{ .Values.quay.vaultPath }}
52-
property: {{ .Values.quay.passwordVaultKey }}
53-
{{- else if .Values.externalRegistry.enabled }}
54-
key: {{ .Values.externalRegistry.vaultPath }}
55-
property: {{ .Values.externalRegistry.passwordVaultKey }}
56-
{{- end }}
57-
{{- end }}
37+
key: {{ required "registry.vaultPath is required when registry.enabled=true" .Values.registry.vaultPath }}
38+
property: {{ required "registry.passwordVaultKey is required when registry.enabled=true" .Values.registry.passwordVaultKey }}
39+
{{- end }}

charts/supply-chain/values.yaml

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,47 +27,59 @@ qtodo:
2727
containerfile: "./Containerfile"
2828

2929
# ===========================================================================
30-
# BUILT-IN QUAY REGISTRY (optional)
31-
# When enabled, deploys internal Quay registry with auto-generated credentials
30+
# QUAY USER PROVISIONER (only for built-in Quay registry)
31+
# When enabled, runs a CronJob that provisions users in the built-in Quay instance.
32+
# This is Quay-specific and not needed for BYO or embedded OCP registries.
3233
# ===========================================================================
3334
quay:
34-
enabled: true
35+
enabled: false
3536
email: "quay-user@example.com"
36-
# Vault path for auto-generated Quay credentials
37-
vaultPath: "secret/data/hub/infra/quay/quay-users"
38-
passwordVaultKey: "quay-user-password"
39-
# User provisioner job settings
4037
job:
4138
image: registry.access.redhat.com/ubi9/ubi:9.7-1764794285
4239
schedule: "*/5 * * * *"
4340

4441
# ===========================================================================
45-
# EXTERNAL/BYO REGISTRY (optional)
46-
# User-provided credentials for external registry (quay.io, ghcr.io, etc.)
47-
# Enable this when using an external registry instead of built-in Quay
48-
# ===========================================================================
49-
externalRegistry:
50-
enabled: false
51-
# Vault path for user-provided credentials
52-
vaultPath: "secret/data/hub/infra/registry/registry-user"
53-
passwordVaultKey: "registry-password"
54-
55-
# ===========================================================================
56-
# COMMON REGISTRY SETTINGS (shared by both built-in Quay and external registry)
42+
# REGISTRY CONFIGURATION (option-agnostic)
43+
# Works for all registry types: built-in Quay, BYO (quay.io, ghcr.io, etc.),
44+
# or embedded OCP image registry. Set the values for your scenario.
45+
#
46+
# Scenario-specific values (set in values-hub.yaml overrides):
47+
# Built-in Quay:
48+
# domain: quay-registry-quay-quay-enterprise.apps.<clusterDomain>
49+
# vaultPath: secret/data/hub/infra/quay/quay-users
50+
# passwordVaultKey: quay-user-password
51+
# BYO (quay.io, ghcr.io, etc.):
52+
# domain: quay.io (or your registry hostname)
53+
# vaultPath: secret/data/hub/infra/registry/registry-user
54+
# passwordVaultKey: registry-password
55+
# Embedded OCP:
56+
# domain: default-route-openshift-image-registry.apps.<clusterDomain>
57+
# vaultPath: secret/data/hub/infra/registry/registry-user
58+
# passwordVaultKey: registry-password
59+
# embeddedOCP.ensureImageNamespaceRBAC: true
5760
# ===========================================================================
5861
registry:
59-
# For built-in Quay: domain is auto-constructed from hubClusterDomain
60-
# For external registry: REQUIRED - set explicitly (e.g., quay.io, ghcr.io)
61-
# domain: "registry.example.com"
62+
# Set to true to create the registry auth secret (dockerconfigjson)
63+
enabled: false
64+
# Registry hostname (REQUIRED when enabled)
65+
domain: ""
66+
# Organization/namespace within the registry
6267
org: "ztvp"
68+
# Repository name
6369
repo: "qtodo"
70+
# Whether to verify TLS when pushing to the registry
6471
tlsVerify: "true"
72+
# Registry username
6573
user: "registry-user"
74+
# Vault path to the secret containing the registry password
75+
vaultPath: ""
76+
# Key within the Vault secret that holds the password
77+
passwordVaultKey: ""
6678
# Secret name for registry auth (dockerconfigjson)
6779
authSecretName: "qtodo-registry-auth"
6880
# Embedded OCP registry only: create image namespace (registry.org) and grant
6981
# pipeline SA system:image-builder so the pipeline can push. Set to true only when
70-
# using the in-cluster OpenShift image registry; leave false for quay.io or other external registries.
82+
# using the in-cluster OpenShift image registry; leave false for other registries.
7183
embeddedOCP:
7284
ensureImageNamespaceRBAC: false
7385

0 commit comments

Comments
 (0)