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:
|
||||
spec:
|
||||
chart: bp-gitea
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-gitea
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: bp-gitea
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
description: |
|
||||
Catalyst-curated Blueprint umbrella chart for Gitea. Depends on the
|
||||
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