From 36468a68edb7fbe51f967d2cdf7900319b976600 Mon Sep 17 00:00:00 2001 From: Niklas Ye Date: Fri, 22 May 2026 10:11:30 +0200 Subject: [PATCH] chart: add bootstrap job (v0.2.0) Post-install/post-upgrade Job that calls /api/bootstrap on first deploy and stores the admin API key in a Secret (-admin-key by default). Exits cleanly on subsequent upgrades when bootstrap is already complete. Adds ServiceAccount, Role (secrets:create), and RoleBinding as hook resources. --- charts/terdut-server/Chart.yaml | 2 +- charts/terdut-server/templates/_helpers.tpl | 4 + .../templates/bootstrap-job.yaml | 91 +++++++++++++++++++ .../templates/bootstrap-rbac.yaml | 50 ++++++++++ charts/terdut-server/values.yaml | 7 ++ 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 charts/terdut-server/templates/bootstrap-job.yaml create mode 100644 charts/terdut-server/templates/bootstrap-rbac.yaml diff --git a/charts/terdut-server/Chart.yaml b/charts/terdut-server/Chart.yaml index 0860e56..98e8a4d 100644 --- a/charts/terdut-server/Chart.yaml +++ b/charts/terdut-server/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: terdut-server description: A Helm chart for Terminal Duty — on-call alert management server type: application -version: 0.1.0 +version: 0.2.0 appVersion: "latest" diff --git a/charts/terdut-server/templates/_helpers.tpl b/charts/terdut-server/templates/_helpers.tpl index 5192fe6..614f19f 100644 --- a/charts/terdut-server/templates/_helpers.tpl +++ b/charts/terdut-server/templates/_helpers.tpl @@ -32,3 +32,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/name: {{ include "terdut-server.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} + +{{- define "terdut-server.bootstrapSecretName" -}} +{{- .Values.bootstrap.secretName | default (printf "%s-admin-key" (include "terdut-server.fullname" .)) }} +{{- end }} diff --git a/charts/terdut-server/templates/bootstrap-job.yaml b/charts/terdut-server/templates/bootstrap-job.yaml new file mode 100644 index 0000000..e2be2ad --- /dev/null +++ b/charts/terdut-server/templates/bootstrap-job.yaml @@ -0,0 +1,91 @@ +{{- if .Values.bootstrap.enabled }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "terdut-server.fullname" . }}-bootstrap + namespace: {{ .Release.Namespace }} + labels: + {{- include "terdut-server.labels" . | nindent 4 }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backoffLimit: 3 + template: + metadata: + labels: + {{- include "terdut-server.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: bootstrap + spec: + restartPolicy: OnFailure + serviceAccountName: {{ include "terdut-server.fullname" . }}-bootstrap + containers: + - name: bootstrap + image: alpine:3 + command: + - /bin/sh + - -c + - | + apk add --no-cache curl > /dev/null 2>&1 + + SERVICE_URL="http://{{ include "terdut-server.fullname" . }}:{{ .Values.service.port }}" + SECRET_NAME="{{ include "terdut-server.bootstrapSecretName" . }}" + K8S_API="https://kubernetes.default.svc" + SA_TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" + CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)" + + echo "Waiting for terdut-server to be ready..." + RETRIES=60 + while [ "$RETRIES" -gt 0 ]; do + curl -sf "$SERVICE_URL/healthz" > /dev/null 2>&1 && break + RETRIES=$((RETRIES - 1)) + sleep 2 + done + if [ "$RETRIES" -eq 0 ]; then + echo "Timed out waiting for server to be ready." + exit 1 + fi + echo "Server is ready." + + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SERVICE_URL/api/bootstrap" \ + -H "Content-Type: application/json" \ + -d '{"username":"{{ .Values.bootstrap.username }}","email":"{{ .Values.bootstrap.email }}"}') + + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | head -1) + + if [ "$HTTP_CODE" = "403" ]; then + echo "Server already bootstrapped, nothing to do." + exit 0 + fi + + if [ "$HTTP_CODE" != "201" ]; then + echo "Bootstrap failed (HTTP $HTTP_CODE): $BODY" + exit 1 + fi + + API_KEY=$(echo "$BODY" | grep -o '"key":"[^"]*"' | cut -d'"' -f4) + if [ -z "$API_KEY" ]; then + echo "Failed to extract API key from response." + exit 1 + fi + + echo "Bootstrap succeeded. Storing API key in secret '$SECRET_NAME'." + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST "$K8S_API/api/v1/namespaces/$NAMESPACE/secrets" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer $SA_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(printf '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"%s"},"stringData":{"api-key":"%s"}}' "$SECRET_NAME" "$API_KEY")") + + if [ "$HTTP_CODE" != "201" ]; then + echo "Failed to create secret (HTTP $HTTP_CODE)." + exit 1 + fi + + echo "Secret '$SECRET_NAME' created successfully." +{{- end }} diff --git a/charts/terdut-server/templates/bootstrap-rbac.yaml b/charts/terdut-server/templates/bootstrap-rbac.yaml new file mode 100644 index 0000000..eb5b408 --- /dev/null +++ b/charts/terdut-server/templates/bootstrap-rbac.yaml @@ -0,0 +1,50 @@ +{{- if .Values.bootstrap.enabled }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "terdut-server.fullname" . }}-bootstrap + namespace: {{ .Release.Namespace }} + labels: + {{- include "terdut-server.labels" . | nindent 4 }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "terdut-server.fullname" . }}-bootstrap + namespace: {{ .Release.Namespace }} + labels: + {{- include "terdut-server.labels" . | nindent 4 }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "terdut-server.fullname" . }}-bootstrap + namespace: {{ .Release.Namespace }} + labels: + {{- include "terdut-server.labels" . | nindent 4 }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "terdut-server.fullname" . }}-bootstrap +subjects: + - kind: ServiceAccount + name: {{ include "terdut-server.fullname" . }}-bootstrap + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/terdut-server/values.yaml b/charts/terdut-server/values.yaml index 553ec9d..5dc466e 100644 --- a/charts/terdut-server/values.yaml +++ b/charts/terdut-server/values.yaml @@ -14,3 +14,10 @@ storage: service: type: ClusterIP port: 8080 + +bootstrap: + enabled: true + username: admin + email: admin@example.com + # secretName overrides the default of -admin-key + secretName: ""