fix(bp-gitea): replace Reflector with database-secret-sync-job (chart 1.2.3) (#668)
Same root cause + same fix as bp-harbor (PR #557). The Reflector-based 'gitea-database-secret reflects gitea-pg-app' pattern races with CNPG: Reflector logs once at install time that the source doesn't exist ('Could not update gitea/gitea-database-secret — Source gitea-pg-app not found') and never retries. The destination stays empty (password "") and gitea init container crashloops with 'pq: password authentication failed for user gitea' — caught live on otech43, manually patched at the time but no chart fix shipped, so otech45 hit the exact same failure (founder caught it in k9s). Fix: replicate bp-harbor's sync-job pattern verbatim. - post-install,post-upgrade Helm hook (weight 5) - curlimages/curl image talking to in-cluster apiserver - Polls until gitea-pg-app exists, reads .data.password, PATCHes gitea-database-secret with the password key - Hook-delete-policy: before-hook-creation,hook-succeeded - Idempotent on re-run; CNPG never rotates without operator action Drops the HARBOR_DATABASE_PASSWORD alias (gitea binds the 'password' key directly via secretKeyRef in values.yaml). The existing pre-install database-secret.yaml placeholder stays so the Secret is Found at install time (some tooling assumes presence for the Pod's lifetime). Co-authored-by: hatiyildiz <hatiyildiz@openova.io>
This commit is contained in:
parent
5b46e077f2
commit
9eff5530cd
@ -47,7 +47,7 @@ spec:
|
|||||||
chart:
|
chart:
|
||||||
spec:
|
spec:
|
||||||
chart: bp-gitea
|
chart: bp-gitea
|
||||||
version: 1.2.2
|
version: 1.2.3
|
||||||
sourceRef:
|
sourceRef:
|
||||||
kind: HelmRepository
|
kind: HelmRepository
|
||||||
name: bp-gitea
|
name: bp-gitea
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: bp-gitea
|
name: bp-gitea
|
||||||
version: 1.2.2
|
version: 1.2.3
|
||||||
description: |
|
description: |
|
||||||
Catalyst-curated Blueprint umbrella chart for Gitea. Depends on the
|
Catalyst-curated Blueprint umbrella chart for Gitea. Depends on the
|
||||||
upstream `gitea` chart (dl.gitea.com) as a Helm subchart so
|
upstream `gitea` chart (dl.gitea.com) as a Helm subchart so
|
||||||
|
|||||||
188
platform/gitea/chart/templates/database-secret-sync-job.yaml
Normal file
188
platform/gitea/chart/templates/database-secret-sync-job.yaml
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
{{- /*
|
||||||
|
Helm post-install Job that copies the CNPG-emitted `gitea-pg-app` Secret
|
||||||
|
into `gitea-database-secret` so Harbor core's env binding finds the
|
||||||
|
expected `password` key.
|
||||||
|
|
||||||
|
WHY A JOB INSTEAD OF REFLECTOR
|
||||||
|
--------------------------------
|
||||||
|
The earlier Reflector-based path relied on the Reflector daemon refiring
|
||||||
|
when CNPG creates `gitea-pg-app` AFTER `gitea-database-secret` was
|
||||||
|
processed. In practice (caught live on otech30) Reflector logs:
|
||||||
|
"Could not update harbor/gitea-database-secret —
|
||||||
|
Source harbor/gitea-pg-app could not be found"
|
||||||
|
once at install time and never retries. Even with `auto-enabled: true`
|
||||||
|
on the source's inherited annotations, Reflector's auto-reflect path
|
||||||
|
copies the SOURCE name (gitea-pg-app) — it does NOT update the
|
||||||
|
explicit `reflects:` destination (gitea-database-secret). So the
|
||||||
|
destination stays empty and gitea stalls forever with
|
||||||
|
`couldn't find key password in Secret harbor/gitea-database-secret`.
|
||||||
|
|
||||||
|
A post-install Job avoids the watcher race entirely:
|
||||||
|
1. Polls until CNPG has provisioned gitea-pg-app
|
||||||
|
2. Reads `password` from it
|
||||||
|
3. kubectl-applies gitea-database-secret with both the original
|
||||||
|
`password` key (used by gitea) and `HARBOR_DATABASE_PASSWORD`
|
||||||
|
(used by upstream Harbor charts that bind via envFrom)
|
||||||
|
4. Exits 0; Helm marks the post-install hook complete
|
||||||
|
|
||||||
|
The Job is idempotent — re-running it overwrites with the same data,
|
||||||
|
which CNPG never rotates without operator action.
|
||||||
|
|
||||||
|
WHY NOT INIT-CONTAINER ON HARBOR-CORE
|
||||||
|
--------------------------------------
|
||||||
|
Harbor's upstream chart's deployment template doesn't expose an
|
||||||
|
init-containers extension point per service, and patching it via a
|
||||||
|
post-render kustomize patch would diverge from the upstream chart's
|
||||||
|
contract. A separate Helm hook keeps the override surgical.
|
||||||
|
|
||||||
|
NB: still keep `templates/database-secret.yaml` as a pre-install
|
||||||
|
placeholder (so `kubectl get secret gitea-database-secret -n harbor`
|
||||||
|
returns Found before this Job populates it; some tooling assumes
|
||||||
|
the Secret is named for the lifetime of the release). Reflector
|
||||||
|
annotations are kept harmless — they just become a redundant
|
||||||
|
no-op once this Job has populated the Secret.
|
||||||
|
*/}}
|
||||||
|
{{- $ns := .Values.postgres.cluster.namespace | default .Release.Namespace }}
|
||||||
|
{{- $clusterName := .Values.postgres.cluster.name | default "gitea-pg" }}
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
namespace: {{ $ns }}
|
||||||
|
labels:
|
||||||
|
{{- include "bp-gitea.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": "post-install,post-upgrade"
|
||||||
|
"helm.sh/hook-weight": "5"
|
||||||
|
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
|
||||||
|
spec:
|
||||||
|
backoffLimit: 6
|
||||||
|
ttlSecondsAfterFinished: 300
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: gitea-database-secret-sync
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
serviceAccountName: gitea-database-secret-sync
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-pull
|
||||||
|
containers:
|
||||||
|
- name: sync
|
||||||
|
# curlimages/curl is a tiny alpine-based image with curl + sh,
|
||||||
|
# avoiding kubectl entirely. We talk to the k8s API directly via
|
||||||
|
# the in-pod ServiceAccount token. Earlier attempts:
|
||||||
|
# - bitnami/kubectl:1.31.4 — bitnami moved to sha256-only tags
|
||||||
|
# - rancher/kubectl:v1.34.6 — distroless, no /bin/sh; container
|
||||||
|
# can't run our inline shell script.
|
||||||
|
image: curlimages/curl:8.10.1
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: SOURCE_SECRET
|
||||||
|
value: {{ printf "%s-app" $clusterName | quote }}
|
||||||
|
- name: DEST_SECRET
|
||||||
|
value: "gitea-database-secret"
|
||||||
|
- name: NAMESPACE
|
||||||
|
value: {{ $ns | quote }}
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
# In-cluster k8s API access via projected ServiceAccount token.
|
||||||
|
APISERVER="https://kubernetes.default.svc"
|
||||||
|
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||||
|
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||||
|
echo "Waiting for ${SOURCE_SECRET} in ${NAMESPACE}…"
|
||||||
|
for i in $(seq 1 120); do
|
||||||
|
code=$(curl -s -o /tmp/src.json -w '%{http_code}' --cacert "${CA}" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
"${APISERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${SOURCE_SECRET}")
|
||||||
|
if [ "${code}" = "200" ]; then
|
||||||
|
echo "Found ${SOURCE_SECRET}"; break
|
||||||
|
fi
|
||||||
|
if [ "$i" = "120" ]; then
|
||||||
|
echo "Timeout: ${SOURCE_SECRET} not present after 10 min" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
# Extract base64 password from possibly multi-line JSON.
|
||||||
|
# k8s apiserver doesn't pretty-print but other proxies might;
|
||||||
|
# tr -d '\n' makes the next grep robust.
|
||||||
|
PWD_B64=$(tr -d '\n' < /tmp/src.json | grep -oE '"password"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||||
|
if [ -z "${PWD_B64}" ]; then
|
||||||
|
echo "Source ${SOURCE_SECRET} has empty password" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# PATCH the destination Secret merging both keys. Strategic
|
||||||
|
# merge tolerates the existing pre-install hook annotations.
|
||||||
|
PATCH=$(printf '{"data":{"password":"%s"}}' "${PWD_B64}")
|
||||||
|
code=$(curl -s -o /tmp/patch.json -w '%{http_code}' --cacert "${CA}" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/strategic-merge-patch+json" \
|
||||||
|
-X PATCH --data "${PATCH}" \
|
||||||
|
"${APISERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${DEST_SECRET}")
|
||||||
|
if [ "${code}" != "200" ] && [ "${code}" != "201" ]; then
|
||||||
|
echo "PATCH ${DEST_SECRET} returned HTTP ${code}:" >&2
|
||||||
|
cat /tmp/patch.json >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Synced ${DEST_SECRET} from ${SOURCE_SECRET}"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 64Mi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
namespace: {{ $ns }}
|
||||||
|
labels:
|
||||||
|
{{- include "bp-gitea.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": "post-install,post-upgrade"
|
||||||
|
"helm.sh/hook-weight": "0"
|
||||||
|
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-pull
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
namespace: {{ $ns }}
|
||||||
|
labels:
|
||||||
|
{{- include "bp-gitea.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": "post-install,post-upgrade"
|
||||||
|
"helm.sh/hook-weight": "0"
|
||||||
|
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "create", "update", "patch", "apply"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
namespace: {{ $ns }}
|
||||||
|
labels:
|
||||||
|
{{- include "bp-gitea.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": "post-install,post-upgrade"
|
||||||
|
"helm.sh/hook-weight": "0"
|
||||||
|
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: gitea-database-secret-sync
|
||||||
|
namespace: {{ $ns }}
|
||||||
Loading…
Reference in New Issue
Block a user