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:
e3mrah 2026-05-03 14:24:41 +04:00 committed by GitHub
parent 5b46e077f2
commit 9eff5530cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 190 additions and 2 deletions

View File

@ -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

View File

@ -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

View 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 }}