New scratch Blueprint chart `bp-wordpress-tenant` v0.1.0 that provisions a turnkey, SSO-pre-wired WordPress instance per SME tenant inside the SME's vcluster, satisfying ticket #800 (SME-5) of the #795 SME-tenant turnkey experience epic. What it provisions: - Deployment of `wordpress:6-php8.3-apache` (manifest-list digest sha256:054e611...196), pulled through the Sovereign Harbor proxy-cache when `global.imageRegistry` is set (per INVIOLABLE-PRINCIPLES #4). - Two initContainers seed wp-content/ from the image onto the PVC and install the openid-connect-generic plugin + pg4wp Postgres drop-in from wordpress.org / GitHub. Idempotent, runs only once per PVC. - Postgres provisioned in-tenant via a `Cluster.postgresql.cnpg.io` (default `wordpress-db`, 1 instance, 10Gi, pg16). The CNPG-emitted `<cluster>-app` Secret is mirrored into `wordpress-database-secret` by Reflector + a post-install sync Job (otech30 race fix carried forward from bp-gitea). - PVC for `/var/www/html/wp-content/` (default 10Gi, RWO, helm.sh/resource-policy: keep so customer content survives `helm uninstall`). - Ingress at `wordpress.<smeDomain>` with cert-manager TLS via operator-supplied ClusterIssuer (default `letsencrypt-prod`). - NetworkPolicy restricting egress to bp-cnpg :5432, Keycloak :8443/:8080, kube-dns, and HTTPS to public IPs (for plugin/theme fetches). - Three post-install Jobs: hook weight 5 — db-secret-sync (PATCHes wordpress-database- secret.password from CNPG <cluster>-app) hook weight 10 — oidc-config (UPSERTs openid_connect_generic_ settings, active_plugins, template/stylesheet, siteurl/home rows in wp_options via PHP+PDO) hook weight 15 — admin-user (INSERT/UPDATE wp_users + wp_usermeta for SME admin's email with administrator role) After all hooks complete, the SME admin's first browser hit lands on /wp-admin authenticated via Keycloak SSO — no install wizard, no manual config. Hollow-chart guard (issue #181) satisfied via the `common` library subchart from sigstore, matching bp-newapi's pattern for scratch charts (no first-party WordPress Helm chart exists upstream). Tests: - chart/tests/observability-toggle.sh verifies BLUEPRINT-AUTHORING §11.2 (default render produces no PodMonitor/ServiceMonitor). - `helm template` smoke render with required values produces 11 K8s resources cleanly; `helm lint` zero-failure. Refs: #800, #795 Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
153 lines
6.0 KiB
YAML
153 lines
6.0 KiB
YAML
{{- if and .Values.wordpress.enabled .Values.database.cluster.enabled }}
|
|
{{- /*
|
|
Helm post-install Job that copies the CNPG-emitted `<cluster>-app`
|
|
Secret into `wordpress-database-secret` so the WordPress Pod's
|
|
WORDPRESS_DB_PASSWORD env binding finds the expected `password` key.
|
|
|
|
WHY A JOB INSTEAD OF RELYING ON RELFLECTOR ALONE
|
|
────────────────────────────────────────────────
|
|
The Reflector-based path can race when CNPG creates
|
|
`<cluster>-app` AFTER `wordpress-database-secret` is processed; on
|
|
otech30 (caught live, see bp-gitea database-secret-sync-job.yaml)
|
|
Reflector logged "Source could not be found" once and never retried.
|
|
The post-install Job waits for CNPG to provision the source Secret,
|
|
then PATCHes the destination, eliminating the watcher race.
|
|
|
|
Pattern lifted from platform/gitea/chart/templates/database-secret-
|
|
sync-job.yaml (canonical seam — see ADR-0001 §11.3 anti-duplication).
|
|
*/}}
|
|
{{- $ns := include "bp-wordpress-tenant.cnpgNamespace" . }}
|
|
{{- $clusterName := .Values.database.cnpgClusterName }}
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
namespace: {{ $ns }}
|
|
labels:
|
|
{{- include "bp-wordpress-tenant.labels" . | nindent 4 }}
|
|
catalyst.openova.io/component: wordpress-database-secret-sync
|
|
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: wordpress-database-secret-sync
|
|
catalyst.openova.io/blueprint: bp-wordpress-tenant
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
serviceAccountName: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
containers:
|
|
- name: sync
|
|
# curlimages/curl matches bp-gitea (no kubectl image
|
|
# required — we talk to the apiserver via the in-pod
|
|
# ServiceAccount token).
|
|
image: curlimages/curl:8.10.1
|
|
imagePullPolicy: IfNotPresent
|
|
env:
|
|
- name: SOURCE_SECRET
|
|
value: {{ printf "%s-app" $clusterName | quote }}
|
|
- name: DEST_SECRET
|
|
value: {{ .Values.database.secretName | quote }}
|
|
- name: NAMESPACE
|
|
value: {{ $ns | quote }}
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
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
|
|
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=$(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: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
namespace: {{ $ns }}
|
|
labels:
|
|
{{- include "bp-wordpress-tenant.labels" . | nindent 4 }}
|
|
annotations:
|
|
"helm.sh/hook": "post-install,post-upgrade"
|
|
"helm.sh/hook-weight": "0"
|
|
"helm.sh/hook-delete-policy": "before-hook-creation"
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
namespace: {{ $ns }}
|
|
labels:
|
|
{{- include "bp-wordpress-tenant.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: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
namespace: {{ $ns }}
|
|
labels:
|
|
{{- include "bp-wordpress-tenant.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: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: {{ include "bp-wordpress-tenant.fullname" . }}-db-secret-sync
|
|
namespace: {{ $ns }}
|
|
{{- end }}
|