From 6b653081d05eb09a53117f5fd45185f7a4649bba Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 11:22:00 -0600 Subject: [PATCH 01/28] remove placeholder values that break testing the chart --- .github/workflows/release.yml | 25 ++++++++++++++++-------- chart/Chart.yaml | 4 ++-- chart/README.md | 2 +- chart/examples/minikube/values.yaml | 2 +- chart/templates/poddisruptionbudget.yaml | 3 ++- chart/values.yaml | 2 +- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5337f71..9e99dbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,25 +17,34 @@ jobs: token: "${{ secrets.PAT_ACTIONS_WORKFLOWS }}" fetch-depth: 0 - - name: update placeholder versions - working-directory: ./chart - run: | - apk add -q curl jq envsubst + - name: add prerequisites + run: apk add -q curl jq envsubst + - name: prepare release chart + run: | export APP_VERSION=$(curl -s https://account.fusionauth.io/api/version | jq -r '.versions[]' | sort -V | tail -n 1) export CHART_VERSION="${GITHUB_REF##*/}" + export RELEASE_CHART=/tmp/fusionauth-chart echo "Chart: $CHART_VERSION" echo "App: $APP_VERSION" + rm -rf "$RELEASE_CHART" + cp -R chart "$RELEASE_CHART" + for file in Chart.yaml values.yaml README.md examples/minikube/values.yaml do - tmpfile=$(mktemp) - cat "$file" > "$tmpfile" - envsubst < "$tmpfile" > "$file" + sed -i \ + -e "s/0\.0\.0-chart-dev/${CHART_VERSION}/g" \ + -e "s/0\.0\.0-app-dev/${APP_VERSION}/g" \ + "$RELEASE_CHART/$file" done + helm lint "$RELEASE_CHART" + echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV + echo "CHART_VERSION=$CHART_VERSION" >> $GITHUB_ENV + echo "RELEASE_CHART=$RELEASE_CHART" >> $GITHUB_ENV - name: configure git run: | @@ -59,7 +68,7 @@ jobs: CR_PAGES_BRANCH: main CR_RELEASE_NAME_TEMPLATE: "{{ .Version }}" run: | - cr package chart + cr package "$RELEASE_CHART" cr upload --release-notes-file /tmp/release-notes.md cr index --push ARCHIVE=$(ls ${CR_PACKAGE_PATH}) diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 882619e..08f2fa0 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: fusionauth description: Helm chart for FusionAuth type: application -version: ${CHART_VERSION} -appVersion: ${APP_VERSION} +version: 0.0.0-chart-dev +appVersion: 0.0.0-app-dev diff --git a/chart/README.md b/chart/README.md index af83849..850f7db 100644 --- a/chart/README.md +++ b/chart/README.md @@ -293,7 +293,7 @@ You should now be able to connect to the FusionAuth application at http://localh image.tag string - "${APP_VERSION}" + "0.0.0-app-dev" The image tag to pull for fusionauth-app (this is the fusionauth-app version). diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index 4ab4a47..ccbb4a1 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -8,7 +8,7 @@ image: # image.repository -- The name of the docker repository for fusionauth-app repository: fusionauth/fusionauth-app # image.repository -- The docker tag to pull for fusionauth-app - tag: ${APP_VERSION} + tag: 0.0.0-app-dev # image.pullPolicy -- Kubernetes image pullPolicy to use for fusionauth-app pullPolicy: IfNotPresent diff --git a/chart/templates/poddisruptionbudget.yaml b/chart/templates/poddisruptionbudget.yaml index 8cacef0..8c3d3d3 100644 --- a/chart/templates/poddisruptionbudget.yaml +++ b/chart/templates/poddisruptionbudget.yaml @@ -8,4 +8,5 @@ spec: selector: matchLabels: app.kubernetes.io/name: {{ include "fusionauth.name" . }} -{{- end }} \ No newline at end of file + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/chart/values.yaml b/chart/values.yaml index 7e72900..92b37a7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -9,7 +9,7 @@ image: # image.repository -- The name of the docker repository for fusionauth-app repository: docker.io/fusionauth/fusionauth-app # image.tag -- The docker tag to pull for fusionauth-app - tag: ${APP_VERSION} + tag: 0.0.0-app-dev # image.pullPolicy -- Kubernetes image pullPolicy to use for fusionauth-app pullPolicy: IfNotPresent From 47f87214f8e49ecf917f924c0adfb41ff48ffbc6 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 11:30:27 -0600 Subject: [PATCH 02/28] gate the chart at k8s v1.23.0 or later to remove old API version support --- chart/Chart.yaml | 1 + chart/templates/_helpers.tpl | 37 ------------------------ chart/templates/hpa.yaml | 2 +- chart/templates/ingress.yaml | 9 ++---- chart/templates/poddisruptionbudget.yaml | 2 +- 5 files changed, 5 insertions(+), 46 deletions(-) diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 08f2fa0..c94b599 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -2,5 +2,6 @@ apiVersion: v2 name: fusionauth description: Helm chart for FusionAuth type: application +kubeVersion: ">=1.23.0-0" version: 0.0.0-chart-dev appVersion: 0.0.0-app-dev diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index a5befc9..395f2b3 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -24,43 +24,6 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{- end -}} -{{/* -Set apiVersion for HPA -*/}} -{{- define "fusionauth.HpaApiVersion" -}} -{{- if .Capabilities.APIVersions.Has "autoscaling/v2" -}} -autoscaling/v2 -{{- else -}} -autoscaling/v2beta2 -{{- end -}} -{{- end -}} - - -{{/* -Set apiVersion for ingress -*/}} -{{- define "fusionauth.ingressApiVersion" -}} -{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" -}} -networking.k8s.io/v1 -{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} -networking.k8s.io/v1beta1 -{{- else -}} -extensions/v1beta1 -{{- end -}} -{{- end -}} - -{{/* -Set apiVersion for PodDisruptionBudget -*/}} -{{- define "fusionauth.PodDisruptionBudget" -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -policy/v1 -{{- else -}} -policy/v1beta1 -{{- end -}} -{{- end -}} - - {{/* Configure TLS if enabled */}} diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml index 66219b8..5bd6f01 100644 --- a/chart/templates/hpa.yaml +++ b/chart/templates/hpa.yaml @@ -1,5 +1,5 @@ {{- if .Values.autoscaling.enabled }} -apiVersion: {{ include "fusionauth.HpaApiVersion" . }} +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ include "fusionauth.fullname" . }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index 46c844b..2d61cc3 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -2,7 +2,7 @@ {{- $fullName := include "fusionauth.fullname" . -}} {{- $ingressPaths := .Values.ingress.paths -}} {{- $extraPaths := .Values.ingress.extraPaths -}} -apiVersion: {{ include "fusionauth.ingressApiVersion" . }} +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} @@ -39,19 +39,14 @@ spec: {{- end }} {{- range $ingressPaths }} - path: {{ or .path . | quote }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + {{- if .pathType }} pathType: {{ .pathType | quote }} {{- end }} backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: name: {{ $fullName }} port: name: http - {{- else }} - serviceName: {{ $fullName }} - servicePort: http - {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/chart/templates/poddisruptionbudget.yaml b/chart/templates/poddisruptionbudget.yaml index 8c3d3d3..2133059 100644 --- a/chart/templates/poddisruptionbudget.yaml +++ b/chart/templates/poddisruptionbudget.yaml @@ -1,5 +1,5 @@ {{- if .Values.podDisruptionBudget.enabled -}} -apiVersion: {{ include "fusionauth.PodDisruptionBudget" . }} +apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: {{ include "fusionauth.fullname" . }} From 5d2ea6343364e4f599b1a24390ed7029029c248a Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 11:36:02 -0600 Subject: [PATCH 03/28] autoscaling check, fix metrics path --- chart/templates/hpa.yaml | 3 +++ chart/values.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml index 5bd6f01..aec6791 100644 --- a/chart/templates/hpa.yaml +++ b/chart/templates/hpa.yaml @@ -1,4 +1,7 @@ {{- if .Values.autoscaling.enabled }} +{{- if not (or .Values.autoscaling.targetCPU .Values.autoscaling.targetMemory) }} +{{- fail "autoscaling.enabled requires at least one of autoscaling.targetCPU or autoscaling.targetMemory" }} +{{- end }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: diff --git a/chart/values.yaml b/chart/values.yaml index 92b37a7..49cdd66 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -328,7 +328,7 @@ serviceMonitor: # name: myBasicAuthSecret # key: user # serviceMonitor.path -- Configures path to metrics, defaults to FusionAuth's prometheus metrics API endpoint - path: api/prometheus/metrics + path: /api/prometheus/metrics namespaceSelector: {} annotations: {} labels: {} From e4aff4f4ca8cee438497faf700a3bb12f5f02062 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 11:56:21 -0600 Subject: [PATCH 04/28] add test to the release workflow, update the readme --- .github/workflows/release.yml | 21 +++++++++++++++++++++ chart/README.md | 26 ++++++++++++++------------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e99dbb..327c6c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,6 +46,27 @@ jobs: echo "CHART_VERSION=$CHART_VERSION" >> $GITHUB_ENV echo "RELEASE_CHART=$RELEASE_CHART" >> $GITHUB_ENV + - name: lint and test the release chart + run: | + helm lint "$RELEASE_CHART" \ + --set database.host=postgres \ + --set database.user=fusionauth \ + --set database.password=password \ + --set search.host=opensearch + + helm template test "$RELEASE_CHART" \ + --set database.host=postgres \ + --set database.user=fusionauth \ + --set database.password=password \ + --set search.engine=database \ + --set ingress.enabled=true \ + --set ingress.hosts[0]=example.com \ + --set ingress.paths[0].path=/ \ + --set ingress.paths[0].pathType=Prefix \ + --set autoscaling.enabled=true \ + --set podDisruptionBudget.enabled=true \ + --set serviceMonitor.enabled=true + - name: configure git run: | git config --global user.name "$GITHUB_ACTOR" diff --git a/chart/README.md b/chart/README.md index 850f7db..6fbfb49 100644 --- a/chart/README.md +++ b/chart/README.md @@ -4,22 +4,21 @@ [FusionAuth](https://fusionauth.io/) is a modern platform for Customer Identity and Access Management (CIAM). FusionAuth provides APIs and a responsive web user interface to support login, registration, localized email, multi-factor authentication, reporting, and much more. - ## Important Upgrade Info -* **In `1.57.1` and later, the chart version now matches the FusionAuth app version.** - - ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. +- **In `1.67.0` and later, the minimum supported Kubernetes version is 1.23.0.** -* **In `1.0.0` and later, the FusionAuth app version will now default to the latest available at the time of the chart's release.** Release notes will indicate the FusionAuth version included in the chart. +- **In `1.57.1` and later, the chart version now matches the FusionAuth app version.** - ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. + ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. +- **In `1.0.0` and later, the FusionAuth app version will now default to the latest available at the time of the chart's release.** Release notes will indicate the FusionAuth version included in the chart. -* **In `0.8.0`, the `environment` value is now an array instead of an object.** Make sure to reformat your values when you update. + ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. -* **In `0.4.0`, the external postgresql and elasticsearch charts were dropped.** You will need to maintain those dependencies on your own. +- **In `0.8.0`, the `environment` value is now an array instead of an object.** Make sure to reformat your values when you update. +- **In `0.4.0`, the external postgresql and elasticsearch charts were dropped.** You will need to maintain those dependencies on your own. ## Installing the Chart @@ -27,8 +26,8 @@ You can read the official instructions, including install steps for AWS, GCP, an ### Prerequisites -* PostgreSQL or MySQL database -* ElasticSearch or OpenSearch instance (optional) +- PostgreSQL or MySQL database +- ElasticSearch or OpenSearch instance (optional) ⚠️ Though an ElasticSearch or OpenSearch instance is optional, it is strongly recommended for most use cases. @@ -45,18 +44,19 @@ helm install fusionauth fusionauth/fusionauth \ --set search.host=[elasticsearch host] ``` - ## Setting Up a Test Deployment This will install FusionAuth and its prerequisites in a single kubernetes namespace, with a configuration suitable for evaluation and testing. **This configuration is not suitable for production.** Create and switch to the test namespace. + ```shell kubectl create namespace fusionauth-test kubectl config set-context --current --namespace=fusionauth-test ``` ### Install PostgreSQL + ```shell helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql ``` @@ -64,6 +64,7 @@ helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql ### Install Opensearch Opensearch is optional, but highly recommended. See the note below. + ```shell helm repo add opensearch https://opensearch-project.github.io/helm-charts/ helm install opensearch opensearch/opensearch \ @@ -74,6 +75,7 @@ helm install opensearch opensearch/opensearch \ ### Install FusionAuth Wait for the Postgres and Opensearch pods to be ready, then install FusionAuth. + ```shell export FA_PSQL_PASS=$(kubectl get secret postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d) helm repo add fusionauth https://fusionauth.github.io/charts @@ -89,6 +91,7 @@ helm install fusionauth fusionauth/fusionauth \ ### Connect to FusionAuth Create a port forward to connect to the FusionAuth app. + ```shell kubectl port-forward svc/fusionauth 9011:9011 ``` @@ -97,7 +100,6 @@ You should now be able to connect to the FusionAuth application at http://localh 📝 You may wish to set up an ingress instead of using a port forward. See the table below for how to configure the FusionAuth chart values to add an ingress. - ## Chart Values From 47ec1db356b1e2edd693b2afc7e2c1afd52090e1 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 12:01:41 -0600 Subject: [PATCH 05/28] quote values in deployment.yaml, fix typo in minikube example --- chart/examples/minikube/values.yaml | 2 +- chart/templates/deployment.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index ccbb4a1..05b91b8 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -75,7 +75,7 @@ database: # These credentials are used for bootstrapping the database root: # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database - username: "postgres" + user: "postgres" # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret is configured password: "" diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 0c8291a..8044a75 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -93,7 +93,7 @@ spec: {{- end }} env: - name: DATABASE_USERNAME - value: {{ required "A valid username for the database is required!" .Values.database.user }} + value: {{ required "A valid username for the database is required!" .Values.database.user | quote }} - name: DATABASE_PASSWORD valueFrom: secretKeyRef: @@ -101,7 +101,7 @@ spec: key: password {{- if .Values.database.root.user }} - name: DATABASE_ROOT_USERNAME - value: {{ .Values.database.root.user }} + value: {{ .Values.database.root.user | quote }} - name: DATABASE_ROOT_PASSWORD valueFrom: secretKeyRef: @@ -111,15 +111,15 @@ spec: - name: DATABASE_URL value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" - name: SEARCH_TYPE - value: {{ .Values.search.engine }} + value: {{ .Values.search.engine | quote }} {{- if eq .Values.search.engine "elasticsearch" }} - name: SEARCH_SERVERS value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" {{- end }} - name: FUSIONAUTH_APP_MEMORY - value: {{ .Values.app.memory }} + value: {{ .Values.app.memory | quote }} - name: FUSIONAUTH_APP_RUNTIME_MODE - value: {{ .Values.app.runtimeMode }} + value: {{ .Values.app.runtimeMode | quote }} {{- if not (contains "FUSIONAUTH_APP_SILENT_MODE" (toString .Values.environment)) }} - name: FUSIONAUTH_APP_SILENT_MODE value: {{ .Values.app.silentMode | quote }} From f711fe8abf2e312176541eaaac1c385c2826920f Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 12:06:26 -0600 Subject: [PATCH 06/28] remove unnecessary servicemonitor labels --- chart/templates/deployment.yaml | 4 ++-- chart/templates/servicemonitor.yaml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 8044a75..eb3d9a3 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -48,7 +48,7 @@ spec: - -c - > set -x; - while [[ "$(nc -zv '{{- .Values.database.host -}}' {{ .Values.database.port }} &> /dev/null; echo $?)" != 0 ]]; do + until nc -zv '{{- .Values.database.host -}}' {{ .Values.database.port }} >/dev/null 2>&1; do echo '.' sleep 15; done @@ -63,7 +63,7 @@ spec: - -c - > set -x; - while [[ "$(nc -zv '{{- .Values.search.host -}}' {{ .Values.search.port }} &> /dev/null; echo $?)" != 0 ]]; do + until nc -zv '{{- .Values.search.host -}}' {{ .Values.search.port }} >/dev/null 2>&1; do echo '.' sleep 15; done diff --git a/chart/templates/servicemonitor.yaml b/chart/templates/servicemonitor.yaml index d33e287..00ab839 100644 --- a/chart/templates/servicemonitor.yaml +++ b/chart/templates/servicemonitor.yaml @@ -23,9 +23,7 @@ spec: selector: matchLabels: app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} endpoints: - port: http {{- with .Values.serviceMonitor.path }} From fdc8bbf30704fcff9ed6670c94ab70effe545465 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 12:36:51 -0600 Subject: [PATCH 07/28] schema updates --- chart/values.schema.json | 281 +++++++++++++++++++++++++++++++++++---- 1 file changed, 255 insertions(+), 26 deletions(-) diff --git a/chart/values.schema.json b/chart/values.schema.json index 1f81d39..3e5c955 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -15,7 +15,11 @@ "type": "string" }, "runtimeMode": { - "type": "string" + "type": "string", + "enum": [ + "development", + "production" + ] }, "silentMode": { "type": "boolean" @@ -27,6 +31,28 @@ "properties": { "enabled": { "type": "boolean" + }, + "maxReplicas": { + "type": "integer", + "minimum": 1 + }, + "minReplicas": { + "type": "integer", + "minimum": 1 + }, + "targetCPU": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "targetMemory": { + "type": [ + "integer", + "null" + ], + "minimum": 1 } } }, @@ -46,10 +72,16 @@ "type": "string" }, "port": { - "type": "integer" + "type": "integer", + "minimum": 1, + "maximum": 65535 }, "protocol": { - "type": "string" + "type": "string", + "enum": [ + "postgresql", + "mysql" + ] }, "root": { "type": "object", @@ -77,7 +109,13 @@ "type": "object" }, "dnsPolicy": { - "type": "string" + "type": "string", + "enum": [ + "ClusterFirst", + "ClusterFirstWithHostNet", + "Default", + "None" + ] }, "environment": { "type": "array" @@ -101,7 +139,12 @@ "type": "object", "properties": { "pullPolicy": { - "type": "string" + "type": "string", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] }, "repository": { "type": "string" @@ -127,13 +170,52 @@ "type": "array" }, "hosts": { - "type": "array" + "type": "array", + "items": { + "type": "string" + } + }, + "ingressClassName": { + "type": [ + "string", + "null" + ] }, "paths": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": [ + "Exact", + "ImplementationSpecific", + "Prefix" + ] + } + } + } }, "tls": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "properties": { + "hosts": { + "type": "array", + "items": { + "type": "string" + } + }, + "secretName": { + "type": "string" + } + } + } } } }, @@ -166,7 +248,10 @@ "type": "object", "properties": { "data": { - "type": "object" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "enabled": { "type": "boolean" @@ -180,7 +265,8 @@ "type": "object", "properties": { "failureThreshold": { - "type": "integer" + "type": "integer", + "minimum": 1 }, "httpGet": { "type": "object", @@ -189,15 +275,35 @@ "type": "string" }, "port": { - "type": "string" + "type": [ + "integer", + "string" + ] + }, + "scheme": { + "type": "string", + "enum": [ + "HTTP", + "HTTPS" + ] } } }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 0 + }, "periodSeconds": { - "type": "integer" + "type": "integer", + "minimum": 1 + }, + "successThreshold": { + "type": "integer", + "minimum": 1 }, "timeoutSeconds": { - "type": "integer" + "type": "integer", + "minimum": 1 } } }, @@ -221,11 +327,15 @@ } } }, + "podLabels": { + "type": "object" + }, "readinessProbe": { "type": "object", "properties": { "failureThreshold": { - "type": "integer" + "type": "integer", + "minimum": 1 }, "httpGet": { "type": "object", @@ -234,17 +344,41 @@ "type": "string" }, "port": { - "type": "string" + "type": [ + "integer", + "string" + ] + }, + "scheme": { + "type": "string", + "enum": [ + "HTTP", + "HTTPS" + ] } } }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 0 + }, + "periodSeconds": { + "type": "integer", + "minimum": 1 + }, + "successThreshold": { + "type": "integer", + "minimum": 1 + }, "timeoutSeconds": { - "type": "integer" + "type": "integer", + "minimum": 1 } } }, "replicaCount": { - "type": "integer" + "type": "integer", + "minimum": 0 }, "resources": { "type": "object" @@ -256,16 +390,26 @@ "type": "object", "properties": { "engine": { - "type": "string" + "type": "string", + "enum": [ + "elasticsearch", + "database" + ] }, "host": { "type": "string" }, "port": { - "type": "integer" + "type": "integer", + "minimum": 1, + "maximum": 65535 }, "protocol": { - "type": "string" + "type": "string", + "enum": [ + "http", + "https" + ] } } }, @@ -276,21 +420,86 @@ "type": "object" }, "port": { - "type": "integer" + "type": "integer", + "minimum": 1, + "maximum": 65535 }, "spec": { "type": "object" }, "type": { + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer", + "ExternalName" + ] + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "automount": { + "type": "boolean" + }, + "create": { + "type": "boolean" + }, + "name": { "type": "string" } } }, + "serviceMonitor": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "basicAuth": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "interval": { + "type": [ + "string", + "null" + ] + }, + "labels": { + "type": "object" + }, + "namespaceSelector": { + "type": "object" + }, + "path": { + "type": "string", + "pattern": "^/" + }, + "relabelings": { + "type": "array" + }, + "scrapeTimeout": { + "type": [ + "string", + "null" + ] + } + } + }, "startupProbe": { "type": "object", "properties": { "failureThreshold": { - "type": "integer" + "type": "integer", + "minimum": 1 }, "httpGet": { "type": "object", @@ -299,15 +508,35 @@ "type": "string" }, "port": { - "type": "string" + "type": [ + "integer", + "string" + ] + }, + "scheme": { + "type": "string", + "enum": [ + "HTTP", + "HTTPS" + ] } } }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 0 + }, "periodSeconds": { - "type": "integer" + "type": "integer", + "minimum": 1 + }, + "successThreshold": { + "type": "integer", + "minimum": 1 }, "timeoutSeconds": { - "type": "integer" + "type": "integer", + "minimum": 1 } } }, @@ -318,4 +547,4 @@ "type": "array" } } -} \ No newline at end of file +} From 4128ed18524fb05c0a8421f89bd282580b6ee542 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 12:52:03 -0600 Subject: [PATCH 08/28] more chart hardening, add minUnavailable to PDB --- chart/examples/minikube/values.yaml | 2 +- chart/templates/NOTES.txt | 2 +- chart/templates/configmap.yaml | 8 +++--- chart/templates/ingress.yaml | 6 ++++ chart/templates/poddisruptionbudget.yaml | 13 ++++++++- chart/templates/secret.yaml | 5 +++- chart/values.schema.json | 36 +++++++++++++++++++++++- chart/values.yaml | 7 +++-- 8 files changed, 68 insertions(+), 11 deletions(-) diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index 05b91b8..03707d2 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -77,7 +77,7 @@ database: # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database user: "postgres" # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret is configured - password: "" + password: "minikube-password" search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt index 5131590..5fb2266 100644 --- a/chart/templates/NOTES.txt +++ b/chart/templates/NOTES.txt @@ -2,7 +2,7 @@ {{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range $.Values.ingress.paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path | default "/" }} {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index e0fdba5..d745fa6 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -4,10 +4,10 @@ kind: ConfigMap metadata: name: {{ template "fusionauth.fullname" . }}-kickstart-config labels: - heritage: {{ .Release.Name }} - release: {{ .Release.Name }} - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "fusionauth.name" . }} + app.kubernetes.io/name: {{ include "fusionauth.name" . }} + helm.sh/chart: {{ include "fusionauth.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} data: {{- if .Values.kickstart.data }} {{- range $key, $value := .Values.kickstart.data }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index 2d61cc3..a64db93 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -2,6 +2,12 @@ {{- $fullName := include "fusionauth.fullname" . -}} {{- $ingressPaths := .Values.ingress.paths -}} {{- $extraPaths := .Values.ingress.extraPaths -}} +{{- if not .Values.ingress.hosts -}} +{{- fail "ingress.enabled requires at least one ingress.hosts entry" -}} +{{- end -}} +{{- if not (or $ingressPaths $extraPaths) -}} +{{- fail "ingress.enabled requires at least one ingress.paths or ingress.extraPaths entry" -}} +{{- end -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/chart/templates/poddisruptionbudget.yaml b/chart/templates/poddisruptionbudget.yaml index 2133059..c92c759 100644 --- a/chart/templates/poddisruptionbudget.yaml +++ b/chart/templates/poddisruptionbudget.yaml @@ -1,10 +1,21 @@ {{- if .Values.podDisruptionBudget.enabled -}} +{{- if and (ne .Values.podDisruptionBudget.minAvailable nil) (ne .Values.podDisruptionBudget.maxUnavailable nil) }} +{{- fail "podDisruptionBudget.minAvailable and podDisruptionBudget.maxUnavailable cannot both be set" }} +{{- end }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: {{ include "fusionauth.fullname" . }} spec: - maxUnavailable: {{ sub .Values.replicaCount 1 }} + {{- if ne .Values.podDisruptionBudget.minAvailable nil }} + {{- $minAvailable := .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ if kindIs "string" $minAvailable }}{{ $minAvailable | quote }}{{ else }}{{ $minAvailable }}{{ end }} + {{- else if ne .Values.podDisruptionBudget.maxUnavailable nil }} + {{- $maxUnavailable := .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ if kindIs "string" $maxUnavailable }}{{ $maxUnavailable | quote }}{{ else }}{{ $maxUnavailable }}{{ end }} + {{- else }} + maxUnavailable: {{ max 0 (sub .Values.replicaCount 1) }} + {{- end }} selector: matchLabels: app.kubernetes.io/name: {{ include "fusionauth.name" . }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 624c064..6e2d211 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,4 +1,7 @@ {{- if eq .Values.database.existingSecret "" -}} +{{- if and .Values.database.root.user (not .Values.database.root.password) -}} +{{- fail "database.root.password is required when database.root.user is set and database.existingSecret is not configured" }} +{{- end -}} apiVersion: v1 data: password: {{ required "A password for your database is required!" .Values.database.password | b64enc }} @@ -14,4 +17,4 @@ metadata: app.kubernetes.io/managed-by: {{ .Release.Service }} name: {{ include "fusionauth.database.secretName" . }} type: Opaque -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/chart/values.schema.json b/chart/values.schema.json index 3e5c955..8ca6c74 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -197,7 +197,11 @@ "Prefix" ] } - } + }, + "required": [ + "path", + "pathType" + ] } }, "tls": { @@ -324,6 +328,36 @@ "properties": { "enabled": { "type": "boolean" + }, + "maxUnavailable": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "pattern": "^[0-9]+%$" + }, + { + "type": "null" + } + ] + }, + "minAvailable": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "pattern": "^[0-9]+%$" + }, + { + "type": "null" + } + ] } } }, diff --git a/chart/values.yaml b/chart/values.yaml index 49cdd66..bd4dd53 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -130,8 +130,7 @@ environment: [] kickstart: enabled: false - data: - {} + data: {} # kickstart.json: | # { # "variables": { @@ -182,6 +181,10 @@ lifecycle: {} podDisruptionBudget: # podDisruptionBudget.enabled -- Enables creation of a PodDisruptionBudget enabled: false + # podDisruptionBudget.minAvailable -- Minimum number of available pods. Cannot be used with maxUnavailable. + # minAvailable: 1 + # podDisruptionBudget.maxUnavailable -- Maximum number of unavailable pods. Cannot be used with minAvailable. Defaults to replicaCount - 1. + # maxUnavailable: 1 ingress: # ingress.enabled -- Enables ingress creation for fusionauth. From 5b275f13fb13859b297c8510c5df1aec54e7a969 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 13:05:46 -0600 Subject: [PATCH 09/28] add icon to chart to silence the linter, more chart hardening --- chart/Chart.yaml | 1 + chart/templates/deployment.yaml | 14 ++++++++- chart/templates/hpa.yaml | 3 ++ chart/templates/serviceaccount.yaml | 5 ++++ chart/values.schema.json | 44 +++++++++++++++++++++++++---- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/chart/Chart.yaml b/chart/Chart.yaml index c94b599..8621fb9 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -1,6 +1,7 @@ apiVersion: v2 name: fusionauth description: Helm chart for FusionAuth +icon: https://fusionauth.io/img/favicon.png type: application kubeVersion: ">=1.23.0-0" version: 0.0.0-chart-dev diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index eb3d9a3..978b207 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,3 +1,15 @@ +{{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} +{{- end }} +{{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} +{{- end }} +{{- $silentModeConfigured := false }} +{{- range .Values.environment }} +{{- if eq .name "FUSIONAUTH_APP_SILENT_MODE" }} +{{- $silentModeConfigured = true }} +{{- end }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -120,7 +132,7 @@ spec: value: {{ .Values.app.memory | quote }} - name: FUSIONAUTH_APP_RUNTIME_MODE value: {{ .Values.app.runtimeMode | quote }} - {{- if not (contains "FUSIONAUTH_APP_SILENT_MODE" (toString .Values.environment)) }} + {{- if not $silentModeConfigured }} - name: FUSIONAUTH_APP_SILENT_MODE value: {{ .Values.app.silentMode | quote }} {{- end }} diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml index aec6791..28db277 100644 --- a/chart/templates/hpa.yaml +++ b/chart/templates/hpa.yaml @@ -2,6 +2,9 @@ {{- if not (or .Values.autoscaling.targetCPU .Values.autoscaling.targetMemory) }} {{- fail "autoscaling.enabled requires at least one of autoscaling.targetCPU or autoscaling.targetMemory" }} {{- end }} +{{- if gt (int .Values.autoscaling.minReplicas) (int .Values.autoscaling.maxReplicas) }} +{{- fail "autoscaling.minReplicas cannot be greater than autoscaling.maxReplicas" }} +{{- end }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml index eee3271..7b1a173 100644 --- a/chart/templates/serviceaccount.yaml +++ b/chart/templates/serviceaccount.yaml @@ -3,6 +3,11 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "fusionauth.serviceAccountName" . }} + labels: + app.kubernetes.io/name: {{ include "fusionauth.name" . }} + helm.sh/chart: {{ include "fusionauth.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} diff --git a/chart/values.schema.json b/chart/values.schema.json index 8ca6c74..8d788e8 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -118,7 +118,24 @@ ] }, "environment": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "type": "object" + } + }, + "required": [ + "name" + ] + } }, "extraContainers": { "type": "array" @@ -303,7 +320,8 @@ }, "successThreshold": { "type": "integer", - "minimum": 1 + "minimum": 1, + "maximum": 1 }, "timeoutSeconds": { "type": "integer", @@ -362,7 +380,21 @@ } }, "podLabels": { - "type": "object" + "type": "object", + "not": { + "anyOf": [ + { + "required": [ + "app.kubernetes.io/name" + ] + }, + { + "required": [ + "app.kubernetes.io/instance" + ] + } + ] + } }, "readinessProbe": { "type": "object", @@ -466,8 +498,7 @@ "enum": [ "ClusterIP", "NodePort", - "LoadBalancer", - "ExternalName" + "LoadBalancer" ] } } @@ -566,7 +597,8 @@ }, "successThreshold": { "type": "integer", - "minimum": 1 + "minimum": 1, + "maximum": 1 }, "timeoutSeconds": { "type": "integer", From aa5501916488d744377c6313feeef69df259cbb4 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 13:28:53 -0600 Subject: [PATCH 10/28] add existingsecret for search creds, breaking change for database creds --- chart/examples/minikube/values.yaml | 17 +++++++++---- chart/templates/_helpers.tpl | 8 +++--- chart/templates/deployment.yaml | 16 ++++++++++-- chart/templates/secret.yaml | 4 +-- chart/values.schema.json | 39 ++++++++++++++++++++++++++++- chart/values.yaml | 27 ++++++++++++++++---- 6 files changed, 93 insertions(+), 18 deletions(-) diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index 03707d2..bfc2cca 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -65,18 +65,25 @@ database: # database.name -- Name of the fusionauth database name: fusionauth - # To use an existing secret, set `existingSecret` to the name of the secret. We expect at most two keys: `password` is required. `rootpassword` is only required if `database.root.user` is set. - # database.existingSecret -- The name of an existing secret that contains the database passwords - existingSecret: "" + # database.existingSecret -- Configures an existing secret that contains the database passwords. + existingSecret: + # database.existingSecret.enabled -- Use an existing secret for database passwords. + enabled: false + # database.existingSecret.name -- The name of an existing secret that contains the database passwords. + name: "" + # database.existingSecret.passwordKey -- The key in database.existingSecret.name that contains the database password. + passwordKey: password + # database.existingSecret.rootPasswordKey -- The key in database.existingSecret.name that contains the database root password. + rootPasswordKey: rootpassword # database.user -- Database username for fusionauth to use in normal operation user: "fusionauth" - # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret is configured + # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret.enabled is true password: "minikube-password" # These credentials are used for bootstrapping the database root: # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database user: "postgres" - # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret is configured + # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret.enabled is true password: "minikube-password" search: diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 395f2b3..83ad585 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -34,7 +34,9 @@ Configure TLS if enabled {{- end -}} {{- define "fusionauth.searchLogin" -}} -{{- if .Values.search.user -}} +{{- if .Values.search.existingSecret.enabled -}} +$(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ +{{- else if .Values.search.user -}} {{- printf "%s:%s@" .Values.search.user .Values.search.password -}} {{- else -}} {{- printf "" -}} @@ -52,8 +54,8 @@ Create chart name and version as used by the chart label. Set name of secret to use for credentials */}} {{- define "fusionauth.database.secretName" -}} -{{- if .Values.database.existingSecret -}} -{{- .Values.database.existingSecret -}} +{{- if .Values.database.existingSecret.enabled -}} +{{- required "database.existingSecret.name is required when database.existingSecret.enabled is true" .Values.database.existingSecret.name -}} {{- else -}} {{ .Release.Name }}-credentials {{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 978b207..d17c906 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -110,7 +110,7 @@ spec: valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} - key: password + key: {{ .Values.database.existingSecret.passwordKey | default "password" | quote }} {{- if .Values.database.root.user }} - name: DATABASE_ROOT_USERNAME value: {{ .Values.database.root.user | quote }} @@ -118,13 +118,25 @@ spec: valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} - key: rootpassword + key: {{ .Values.database.existingSecret.rootPasswordKey | default "rootpassword" | quote }} {{- end }} - name: DATABASE_URL value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" - name: SEARCH_TYPE value: {{ .Values.search.engine | quote }} {{- if eq .Values.search.engine "elasticsearch" }} + {{- if .Values.search.existingSecret.enabled }} + - name: SEARCH_USERNAME + valueFrom: + secretKeyRef: + name: {{ required "search.existingSecret.name is required when search.existingSecret.enabled is true" .Values.search.existingSecret.name | quote }} + key: {{ .Values.search.existingSecret.userKey | default "username" | quote }} + - name: SEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ required "search.existingSecret.name is required when search.existingSecret.enabled is true" .Values.search.existingSecret.name | quote }} + key: {{ .Values.search.existingSecret.passwordKey | default "password" | quote }} + {{- end }} - name: SEARCH_SERVERS value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" {{- end }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 6e2d211..70c8258 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,6 +1,6 @@ -{{- if eq .Values.database.existingSecret "" -}} +{{- if not .Values.database.existingSecret.enabled -}} {{- if and .Values.database.root.user (not .Values.database.root.password) -}} -{{- fail "database.root.password is required when database.root.user is set and database.existingSecret is not configured" }} +{{- fail "database.root.password is required when database.root.user is set and database.existingSecret.enabled is false" }} {{- end -}} apiVersion: v1 data: diff --git a/chart/values.schema.json b/chart/values.schema.json index 8d788e8..5b9acf2 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -60,7 +60,21 @@ "type": "object", "properties": { "existingSecret": { - "type": "string" + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "rootPasswordKey": { + "type": "string" + } + } }, "host": { "type": "string" @@ -462,9 +476,29 @@ "database" ] }, + "existingSecret": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "userKey": { + "type": "string" + } + } + }, "host": { "type": "string" }, + "password": { + "type": "string" + }, "port": { "type": "integer", "minimum": 1, @@ -476,6 +510,9 @@ "http", "https" ] + }, + "user": { + "type": "string" } } }, diff --git a/chart/values.yaml b/chart/values.yaml index bd4dd53..fc1e8ae 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -76,18 +76,25 @@ database: # database.name -- Name of the fusionauth database name: fusionauth - # To use an existing secret, set `existingSecret` to the name of the secret. We expect at most two keys: `password` is required. `rootpassword` is only required if `database.root.user` is set. - # database.existingSecret -- The name of an existing secret that contains the database passwords - existingSecret: "" + # database.existingSecret -- Configures an existing secret that contains the database passwords. + existingSecret: + # database.existingSecret.enabled -- Use an existing secret for database passwords. + enabled: false + # database.existingSecret.name -- The name of an existing secret that contains the database passwords. + name: "" + # database.existingSecret.passwordKey -- The key in database.existingSecret.name that contains the database password. + passwordKey: password + # database.existingSecret.rootPasswordKey -- The key in database.existingSecret.name that contains the database root password. + rootPasswordKey: rootpassword # database.user -- Database username for fusionauth to use in normal operation user: "" - # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret is configured + # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret.enabled is true password: "" # These credentials are used for bootstrapping the database root: # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database user: "" - # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret is configured + # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret.enabled is true password: "" search: @@ -99,6 +106,16 @@ search: host: "" # search.port -- Port to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch port: 9200 + # search.existingSecret -- Configures an existing secret that contains elasticsearch basic auth credentials. Ignored when search.engine is NOT elasticsearch + existingSecret: + # search.existingSecret.enabled -- Use an existing secret for elasticsearch basic auth credentials. + enabled: false + # search.existingSecret.name -- The name of an existing secret that contains elasticsearch basic auth credentials. + name: "" + # search.existingSecret.userKey -- The key in search.existingSecret.name that contains the elasticsearch username. + userKey: username + # search.existingSecret.passwordKey -- The key in search.existingSecret.name that contains the elasticsearch password. + passwordKey: password # search.user -- Username to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch # user: "" # search.password -- Password to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch From 63807be673317cca94243e2948e587440a070adf Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 14:30:05 -0600 Subject: [PATCH 11/28] helpers for backward compat, shared labels --- chart/examples/minikube/values.yaml | 40 ++-- chart/templates/_helpers.tpl | 227 ++++++++++++++++++++- chart/templates/configmap.yaml | 5 +- chart/templates/deployment.yaml | 81 +++++--- chart/templates/hpa.yaml | 5 +- chart/templates/ingress.yaml | 5 +- chart/templates/poddisruptionbudget.yaml | 5 +- chart/templates/secret.yaml | 7 +- chart/templates/service.yaml | 11 +- chart/templates/serviceaccount.yaml | 5 +- chart/templates/servicemonitor.yaml | 8 +- chart/templates/tests/test-connection.yaml | 5 +- chart/values.schema.json | 147 ++++++++++--- chart/values.yaml | 54 ++--- 14 files changed, 457 insertions(+), 148 deletions(-) diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index bfc2cca..14d6d90 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -7,7 +7,7 @@ replicaCount: 1 image: # image.repository -- The name of the docker repository for fusionauth-app repository: fusionauth/fusionauth-app - # image.repository -- The docker tag to pull for fusionauth-app + # image.tag -- The docker tag to pull for fusionauth-app tag: 0.0.0-app-dev # image.pullPolicy -- Kubernetes image pullPolicy to use for fusionauth-app pullPolicy: IfNotPresent @@ -20,7 +20,7 @@ initContainers: image: # initContainers.image.repository -- Docker image to use for initContainers repository: busybox - # initContainers.image.repository -- Tag to use for initContainers docker image + # initContainers.image.tag -- Tag to use for initContainers docker image tag: latest # initContainers.resources -- Resource requests and limits to use for initContainers resources: @@ -48,15 +48,13 @@ service: port: 9011 # service.annotations -- Extra annotations to add to service object annotations: {} - # service.spec -- Any extra fields to add to the service object spec - spec: {} database: # database.protocol -- Should either be postgresql or mysql. Protocol for jdbc connection to database protocol: postgresql # database.host -- Hostname or ip of the database instance host: pg-minikube-postgresql.default.svc.cluster.local - # database.host -- Port of the database instance + # database.port -- Port of the database instance port: 5432 # database.tls -- Configures whether or not to use tls when connecting to the database tls: false @@ -89,16 +87,30 @@ database: search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. engine: elasticsearch - # search.engine -- Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch + # search.protocol -- Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch protocol: http # search.host -- Hostname or ip to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch host: elasticsearch-master.default.svc.cluster.local # search.port -- Port to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch port: 9200 - # search.user -- Username to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch - # user: "" - # search.password -- Password to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch - # password: "" + # search.basicAuth -- Configures elasticsearch basic auth credentials. Ignored when search.engine is NOT elasticsearch. + basicAuth: + # search.basicAuth.enabled -- Enables elasticsearch basic auth using inline username/password. Not required when search.basicAuth.existingSecret.enabled is true. + enabled: false + # search.basicAuth.username -- Username to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. + username: "" + # search.basicAuth.password -- Password to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. + password: "" + # search.basicAuth.existingSecret -- Configures an existing secret that contains elasticsearch basic auth credentials. + existingSecret: + # search.basicAuth.existingSecret.enabled -- Use an existing secret for elasticsearch basic auth credentials. + enabled: false + # search.basicAuth.existingSecret.name -- The name of an existing secret that contains elasticsearch basic auth credentials. + name: "" + # search.basicAuth.existingSecret.userKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch username. + userKey: username + # search.basicAuth.existingSecret.passwordKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. + passwordKey: password app: # app.memory -- Configures the amount of memory Java can use @@ -231,7 +243,7 @@ nodeSelector: {} # tolerations -- Define tolerations for kubernetes to use when scheduling fusionauth pods. tolerations: [] -# affinty -- Define affinity for kubernetes to use when scheduling fusionauth pods. +# affinity -- Define affinity for kubernetes to use when scheduling fusionauth pods. affinity: {} # dnsConfig -- Define dnsConfig for fusionauth pods. @@ -239,8 +251,8 @@ dnsConfig: {} # dnsPolicy -- Define dnsPolicy for fusionauth pods. dnsPolicy: ClusterFirst -# annotations -- Define annotations for fusionauth deployment. -annotations: {} +# deploymentAnnotations -- Define annotations for fusionauth deployment. +deploymentAnnotations: {} # podAnnotations -- Define annotations for fusionauth pods. podAnnotations: {} @@ -275,7 +287,7 @@ extraVolumes: # persistentVolumeClaim: # claimName: custom-css-data -# extraVolumes -- Associate mountPath for each extraVolumes +# extraVolumeMounts -- Associate mountPath for each extraVolumes extraVolumeMounts: [] # - name: custom-css-data diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 83ad585..112e195 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -33,16 +33,219 @@ Configure TLS if enabled {{- end -}} {{- end -}} +{{/* +Resolve deployment annotations. +Current value: deploymentAnnotations. +Backward compatibility: top-level annotations is deprecated but still accepted. +When both are set, deploymentAnnotations wins. +*/}} +{{- define "fusionauth.deploymentAnnotations" -}} +{{- if .Values.deploymentAnnotations -}} +{{- toYaml .Values.deploymentAnnotations -}} +{{- else if and (hasKey .Values "annotations") .Values.annotations -}} +{{- toYaml .Values.annotations -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether database credentials should come from an existing secret. +Current value: database.existingSecret.enabled. +Backward compatibility: database.existingSecret used to support a scalar secret +name. A legacy string value is treated as enabled=true with that secret name. +*/}} +{{- define "fusionauth.database.existingSecret.enabled" -}} +{{- if kindIs "string" .Values.database.existingSecret -}} +{{- if .Values.database.existingSecret -}}true{{- else -}}false{{- end -}} +{{- else -}} +{{- .Values.database.existingSecret.enabled | default false -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve the database secret name. +Current value: database.existingSecret.name. +Backward compatibility: database.existingSecret used to support a scalar secret +name. A legacy string value is used directly as the secret name. +*/}} +{{- define "fusionauth.database.secretName" -}} +{{- if eq (include "fusionauth.database.existingSecret.enabled" .) "true" -}} +{{- if kindIs "string" .Values.database.existingSecret -}} +{{- required "database.existingSecret must not be empty when used as a secret name" .Values.database.existingSecret -}} +{{- else -}} +{{- required "database.existingSecret.name is required when database.existingSecret.enabled is true" .Values.database.existingSecret.name -}} +{{- end -}} +{{- else -}} +{{ .Release.Name }}-credentials +{{- end -}} +{{- end -}} + +{{/* +Resolve the database password key. +Current value: database.existingSecret.passwordKey. +Backward compatibility: legacy scalar database.existingSecret values have no +key fields, so they use the default password key. +*/}} +{{- define "fusionauth.database.passwordKey" -}} +{{- if kindIs "map" .Values.database.existingSecret -}} +{{- .Values.database.existingSecret.passwordKey | default "password" -}} +{{- else -}} +password +{{- end -}} +{{- end -}} + +{{/* +Resolve the database root password key. +Current value: database.existingSecret.rootPasswordKey. +Backward compatibility: legacy scalar database.existingSecret values have no +key fields, so they use the default root password key. +*/}} +{{- define "fusionauth.database.rootPasswordKey" -}} +{{- if kindIs "map" .Values.database.existingSecret -}} +{{- .Values.database.existingSecret.rootPasswordKey | default "rootpassword" -}} +{{- else -}} +rootpassword +{{- end -}} +{{- end -}} + +{{/* +Resolve whether search basic auth is enabled. +Current value: search.basicAuth.enabled. +Backward compatibility: deprecated search.user/search.password and deprecated +search.existingSecret still imply that basic auth is enabled. +*/}} +{{- define "fusionauth.search.basicAuth.enabled" -}} +{{- if .Values.search.basicAuth.enabled -}} +true +{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve whether search basic auth credentials should come from an existing +secret. +Current value: search.basicAuth.existingSecret.enabled. +Backward compatibility: deprecated search.existingSecret is still accepted as +either an object or a scalar secret name. +*/}} +{{- define "fusionauth.search.existingSecret.enabled" -}} +{{- if .Values.search.basicAuth.existingSecret.enabled -}} +true +{{- else if .Values.search.existingSecret -}} +{{- if kindIs "string" .Values.search.existingSecret -}} +{{- if .Values.search.existingSecret -}}true{{- else -}}false{{- end -}} +{{- else -}} +{{- .Values.search.existingSecret.enabled | default false -}} +{{- end -}} +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve the search existing secret name. +Current value: search.basicAuth.existingSecret.name. +Backward compatibility: deprecated search.existingSecret string values are used +directly, and deprecated search.existingSecret.name values are also accepted. +*/}} +{{- define "fusionauth.search.existingSecret.name" -}} +{{- if .Values.search.basicAuth.existingSecret.name -}} +{{- .Values.search.basicAuth.existingSecret.name -}} +{{- else if .Values.search.existingSecret -}} +{{- if kindIs "string" .Values.search.existingSecret -}} +{{- .Values.search.existingSecret -}} +{{- else -}} +{{- .Values.search.existingSecret.name -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve the search username key. +Current value: search.basicAuth.existingSecret.userKey. +Backward compatibility: deprecated search.existingSecret.userKey is still +accepted. +*/}} +{{- define "fusionauth.search.existingSecret.userKey" -}} +{{- if .Values.search.basicAuth.existingSecret.userKey -}} +{{- .Values.search.basicAuth.existingSecret.userKey -}} +{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.userKey -}} +{{- .Values.search.existingSecret.userKey -}} +{{- else -}} +username +{{- end -}} +{{- end -}} + +{{/* +Resolve the search password key. +Current value: search.basicAuth.existingSecret.passwordKey. +Backward compatibility: deprecated search.existingSecret.passwordKey is still +accepted. +*/}} +{{- define "fusionauth.search.existingSecret.passwordKey" -}} +{{- if .Values.search.basicAuth.existingSecret.passwordKey -}} +{{- .Values.search.basicAuth.existingSecret.passwordKey -}} +{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.passwordKey -}} +{{- .Values.search.existingSecret.passwordKey -}} +{{- else -}} +password +{{- end -}} +{{- end -}} + +{{/* +Build the search login prefix for SEARCH_SERVERS. +Backward compatibility: this uses the search compatibility helpers above, so +deprecated search.user/search.password and search.existingSecret shapes still +render the same URL prefix. +*/}} {{- define "fusionauth.searchLogin" -}} -{{- if .Values.search.existingSecret.enabled -}} +{{- if eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ -{{- else if .Values.search.user -}} +{{- else if eq (include "fusionauth.search.basicAuth.enabled" .) "true" -}} +{{- if .Values.search.basicAuth.enabled -}} +{{- printf "%s:%s@" .Values.search.basicAuth.username .Values.search.basicAuth.password -}} +{{- else -}} {{- printf "%s:%s@" .Values.search.user .Values.search.password -}} +{{- end -}} {{- else -}} {{- printf "" -}} {{- end -}} {{- end -}} +{{/* +Resolve the database wait init-container flag. +Current value: initContainers.waitForDatabase. +Backward compatibility: deprecated initContainers.waitForDb is still accepted. +When both are set, waitForDatabase wins. +*/}} +{{- define "fusionauth.initContainers.waitForDatabase" -}} +{{- if hasKey .Values.initContainers "waitForDatabase" -}} +{{- .Values.initContainers.waitForDatabase -}} +{{- else if hasKey .Values.initContainers "waitForDb" -}} +{{- .Values.initContainers.waitForDb -}} +{{- else -}} +true +{{- end -}} +{{- end -}} + +{{/* +Resolve the search wait init-container flag. +Current value: initContainers.waitForSearch. +Backward compatibility: deprecated initContainers.waitForEs is still accepted. +When both are set, waitForSearch wins. +*/}} +{{- define "fusionauth.initContainers.waitForSearch" -}} +{{- if hasKey .Values.initContainers "waitForSearch" -}} +{{- .Values.initContainers.waitForSearch -}} +{{- else if hasKey .Values.initContainers "waitForEs" -}} +{{- .Values.initContainers.waitForEs -}} +{{- else -}} +true +{{- end -}} +{{- end -}} + {{/* Create chart name and version as used by the chart label. */}} @@ -51,14 +254,22 @@ Create chart name and version as used by the chart label. {{- end -}} {{/* -Set name of secret to use for credentials +Common labels applied to FusionAuth resources. */}} -{{- define "fusionauth.database.secretName" -}} -{{- if .Values.database.existingSecret.enabled -}} -{{- required "database.existingSecret.name is required when database.existingSecret.enabled is true" .Values.database.existingSecret.name -}} -{{- else -}} -{{ .Release.Name }}-credentials +{{- define "fusionauth.labels" -}} +app.kubernetes.io/name: {{ include "fusionauth.name" . }} +helm.sh/chart: {{ include "fusionauth.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} + +{{/* +Selector labels used by Services, workload selectors, and pods. +Keep these stable because selector labels are immutable on several resources. +*/}} +{{- define "fusionauth.selectorLabels" -}} +app.kubernetes.io/name: {{ include "fusionauth.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{/* diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index d745fa6..56a698a 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -4,10 +4,7 @@ kind: ConfigMap metadata: name: {{ template "fusionauth.fullname" . }}-kickstart-config labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} data: {{- if .Values.kickstart.data }} {{- range $key, $value := .Values.kickstart.data }} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index d17c906..21d6b70 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -4,24 +4,51 @@ {{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} {{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} {{- end }} -{{- $silentModeConfigured := false }} +{{- $reservedEnv := list "DATABASE_USERNAME" "DATABASE_PASSWORD" "DATABASE_ROOT_USERNAME" "DATABASE_ROOT_PASSWORD" "DATABASE_URL" "SEARCH_TYPE" "SEARCH_USERNAME" "SEARCH_PASSWORD" "SEARCH_SERVERS" "FUSIONAUTH_APP_MEMORY" "FUSIONAUTH_APP_RUNTIME_MODE" "FUSIONAUTH_APP_SILENT_MODE" "FUSIONAUTH_APP_KICKSTART_FILE" }} {{- range .Values.environment }} -{{- if eq .name "FUSIONAUTH_APP_SILENT_MODE" }} -{{- $silentModeConfigured = true }} +{{- if has .name $reservedEnv }} +{{- fail (printf "environment cannot override reserved chart-managed variable %s" .name) }} {{- end }} {{- end }} +{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" }} +{{- $kickstartVolumeName := printf "%s-config-volume" (include "fusionauth.fullname" .) }} +{{- if .Values.kickstart.enabled }} +{{- range .Values.extraVolumes }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumes cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- end }} +{{- range .Values.extraVolumeMounts }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumeMounts cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- if eq .mountPath "/kickstart" }} +{{- fail "extraVolumeMounts cannot use reserved kickstart mountPath /kickstart when kickstart.enabled is true" }} +{{- end }} +{{- end }} +{{- end }} +{{- range .Values.extraInitContainers }} +{{- if has .name (list "wait-for-db" "wait-for-search") }} +{{- fail (printf "extraInitContainers cannot use reserved init container name %s" .name) }} +{{- end }} +{{- end }} +{{- range .Values.extraContainers }} +{{- if eq .name $.Chart.Name }} +{{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} +{{- end }} +{{- end }} +{{- $waitForDatabase := eq (include "fusionauth.initContainers.waitForDatabase" .) "true" }} +{{- $waitForSearch := eq (include "fusionauth.initContainers.waitForSearch" .) "true" }} +{{- $deploymentAnnotations := include "fusionauth.deploymentAnnotations" . }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "fusionauth.fullname" . }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - {{- with .Values.annotations }} + {{- include "fusionauth.labels" . | nindent 4 }} + {{- with $deploymentAnnotations }} annotations: - {{- toYaml . | nindent 4 }} + {{- . | nindent 4 }} {{- end }} spec: {{- if not .Values.autoscaling.enabled }} @@ -29,15 +56,13 @@ spec: {{- end }} selector: matchLabels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "fusionauth.selectorLabels" . | nindent 6 }} template: metadata: labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "fusionauth.selectorLabels" . | nindent 8 }} {{- with .Values.podLabels }} - {{- toYaml . | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.podAnnotations }} annotations: @@ -49,10 +74,10 @@ spec: imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }} {{- end }} - {{- if or (.Values.extraInitContainers) (or (.Values.initContainers.waitForDb) (and (eq .Values.search.engine "elasticsearch") (.Values.initContainers.waitForEs))) }} + {{- if or (.Values.extraInitContainers) (or $waitForDatabase (and (eq .Values.search.engine "elasticsearch") $waitForSearch)) }} initContainers: {{- end }} - {{- if .Values.initContainers.waitForDb }} + {{- if $waitForDatabase }} - name: wait-for-db image: "{{ .Values.initContainers.image.repository }}:{{ .Values.initContainers.image.tag }}" args: @@ -67,7 +92,7 @@ spec: resources: {{- toYaml .Values.initContainers.resources | nindent 12 }} {{- end }} - {{- if and (eq .Values.search.engine "elasticsearch") (.Values.initContainers.waitForEs) }} + {{- if and (eq .Values.search.engine "elasticsearch") $waitForSearch }} - name: wait-for-search image: "{{ .Values.initContainers.image.repository }}:{{ .Values.initContainers.image.tag }}" args: @@ -110,7 +135,7 @@ spec: valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} - key: {{ .Values.database.existingSecret.passwordKey | default "password" | quote }} + key: {{ include "fusionauth.database.passwordKey" . | quote }} {{- if .Values.database.root.user }} - name: DATABASE_ROOT_USERNAME value: {{ .Values.database.root.user | quote }} @@ -118,24 +143,24 @@ spec: valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} - key: {{ .Values.database.existingSecret.rootPasswordKey | default "rootpassword" | quote }} + key: {{ include "fusionauth.database.rootPasswordKey" . | quote }} {{- end }} - name: DATABASE_URL value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" - name: SEARCH_TYPE value: {{ .Values.search.engine | quote }} {{- if eq .Values.search.engine "elasticsearch" }} - {{- if .Values.search.existingSecret.enabled }} + {{- if $searchExistingSecretEnabled }} - name: SEARCH_USERNAME valueFrom: secretKeyRef: - name: {{ required "search.existingSecret.name is required when search.existingSecret.enabled is true" .Values.search.existingSecret.name | quote }} - key: {{ .Values.search.existingSecret.userKey | default "username" | quote }} + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} - name: SEARCH_PASSWORD valueFrom: secretKeyRef: - name: {{ required "search.existingSecret.name is required when search.existingSecret.enabled is true" .Values.search.existingSecret.name | quote }} - key: {{ .Values.search.existingSecret.passwordKey | default "password" | quote }} + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} {{- end }} - name: SEARCH_SERVERS value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" @@ -144,13 +169,11 @@ spec: value: {{ .Values.app.memory | quote }} - name: FUSIONAUTH_APP_RUNTIME_MODE value: {{ .Values.app.runtimeMode | quote }} - {{- if not $silentModeConfigured }} - name: FUSIONAUTH_APP_SILENT_MODE value: {{ .Values.app.silentMode | quote }} - {{- end }} {{- if .Values.kickstart.enabled }} - name: FUSIONAUTH_APP_KICKSTART_FILE - value: "/kickstart/kickstart.json" + value: {{ .Values.kickstart.file | quote }} {{- end }} {{- if .Values.environment }} {{- toYaml .Values.environment |nindent 12 }} @@ -167,7 +190,7 @@ spec: {{- end }} {{- if .Values.kickstart.enabled }} - - name: {{ template "fusionauth.fullname" . }}-config-volume + - name: {{ $kickstartVolumeName }} mountPath: /kickstart {{- end }} @@ -196,7 +219,7 @@ spec: {{- end }} {{- if .Values.kickstart.enabled }} - - name: {{ template "fusionauth.fullname" . }}-config-volume + - name: {{ $kickstartVolumeName }} configMap: name: {{ template "fusionauth.fullname" . }}-kickstart-config {{- end }} diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml index 28db277..7b0d90f 100644 --- a/chart/templates/hpa.yaml +++ b/chart/templates/hpa.yaml @@ -10,10 +10,7 @@ kind: HorizontalPodAutoscaler metadata: name: {{ include "fusionauth.fullname" . }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index a64db93..96fa71b 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -13,10 +13,7 @@ kind: Ingress metadata: name: {{ $fullName }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} diff --git a/chart/templates/poddisruptionbudget.yaml b/chart/templates/poddisruptionbudget.yaml index c92c759..36773d7 100644 --- a/chart/templates/poddisruptionbudget.yaml +++ b/chart/templates/poddisruptionbudget.yaml @@ -6,6 +6,8 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: {{ include "fusionauth.fullname" . }} + labels: + {{- include "fusionauth.labels" . | nindent 4 }} spec: {{- if ne .Values.podDisruptionBudget.minAvailable nil }} {{- $minAvailable := .Values.podDisruptionBudget.minAvailable }} @@ -18,6 +20,5 @@ spec: {{- end }} selector: matchLabels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "fusionauth.selectorLabels" . | nindent 6 }} {{- end }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 70c8258..c8deaa8 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if not .Values.database.existingSecret.enabled -}} +{{- if not (eq (include "fusionauth.database.existingSecret.enabled" .) "true") -}} {{- if and .Values.database.root.user (not .Values.database.root.password) -}} {{- fail "database.root.password is required when database.root.user is set and database.existingSecret.enabled is false" }} {{- end -}} @@ -11,10 +11,7 @@ data: kind: Secret metadata: labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} name: {{ include "fusionauth.database.secretName" . }} type: Opaque {{- end -}} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml index 850e93f..e2f2c2c 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -3,10 +3,7 @@ kind: Service metadata: name: {{ include "fusionauth.fullname" . }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} {{- if .Values.service.annotations }} annotations: {{ .Values.service.annotations | toYaml | indent 4 }} @@ -19,8 +16,4 @@ spec: protocol: TCP name: http selector: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - {{- if .Values.service.spec }} -{{ .Values.service.spec | toYaml | indent 2 }} - {{- end }} + {{- include "fusionauth.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml index 7b1a173..4d6ac79 100644 --- a/chart/templates/serviceaccount.yaml +++ b/chart/templates/serviceaccount.yaml @@ -4,10 +4,7 @@ kind: ServiceAccount metadata: name: {{ include "fusionauth.serviceAccountName" . }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} diff --git a/chart/templates/servicemonitor.yaml b/chart/templates/servicemonitor.yaml index 00ab839..9db06b7 100644 --- a/chart/templates/servicemonitor.yaml +++ b/chart/templates/servicemonitor.yaml @@ -8,10 +8,7 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} {{- with .Values.serviceMonitor.labels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -22,8 +19,7 @@ spec: {{- end }} selector: matchLabels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "fusionauth.selectorLabels" . | nindent 6 }} endpoints: - port: http {{- with .Values.serviceMonitor.path }} diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml index 67b02c7..5b1d515 100644 --- a/chart/templates/tests/test-connection.yaml +++ b/chart/templates/tests/test-connection.yaml @@ -3,10 +3,7 @@ kind: Pod metadata: name: "{{ include "fusionauth.fullname" . }}-test-connection" labels: - app.kubernetes.io/name: {{ include "fusionauth.name" . }} - helm.sh/chart: {{ include "fusionauth.chart" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- include "fusionauth.labels" . | nindent 4 }} annotations: "helm.sh/hook": test-success spec: diff --git a/chart/values.schema.json b/chart/values.schema.json index 5b9acf2..754235b 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -8,6 +8,9 @@ "annotations": { "type": "object" }, + "deploymentAnnotations": { + "type": "object" + }, "app": { "type": "object", "properties": { @@ -60,21 +63,28 @@ "type": "object", "properties": { "existingSecret": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "passwordKey": { + "oneOf": [ + { "type": "string" }, - "rootPasswordKey": { - "type": "string" + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "rootPasswordKey": { + "type": "string" + } + } } - } + ] }, "host": { "type": "string" @@ -152,16 +162,41 @@ } }, "extraContainers": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ] + } }, "extraInitContainers": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ] + } }, "extraVolumeMounts": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "mountPath" + ] + } }, "extraVolumes": { - "type": "array" + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ] + } }, "fullnameOverride": { "type": "string" @@ -271,11 +306,17 @@ "resources": { "type": "object" }, + "waitForDatabase": { + "type": "boolean" + }, "waitForDb": { "type": "boolean" }, "waitForEs": { "type": "boolean" + }, + "waitForSearch": { + "type": "boolean" } } }, @@ -290,6 +331,10 @@ }, "enabled": { "type": "boolean" + }, + "file": { + "type": "string", + "pattern": "^/" } } }, @@ -468,34 +513,76 @@ }, "search": { "type": "object", + "additionalProperties": false, "properties": { - "engine": { - "type": "string", - "enum": [ - "elasticsearch", - "database" - ] - }, - "existingSecret": { + "basicAuth": { "type": "object", + "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, - "name": { - "type": "string" + "existingSecret": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "userKey": { + "type": "string" + } + } }, - "passwordKey": { + "password": { "type": "string" }, - "userKey": { + "username": { "type": "string" } } }, + "engine": { + "type": "string", + "enum": [ + "elasticsearch", + "database" + ] + }, "host": { "type": "string" }, + "existingSecret": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "userKey": { + "type": "string" + } + } + } + ] + }, "password": { "type": "string" }, @@ -518,6 +605,7 @@ }, "service": { "type": "object", + "additionalProperties": false, "properties": { "annotations": { "type": "object" @@ -527,9 +615,6 @@ "minimum": 1, "maximum": 65535 }, - "spec": { - "type": "object" - }, "type": { "type": "string", "enum": [ diff --git a/chart/values.yaml b/chart/values.yaml index fc1e8ae..2ee8ab4 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -26,8 +26,10 @@ fullnameOverride: "" extraInitContainers: [] initContainers: - waitForDb: true - waitForEs: true + # initContainers.waitForDatabase -- Create an init container that waits for the database to be ready. + # waitForDatabase: true + # initContainers.waitForSearch -- Create an init container that waits for search to be ready. + # waitForSearch: true # This image should contain `nc`, `wget` and a shell of some kind to do a simple loop. image: # initContainers.image.repository -- Docker image to use for initContainers @@ -59,15 +61,13 @@ service: port: 9011 # service.annotations -- Extra annotations to add to service object annotations: {} - # service.spec -- Any extra fields to add to the service object spec - spec: {} database: # database.protocol -- Should either be postgresql or mysql. Protocol for jdbc connection to database protocol: postgresql # database.host -- Hostname or ip of the database instance host: "" - # database.host -- Port of the database instance + # database.port -- Port of the database instance port: 5432 # database.tls -- Configures whether or not to use tls when connecting to the database tls: false @@ -100,26 +100,30 @@ database: search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. engine: elasticsearch - # search.engine -- Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch + # search.protocol -- Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch protocol: http # search.host -- Hostname or ip to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch host: "" # search.port -- Port to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch port: 9200 - # search.existingSecret -- Configures an existing secret that contains elasticsearch basic auth credentials. Ignored when search.engine is NOT elasticsearch - existingSecret: - # search.existingSecret.enabled -- Use an existing secret for elasticsearch basic auth credentials. + # search.basicAuth -- Configures elasticsearch basic auth credentials. Ignored when search.engine is NOT elasticsearch. + basicAuth: + # search.basicAuth.enabled -- Enables elasticsearch basic auth using inline username/password. Not required when search.basicAuth.existingSecret.enabled is true. enabled: false - # search.existingSecret.name -- The name of an existing secret that contains elasticsearch basic auth credentials. - name: "" - # search.existingSecret.userKey -- The key in search.existingSecret.name that contains the elasticsearch username. - userKey: username - # search.existingSecret.passwordKey -- The key in search.existingSecret.name that contains the elasticsearch password. - passwordKey: password - # search.user -- Username to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch - # user: "" - # search.password -- Password to use with basic auth when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch - # password: "" + # search.basicAuth.username -- Username to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. + username: "" + # search.basicAuth.password -- Password to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. + password: "" + # search.basicAuth.existingSecret -- Configures an existing secret that contains elasticsearch basic auth credentials. + existingSecret: + # search.basicAuth.existingSecret.enabled -- Use an existing secret for elasticsearch basic auth credentials. + enabled: false + # search.basicAuth.existingSecret.name -- The name of an existing secret that contains elasticsearch basic auth credentials. + name: "" + # search.basicAuth.existingSecret.userKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch username. + userKey: username + # search.basicAuth.existingSecret.passwordKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. + passwordKey: password app: # app.memory -- Configures the amount of memory Java can use @@ -147,6 +151,8 @@ environment: [] kickstart: enabled: false + # kickstart.file -- File path FusionAuth should use for kickstart configuration. + file: /kickstart/kickstart.json data: {} # kickstart.json: | # { @@ -268,7 +274,7 @@ nodeSelector: {} # tolerations -- Define tolerations for kubernetes to use when scheduling fusionauth pods. tolerations: [] -# affinty -- Define affinity for kubernetes to use when scheduling fusionauth pods. +# affinity -- Define affinity for kubernetes to use when scheduling fusionauth pods. affinity: {} # topologySpreadConstraints -- Define topologySpreadConstraints for kubernetes to use @@ -280,8 +286,8 @@ dnsConfig: {} # dnsPolicy -- Define dnsPolicy for fusionauth pods. dnsPolicy: ClusterFirst -# annotations -- Define annotations for fusionauth deployment. -annotations: {} +# deploymentAnnotations -- Define annotations for fusionauth deployment. +deploymentAnnotations: {} # podAnnotations -- Define annotations for fusionauth pods. podAnnotations: {} @@ -318,7 +324,7 @@ extraVolumes: [] # persistentVolumeClaim: # claimName: custom-css-data -# extraVolumes -- Associate mountPath for each extraVolumes +# extraVolumeMounts -- Associate mountPath for each extraVolumes extraVolumeMounts: [] # - name: custom-css-data # mountPath: /usr/local/fusionauth/fusionauth-app/web/custom @@ -328,7 +334,7 @@ serviceAccount: create: false # serviceAccount.automount - Automatically mount a ServiceAccount's API credentials? automount: true - # serviceAccount.annotation - Annotations to add to the service account + # serviceAccount.annotations - Annotations to add to the service account annotations: {} # serviceAccount.name - The name of the service account to use. # If not set and create is true, a name is generated using the fullname template From 2f3da8b4157acb0177be17d4ac2672b436c1fa83 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 14:32:52 -0600 Subject: [PATCH 12/28] kickstart guardrails, fix indents --- chart/templates/configmap.yaml | 5 +++-- chart/templates/deployment.yaml | 14 +++++++------- chart/templates/service.yaml | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml index 56a698a..8e5c2b3 100644 --- a/chart/templates/configmap.yaml +++ b/chart/templates/configmap.yaml @@ -1,4 +1,7 @@ {{- if .Values.kickstart.enabled }} +{{- if not .Values.kickstart.data }} +{{- fail "kickstart.data must contain at least one file when kickstart.enabled is true" }} +{{- end }} apiVersion: v1 kind: ConfigMap metadata: @@ -6,10 +9,8 @@ metadata: labels: {{- include "fusionauth.labels" . | nindent 4 }} data: - {{- if .Values.kickstart.data }} {{- range $key, $value := .Values.kickstart.data }} {{ $key }}: | {{ $value | indent 4 }} {{- end -}} {{- end -}} -{{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 21d6b70..3417eb1 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -226,18 +226,18 @@ spec: {{- with .Values.nodeSelector }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} + {{- end }} + {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.topologySpreadConstraints }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} topologySpreadConstraints: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} restartPolicy: Always diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml index e2f2c2c..ca9c792 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -4,9 +4,9 @@ metadata: name: {{ include "fusionauth.fullname" . }} labels: {{- include "fusionauth.labels" . | nindent 4 }} - {{- if .Values.service.annotations }} + {{- with .Values.service.annotations }} annotations: -{{ .Values.service.annotations | toYaml | indent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} spec: type: {{ .Values.service.type }} From 2591b364b874828c7badef1137e8682d061f5d25 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 14:34:48 -0600 Subject: [PATCH 13/28] schema updates for env --- chart/values.schema.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/chart/values.schema.json b/chart/values.schema.json index 754235b..9f230e4 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -158,6 +158,28 @@ }, "required": [ "name" + ], + "oneOf": [ + { + "required": [ + "value" + ], + "not": { + "required": [ + "valueFrom" + ] + } + }, + { + "required": [ + "valueFrom" + ], + "not": { + "required": [ + "value" + ] + } + } ] } }, From 26027447f8ec8893e0fa5ee486f686cf7afe3736 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 16:02:01 -0600 Subject: [PATCH 14/28] set env vars take precedence over chart values --- chart/README.md | 75 ++++++++++++++++++++++++++++++--- chart/templates/_helpers.tpl | 52 +++++++++++++++++++++-- chart/templates/deployment.yaml | 63 ++++++++++++++++++++------- chart/templates/secret.yaml | 12 ++++-- 4 files changed, 174 insertions(+), 28 deletions(-) diff --git a/chart/README.md b/chart/README.md index 6fbfb49..82a656d 100644 --- a/chart/README.md +++ b/chart/README.md @@ -4,21 +4,84 @@ [FusionAuth](https://fusionauth.io/) is a modern platform for Customer Identity and Access Management (CIAM). FusionAuth provides APIs and a responsive web user interface to support login, registration, localized email, multi-factor authentication, reporting, and much more. -## Important Upgrade Info +## Breaking Changes + +### 1.67.0 + +- **The minimum supported Kubernetes version is now 1.23.0.** This is due + to removing support for long-deprecated beta APIs for `HorizontalPodAutoscaler`, + `Ingress`, and `PodDisruptionBudget`. + +- **`service.spec` has been removed from the chart** to eliminate the risk of + overwriting valid service configurations. + +- **`service.type` no longer supports `ExternalName`.** `ExternalName` support + is not relevant for this chart. + +#### Recommended value migrations + +The following values have been updated, but compatibility shims are in +place in `_helpers.tpl`. It is recommended to migrate to the new values, +as the compatibility shims may be removed in a future release. + +| Previous value | New value | +| -------------------------------------- | --------------------------------------------------- | +| `annotations` | `deploymentAnnotations` | +| `initContainers.waitForDb` | `initContainers.waitForDatabase` | +| `initContainers.waitForEs` | `initContainers.waitForSearch` | +| `database.existingSecret: secret-name` | `database.existingSecret.name: secret-name` | +| `search.user` | `search.basicAuth.username` | +| `search.password` | `search.basicAuth.password` | +| `search.existingSecret: secret-name` | `search.basicAuth.existingSecret.name: secret-name` | +| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | +| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | +| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | +| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | + +When migrating `database.existingSecret`, set `database.existingSecret.enabled: true`. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. + +Prefer the following structure for database credentials: + +```yaml +database: + existingSecret: + enabled: true + name: secret-name + passwordKey: password + rootPasswordKey: rootpassword +``` + +Prefer the following structure for search basic auth credentials: + +```yaml +search: + basicAuth: + existingSecret: + enabled: true + name: secret-name + userKey: username + passwordKey: password +``` -- **In `1.67.0` and later, the minimum supported Kubernetes version is 1.23.0.** +### 1.57.1 -- **In `1.57.1` and later, the chart version now matches the FusionAuth app version.** +- **The chart version now matches the FusionAuth app version.** ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. -- **In `1.0.0` and later, the FusionAuth app version will now default to the latest available at the time of the chart's release.** Release notes will indicate the FusionAuth version included in the chart. +### 1.0.0 + +- **The FusionAuth app version will now default to the latest available at the time of the chart's release.** Release notes will indicate the FusionAuth version included in the chart. ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. -- **In `0.8.0`, the `environment` value is now an array instead of an object.** Make sure to reformat your values when you update. +### 0.8.0 + +- **The `environment` value is now an array instead of an object.** Make sure to reformat your values when you update. + +### 0.4.0 -- **In `0.4.0`, the external postgresql and elasticsearch charts were dropped.** You will need to maintain those dependencies on your own. +- **The external postgresql and elasticsearch charts were dropped.** You will need to maintain those dependencies on your own. ## Installing the Chart diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 112e195..9827d4d 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -33,6 +33,22 @@ Configure TLS if enabled {{- end -}} {{- end -}} +{{/* +Return true when .Values.environment contains an entry with the given name. +Used so user-provided environment variables can take precedence over chart +values without rendering duplicate env names. +*/}} +{{- define "fusionauth.environment.has" -}} +{{- $name := .name -}} +{{- $found := false -}} +{{- range .context.Values.environment -}} +{{- if eq .name $name -}} +{{- $found = true -}} +{{- end -}} +{{- end -}} +{{- if $found -}}true{{- else -}}false{{- end -}} +{{- end -}} + {{/* Resolve deployment annotations. Current value: deploymentAnnotations. @@ -61,6 +77,25 @@ name. A legacy string value is treated as enabled=true with that secret name. {{- end -}} {{- end -}} +{{/* +Resolve whether the chart should create its database credentials Secret. +If DATABASE_PASSWORD or DATABASE_ROOT_PASSWORD are supplied through +.Values.environment, those env vars take precedence and their corresponding +generated Secret keys are not needed. +*/}} +{{- define "fusionauth.database.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.existingSecret.enabled" .) "true" -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- $rootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv -}} +{{- if and (not $existingSecret) (or (not $databasePasswordEnv) (and $rootUserConfigured (not $databaseRootPasswordEnv))) -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + {{/* Resolve the database secret name. Current value: database.existingSecret.name. @@ -116,7 +151,7 @@ search.existingSecret still imply that basic auth is enabled. {{- define "fusionauth.search.basicAuth.enabled" -}} {{- if .Values.search.basicAuth.enabled -}} true -{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") -}} +{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} true {{- else -}} false @@ -204,11 +239,22 @@ render the same URL prefix. {{- if eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{- else if eq (include "fusionauth.search.basicAuth.enabled" .) "true" -}} +{{- $username := "" -}} +{{- $password := "" -}} {{- if .Values.search.basicAuth.enabled -}} -{{- printf "%s:%s@" .Values.search.basicAuth.username .Values.search.basicAuth.password -}} +{{- $username = .Values.search.basicAuth.username -}} +{{- $password = .Values.search.basicAuth.password -}} {{- else -}} -{{- printf "%s:%s@" .Values.search.user .Values.search.password -}} +{{- $username = .Values.search.user -}} +{{- $password = .Values.search.password -}} +{{- end -}} +{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} +{{- $username = "$(SEARCH_USERNAME)" -}} +{{- end -}} +{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} +{{- $password = "$(SEARCH_PASSWORD)" -}} {{- end -}} +{{- printf "%s:%s@" $username $password -}} {{- else -}} {{- printf "" -}} {{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 3417eb1..c2bf522 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -4,13 +4,22 @@ {{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} {{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} {{- end }} -{{- $reservedEnv := list "DATABASE_USERNAME" "DATABASE_PASSWORD" "DATABASE_ROOT_USERNAME" "DATABASE_ROOT_PASSWORD" "DATABASE_URL" "SEARCH_TYPE" "SEARCH_USERNAME" "SEARCH_PASSWORD" "SEARCH_SERVERS" "FUSIONAUTH_APP_MEMORY" "FUSIONAUTH_APP_RUNTIME_MODE" "FUSIONAUTH_APP_SILENT_MODE" "FUSIONAUTH_APP_KICKSTART_FILE" }} -{{- range .Values.environment }} -{{- if has .name $reservedEnv }} -{{- fail (printf "environment cannot override reserved chart-managed variable %s" .name) }} -{{- end }} -{{- end }} +{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" }} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" }} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" }} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" }} +{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" }} +{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" }} +{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" }} +{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" }} +{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" }} +{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" }} +{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" }} +{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" }} +{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" }} {{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" }} +{{- $chartSearchEnabled := and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") }} +{{- $databaseRootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv }} {{- $kickstartVolumeName := printf "%s-config-volume" (include "fusionauth.fullname" .) }} {{- if .Values.kickstart.enabled }} {{- range .Values.extraVolumes }} @@ -37,8 +46,8 @@ {{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} {{- end }} {{- end }} -{{- $waitForDatabase := eq (include "fusionauth.initContainers.waitForDatabase" .) "true" }} -{{- $waitForSearch := eq (include "fusionauth.initContainers.waitForSearch" .) "true" }} +{{- $waitForDatabase := and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host }} +{{- $waitForSearch := and $chartSearchEnabled (eq (include "fusionauth.initContainers.waitForSearch" .) "true") .Values.search.host }} {{- $deploymentAnnotations := include "fusionauth.deploymentAnnotations" . }} apiVersion: apps/v1 kind: Deployment @@ -74,7 +83,7 @@ spec: imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }} {{- end }} - {{- if or (.Values.extraInitContainers) (or $waitForDatabase (and (eq .Values.search.engine "elasticsearch") $waitForSearch)) }} + {{- if or (.Values.extraInitContainers) (or $waitForDatabase $waitForSearch) }} initContainers: {{- end }} {{- if $waitForDatabase }} @@ -129,55 +138,77 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} env: + {{- if .Values.environment }} + {{- toYaml .Values.environment | nindent 12 }} + {{- end }} + {{- if not $databaseUsernameEnv }} - name: DATABASE_USERNAME value: {{ required "A valid username for the database is required!" .Values.database.user | quote }} + {{- end }} + {{- if not $databasePasswordEnv }} - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} key: {{ include "fusionauth.database.passwordKey" . | quote }} - {{- if .Values.database.root.user }} + {{- end }} + {{- if $databaseRootUserConfigured }} + {{- if not $databaseRootUsernameEnv }} - name: DATABASE_ROOT_USERNAME value: {{ .Values.database.root.user | quote }} + {{- end }} + {{- if not $databaseRootPasswordEnv }} - name: DATABASE_ROOT_PASSWORD valueFrom: secretKeyRef: name: {{ include "fusionauth.database.secretName" . }} key: {{ include "fusionauth.database.rootPasswordKey" . | quote }} {{- end }} + {{- end }} + {{- if not $databaseUrlEnv }} - name: DATABASE_URL value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" + {{- end }} + {{- if not $searchTypeEnv }} - name: SEARCH_TYPE value: {{ .Values.search.engine | quote }} - {{- if eq .Values.search.engine "elasticsearch" }} - {{- if $searchExistingSecretEnabled }} + {{- end }} + {{- if or $chartSearchEnabled $searchServersEnv }} + {{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} - name: SEARCH_USERNAME valueFrom: secretKeyRef: name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} + {{- end }} + {{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} - name: SEARCH_PASSWORD valueFrom: secretKeyRef: name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} {{- end }} + {{- end }} + {{- if and $chartSearchEnabled (not $searchServersEnv) }} - name: SEARCH_SERVERS value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" {{- end }} + {{- if not $appMemoryEnv }} - name: FUSIONAUTH_APP_MEMORY value: {{ .Values.app.memory | quote }} + {{- end }} + {{- if not $appRuntimeModeEnv }} - name: FUSIONAUTH_APP_RUNTIME_MODE value: {{ .Values.app.runtimeMode | quote }} + {{- end }} + {{- if not $appSilentModeEnv }} - name: FUSIONAUTH_APP_SILENT_MODE value: {{ .Values.app.silentMode | quote }} - {{- if .Values.kickstart.enabled }} + {{- end }} + {{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} - name: FUSIONAUTH_APP_KICKSTART_FILE value: {{ .Values.kickstart.file | quote }} {{- end }} - {{- if .Values.environment }} - {{- toYaml .Values.environment |nindent 12 }} - {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.securityContext }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index c8deaa8..95a6f67 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,11 +1,17 @@ -{{- if not (eq (include "fusionauth.database.existingSecret.enabled" .) "true") -}} -{{- if and .Values.database.root.user (not .Values.database.root.password) -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- $databaseRootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv -}} +{{- if eq (include "fusionauth.database.generatedSecret.enabled" .) "true" -}} +{{- if and (not $databaseRootPasswordEnv) $databaseRootUserConfigured (not .Values.database.root.password) -}} {{- fail "database.root.password is required when database.root.user is set and database.existingSecret.enabled is false" }} {{- end -}} apiVersion: v1 data: +{{- if not $databasePasswordEnv }} password: {{ required "A password for your database is required!" .Values.database.password | b64enc }} -{{- if .Values.database.root.password }} +{{- end }} +{{- if and $databaseRootUserConfigured (not $databaseRootPasswordEnv) }} rootpassword: {{ .Values.database.root.password | b64enc }} {{- end }} kind: Secret From e6274544360a82487bd7eb9e5c0fa8ac0609752d Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 16:20:29 -0600 Subject: [PATCH 15/28] db credentials shape --- chart/README.md | 147 +++++++++----- chart/examples/minikube/values.yaml | 50 +++-- chart/templates/_helpers.tpl | 288 +++++++++++++++++++++++----- chart/templates/deployment.yaml | 30 ++- chart/templates/secret.yaml | 36 ++-- chart/values.schema.json | 56 ++++++ chart/values.yaml | 50 +++-- 7 files changed, 511 insertions(+), 146 deletions(-) diff --git a/chart/README.md b/chart/README.md index 82a656d..de46e3d 100644 --- a/chart/README.md +++ b/chart/README.md @@ -24,31 +24,46 @@ The following values have been updated, but compatibility shims are in place in `_helpers.tpl`. It is recommended to migrate to the new values, as the compatibility shims may be removed in a future release. -| Previous value | New value | -| -------------------------------------- | --------------------------------------------------- | -| `annotations` | `deploymentAnnotations` | -| `initContainers.waitForDb` | `initContainers.waitForDatabase` | -| `initContainers.waitForEs` | `initContainers.waitForSearch` | -| `database.existingSecret: secret-name` | `database.existingSecret.name: secret-name` | -| `search.user` | `search.basicAuth.username` | -| `search.password` | `search.basicAuth.password` | -| `search.existingSecret: secret-name` | `search.basicAuth.existingSecret.name: secret-name` | -| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | -| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | -| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | -| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | - -When migrating `database.existingSecret`, set `database.existingSecret.enabled: true`. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. +| Previous value | New value | +| -------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `annotations` | `deploymentAnnotations` | +| `initContainers.waitForDb` | `initContainers.waitForDatabase` | +| `initContainers.waitForEs` | `initContainers.waitForSearch` | +| `database.user` | `database.fusionauthUser.username` | +| `database.password` | `database.fusionauthUser.password` | +| `database.root.user` | `database.rootUser.username` | +| `database.root.password` | `database.rootUser.password` | +| `database.existingSecret: secret-name` | `database.fusionauthUser.existingSecret.name` and `database.rootUser.existingSecret.name` if needed | +| `database.existingSecret.enabled` | `database.fusionauthUser.existingSecret.enabled` and `database.rootUser.existingSecret.enabled` | +| `database.existingSecret.name` | `database.fusionauthUser.existingSecret.name` and `database.rootUser.existingSecret.name` if needed | +| `database.existingSecret.passwordKey` | `database.fusionauthUser.existingSecret.passwordKey` | +| `database.existingSecret.rootPasswordKey` | `database.rootUser.existingSecret.passwordKey` | +| `search.user` | `search.basicAuth.username` | +| `search.password` | `search.basicAuth.password` | +| `search.existingSecret: secret-name` | `search.basicAuth.existingSecret.name: secret-name` | +| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | +| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | +| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | +| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | + +When migrating existing database Secrets, set `database.fusionauthUser.existingSecret.enabled: true` and, if you use root bootstrap credentials, set `database.rootUser.existingSecret.enabled: true`. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. Prefer the following structure for database credentials: ```yaml database: - existingSecret: - enabled: true - name: secret-name - passwordKey: password - rootPasswordKey: rootpassword + fusionauthUser: + existingSecret: + enabled: true + name: database-user-secret + usernameKey: username + passwordKey: password + rootUser: + existingSecret: + enabled: true + name: database-root-secret + usernameKey: username + passwordKey: password ``` Prefer the following structure for search basic auth credentials: @@ -102,8 +117,8 @@ To install the chart with the release name `fusionauth`: helm repo add fusionauth https://fusionauth.github.io/charts helm install fusionauth fusionauth/fusionauth \ --set database.host=[database host] \ - --set database.user=[database username] \ - --set database.password=[database password] \ + --set database.fusionauthUser.username=[database username] \ + --set database.fusionauthUser.password=[database password] \ --set search.host=[elasticsearch host] ``` @@ -144,8 +159,8 @@ export FA_PSQL_PASS=$(kubectl get secret postgres-postgresql -o jsonpath="{.data helm repo add fusionauth https://fusionauth.github.io/charts helm install fusionauth fusionauth/fusionauth \ --set database.host=postgres-postgresql \ ---set database.user=fusionauth \ ---set database.password=$FA_PSQL_PASS \ +--set database.fusionauthUser.username=fusionauth \ +--set database.fusionauthUser.password=$FA_PSQL_PASS \ --set search.host=opensearch-cluster-master ``` @@ -229,12 +244,6 @@ You should now be able to connect to the FusionAuth application at http://localh - - - - - - @@ -247,12 +256,6 @@ You should now be able to connect to the FusionAuth application at http://localh - - - - - - @@ -266,17 +269,77 @@ You should now be able to connect to the FusionAuth application at http://localh - + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -289,12 +352,6 @@ You should now be able to connect to the FusionAuth application at http://localh - - - - - - diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index 14d6d90..ec7758a 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -63,26 +63,38 @@ database: # database.name -- Name of the fusionauth database name: fusionauth - # database.existingSecret -- Configures an existing secret that contains the database passwords. - existingSecret: - # database.existingSecret.enabled -- Use an existing secret for database passwords. - enabled: false - # database.existingSecret.name -- The name of an existing secret that contains the database passwords. - name: "" - # database.existingSecret.passwordKey -- The key in database.existingSecret.name that contains the database password. - passwordKey: password - # database.existingSecret.rootPasswordKey -- The key in database.existingSecret.name that contains the database root password. - rootPasswordKey: rootpassword - # database.user -- Database username for fusionauth to use in normal operation - user: "fusionauth" - # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret.enabled is true - password: "minikube-password" - # These credentials are used for bootstrapping the database - root: - # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database - user: "postgres" - # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret.enabled is true + # database.fusionauthUser -- Database credentials for fusionauth to use in normal operation + fusionauthUser: + # database.fusionauthUser.username -- Database username for fusionauth to use in normal operation + username: "fusionauth" + # database.fusionauthUser.password -- Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true + password: "minikube-password" + # database.fusionauthUser.existingSecret -- Configures an existing secret that contains the normal database user credentials. + existingSecret: + # database.fusionauthUser.existingSecret.enabled -- Use an existing secret for the normal database user credentials. + enabled: false + # database.fusionauthUser.existingSecret.name -- The name of an existing secret that contains the normal database user credentials. + name: "" + # database.fusionauthUser.existingSecret.usernameKey -- The key in database.fusionauthUser.existingSecret.name that contains the database username. + usernameKey: username + # database.fusionauthUser.existingSecret.passwordKey -- The key in database.fusionauthUser.existingSecret.name that contains the database password. + passwordKey: password + # database.rootUser -- Database credentials for fusionauth to use during initial bootstrap + rootUser: + # database.rootUser.username -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database + username: "postgres" + # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true password: "minikube-password" + # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user credentials. + existingSecret: + # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user credentials. + enabled: false + # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user credentials. + name: "" + # database.rootUser.existingSecret.usernameKey -- The key in database.rootUser.existingSecret.name that contains the root database username. + usernameKey: username + # database.rootUser.existingSecret.passwordKey -- The key in database.rootUser.existingSecret.name that contains the root database password. + passwordKey: password search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 9827d4d..fe853b8 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -64,50 +64,89 @@ When both are set, deploymentAnnotations wins. {{- end -}} {{/* -Resolve whether database credentials should come from an existing secret. -Current value: database.existingSecret.enabled. -Backward compatibility: database.existingSecret used to support a scalar secret -name. A legacy string value is treated as enabled=true with that secret name. -*/}} -{{- define "fusionauth.database.existingSecret.enabled" -}} -{{- if kindIs "string" .Values.database.existingSecret -}} -{{- if .Values.database.existingSecret -}}true{{- else -}}false{{- end -}} +Resolve FusionAuth database username. +Current value: database.fusionauthUser.username. +Backward compatibility: database.user is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.fusionauthUser.username" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- if $fusionauthUser.username -}} +{{- $fusionauthUser.username -}} {{- else -}} -{{- .Values.database.existingSecret.enabled | default false -}} +{{- $database.user | default "" -}} {{- end -}} {{- end -}} {{/* -Resolve whether the chart should create its database credentials Secret. -If DATABASE_PASSWORD or DATABASE_ROOT_PASSWORD are supplied through -.Values.environment, those env vars take precedence and their corresponding -generated Secret keys are not needed. +Resolve FusionAuth database password. +Current value: database.fusionauthUser.password. +Backward compatibility: database.password is deprecated but still accepted. */}} -{{- define "fusionauth.database.generatedSecret.enabled" -}} -{{- $existingSecret := eq (include "fusionauth.database.existingSecret.enabled" .) "true" -}} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- $rootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv -}} -{{- if and (not $existingSecret) (or (not $databasePasswordEnv) (and $rootUserConfigured (not $databaseRootPasswordEnv))) -}} +{{- define "fusionauth.database.fusionauthUser.password" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- if $fusionauthUser.password -}} +{{- $fusionauthUser.password -}} +{{- else -}} +{{- $database.password | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether FusionAuth database credentials should come from an existing +secret. +Current value: database.fusionauthUser.existingSecret.enabled. +Backward compatibility: database.existingSecret used to configure a shared +database secret and is still accepted for the FusionAuth user. +*/}} +{{- define "fusionauth.database.fusionauthUser.existingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if $fusionauthUserExistingSecret.enabled -}} true +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} +{{- else if $legacyExistingSecret -}} +{{- $legacyExistingSecret.enabled | default false -}} {{- else -}} false {{- end -}} {{- end -}} {{/* -Resolve the database secret name. -Current value: database.existingSecret.name. -Backward compatibility: database.existingSecret used to support a scalar secret -name. A legacy string value is used directly as the secret name. +Resolve whether the FusionAuth database username should come from an existing +secret. This only uses the current value. +Backward compatibility: legacy database.existingSecret was password-only, so it +does not imply that the username comes from a secret. */}} -{{- define "fusionauth.database.secretName" -}} -{{- if eq (include "fusionauth.database.existingSecret.enabled" .) "true" -}} -{{- if kindIs "string" .Values.database.existingSecret -}} -{{- required "database.existingSecret must not be empty when used as a secret name" .Values.database.existingSecret -}} +{{- define "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- if $fusionauthUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve the FusionAuth database Secret name. +Current value: database.fusionauthUser.existingSecret.name. +Backward compatibility: database.existingSecret used to configure a shared +database secret. A legacy string value is used directly as the secret name. +*/}} +{{- define "fusionauth.database.fusionauthUser.secretName" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} +{{- if $fusionauthUserExistingSecret.name -}} +{{- $fusionauthUserExistingSecret.name -}} +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} {{- else -}} -{{- required "database.existingSecret.name is required when database.existingSecret.enabled is true" .Values.database.existingSecret.name -}} +{{- required "database.fusionauthUser.existingSecret.name is required when database.fusionauthUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} {{- end -}} {{- else -}} {{ .Release.Name }}-credentials @@ -115,33 +154,196 @@ name. A legacy string value is used directly as the secret name. {{- end -}} {{/* -Resolve the database password key. -Current value: database.existingSecret.passwordKey. -Backward compatibility: legacy scalar database.existingSecret values have no -key fields, so they use the default password key. +Resolve the FusionAuth database username key. +Current value: database.fusionauthUser.existingSecret.usernameKey. +Backward compatibility: legacy database.existingSecret values did not support a +username key, so they use the default username key. +*/}} +{{- define "fusionauth.database.fusionauthUser.usernameKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $fusionauthUserExistingSecret.usernameKey | default "username" -}} +{{- end -}} + +{{/* +Resolve the FusionAuth database password key. +Current value: database.fusionauthUser.existingSecret.passwordKey. +Backward compatibility: database.existingSecret.passwordKey is deprecated but +still accepted. */}} -{{- define "fusionauth.database.passwordKey" -}} -{{- if kindIs "map" .Values.database.existingSecret -}} -{{- .Values.database.existingSecret.passwordKey | default "password" -}} +{{- define "fusionauth.database.fusionauthUser.passwordKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- $fusionauthUserExistingSecretConfigured := or $fusionauthUserExistingSecret.enabled $fusionauthUserExistingSecret.name -}} +{{- if and $fusionauthUserExistingSecretConfigured $fusionauthUserExistingSecret.passwordKey -}} +{{- $fusionauthUserExistingSecret.passwordKey -}} +{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.passwordKey -}} +{{- $legacyExistingSecret.passwordKey -}} {{- else -}} password {{- end -}} {{- end -}} {{/* -Resolve the database root password key. -Current value: database.existingSecret.rootPasswordKey. -Backward compatibility: legacy scalar database.existingSecret values have no -key fields, so they use the default root password key. +Resolve whether the root database username should come from an existing secret. +This only uses the current value. +Backward compatibility: legacy database.existingSecret was password-only, so it +does not imply that the username comes from a secret. +*/}} +{{- define "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- if $rootUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve whether the chart should create the FusionAuth database credentials +Secret. If DATABASE_PASSWORD is supplied through .Values.environment, that env +var takes precedence and the generated Secret is not needed. +*/}} +{{- define "fusionauth.database.fusionauthUser.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- if and (not $existingSecret) (not $databasePasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve root database username. +Current value: database.rootUser.username. +Backward compatibility: database.root.user is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.rootUser.username" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $legacyRootUser := $database.root | default dict -}} +{{- if $rootUser.username -}} +{{- $rootUser.username -}} +{{- else -}} +{{- $legacyRootUser.user | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve root database password. +Current value: database.rootUser.password. +Backward compatibility: database.root.password is deprecated but still accepted. */}} -{{- define "fusionauth.database.rootPasswordKey" -}} -{{- if kindIs "map" .Values.database.existingSecret -}} -{{- .Values.database.existingSecret.rootPasswordKey | default "rootpassword" -}} +{{- define "fusionauth.database.rootUser.password" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $legacyRootUser := $database.root | default dict -}} +{{- if $rootUser.password -}} +{{- $rootUser.password -}} {{- else -}} +{{- $legacyRootUser.password | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether root database credentials should come from an existing secret. +Current value: database.rootUser.existingSecret.enabled. +Backward compatibility: database.existingSecret used to configure a shared +database secret and is still accepted for the root user. +*/}} +{{- define "fusionauth.database.rootUser.existingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if $rootUserExistingSecret.enabled -}} +true +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} +{{- else if $legacyExistingSecret -}} +{{- $legacyExistingSecret.enabled | default false -}} +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve the root database Secret name. +Current value: database.rootUser.existingSecret.name. +Backward compatibility: database.existingSecret used to configure a shared +database secret. A legacy string value is used directly as the secret name. +*/}} +{{- define "fusionauth.database.rootUser.secretName" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} +{{- if $rootUserExistingSecret.name -}} +{{- $rootUserExistingSecret.name -}} +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} +{{- else -}} +{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} +{{- end -}} +{{- else -}} +{{ .Release.Name }}-root-credentials +{{- end -}} +{{- end -}} + +{{/* +Resolve the root database username key. +Current value: database.rootUser.existingSecret.usernameKey. +Backward compatibility: legacy database.existingSecret values did not support a +username key, so they use the default username key. +*/}} +{{- define "fusionauth.database.rootUser.usernameKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $rootUserExistingSecret.usernameKey | default "username" -}} +{{- end -}} + +{{/* +Resolve the root database password key. +Current value: database.rootUser.existingSecret.passwordKey. +Backward compatibility: database.existingSecret.rootPasswordKey is deprecated +but still accepted. +*/}} +{{- define "fusionauth.database.rootUser.passwordKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- $rootUserExistingSecretConfigured := or $rootUserExistingSecret.enabled $rootUserExistingSecret.name -}} +{{- if and $rootUserExistingSecretConfigured $rootUserExistingSecret.passwordKey -}} +{{- $rootUserExistingSecret.passwordKey -}} +{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.rootPasswordKey -}} +{{- $legacyExistingSecret.rootPasswordKey -}} +{{- else if and $legacyExistingSecret (kindIs "string" $legacyExistingSecret) -}} rootpassword +{{- else -}} +password {{- end -}} {{- end -}} +{{/* +Resolve whether root database credentials are configured. +*/}} +{{- define "fusionauth.database.rootUser.configured" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv (eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true") -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve whether the chart should create the root database credentials Secret. +If DATABASE_ROOT_PASSWORD is supplied through .Values.environment, that env var +takes precedence and the generated Secret is not needed. +*/}} +{{- define "fusionauth.database.rootUser.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- if and (eq (include "fusionauth.database.rootUser.configured" .) "true") (not $existingSecret) (not $databaseRootPasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- end -}} + {{/* Resolve whether search basic auth is enabled. Current value: search.basicAuth.enabled. diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index c2bf522..35d8214 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -17,9 +17,11 @@ {{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" }} {{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" }} {{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" }} +{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" }} +{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" }} {{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" }} {{- $chartSearchEnabled := and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") }} -{{- $databaseRootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv }} +{{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" }} {{- $kickstartVolumeName := printf "%s-config-volume" (include "fusionauth.fullname" .) }} {{- if .Values.kickstart.enabled }} {{- range .Values.extraVolumes }} @@ -143,26 +145,40 @@ spec: {{- end }} {{- if not $databaseUsernameEnv }} - name: DATABASE_USERNAME - value: {{ required "A valid username for the database is required!" .Values.database.user | quote }} + {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} + {{- else }} + value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} + {{- end }} {{- end }} {{- if not $databasePasswordEnv }} - name: DATABASE_PASSWORD valueFrom: secretKeyRef: - name: {{ include "fusionauth.database.secretName" . }} - key: {{ include "fusionauth.database.passwordKey" . | quote }} + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} {{- end }} {{- if $databaseRootUserConfigured }} {{- if not $databaseRootUsernameEnv }} - name: DATABASE_ROOT_USERNAME - value: {{ .Values.database.root.user | quote }} + {{- if $databaseRootUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} + {{- else }} + value: {{ include "fusionauth.database.rootUser.username" . | quote }} + {{- end }} {{- end }} {{- if not $databaseRootPasswordEnv }} - name: DATABASE_ROOT_PASSWORD valueFrom: secretKeyRef: - name: {{ include "fusionauth.database.secretName" . }} - key: {{ include "fusionauth.database.rootPasswordKey" . | quote }} + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} {{- end }} {{- end }} {{- if not $databaseUrlEnv }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 95a6f67..60fe66e 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,23 +1,33 @@ -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- $databaseRootUserConfigured := or .Values.database.root.user $databaseRootUsernameEnv -}} -{{- if eq (include "fusionauth.database.generatedSecret.enabled" .) "true" -}} -{{- if and (not $databaseRootPasswordEnv) $databaseRootUserConfigured (not .Values.database.root.password) -}} -{{- fail "database.root.password is required when database.root.user is set and database.existingSecret.enabled is false" }} +{{- $fusionauthUserGeneratedSecretEnabled := eq (include "fusionauth.database.fusionauthUser.generatedSecret.enabled" .) "true" -}} +{{- $rootUserGeneratedSecretEnabled := eq (include "fusionauth.database.rootUser.generatedSecret.enabled" .) "true" -}} +{{- if $fusionauthUserGeneratedSecretEnabled -}} +{{- if not (include "fusionauth.database.fusionauthUser.password" .) -}} +{{- fail "database.fusionauthUser.password is required when database.fusionauthUser.existingSecret.enabled is false" }} {{- end -}} apiVersion: v1 data: -{{- if not $databasePasswordEnv }} - password: {{ required "A password for your database is required!" .Values.database.password | b64enc }} -{{- end }} -{{- if and $databaseRootUserConfigured (not $databaseRootPasswordEnv) }} - rootpassword: {{ .Values.database.root.password | b64enc }} + password: {{ include "fusionauth.database.fusionauthUser.password" . | b64enc }} +kind: Secret +metadata: + labels: + {{- include "fusionauth.labels" . | nindent 4 }} + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} +type: Opaque {{- end }} +{{ if $rootUserGeneratedSecretEnabled }} +{{ if $fusionauthUserGeneratedSecretEnabled }} +--- +{{ end }} +{{- if not (include "fusionauth.database.rootUser.password" .) -}} +{{- fail "database.rootUser.password is required when database.rootUser.username is set and database.rootUser.existingSecret.enabled is false" }} +{{- end -}} +apiVersion: v1 +data: + password: {{ include "fusionauth.database.rootUser.password" . | b64enc }} kind: Secret metadata: labels: {{- include "fusionauth.labels" . | nindent 4 }} - name: {{ include "fusionauth.database.secretName" . }} + name: {{ include "fusionauth.database.rootUser.secretName" . }} type: Opaque {{- end -}} diff --git a/chart/values.schema.json b/chart/values.schema.json index 9f230e4..04b6277 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -62,6 +62,34 @@ "database": { "type": "object", "properties": { + "fusionauthUser": { + "type": "object", + "properties": { + "existingSecret": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "usernameKey": { + "type": "string" + } + } + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "existingSecret": { "oneOf": [ { @@ -118,6 +146,34 @@ } } }, + "rootUser": { + "type": "object", + "properties": { + "existingSecret": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "passwordKey": { + "type": "string" + }, + "usernameKey": { + "type": "string" + } + } + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "tls": { "type": "boolean" }, diff --git a/chart/values.yaml b/chart/values.yaml index 2ee8ab4..c1c8b94 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -76,26 +76,38 @@ database: # database.name -- Name of the fusionauth database name: fusionauth - # database.existingSecret -- Configures an existing secret that contains the database passwords. - existingSecret: - # database.existingSecret.enabled -- Use an existing secret for database passwords. - enabled: false - # database.existingSecret.name -- The name of an existing secret that contains the database passwords. - name: "" - # database.existingSecret.passwordKey -- The key in database.existingSecret.name that contains the database password. - passwordKey: password - # database.existingSecret.rootPasswordKey -- The key in database.existingSecret.name that contains the database root password. - rootPasswordKey: rootpassword - # database.user -- Database username for fusionauth to use in normal operation - user: "" - # database.password -- Database password for fusionauth to use in normal operation - not required if database.existingSecret.enabled is true - password: "" - # These credentials are used for bootstrapping the database - root: - # database.root.user -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database - user: "" - # database.root.password -- Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret.enabled is true + # database.fusionauthUser -- Database credentials for fusionauth to use in normal operation + fusionauthUser: + # database.fusionauthUser.username -- Database username for fusionauth to use in normal operation + username: "" + # database.fusionauthUser.password -- Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true + password: "" + # database.fusionauthUser.existingSecret -- Configures an existing secret that contains the normal database user credentials. + existingSecret: + # database.fusionauthUser.existingSecret.enabled -- Use an existing secret for the normal database user credentials. + enabled: false + # database.fusionauthUser.existingSecret.name -- The name of an existing secret that contains the normal database user credentials. + name: "" + # database.fusionauthUser.existingSecret.usernameKey -- The key in database.fusionauthUser.existingSecret.name that contains the database username. + usernameKey: username + # database.fusionauthUser.existingSecret.passwordKey -- The key in database.fusionauthUser.existingSecret.name that contains the database password. + passwordKey: password + # database.rootUser -- Database credentials for fusionauth to use during initial bootstrap + rootUser: + # database.rootUser.username -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database + username: "" + # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true password: "" + # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user credentials. + existingSecret: + # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user credentials. + enabled: false + # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user credentials. + name: "" + # database.rootUser.existingSecret.usernameKey -- The key in database.rootUser.existingSecret.name that contains the root database username. + usernameKey: username + # database.rootUser.existingSecret.passwordKey -- The key in database.rootUser.existingSecret.name that contains the root database password. + passwordKey: password search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. From 012bef82e4c9bd4f5984eb12a26ceccab4242d42 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Wed, 20 May 2026 16:26:16 -0600 Subject: [PATCH 16/28] move logic to helpers --- chart/templates/_helpers.tpl | 181 ++++++++++++++++++++++++++++++++ chart/templates/deployment.yaml | 144 ++----------------------- 2 files changed, 187 insertions(+), 138 deletions(-) diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index fe853b8..69778f8 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -33,6 +33,13 @@ Configure TLS if enabled {{- end -}} {{- end -}} +{{/* +Resolve the reserved kickstart config volume name. +*/}} +{{- define "fusionauth.kickstart.volumeName" -}} +{{- printf "%s-config-volume" (include "fusionauth.fullname" .) -}} +{{- end -}} + {{/* Return true when .Values.environment contains an entry with the given name. Used so user-provided environment variables can take precedence over chart @@ -462,6 +469,16 @@ $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{- end -}} {{- end -}} +{{/* +Resolve whether chart-managed Elasticsearch/OpenSearch env and wait behavior +should be rendered. SEARCH_TYPE supplied through .Values.environment takes +precedence over the chart search values. +*/}} +{{- define "fusionauth.search.chartEnabled" -}} +{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} +{{- if and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") -}}true{{- else -}}false{{- end -}} +{{- end -}} + {{/* Resolve the database wait init-container flag. Current value: initContainers.waitForDatabase. @@ -494,6 +511,170 @@ true {{- end -}} {{- end -}} +{{/* +Resolve whether the database wait init container should be rendered. +DATABASE_URL supplied through .Values.environment takes precedence over the +chart database values, so the chart does not wait on database.host in that mode. +*/}} +{{- define "fusionauth.deployment.waitForDatabase.enabled" -}} +{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} +{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve whether the search wait init container should be rendered. +*/}} +{{- define "fusionauth.deployment.waitForSearch.enabled" -}} +{{- if and (eq (include "fusionauth.search.chartEnabled" .) "true") (eq (include "fusionauth.initContainers.waitForSearch" .) "true") .Values.search.host -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Validate deployment-only conflicts before rendering the Deployment manifest. +*/}} +{{- define "fusionauth.deployment.validate" -}} +{{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} +{{- end }} +{{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} +{{- end }} +{{- $kickstartVolumeName := include "fusionauth.kickstart.volumeName" . }} +{{- if .Values.kickstart.enabled }} +{{- range .Values.extraVolumes }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumes cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- end }} +{{- range .Values.extraVolumeMounts }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumeMounts cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- if eq .mountPath "/kickstart" }} +{{- fail "extraVolumeMounts cannot use reserved kickstart mountPath /kickstart when kickstart.enabled is true" }} +{{- end }} +{{- end }} +{{- end }} +{{- range .Values.extraInitContainers }} +{{- if has .name (list "wait-for-db" "wait-for-search") }} +{{- fail (printf "extraInitContainers cannot use reserved init container name %s" .name) }} +{{- end }} +{{- end }} +{{- range .Values.extraContainers }} +{{- if eq .name $.Chart.Name }} +{{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +Render FusionAuth container environment variables. User-supplied entries in +.Values.environment are rendered first and take precedence over chart-managed +entries with the same name. +*/}} +{{- define "fusionauth.deployment.env" -}} +{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} +{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} +{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} +{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} +{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" -}} +{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" -}} +{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" -}} +{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} +{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} +{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" -}} +{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" -}} +{{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} +{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} +{{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} +{{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} +{{- if not $databaseUsernameEnv }} +- name: DATABASE_USERNAME + {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} + {{- else }} + value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} + {{- end }} +{{- end }} +{{- if not $databasePasswordEnv }} +- name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} +{{- end }} +{{- if $databaseRootUserConfigured }} +{{- if not $databaseRootUsernameEnv }} +- name: DATABASE_ROOT_USERNAME + {{- if $databaseRootUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} + {{- else }} + value: {{ include "fusionauth.database.rootUser.username" . | quote }} + {{- end }} +{{- end }} +{{- if not $databaseRootPasswordEnv }} +- name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} +{{- end }} +{{- end }} +{{- if not $databaseUrlEnv }} +- name: DATABASE_URL + value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" +{{- end }} +{{- if not $searchTypeEnv }} +- name: SEARCH_TYPE + value: {{ .Values.search.engine | quote }} +{{- end }} +{{- if or $chartSearchEnabled $searchServersEnv }} +{{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} +- name: SEARCH_USERNAME + valueFrom: + secretKeyRef: + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} +{{- end }} +{{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} +- name: SEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} +{{- end }} +{{- end }} +{{- if and $chartSearchEnabled (not $searchServersEnv) }} +- name: SEARCH_SERVERS + value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" +{{- end }} +{{- if not $appMemoryEnv }} +- name: FUSIONAUTH_APP_MEMORY + value: {{ .Values.app.memory | quote }} +{{- end }} +{{- if not $appRuntimeModeEnv }} +- name: FUSIONAUTH_APP_RUNTIME_MODE + value: {{ .Values.app.runtimeMode | quote }} +{{- end }} +{{- if not $appSilentModeEnv }} +- name: FUSIONAUTH_APP_SILENT_MODE + value: {{ .Values.app.silentMode | quote }} +{{- end }} +{{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} +- name: FUSIONAUTH_APP_KICKSTART_FILE + value: {{ .Values.kickstart.file | quote }} +{{- end }} +{{- end -}} + {{/* Create chart name and version as used by the chart label. */}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 35d8214..5917fec 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,55 +1,7 @@ -{{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} -{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} -{{- end }} -{{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} -{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} -{{- end }} -{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" }} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" }} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" }} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" }} -{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" }} -{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" }} -{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" }} -{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" }} -{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" }} -{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" }} -{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" }} -{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" }} -{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" }} -{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" }} -{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" }} -{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" }} -{{- $chartSearchEnabled := and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") }} -{{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" }} -{{- $kickstartVolumeName := printf "%s-config-volume" (include "fusionauth.fullname" .) }} -{{- if .Values.kickstart.enabled }} -{{- range .Values.extraVolumes }} -{{- if eq .name $kickstartVolumeName }} -{{- fail (printf "extraVolumes cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} -{{- end }} -{{- end }} -{{- range .Values.extraVolumeMounts }} -{{- if eq .name $kickstartVolumeName }} -{{- fail (printf "extraVolumeMounts cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} -{{- end }} -{{- if eq .mountPath "/kickstart" }} -{{- fail "extraVolumeMounts cannot use reserved kickstart mountPath /kickstart when kickstart.enabled is true" }} -{{- end }} -{{- end }} -{{- end }} -{{- range .Values.extraInitContainers }} -{{- if has .name (list "wait-for-db" "wait-for-search") }} -{{- fail (printf "extraInitContainers cannot use reserved init container name %s" .name) }} -{{- end }} -{{- end }} -{{- range .Values.extraContainers }} -{{- if eq .name $.Chart.Name }} -{{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} -{{- end }} -{{- end }} -{{- $waitForDatabase := and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host }} -{{- $waitForSearch := and $chartSearchEnabled (eq (include "fusionauth.initContainers.waitForSearch" .) "true") .Values.search.host }} +{{- include "fusionauth.deployment.validate" . }} +{{- $waitForDatabase := eq (include "fusionauth.deployment.waitForDatabase.enabled" .) "true" }} +{{- $waitForSearch := eq (include "fusionauth.deployment.waitForSearch.enabled" .) "true" }} +{{- $kickstartVolumeName := include "fusionauth.kickstart.volumeName" . }} {{- $deploymentAnnotations := include "fusionauth.deploymentAnnotations" . }} apiVersion: apps/v1 kind: Deployment @@ -103,7 +55,7 @@ spec: resources: {{- toYaml .Values.initContainers.resources | nindent 12 }} {{- end }} - {{- if and (eq .Values.search.engine "elasticsearch") $waitForSearch }} + {{- if $waitForSearch }} - name: wait-for-search image: "{{ .Values.initContainers.image.repository }}:{{ .Values.initContainers.image.tag }}" args: @@ -140,91 +92,7 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} env: - {{- if .Values.environment }} - {{- toYaml .Values.environment | nindent 12 }} - {{- end }} - {{- if not $databaseUsernameEnv }} - - name: DATABASE_USERNAME - {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} - {{- else }} - value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} - {{- end }} - {{- end }} - {{- if not $databasePasswordEnv }} - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} - {{- end }} - {{- if $databaseRootUserConfigured }} - {{- if not $databaseRootUsernameEnv }} - - name: DATABASE_ROOT_USERNAME - {{- if $databaseRootUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.rootUser.secretName" . }} - key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} - {{- else }} - value: {{ include "fusionauth.database.rootUser.username" . | quote }} - {{- end }} - {{- end }} - {{- if not $databaseRootPasswordEnv }} - - name: DATABASE_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.rootUser.secretName" . }} - key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} - {{- end }} - {{- end }} - {{- if not $databaseUrlEnv }} - - name: DATABASE_URL - value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" - {{- end }} - {{- if not $searchTypeEnv }} - - name: SEARCH_TYPE - value: {{ .Values.search.engine | quote }} - {{- end }} - {{- if or $chartSearchEnabled $searchServersEnv }} - {{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} - - name: SEARCH_USERNAME - valueFrom: - secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} - {{- end }} - {{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} - - name: SEARCH_PASSWORD - valueFrom: - secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} - {{- end }} - {{- end }} - {{- if and $chartSearchEnabled (not $searchServersEnv) }} - - name: SEARCH_SERVERS - value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" - {{- end }} - {{- if not $appMemoryEnv }} - - name: FUSIONAUTH_APP_MEMORY - value: {{ .Values.app.memory | quote }} - {{- end }} - {{- if not $appRuntimeModeEnv }} - - name: FUSIONAUTH_APP_RUNTIME_MODE - value: {{ .Values.app.runtimeMode | quote }} - {{- end }} - {{- if not $appSilentModeEnv }} - - name: FUSIONAUTH_APP_SILENT_MODE - value: {{ .Values.app.silentMode | quote }} - {{- end }} - {{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} - - name: FUSIONAUTH_APP_KICKSTART_FILE - value: {{ .Values.kickstart.file | quote }} - {{- end }} +{{ include "fusionauth.deployment.env" . | trim | indent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.securityContext }} From 4fd0487f89072bb6ba89ba785ede2f542d7f57e7 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Thu, 21 May 2026 09:27:53 -0600 Subject: [PATCH 17/28] split the templates --- chart/templates/_database.tpl | 290 ++++++++++++ chart/templates/_deployment.tpl | 185 ++++++++ chart/templates/_helpers.tpl | 672 --------------------------- chart/templates/_init_containers.tpl | 32 ++ chart/templates/_labels.tpl | 37 ++ chart/templates/_search.tpl | 128 +++++ 6 files changed, 672 insertions(+), 672 deletions(-) create mode 100644 chart/templates/_database.tpl create mode 100644 chart/templates/_deployment.tpl create mode 100644 chart/templates/_init_containers.tpl create mode 100644 chart/templates/_labels.tpl create mode 100644 chart/templates/_search.tpl diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl new file mode 100644 index 0000000..41c7790 --- /dev/null +++ b/chart/templates/_database.tpl @@ -0,0 +1,290 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Configure TLS if enabled +*/}} +{{- define "fusionauth.databaseTLS" -}} +{{- if .Values.database.tls -}} +?sslmode={{ .Values.database.tlsMode }} +{{- end -}} +{{- end -}} + +{{/* +Resolve FusionAuth database username. +Current value: database.fusionauthUser.username. +Backward compatibility: database.user is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.fusionauthUser.username" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- if $fusionauthUser.username -}} +{{- $fusionauthUser.username -}} +{{- else -}} +{{- $database.user | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve FusionAuth database password. +Current value: database.fusionauthUser.password. +Backward compatibility: database.password is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.fusionauthUser.password" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- if $fusionauthUser.password -}} +{{- $fusionauthUser.password -}} +{{- else -}} +{{- $database.password | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether FusionAuth database credentials should come from an existing +secret. +Current value: database.fusionauthUser.existingSecret.enabled. +Backward compatibility: database.existingSecret used to configure a shared +database secret and is still accepted for the FusionAuth user. +*/}} +{{- define "fusionauth.database.fusionauthUser.existingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if $fusionauthUserExistingSecret.enabled -}} +true +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} +{{- else if $legacyExistingSecret -}} +{{- $legacyExistingSecret.enabled | default false -}} +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve whether the FusionAuth database username should come from an existing +secret. This only uses the current value. +Backward compatibility: legacy database.existingSecret was password-only, so it +does not imply that the username comes from a secret. +*/}} +{{- define "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- if $fusionauthUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve the FusionAuth database Secret name. +Current value: database.fusionauthUser.existingSecret.name. +Backward compatibility: database.existingSecret used to configure a shared +database secret. A legacy string value is used directly as the secret name. +*/}} +{{- define "fusionauth.database.fusionauthUser.secretName" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} +{{- if $fusionauthUserExistingSecret.name -}} +{{- $fusionauthUserExistingSecret.name -}} +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} +{{- else -}} +{{- required "database.fusionauthUser.existingSecret.name is required when database.fusionauthUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} +{{- end -}} +{{- else -}} +{{ .Release.Name }}-credentials +{{- end -}} +{{- end -}} + +{{/* +Resolve the FusionAuth database username key. +Current value: database.fusionauthUser.existingSecret.usernameKey. +Backward compatibility: legacy database.existingSecret values did not support a +username key, so they use the default username key. +*/}} +{{- define "fusionauth.database.fusionauthUser.usernameKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $fusionauthUserExistingSecret.usernameKey | default "username" -}} +{{- end -}} + +{{/* +Resolve the FusionAuth database password key. +Current value: database.fusionauthUser.existingSecret.passwordKey. +Backward compatibility: database.existingSecret.passwordKey is deprecated but +still accepted. +*/}} +{{- define "fusionauth.database.fusionauthUser.passwordKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $fusionauthUser := $database.fusionauthUser | default dict -}} +{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- $fusionauthUserExistingSecretConfigured := or $fusionauthUserExistingSecret.enabled $fusionauthUserExistingSecret.name -}} +{{- if and $fusionauthUserExistingSecretConfigured $fusionauthUserExistingSecret.passwordKey -}} +{{- $fusionauthUserExistingSecret.passwordKey -}} +{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.passwordKey -}} +{{- $legacyExistingSecret.passwordKey -}} +{{- else -}} +password +{{- end -}} +{{- end -}} + +{{/* +Resolve whether the chart should create the FusionAuth database credentials +Secret. If DATABASE_PASSWORD is supplied through .Values.environment, that env +var takes precedence and the generated Secret is not needed. +*/}} +{{- define "fusionauth.database.fusionauthUser.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- if and (not $existingSecret) (not $databasePasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve root database username. +Current value: database.rootUser.username. +Backward compatibility: database.root.user is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.rootUser.username" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $legacyRootUser := $database.root | default dict -}} +{{- if $rootUser.username -}} +{{- $rootUser.username -}} +{{- else -}} +{{- $legacyRootUser.user | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve root database password. +Current value: database.rootUser.password. +Backward compatibility: database.root.password is deprecated but still accepted. +*/}} +{{- define "fusionauth.database.rootUser.password" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $legacyRootUser := $database.root | default dict -}} +{{- if $rootUser.password -}} +{{- $rootUser.password -}} +{{- else -}} +{{- $legacyRootUser.password | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether root database credentials should come from an existing secret. +Current value: database.rootUser.existingSecret.enabled. +Backward compatibility: database.existingSecret used to configure a shared +database secret and is still accepted for the root user. +*/}} +{{- define "fusionauth.database.rootUser.existingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if $rootUserExistingSecret.enabled -}} +true +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} +{{- else if $legacyExistingSecret -}} +{{- $legacyExistingSecret.enabled | default false -}} +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve whether the root database username should come from an existing secret. +This only uses the current value. +Backward compatibility: legacy database.existingSecret was password-only, so it +does not imply that the username comes from a secret. +*/}} +{{- define "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- if $rootUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve the root database Secret name. +Current value: database.rootUser.existingSecret.name. +Backward compatibility: database.existingSecret used to configure a shared +database secret. A legacy string value is used directly as the secret name. +*/}} +{{- define "fusionauth.database.rootUser.secretName" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- if eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} +{{- if $rootUserExistingSecret.name -}} +{{- $rootUserExistingSecret.name -}} +{{- else if kindIs "string" $legacyExistingSecret -}} +{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} +{{- else -}} +{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} +{{- end -}} +{{- else -}} +{{ .Release.Name }}-root-credentials +{{- end -}} +{{- end -}} + +{{/* +Resolve the root database username key. +Current value: database.rootUser.existingSecret.usernameKey. +Backward compatibility: legacy database.existingSecret values did not support a +username key, so they use the default username key. +*/}} +{{- define "fusionauth.database.rootUser.usernameKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $rootUserExistingSecret.usernameKey | default "username" -}} +{{- end -}} + +{{/* +Resolve the root database password key. +Current value: database.rootUser.existingSecret.passwordKey. +Backward compatibility: database.existingSecret.rootPasswordKey is deprecated +but still accepted. +*/}} +{{- define "fusionauth.database.rootUser.passwordKey" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} +{{- $legacyExistingSecret := $database.existingSecret -}} +{{- $rootUserExistingSecretConfigured := or $rootUserExistingSecret.enabled $rootUserExistingSecret.name -}} +{{- if and $rootUserExistingSecretConfigured $rootUserExistingSecret.passwordKey -}} +{{- $rootUserExistingSecret.passwordKey -}} +{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.rootPasswordKey -}} +{{- $legacyExistingSecret.rootPasswordKey -}} +{{- else if and $legacyExistingSecret (kindIs "string" $legacyExistingSecret) -}} +rootpassword +{{- else -}} +password +{{- end -}} +{{- end -}} + +{{/* +Resolve whether root database credentials are configured. +*/}} +{{- define "fusionauth.database.rootUser.configured" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv (eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true") -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve whether the chart should create the root database credentials Secret. +If DATABASE_ROOT_PASSWORD is supplied through .Values.environment, that env var +takes precedence and the generated Secret is not needed. +*/}} +{{- define "fusionauth.database.rootUser.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- if and (eq (include "fusionauth.database.rootUser.configured" .) "true") (not $existingSecret) (not $databaseRootPasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- end -}} diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl new file mode 100644 index 0000000..d49d187 --- /dev/null +++ b/chart/templates/_deployment.tpl @@ -0,0 +1,185 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Resolve the reserved kickstart config volume name. +*/}} +{{- define "fusionauth.kickstart.volumeName" -}} +{{- printf "%s-config-volume" (include "fusionauth.fullname" .) -}} +{{- end -}} + +{{/* +Resolve deployment annotations. +Current value: deploymentAnnotations. +Backward compatibility: top-level annotations is deprecated but still accepted. +When both are set, deploymentAnnotations wins. +*/}} +{{- define "fusionauth.deploymentAnnotations" -}} +{{- if .Values.deploymentAnnotations -}} +{{- toYaml .Values.deploymentAnnotations -}} +{{- else if and (hasKey .Values "annotations") .Values.annotations -}} +{{- toYaml .Values.annotations -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether the database wait init container should be rendered. +DATABASE_URL supplied through .Values.environment takes precedence over the +chart database values, so the chart does not wait on database.host in that mode. +*/}} +{{- define "fusionauth.deployment.waitForDatabase.enabled" -}} +{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} +{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Resolve whether the search wait init container should be rendered. +*/}} +{{- define "fusionauth.deployment.waitForSearch.enabled" -}} +{{- if and (eq (include "fusionauth.search.chartEnabled" .) "true") (eq (include "fusionauth.initContainers.waitForSearch" .) "true") .Values.search.host -}}true{{- else -}}false{{- end -}} +{{- end -}} + +{{/* +Validate deployment-only conflicts before rendering the Deployment manifest. +*/}} +{{- define "fusionauth.deployment.validate" -}} +{{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} +{{- end }} +{{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} +{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} +{{- end }} +{{- $kickstartVolumeName := include "fusionauth.kickstart.volumeName" . }} +{{- if .Values.kickstart.enabled }} +{{- range .Values.extraVolumes }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumes cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- end }} +{{- range .Values.extraVolumeMounts }} +{{- if eq .name $kickstartVolumeName }} +{{- fail (printf "extraVolumeMounts cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} +{{- end }} +{{- if eq .mountPath "/kickstart" }} +{{- fail "extraVolumeMounts cannot use reserved kickstart mountPath /kickstart when kickstart.enabled is true" }} +{{- end }} +{{- end }} +{{- end }} +{{- range .Values.extraInitContainers }} +{{- if has .name (list "wait-for-db" "wait-for-search") }} +{{- fail (printf "extraInitContainers cannot use reserved init container name %s" .name) }} +{{- end }} +{{- end }} +{{- range .Values.extraContainers }} +{{- if eq .name $.Chart.Name }} +{{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +Render FusionAuth container environment variables. User-supplied entries in +.Values.environment are rendered first and take precedence over chart-managed +entries with the same name. +*/}} +{{- define "fusionauth.deployment.env" -}} +{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" -}} +{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} +{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} +{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} +{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} +{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} +{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} +{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} +{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" -}} +{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" -}} +{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" -}} +{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} +{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} +{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" -}} +{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" -}} +{{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} +{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} +{{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} +{{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} +{{- if not $databaseUsernameEnv }} +- name: DATABASE_USERNAME + {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} + {{- else }} + value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} + {{- end }} +{{- end }} +{{- if not $databasePasswordEnv }} +- name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} +{{- end }} +{{- if $databaseRootUserConfigured }} +{{- if not $databaseRootUsernameEnv }} +- name: DATABASE_ROOT_USERNAME + {{- if $databaseRootUsernameFromExistingSecretEnabled }} + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} + {{- else }} + value: {{ include "fusionauth.database.rootUser.username" . | quote }} + {{- end }} +{{- end }} +{{- if not $databaseRootPasswordEnv }} +- name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "fusionauth.database.rootUser.secretName" . }} + key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} +{{- end }} +{{- end }} +{{- if not $databaseUrlEnv }} +- name: DATABASE_URL + value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" +{{- end }} +{{- if not $searchTypeEnv }} +- name: SEARCH_TYPE + value: {{ .Values.search.engine | quote }} +{{- end }} +{{- if or $chartSearchEnabled $searchServersEnv }} +{{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} +- name: SEARCH_USERNAME + valueFrom: + secretKeyRef: + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} +{{- end }} +{{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} +- name: SEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} + key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} +{{- end }} +{{- end }} +{{- if and $chartSearchEnabled (not $searchServersEnv) }} +- name: SEARCH_SERVERS + value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" +{{- end }} +{{- if not $appMemoryEnv }} +- name: FUSIONAUTH_APP_MEMORY + value: {{ .Values.app.memory | quote }} +{{- end }} +{{- if not $appRuntimeModeEnv }} +- name: FUSIONAUTH_APP_RUNTIME_MODE + value: {{ .Values.app.runtimeMode | quote }} +{{- end }} +{{- if not $appSilentModeEnv }} +- name: FUSIONAUTH_APP_SILENT_MODE + value: {{ .Values.app.silentMode | quote }} +{{- end }} +{{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} +- name: FUSIONAUTH_APP_KICKSTART_FILE + value: {{ .Values.kickstart.file | quote }} +{{- end }} +{{- end -}} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 69778f8..aaad475 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -24,22 +24,6 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{- end -}} -{{/* -Configure TLS if enabled -*/}} -{{- define "fusionauth.databaseTLS" -}} -{{- if .Values.database.tls -}} -?sslmode={{ .Values.database.tlsMode }} -{{- end -}} -{{- end -}} - -{{/* -Resolve the reserved kickstart config volume name. -*/}} -{{- define "fusionauth.kickstart.volumeName" -}} -{{- printf "%s-config-volume" (include "fusionauth.fullname" .) -}} -{{- end -}} - {{/* Return true when .Values.environment contains an entry with the given name. Used so user-provided environment variables can take precedence over chart @@ -55,659 +39,3 @@ values without rendering duplicate env names. {{- end -}} {{- if $found -}}true{{- else -}}false{{- end -}} {{- end -}} - -{{/* -Resolve deployment annotations. -Current value: deploymentAnnotations. -Backward compatibility: top-level annotations is deprecated but still accepted. -When both are set, deploymentAnnotations wins. -*/}} -{{- define "fusionauth.deploymentAnnotations" -}} -{{- if .Values.deploymentAnnotations -}} -{{- toYaml .Values.deploymentAnnotations -}} -{{- else if and (hasKey .Values "annotations") .Values.annotations -}} -{{- toYaml .Values.annotations -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve FusionAuth database username. -Current value: database.fusionauthUser.username. -Backward compatibility: database.user is deprecated but still accepted. -*/}} -{{- define "fusionauth.database.fusionauthUser.username" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- if $fusionauthUser.username -}} -{{- $fusionauthUser.username -}} -{{- else -}} -{{- $database.user | default "" -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve FusionAuth database password. -Current value: database.fusionauthUser.password. -Backward compatibility: database.password is deprecated but still accepted. -*/}} -{{- define "fusionauth.database.fusionauthUser.password" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- if $fusionauthUser.password -}} -{{- $fusionauthUser.password -}} -{{- else -}} -{{- $database.password | default "" -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve whether FusionAuth database credentials should come from an existing -secret. -Current value: database.fusionauthUser.existingSecret.enabled. -Backward compatibility: database.existingSecret used to configure a shared -database secret and is still accepted for the FusionAuth user. -*/}} -{{- define "fusionauth.database.fusionauthUser.existingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- if $fusionauthUserExistingSecret.enabled -}} -true -{{- else if kindIs "string" $legacyExistingSecret -}} -{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} -{{- else if $legacyExistingSecret -}} -{{- $legacyExistingSecret.enabled | default false -}} -{{- else -}} -false -{{- end -}} -{{- end -}} - -{{/* -Resolve whether the FusionAuth database username should come from an existing -secret. This only uses the current value. -Backward compatibility: legacy database.existingSecret was password-only, so it -does not imply that the username comes from a secret. -*/}} -{{- define "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- if $fusionauthUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve the FusionAuth database Secret name. -Current value: database.fusionauthUser.existingSecret.name. -Backward compatibility: database.existingSecret used to configure a shared -database secret. A legacy string value is used directly as the secret name. -*/}} -{{- define "fusionauth.database.fusionauthUser.secretName" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- if eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} -{{- if $fusionauthUserExistingSecret.name -}} -{{- $fusionauthUserExistingSecret.name -}} -{{- else if kindIs "string" $legacyExistingSecret -}} -{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} -{{- else -}} -{{- required "database.fusionauthUser.existingSecret.name is required when database.fusionauthUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} -{{- end -}} -{{- else -}} -{{ .Release.Name }}-credentials -{{- end -}} -{{- end -}} - -{{/* -Resolve the FusionAuth database username key. -Current value: database.fusionauthUser.existingSecret.usernameKey. -Backward compatibility: legacy database.existingSecret values did not support a -username key, so they use the default username key. -*/}} -{{- define "fusionauth.database.fusionauthUser.usernameKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $fusionauthUserExistingSecret.usernameKey | default "username" -}} -{{- end -}} - -{{/* -Resolve the FusionAuth database password key. -Current value: database.fusionauthUser.existingSecret.passwordKey. -Backward compatibility: database.existingSecret.passwordKey is deprecated but -still accepted. -*/}} -{{- define "fusionauth.database.fusionauthUser.passwordKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- $fusionauthUserExistingSecretConfigured := or $fusionauthUserExistingSecret.enabled $fusionauthUserExistingSecret.name -}} -{{- if and $fusionauthUserExistingSecretConfigured $fusionauthUserExistingSecret.passwordKey -}} -{{- $fusionauthUserExistingSecret.passwordKey -}} -{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.passwordKey -}} -{{- $legacyExistingSecret.passwordKey -}} -{{- else -}} -password -{{- end -}} -{{- end -}} - -{{/* -Resolve whether the root database username should come from an existing secret. -This only uses the current value. -Backward compatibility: legacy database.existingSecret was password-only, so it -does not imply that the username comes from a secret. -*/}} -{{- define "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- if $rootUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve whether the chart should create the FusionAuth database credentials -Secret. If DATABASE_PASSWORD is supplied through .Values.environment, that env -var takes precedence and the generated Secret is not needed. -*/}} -{{- define "fusionauth.database.fusionauthUser.generatedSecret.enabled" -}} -{{- $existingSecret := eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- if and (not $existingSecret) (not $databasePasswordEnv) -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve root database username. -Current value: database.rootUser.username. -Backward compatibility: database.root.user is deprecated but still accepted. -*/}} -{{- define "fusionauth.database.rootUser.username" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $legacyRootUser := $database.root | default dict -}} -{{- if $rootUser.username -}} -{{- $rootUser.username -}} -{{- else -}} -{{- $legacyRootUser.user | default "" -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve root database password. -Current value: database.rootUser.password. -Backward compatibility: database.root.password is deprecated but still accepted. -*/}} -{{- define "fusionauth.database.rootUser.password" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $legacyRootUser := $database.root | default dict -}} -{{- if $rootUser.password -}} -{{- $rootUser.password -}} -{{- else -}} -{{- $legacyRootUser.password | default "" -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve whether root database credentials should come from an existing secret. -Current value: database.rootUser.existingSecret.enabled. -Backward compatibility: database.existingSecret used to configure a shared -database secret and is still accepted for the root user. -*/}} -{{- define "fusionauth.database.rootUser.existingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- if $rootUserExistingSecret.enabled -}} -true -{{- else if kindIs "string" $legacyExistingSecret -}} -{{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} -{{- else if $legacyExistingSecret -}} -{{- $legacyExistingSecret.enabled | default false -}} -{{- else -}} -false -{{- end -}} -{{- end -}} - -{{/* -Resolve the root database Secret name. -Current value: database.rootUser.existingSecret.name. -Backward compatibility: database.existingSecret used to configure a shared -database secret. A legacy string value is used directly as the secret name. -*/}} -{{- define "fusionauth.database.rootUser.secretName" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- if eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} -{{- if $rootUserExistingSecret.name -}} -{{- $rootUserExistingSecret.name -}} -{{- else if kindIs "string" $legacyExistingSecret -}} -{{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} -{{- else -}} -{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} -{{- end -}} -{{- else -}} -{{ .Release.Name }}-root-credentials -{{- end -}} -{{- end -}} - -{{/* -Resolve the root database username key. -Current value: database.rootUser.existingSecret.usernameKey. -Backward compatibility: legacy database.existingSecret values did not support a -username key, so they use the default username key. -*/}} -{{- define "fusionauth.database.rootUser.usernameKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- $rootUserExistingSecret.usernameKey | default "username" -}} -{{- end -}} - -{{/* -Resolve the root database password key. -Current value: database.rootUser.existingSecret.passwordKey. -Backward compatibility: database.existingSecret.rootPasswordKey is deprecated -but still accepted. -*/}} -{{- define "fusionauth.database.rootUser.passwordKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- $rootUserExistingSecretConfigured := or $rootUserExistingSecret.enabled $rootUserExistingSecret.name -}} -{{- if and $rootUserExistingSecretConfigured $rootUserExistingSecret.passwordKey -}} -{{- $rootUserExistingSecret.passwordKey -}} -{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.rootPasswordKey -}} -{{- $legacyExistingSecret.rootPasswordKey -}} -{{- else if and $legacyExistingSecret (kindIs "string" $legacyExistingSecret) -}} -rootpassword -{{- else -}} -password -{{- end -}} -{{- end -}} - -{{/* -Resolve whether root database credentials are configured. -*/}} -{{- define "fusionauth.database.rootUser.configured" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv (eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true") -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve whether the chart should create the root database credentials Secret. -If DATABASE_ROOT_PASSWORD is supplied through .Values.environment, that env var -takes precedence and the generated Secret is not needed. -*/}} -{{- define "fusionauth.database.rootUser.generatedSecret.enabled" -}} -{{- $existingSecret := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- if and (eq (include "fusionauth.database.rootUser.configured" .) "true") (not $existingSecret) (not $databaseRootPasswordEnv) -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve whether search basic auth is enabled. -Current value: search.basicAuth.enabled. -Backward compatibility: deprecated search.user/search.password and deprecated -search.existingSecret still imply that basic auth is enabled. -*/}} -{{- define "fusionauth.search.basicAuth.enabled" -}} -{{- if .Values.search.basicAuth.enabled -}} -true -{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} -true -{{- else -}} -false -{{- end -}} -{{- end -}} - -{{/* -Resolve whether search basic auth credentials should come from an existing -secret. -Current value: search.basicAuth.existingSecret.enabled. -Backward compatibility: deprecated search.existingSecret is still accepted as -either an object or a scalar secret name. -*/}} -{{- define "fusionauth.search.existingSecret.enabled" -}} -{{- if .Values.search.basicAuth.existingSecret.enabled -}} -true -{{- else if .Values.search.existingSecret -}} -{{- if kindIs "string" .Values.search.existingSecret -}} -{{- if .Values.search.existingSecret -}}true{{- else -}}false{{- end -}} -{{- else -}} -{{- .Values.search.existingSecret.enabled | default false -}} -{{- end -}} -{{- else -}} -false -{{- end -}} -{{- end -}} - -{{/* -Resolve the search existing secret name. -Current value: search.basicAuth.existingSecret.name. -Backward compatibility: deprecated search.existingSecret string values are used -directly, and deprecated search.existingSecret.name values are also accepted. -*/}} -{{- define "fusionauth.search.existingSecret.name" -}} -{{- if .Values.search.basicAuth.existingSecret.name -}} -{{- .Values.search.basicAuth.existingSecret.name -}} -{{- else if .Values.search.existingSecret -}} -{{- if kindIs "string" .Values.search.existingSecret -}} -{{- .Values.search.existingSecret -}} -{{- else -}} -{{- .Values.search.existingSecret.name -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve the search username key. -Current value: search.basicAuth.existingSecret.userKey. -Backward compatibility: deprecated search.existingSecret.userKey is still -accepted. -*/}} -{{- define "fusionauth.search.existingSecret.userKey" -}} -{{- if .Values.search.basicAuth.existingSecret.userKey -}} -{{- .Values.search.basicAuth.existingSecret.userKey -}} -{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.userKey -}} -{{- .Values.search.existingSecret.userKey -}} -{{- else -}} -username -{{- end -}} -{{- end -}} - -{{/* -Resolve the search password key. -Current value: search.basicAuth.existingSecret.passwordKey. -Backward compatibility: deprecated search.existingSecret.passwordKey is still -accepted. -*/}} -{{- define "fusionauth.search.existingSecret.passwordKey" -}} -{{- if .Values.search.basicAuth.existingSecret.passwordKey -}} -{{- .Values.search.basicAuth.existingSecret.passwordKey -}} -{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.passwordKey -}} -{{- .Values.search.existingSecret.passwordKey -}} -{{- else -}} -password -{{- end -}} -{{- end -}} - -{{/* -Build the search login prefix for SEARCH_SERVERS. -Backward compatibility: this uses the search compatibility helpers above, so -deprecated search.user/search.password and search.existingSecret shapes still -render the same URL prefix. -*/}} -{{- define "fusionauth.searchLogin" -}} -{{- if eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} -$(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ -{{- else if eq (include "fusionauth.search.basicAuth.enabled" .) "true" -}} -{{- $username := "" -}} -{{- $password := "" -}} -{{- if .Values.search.basicAuth.enabled -}} -{{- $username = .Values.search.basicAuth.username -}} -{{- $password = .Values.search.basicAuth.password -}} -{{- else -}} -{{- $username = .Values.search.user -}} -{{- $password = .Values.search.password -}} -{{- end -}} -{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} -{{- $username = "$(SEARCH_USERNAME)" -}} -{{- end -}} -{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} -{{- $password = "$(SEARCH_PASSWORD)" -}} -{{- end -}} -{{- printf "%s:%s@" $username $password -}} -{{- else -}} -{{- printf "" -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve whether chart-managed Elasticsearch/OpenSearch env and wait behavior -should be rendered. SEARCH_TYPE supplied through .Values.environment takes -precedence over the chart search values. -*/}} -{{- define "fusionauth.search.chartEnabled" -}} -{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} -{{- if and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve the database wait init-container flag. -Current value: initContainers.waitForDatabase. -Backward compatibility: deprecated initContainers.waitForDb is still accepted. -When both are set, waitForDatabase wins. -*/}} -{{- define "fusionauth.initContainers.waitForDatabase" -}} -{{- if hasKey .Values.initContainers "waitForDatabase" -}} -{{- .Values.initContainers.waitForDatabase -}} -{{- else if hasKey .Values.initContainers "waitForDb" -}} -{{- .Values.initContainers.waitForDb -}} -{{- else -}} -true -{{- end -}} -{{- end -}} - -{{/* -Resolve the search wait init-container flag. -Current value: initContainers.waitForSearch. -Backward compatibility: deprecated initContainers.waitForEs is still accepted. -When both are set, waitForSearch wins. -*/}} -{{- define "fusionauth.initContainers.waitForSearch" -}} -{{- if hasKey .Values.initContainers "waitForSearch" -}} -{{- .Values.initContainers.waitForSearch -}} -{{- else if hasKey .Values.initContainers "waitForEs" -}} -{{- .Values.initContainers.waitForEs -}} -{{- else -}} -true -{{- end -}} -{{- end -}} - -{{/* -Resolve whether the database wait init container should be rendered. -DATABASE_URL supplied through .Values.environment takes precedence over the -chart database values, so the chart does not wait on database.host in that mode. -*/}} -{{- define "fusionauth.deployment.waitForDatabase.enabled" -}} -{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} -{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Resolve whether the search wait init container should be rendered. -*/}} -{{- define "fusionauth.deployment.waitForSearch.enabled" -}} -{{- if and (eq (include "fusionauth.search.chartEnabled" .) "true") (eq (include "fusionauth.initContainers.waitForSearch" .) "true") .Values.search.host -}}true{{- else -}}false{{- end -}} -{{- end -}} - -{{/* -Validate deployment-only conflicts before rendering the Deployment manifest. -*/}} -{{- define "fusionauth.deployment.validate" -}} -{{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} -{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} -{{- end }} -{{- if hasKey .Values.podLabels "app.kubernetes.io/instance" }} -{{- fail "podLabels cannot override reserved selector label app.kubernetes.io/instance" }} -{{- end }} -{{- $kickstartVolumeName := include "fusionauth.kickstart.volumeName" . }} -{{- if .Values.kickstart.enabled }} -{{- range .Values.extraVolumes }} -{{- if eq .name $kickstartVolumeName }} -{{- fail (printf "extraVolumes cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} -{{- end }} -{{- end }} -{{- range .Values.extraVolumeMounts }} -{{- if eq .name $kickstartVolumeName }} -{{- fail (printf "extraVolumeMounts cannot use reserved kickstart volume name %s" $kickstartVolumeName) }} -{{- end }} -{{- if eq .mountPath "/kickstart" }} -{{- fail "extraVolumeMounts cannot use reserved kickstart mountPath /kickstart when kickstart.enabled is true" }} -{{- end }} -{{- end }} -{{- end }} -{{- range .Values.extraInitContainers }} -{{- if has .name (list "wait-for-db" "wait-for-search") }} -{{- fail (printf "extraInitContainers cannot use reserved init container name %s" .name) }} -{{- end }} -{{- end }} -{{- range .Values.extraContainers }} -{{- if eq .name $.Chart.Name }} -{{- fail (printf "extraContainers cannot use reserved container name %s" $.Chart.Name) }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* -Render FusionAuth container environment variables. User-supplied entries in -.Values.environment are rendered first and take precedence over chart-managed -entries with the same name. -*/}} -{{- define "fusionauth.deployment.env" -}} -{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" -}} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} -{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} -{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} -{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} -{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" -}} -{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" -}} -{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" -}} -{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} -{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} -{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" -}} -{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" -}} -{{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} -{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} -{{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} -{{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} -{{- if not $databaseUsernameEnv }} -- name: DATABASE_USERNAME - {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} - {{- else }} - value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} - {{- end }} -{{- end }} -{{- if not $databasePasswordEnv }} -- name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} -{{- end }} -{{- if $databaseRootUserConfigured }} -{{- if not $databaseRootUsernameEnv }} -- name: DATABASE_ROOT_USERNAME - {{- if $databaseRootUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.rootUser.secretName" . }} - key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} - {{- else }} - value: {{ include "fusionauth.database.rootUser.username" . | quote }} - {{- end }} -{{- end }} -{{- if not $databaseRootPasswordEnv }} -- name: DATABASE_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.rootUser.secretName" . }} - key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} -{{- end }} -{{- end }} -{{- if not $databaseUrlEnv }} -- name: DATABASE_URL - value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" -{{- end }} -{{- if not $searchTypeEnv }} -- name: SEARCH_TYPE - value: {{ .Values.search.engine | quote }} -{{- end }} -{{- if or $chartSearchEnabled $searchServersEnv }} -{{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} -- name: SEARCH_USERNAME - valueFrom: - secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} -{{- end }} -{{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} -- name: SEARCH_PASSWORD - valueFrom: - secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} -{{- end }} -{{- end }} -{{- if and $chartSearchEnabled (not $searchServersEnv) }} -- name: SEARCH_SERVERS - value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" -{{- end }} -{{- if not $appMemoryEnv }} -- name: FUSIONAUTH_APP_MEMORY - value: {{ .Values.app.memory | quote }} -{{- end }} -{{- if not $appRuntimeModeEnv }} -- name: FUSIONAUTH_APP_RUNTIME_MODE - value: {{ .Values.app.runtimeMode | quote }} -{{- end }} -{{- if not $appSilentModeEnv }} -- name: FUSIONAUTH_APP_SILENT_MODE - value: {{ .Values.app.silentMode | quote }} -{{- end }} -{{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} -- name: FUSIONAUTH_APP_KICKSTART_FILE - value: {{ .Values.kickstart.file | quote }} -{{- end }} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "fusionauth.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Common labels applied to FusionAuth resources. -*/}} -{{- define "fusionauth.labels" -}} -app.kubernetes.io/name: {{ include "fusionauth.name" . }} -helm.sh/chart: {{ include "fusionauth.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Selector labels used by Services, workload selectors, and pods. -Keep these stable because selector labels are immutable on several resources. -*/}} -{{- define "fusionauth.selectorLabels" -}} -app.kubernetes.io/name: {{ include "fusionauth.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - -{{/* -Create the name of the service account to use -*/}} -{{- define "fusionauth.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "fusionauth.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/chart/templates/_init_containers.tpl b/chart/templates/_init_containers.tpl new file mode 100644 index 0000000..19c2b25 --- /dev/null +++ b/chart/templates/_init_containers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Resolve the database wait init-container flag. +Current value: initContainers.waitForDatabase. +Backward compatibility: deprecated initContainers.waitForDb is still accepted. +When both are set, waitForDatabase wins. +*/}} +{{- define "fusionauth.initContainers.waitForDatabase" -}} +{{- if hasKey .Values.initContainers "waitForDatabase" -}} +{{- .Values.initContainers.waitForDatabase -}} +{{- else if hasKey .Values.initContainers "waitForDb" -}} +{{- .Values.initContainers.waitForDb -}} +{{- else -}} +true +{{- end -}} +{{- end -}} + +{{/* +Resolve the search wait init-container flag. +Current value: initContainers.waitForSearch. +Backward compatibility: deprecated initContainers.waitForEs is still accepted. +When both are set, waitForSearch wins. +*/}} +{{- define "fusionauth.initContainers.waitForSearch" -}} +{{- if hasKey .Values.initContainers "waitForSearch" -}} +{{- .Values.initContainers.waitForSearch -}} +{{- else if hasKey .Values.initContainers "waitForEs" -}} +{{- .Values.initContainers.waitForEs -}} +{{- else -}} +true +{{- end -}} +{{- end -}} diff --git a/chart/templates/_labels.tpl b/chart/templates/_labels.tpl new file mode 100644 index 0000000..149ee26 --- /dev/null +++ b/chart/templates/_labels.tpl @@ -0,0 +1,37 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "fusionauth.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels applied to FusionAuth resources. +*/}} +{{- define "fusionauth.labels" -}} +app.kubernetes.io/name: {{ include "fusionauth.name" . }} +helm.sh/chart: {{ include "fusionauth.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels used by Services, workload selectors, and pods. +Keep these stable because selector labels are immutable on several resources. +*/}} +{{- define "fusionauth.selectorLabels" -}} +app.kubernetes.io/name: {{ include "fusionauth.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "fusionauth.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "fusionauth.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/chart/templates/_search.tpl b/chart/templates/_search.tpl new file mode 100644 index 0000000..617e96c --- /dev/null +++ b/chart/templates/_search.tpl @@ -0,0 +1,128 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Resolve whether search basic auth is enabled. +Current value: search.basicAuth.enabled. +Backward compatibility: deprecated search.user/search.password and deprecated +search.existingSecret still imply that basic auth is enabled. +*/}} +{{- define "fusionauth.search.basicAuth.enabled" -}} +{{- if .Values.search.basicAuth.enabled -}} +true +{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve whether search basic auth credentials should come from an existing +secret. +Current value: search.basicAuth.existingSecret.enabled. +Backward compatibility: deprecated search.existingSecret is still accepted as +either an object or a scalar secret name. +*/}} +{{- define "fusionauth.search.existingSecret.enabled" -}} +{{- if .Values.search.basicAuth.existingSecret.enabled -}} +true +{{- else if .Values.search.existingSecret -}} +{{- if kindIs "string" .Values.search.existingSecret -}} +{{- if .Values.search.existingSecret -}}true{{- else -}}false{{- end -}} +{{- else -}} +{{- .Values.search.existingSecret.enabled | default false -}} +{{- end -}} +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Resolve the search existing secret name. +Current value: search.basicAuth.existingSecret.name. +Backward compatibility: deprecated search.existingSecret string values are used +directly, and deprecated search.existingSecret.name values are also accepted. +*/}} +{{- define "fusionauth.search.existingSecret.name" -}} +{{- if .Values.search.basicAuth.existingSecret.name -}} +{{- .Values.search.basicAuth.existingSecret.name -}} +{{- else if .Values.search.existingSecret -}} +{{- if kindIs "string" .Values.search.existingSecret -}} +{{- .Values.search.existingSecret -}} +{{- else -}} +{{- .Values.search.existingSecret.name -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve the search username key. +Current value: search.basicAuth.existingSecret.userKey. +Backward compatibility: deprecated search.existingSecret.userKey is still +accepted. +*/}} +{{- define "fusionauth.search.existingSecret.userKey" -}} +{{- if .Values.search.basicAuth.existingSecret.userKey -}} +{{- .Values.search.basicAuth.existingSecret.userKey -}} +{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.userKey -}} +{{- .Values.search.existingSecret.userKey -}} +{{- else -}} +username +{{- end -}} +{{- end -}} + +{{/* +Resolve the search password key. +Current value: search.basicAuth.existingSecret.passwordKey. +Backward compatibility: deprecated search.existingSecret.passwordKey is still +accepted. +*/}} +{{- define "fusionauth.search.existingSecret.passwordKey" -}} +{{- if .Values.search.basicAuth.existingSecret.passwordKey -}} +{{- .Values.search.basicAuth.existingSecret.passwordKey -}} +{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.passwordKey -}} +{{- .Values.search.existingSecret.passwordKey -}} +{{- else -}} +password +{{- end -}} +{{- end -}} + +{{/* +Build the search login prefix for SEARCH_SERVERS. +Backward compatibility: this uses the search compatibility helpers above, so +deprecated search.user/search.password and search.existingSecret shapes still +render the same URL prefix. +*/}} +{{- define "fusionauth.searchLogin" -}} +{{- if eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} +$(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ +{{- else if eq (include "fusionauth.search.basicAuth.enabled" .) "true" -}} +{{- $username := "" -}} +{{- $password := "" -}} +{{- if .Values.search.basicAuth.enabled -}} +{{- $username = .Values.search.basicAuth.username -}} +{{- $password = .Values.search.basicAuth.password -}} +{{- else -}} +{{- $username = .Values.search.user -}} +{{- $password = .Values.search.password -}} +{{- end -}} +{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} +{{- $username = "$(SEARCH_USERNAME)" -}} +{{- end -}} +{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} +{{- $password = "$(SEARCH_PASSWORD)" -}} +{{- end -}} +{{- printf "%s:%s@" $username $password -}} +{{- else -}} +{{- printf "" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve whether chart-managed Elasticsearch/OpenSearch env and wait behavior +should be rendered. SEARCH_TYPE supplied through .Values.environment takes +precedence over the chart search values. +*/}} +{{- define "fusionauth.search.chartEnabled" -}} +{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} +{{- if and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") -}}true{{- else -}}false{{- end -}} +{{- end -}} From 6e8993ae063221fd9075f1d7f71894b275a5f1ea Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Thu, 21 May 2026 17:14:17 -0600 Subject: [PATCH 18/28] wip --- .github/workflows/release.yml | 12 +- chart/README.md | 116 +++++++-------- chart/__test-values.yaml | 27 ++++ chart/examples/minikube/values.yaml | 55 +++----- chart/templates/_database.tpl | 210 +++++++++++----------------- chart/templates/_deployment.tpl | 28 +--- chart/templates/secret.yaml | 18 +-- chart/values.schema.json | 31 +--- chart/values.yaml | 89 ++++++------ 9 files changed, 259 insertions(+), 327 deletions(-) create mode 100644 chart/__test-values.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327c6c8..d2f3170 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,14 +50,14 @@ jobs: run: | helm lint "$RELEASE_CHART" \ --set database.host=postgres \ - --set database.user=fusionauth \ - --set database.password=password \ + --set database.dbUser.username=fusionauth \ + --set database.dbUser.password=password \ --set search.host=opensearch helm template test "$RELEASE_CHART" \ --set database.host=postgres \ - --set database.user=fusionauth \ - --set database.password=password \ + --set database.dbUser.username=fusionauth \ + --set database.dbUser.password=password \ --set search.engine=database \ --set ingress.enabled=true \ --set ingress.hosts[0]=example.com \ @@ -112,8 +112,8 @@ jobs: helm repo add fusionauth ${CHARTS_REPO} helm install fusionauth fusionauth/fusionauth \ --set database.host=host \ - --set database.user=user \ - --set database.password=password \ + --set database.dbUser.username=user \ + --set database.dbUser.password=password \ --set search.host=host - name: Verify the chart version diff --git a/chart/README.md b/chart/README.md index de46e3d..dd50651 100644 --- a/chart/README.md +++ b/chart/README.md @@ -18,51 +18,69 @@ - **`service.type` no longer supports `ExternalName`.** `ExternalName` support is not relevant for this chart. +- **`database.existingSecret` has been replaced.** The legacy scalar Secret + name is still accepted for compatibility, but new installs should use the + following password-only existing Secret configuration. Database usernames + continue to come from `database.dbUser.username` and + `database.rootUser.username`. + + ```yaml + database: + dbUser: + username: fusionauth + existingSecret: + enabled: true + name: fusionauth-db-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the password + rootUser: + username: postgres + existingSecret: + enabled: true + name: fusionauth-root-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the password + ``` + #### Recommended value migrations The following values have been updated, but compatibility shims are in place in `_helpers.tpl`. It is recommended to migrate to the new values, as the compatibility shims may be removed in a future release. -| Previous value | New value | -| -------------------------------------- | --------------------------------------------------------------------------------------------------- | -| `annotations` | `deploymentAnnotations` | -| `initContainers.waitForDb` | `initContainers.waitForDatabase` | -| `initContainers.waitForEs` | `initContainers.waitForSearch` | -| `database.user` | `database.fusionauthUser.username` | -| `database.password` | `database.fusionauthUser.password` | -| `database.root.user` | `database.rootUser.username` | -| `database.root.password` | `database.rootUser.password` | -| `database.existingSecret: secret-name` | `database.fusionauthUser.existingSecret.name` and `database.rootUser.existingSecret.name` if needed | -| `database.existingSecret.enabled` | `database.fusionauthUser.existingSecret.enabled` and `database.rootUser.existingSecret.enabled` | -| `database.existingSecret.name` | `database.fusionauthUser.existingSecret.name` and `database.rootUser.existingSecret.name` if needed | -| `database.existingSecret.passwordKey` | `database.fusionauthUser.existingSecret.passwordKey` | -| `database.existingSecret.rootPasswordKey` | `database.rootUser.existingSecret.passwordKey` | -| `search.user` | `search.basicAuth.username` | -| `search.password` | `search.basicAuth.password` | -| `search.existingSecret: secret-name` | `search.basicAuth.existingSecret.name: secret-name` | -| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | -| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | -| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | -| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | - -When migrating existing database Secrets, set `database.fusionauthUser.existingSecret.enabled: true` and, if you use root bootstrap credentials, set `database.rootUser.existingSecret.enabled: true`. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. +| Previous value | New value | +| ----------------------------------- | --------------------------------------------------------------------------------- | +| `annotations` | `deploymentAnnotations` | +| `initContainers.waitForDb` | `initContainers.waitForDatabase` | +| `initContainers.waitForEs` | `initContainers.waitForSearch` | +| `database.existingSecret` | `database.dbUser.existingSecret` and `database.rootUser.existingSecret` if needed | +| `database.user` | `database.dbUser.username` | +| `database.password` | `database.dbUser.password` | +| `database.root.user` | `database.rootUser.username` | +| `database.root.password` | `database.rootUser.password` | +| `search.user` | `search.basicAuth.username` | +| `search.password` | `search.basicAuth.password` | +| `search.existingSecret` | `search.basicAuth.existingSecret.name: secret-name` | +| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | +| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | +| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | +| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | + +When migrating existing database Secrets, keep database usernames in `database.dbUser.username` and `database.rootUser.username`, set `database.dbUser.existingSecret.enabled: true`, and, if you use root bootstrap credentials, set `database.rootUser.existingSecret.enabled: true`. Database existing Secrets only need password keys. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. Prefer the following structure for database credentials: ```yaml database: - fusionauthUser: + dbUser: + username: fusionauth existingSecret: enabled: true name: database-user-secret - usernameKey: username passwordKey: password rootUser: + username: postgres existingSecret: enabled: true name: database-root-secret - usernameKey: username passwordKey: password ``` @@ -117,8 +135,8 @@ To install the chart with the release name `fusionauth`: helm repo add fusionauth https://fusionauth.github.io/charts helm install fusionauth fusionauth/fusionauth \ --set database.host=[database host] \ - --set database.fusionauthUser.username=[database username] \ - --set database.fusionauthUser.password=[database password] \ + --set database.dbUser.username=[database username] \ + --set database.dbUser.password=[database password] \ --set search.host=[elasticsearch host] ``` @@ -159,8 +177,8 @@ export FA_PSQL_PASS=$(kubectl get secret postgres-postgresql -o jsonpath="{.data helm repo add fusionauth https://fusionauth.github.io/charts helm install fusionauth fusionauth/fusionauth \ --set database.host=postgres-postgresql \ ---set database.fusionauthUser.username=fusionauth \ ---set database.fusionauthUser.password=$FA_PSQL_PASS \ +--set database.dbUser.username=fusionauth \ +--set database.dbUser.password=$FA_PSQL_PASS \ --set search.host=opensearch-cluster-master ``` @@ -269,45 +287,39 @@ You should now be able to connect to the FusionAuth application at http://localh - + - + - + - + - + - + - + - - - - - - - + - + - + - + @@ -320,19 +332,13 @@ You should now be able to connect to the FusionAuth application at http://localh - + - - - - - - - + diff --git a/chart/__test-values.yaml b/chart/__test-values.yaml new file mode 100644 index 0000000..3e50196 --- /dev/null +++ b/chart/__test-values.yaml @@ -0,0 +1,27 @@ +search: + host: localhost + +# database: +# host: localhost +# user: fa-test +# password: test123 +# root: +# user: asdfasdfasdfasdfasd +# password: asdfasdf + +database: + host: localhost + dbUser: + username: test + password: test123 + # existingSecret: + # enabled: false + # name: fa-db-user-creds + # passwordKey: fa-password + # rootUser: + # username: testroot + # password: testroot123 + # existingSecret: + # enabled: false + # name: fa-db-root-creds + # passwordKey: root-password diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index ec7758a..e8bbbc9 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -23,8 +23,7 @@ initContainers: # initContainers.image.tag -- Tag to use for initContainers docker image tag: latest # initContainers.resources -- Resource requests and limits to use for initContainers - resources: - {} + resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -63,21 +62,19 @@ database: # database.name -- Name of the fusionauth database name: fusionauth - # database.fusionauthUser -- Database credentials for fusionauth to use in normal operation - fusionauthUser: - # database.fusionauthUser.username -- Database username for fusionauth to use in normal operation + # database.dbUser -- Database credentials for fusionauth to use in normal operation + dbUser: + # database.dbUser.username -- Database username for fusionauth to use in normal operation username: "fusionauth" - # database.fusionauthUser.password -- Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true + # database.dbUser.password -- Database password for fusionauth to use in normal operation - not required if database.dbUser.existingSecret.enabled is true password: "minikube-password" - # database.fusionauthUser.existingSecret -- Configures an existing secret that contains the normal database user credentials. + # database.dbUser.existingSecret -- Configures an existing secret that contains the normal database user password. existingSecret: - # database.fusionauthUser.existingSecret.enabled -- Use an existing secret for the normal database user credentials. + # database.dbUser.existingSecret.enabled -- Use an existing secret for the normal database user password. enabled: false - # database.fusionauthUser.existingSecret.name -- The name of an existing secret that contains the normal database user credentials. + # database.dbUser.existingSecret.name -- The name of an existing secret that contains the normal database user password. name: "" - # database.fusionauthUser.existingSecret.usernameKey -- The key in database.fusionauthUser.existingSecret.name that contains the database username. - usernameKey: username - # database.fusionauthUser.existingSecret.passwordKey -- The key in database.fusionauthUser.existingSecret.name that contains the database password. + # database.dbUser.existingSecret.passwordKey -- The key in database.dbUser.existingSecret.name that contains the database password. passwordKey: password # database.rootUser -- Database credentials for fusionauth to use during initial bootstrap rootUser: @@ -85,14 +82,12 @@ database: username: "postgres" # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true password: "minikube-password" - # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user credentials. + # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user password. existingSecret: - # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user credentials. + # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user password. enabled: false - # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user credentials. + # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user password. name: "" - # database.rootUser.existingSecret.usernameKey -- The key in database.rootUser.existingSecret.name that contains the root database username. - usernameKey: username # database.rootUser.existingSecret.passwordKey -- The key in database.rootUser.existingSecret.name that contains the root database password. passwordKey: password @@ -133,8 +128,7 @@ app: silentMode: true # environment - Configure additional environment variables. Should only be used for things that are not explicitly set elsewhere in the chart. -environment: - [] +environment: [] # - name: POD_IP # valueFrom: # fieldRef: @@ -148,8 +142,7 @@ environment: kickstart: enabled: false - data: - {} + data: {} # kickstart.json: | # { # "variables": { @@ -187,8 +180,7 @@ kickstart: # setup-password.txt: | # Hallo -lifecycle: - {} +lifecycle: {} # # lifecycle.postStart -- postStart lifecycle command for fusionauth container # postStart: # exec: @@ -203,14 +195,11 @@ podDisruptionBudget: enabled: false ingress: - # ingress.enabled -- Enables ingress creation for fusionauth. enabled: true # ingress.annotations -- Configure annotations to add to the ingress object annotations: - { - kubernetes.io/ingress.class: nginx - } + { kubernetes.io/ingress.class: nginx } # kubernetes.io/tls-acme: "true" paths: - path: / @@ -223,7 +212,8 @@ ingress: # ingress.extraPaths -- Define complete path objects, will be inserted before regular paths. Can be useful for things like ALB Ingress Controller actions extraPaths: [] # ingress.hosts -- List of hostnames to configure the ingress with - hosts: ["localhost"] + hosts: + ["localhost"] # - chart-example.local # ingress.tls -- List of secrets used to configure TLS for the ingress. tls: [] @@ -232,8 +222,7 @@ ingress: # - chart-example.local # resources -- Define resource requests and limits for fusionauth-app. -resources: - {} +resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -293,14 +282,12 @@ startupProbe: timeoutSeconds: 5 # extraVolumes -- Define extra Volumes. Allow to add existing claimName -extraVolumes: - [] +extraVolumes: [] # - name: custom-css-data # persistentVolumeClaim: # claimName: custom-css-data # extraVolumeMounts -- Associate mountPath for each extraVolumes -extraVolumeMounts: - [] +extraVolumeMounts: [] # - name: custom-css-data # mountPath: /usr/local/fusionauth/fusionauth-app/web/custom diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl index 41c7790..16fbe91 100644 --- a/chart/templates/_database.tpl +++ b/chart/templates/_database.tpl @@ -10,123 +10,102 @@ Configure TLS if enabled {{/* Resolve FusionAuth database username. -Current value: database.fusionauthUser.username. -Backward compatibility: database.user is deprecated but still accepted. +- Current: database.dbUser.username +- Legacy: database.user, which takes precedence when set for compatibility */}} -{{- define "fusionauth.database.fusionauthUser.username" -}} +{{- define "fusionauth.database.dbUser.username" -}} {{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- if $fusionauthUser.username -}} -{{- $fusionauthUser.username -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- if $database.user -}} +{{- $database.user -}} {{- else -}} -{{- $database.user | default "" -}} +{{- $dbUser.username | default "" -}} {{- end -}} {{- end -}} {{/* Resolve FusionAuth database password. -Current value: database.fusionauthUser.password. -Backward compatibility: database.password is deprecated but still accepted. +- Current: database.dbUser.password +- Legacy: database.password */}} -{{- define "fusionauth.database.fusionauthUser.password" -}} +{{- define "fusionauth.database.dbUser.password" -}} {{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- if $fusionauthUser.password -}} -{{- $fusionauthUser.password -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- if $dbUser.password -}} +{{- $dbUser.password -}} {{- else -}} {{- $database.password | default "" -}} {{- end -}} {{- end -}} +{{/* +Validate FusionAuth database credential combinations. +*/}} +{{- define "fusionauth.database.dbUser.validate" -}} +{{- $database := .Values.database | default dict -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- if and $database.password (not $database.user) -}} +{{- fail "database.user is required when database.password is set" -}} +{{- end -}} +{{- if and $dbUser.password (not $dbUser.username) -}} +{{- fail "database.dbUser.username is required when database.dbUser.password is set" -}} +{{- end -}} +{{- end -}} + {{/* Resolve whether FusionAuth database credentials should come from an existing secret. -Current value: database.fusionauthUser.existingSecret.enabled. -Backward compatibility: database.existingSecret used to configure a shared -database secret and is still accepted for the FusionAuth user. +- Current: database.dbUser.existingSecret.enabled +- Legacy: database.existingSecret string */}} -{{- define "fusionauth.database.fusionauthUser.existingSecret.enabled" -}} +{{- define "fusionauth.database.dbUser.existingSecret.enabled" -}} {{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- $dbUserExistingSecret := $dbUser.existingSecret | default dict -}} {{- $legacyExistingSecret := $database.existingSecret -}} -{{- if $fusionauthUserExistingSecret.enabled -}} +{{- if $dbUserExistingSecret.enabled -}} true {{- else if kindIs "string" $legacyExistingSecret -}} {{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} -{{- else if $legacyExistingSecret -}} -{{- $legacyExistingSecret.enabled | default false -}} {{- else -}} false {{- end -}} {{- end -}} -{{/* -Resolve whether the FusionAuth database username should come from an existing -secret. This only uses the current value. -Backward compatibility: legacy database.existingSecret was password-only, so it -does not imply that the username comes from a secret. -*/}} -{{- define "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- if $fusionauthUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} -{{- end -}} - {{/* Resolve the FusionAuth database Secret name. -Current value: database.fusionauthUser.existingSecret.name. -Backward compatibility: database.existingSecret used to configure a shared -database secret. A legacy string value is used directly as the secret name. +- Current: database.dbUser.existingSecret.name +- Legacy: database.existingSecret */}} -{{- define "fusionauth.database.fusionauthUser.secretName" -}} +{{- define "fusionauth.database.dbUser.secretName" -}} {{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- $dbUserExistingSecret := $dbUser.existingSecret | default dict -}} {{- $legacyExistingSecret := $database.existingSecret -}} -{{- if eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} -{{- if $fusionauthUserExistingSecret.name -}} -{{- $fusionauthUserExistingSecret.name -}} +{{- if eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} +{{- if $dbUserExistingSecret.enabled -}} +{{- required "database.dbUser.existingSecret.name is required when database.dbUser.existingSecret.enabled is true" $dbUserExistingSecret.name -}} {{- else if kindIs "string" $legacyExistingSecret -}} {{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} {{- else -}} -{{- required "database.fusionauthUser.existingSecret.name is required when database.fusionauthUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} +{{- required "database.dbUser.existingSecret.name is required when database.dbUser.existingSecret.enabled is true" $dbUserExistingSecret.name -}} {{- end -}} {{- else -}} {{ .Release.Name }}-credentials {{- end -}} {{- end -}} -{{/* -Resolve the FusionAuth database username key. -Current value: database.fusionauthUser.existingSecret.usernameKey. -Backward compatibility: legacy database.existingSecret values did not support a -username key, so they use the default username key. -*/}} -{{- define "fusionauth.database.fusionauthUser.usernameKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $fusionauthUserExistingSecret.usernameKey | default "username" -}} -{{- end -}} - {{/* Resolve the FusionAuth database password key. -Current value: database.fusionauthUser.existingSecret.passwordKey. -Backward compatibility: database.existingSecret.passwordKey is deprecated but -still accepted. +- Current: database.dbUser.existingSecret.passwordKey +- Legacy: n/a */}} -{{- define "fusionauth.database.fusionauthUser.passwordKey" -}} +{{- define "fusionauth.database.dbUser.passwordKey" -}} {{- $database := .Values.database | default dict -}} -{{- $fusionauthUser := $database.fusionauthUser | default dict -}} -{{- $fusionauthUserExistingSecret := $fusionauthUser.existingSecret | default dict -}} -{{- $legacyExistingSecret := $database.existingSecret -}} -{{- $fusionauthUserExistingSecretConfigured := or $fusionauthUserExistingSecret.enabled $fusionauthUserExistingSecret.name -}} -{{- if and $fusionauthUserExistingSecretConfigured $fusionauthUserExistingSecret.passwordKey -}} -{{- $fusionauthUserExistingSecret.passwordKey -}} -{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.passwordKey -}} -{{- $legacyExistingSecret.passwordKey -}} +{{- $dbUser := $database.dbUser | default dict -}} +{{- $dbUserExistingSecret := $dbUser.existingSecret | default dict -}} +{{- if and $dbUserExistingSecret.enabled $dbUserExistingSecret.passwordKey -}} +{{- $dbUserExistingSecret.passwordKey -}} {{- else -}} password {{- end -}} @@ -137,32 +116,32 @@ Resolve whether the chart should create the FusionAuth database credentials Secret. If DATABASE_PASSWORD is supplied through .Values.environment, that env var takes precedence and the generated Secret is not needed. */}} -{{- define "fusionauth.database.fusionauthUser.generatedSecret.enabled" -}} -{{- $existingSecret := eq (include "fusionauth.database.fusionauthUser.existingSecret.enabled" .) "true" -}} +{{- define "fusionauth.database.dbUser.generatedSecret.enabled" -}} +{{- $existingSecret := eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} {{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} {{- if and (not $existingSecret) (not $databasePasswordEnv) -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* Resolve root database username. -Current value: database.rootUser.username. -Backward compatibility: database.root.user is deprecated but still accepted. +- Current: database.rootUser.username +- Legacy: database.root.user, which takes precedence when set for compatibility */}} {{- define "fusionauth.database.rootUser.username" -}} {{- $database := .Values.database | default dict -}} {{- $rootUser := $database.rootUser | default dict -}} {{- $legacyRootUser := $database.root | default dict -}} -{{- if $rootUser.username -}} -{{- $rootUser.username -}} +{{- if $legacyRootUser.user -}} +{{- $legacyRootUser.user -}} {{- else -}} -{{- $legacyRootUser.user | default "" -}} +{{- $rootUser.username | default "" -}} {{- end -}} {{- end -}} {{/* Resolve root database password. -Current value: database.rootUser.password. -Backward compatibility: database.root.password is deprecated but still accepted. +- Current: database.rootUser.password +- Legacy: database.root.password */}} {{- define "fusionauth.database.rootUser.password" -}} {{- $database := .Values.database | default dict -}} @@ -175,11 +154,25 @@ Backward compatibility: database.root.password is deprecated but still accepted. {{- end -}} {{- end -}} +{{/* +Validate root database credential combinations. +*/}} +{{- define "fusionauth.database.rootUser.validate" -}} +{{- $database := .Values.database | default dict -}} +{{- $rootUser := $database.rootUser | default dict -}} +{{- $legacyRootUser := $database.root | default dict -}} +{{- if and $legacyRootUser.password (not $legacyRootUser.user) -}} +{{- fail "database.root.user is required when database.root.password is set" -}} +{{- end -}} +{{- if and $rootUser.password (not $rootUser.username) -}} +{{- fail "database.rootUser.username is required when database.rootUser.password is set" -}} +{{- end -}} +{{- end -}} + {{/* Resolve whether root database credentials should come from an existing secret. -Current value: database.rootUser.existingSecret.enabled. -Backward compatibility: database.existingSecret used to configure a shared -database secret and is still accepted for the root user. +- Current: database.rootUser.existingSecret.enabled +- Legacy: database.existingSecret string */}} {{- define "fusionauth.database.rootUser.existingSecret.enabled" -}} {{- $database := .Values.database | default dict -}} @@ -190,31 +183,15 @@ database secret and is still accepted for the root user. true {{- else if kindIs "string" $legacyExistingSecret -}} {{- if $legacyExistingSecret -}}true{{- else -}}false{{- end -}} -{{- else if $legacyExistingSecret -}} -{{- $legacyExistingSecret.enabled | default false -}} {{- else -}} false {{- end -}} {{- end -}} -{{/* -Resolve whether the root database username should come from an existing secret. -This only uses the current value. -Backward compatibility: legacy database.existingSecret was password-only, so it -does not imply that the username comes from a secret. -*/}} -{{- define "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- if $rootUserExistingSecret.enabled -}}true{{- else -}}false{{- end -}} -{{- end -}} - {{/* Resolve the root database Secret name. -Current value: database.rootUser.existingSecret.name. -Backward compatibility: database.existingSecret used to configure a shared -database secret. A legacy string value is used directly as the secret name. +- Current: database.rootUser.existingSecret.name +- Legacy: database.existingSecret */}} {{- define "fusionauth.database.rootUser.secretName" -}} {{- $database := .Values.database | default dict -}} @@ -222,48 +199,31 @@ database secret. A legacy string value is used directly as the secret name. {{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} {{- $legacyExistingSecret := $database.existingSecret -}} {{- if eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} -{{- if $rootUserExistingSecret.name -}} -{{- $rootUserExistingSecret.name -}} +{{- if $rootUserExistingSecret.enabled -}} +{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $rootUserExistingSecret.name -}} {{- else if kindIs "string" $legacyExistingSecret -}} {{- required "database.existingSecret must not be empty when used as a secret name" $legacyExistingSecret -}} {{- else -}} -{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $legacyExistingSecret.name -}} +{{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $rootUserExistingSecret.name -}} {{- end -}} {{- else -}} {{ .Release.Name }}-root-credentials {{- end -}} {{- end -}} -{{/* -Resolve the root database username key. -Current value: database.rootUser.existingSecret.usernameKey. -Backward compatibility: legacy database.existingSecret values did not support a -username key, so they use the default username key. -*/}} -{{- define "fusionauth.database.rootUser.usernameKey" -}} -{{- $database := .Values.database | default dict -}} -{{- $rootUser := $database.rootUser | default dict -}} -{{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} -{{- $rootUserExistingSecret.usernameKey | default "username" -}} -{{- end -}} - {{/* Resolve the root database password key. -Current value: database.rootUser.existingSecret.passwordKey. -Backward compatibility: database.existingSecret.rootPasswordKey is deprecated -but still accepted. +- Current: database.rootUser.existingSecret.passwordKey +- Legacy: database.existingSecret string uses rootpassword */}} {{- define "fusionauth.database.rootUser.passwordKey" -}} {{- $database := .Values.database | default dict -}} {{- $rootUser := $database.rootUser | default dict -}} {{- $rootUserExistingSecret := $rootUser.existingSecret | default dict -}} {{- $legacyExistingSecret := $database.existingSecret -}} -{{- $rootUserExistingSecretConfigured := or $rootUserExistingSecret.enabled $rootUserExistingSecret.name -}} -{{- if and $rootUserExistingSecretConfigured $rootUserExistingSecret.passwordKey -}} +{{- if and $rootUserExistingSecret.enabled $rootUserExistingSecret.passwordKey -}} {{- $rootUserExistingSecret.passwordKey -}} -{{- else if and $legacyExistingSecret (kindIs "map" $legacyExistingSecret) $legacyExistingSecret.rootPasswordKey -}} -{{- $legacyExistingSecret.rootPasswordKey -}} -{{- else if and $legacyExistingSecret (kindIs "string" $legacyExistingSecret) -}} +{{- else if and (not $rootUserExistingSecret.enabled) $legacyExistingSecret (kindIs "string" $legacyExistingSecret) -}} rootpassword {{- else -}} password @@ -275,7 +235,7 @@ Resolve whether root database credentials are configured. */}} {{- define "fusionauth.database.rootUser.configured" -}} {{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv (eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true") -}}true{{- else -}}false{{- end -}} +{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl index d49d187..ab60c31 100644 --- a/chart/templates/_deployment.tpl +++ b/chart/templates/_deployment.tpl @@ -41,6 +41,8 @@ Resolve whether the search wait init container should be rendered. Validate deployment-only conflicts before rendering the Deployment manifest. */}} {{- define "fusionauth.deployment.validate" -}} +{{- include "fusionauth.database.dbUser.validate" . }} +{{- include "fusionauth.database.rootUser.validate" . }} {{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} {{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} {{- end }} @@ -94,41 +96,25 @@ entries with the same name. {{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" -}} {{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} {{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} -{{- $databaseFusionAuthUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.fusionauthUser.usernameFromExistingSecret.enabled" .) "true" -}} -{{- $databaseRootUsernameFromExistingSecretEnabled := eq (include "fusionauth.database.rootUser.usernameFromExistingSecret.enabled" .) "true" -}} {{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} {{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} {{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} {{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} {{- if not $databaseUsernameEnv }} - name: DATABASE_USERNAME - {{- if $databaseFusionAuthUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.usernameKey" . | quote }} - {{- else }} - value: {{ required "A valid username for the database is required!" (include "fusionauth.database.fusionauthUser.username" .) | quote }} - {{- end }} + value: {{ required "database.dbUser.username is required unless DATABASE_USERNAME is set in environment; legacy database.user is also accepted" (include "fusionauth.database.dbUser.username" .) | quote }} {{- end }} {{- if not $databasePasswordEnv }} - name: DATABASE_PASSWORD valueFrom: secretKeyRef: - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} - key: {{ include "fusionauth.database.fusionauthUser.passwordKey" . | quote }} + name: {{ include "fusionauth.database.dbUser.secretName" . }} + key: {{ include "fusionauth.database.dbUser.passwordKey" . | quote }} {{- end }} {{- if $databaseRootUserConfigured }} {{- if not $databaseRootUsernameEnv }} - name: DATABASE_ROOT_USERNAME - {{- if $databaseRootUsernameFromExistingSecretEnabled }} - valueFrom: - secretKeyRef: - name: {{ include "fusionauth.database.rootUser.secretName" . }} - key: {{ include "fusionauth.database.rootUser.usernameKey" . | quote }} - {{- else }} value: {{ include "fusionauth.database.rootUser.username" . | quote }} - {{- end }} {{- end }} {{- if not $databaseRootPasswordEnv }} - name: DATABASE_ROOT_PASSWORD @@ -140,7 +126,7 @@ entries with the same name. {{- end }} {{- if not $databaseUrlEnv }} - name: DATABASE_URL - value: "jdbc:{{ .Values.database.protocol }}://{{- required "A valid database host is required!" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" + value: "jdbc:{{ .Values.database.protocol }}://{{- required "database.host is required unless DATABASE_URL is set in environment" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" {{- end }} {{- if not $searchTypeEnv }} - name: SEARCH_TYPE @@ -164,7 +150,7 @@ entries with the same name. {{- end }} {{- if and $chartSearchEnabled (not $searchServersEnv) }} - name: SEARCH_SERVERS - value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "A valid elasticsearch host is required!" .Values.search.host -}}:{{ .Values.search.port }}" + value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "search.host is required when search.engine is elasticsearch unless SEARCH_SERVERS is set in environment" .Values.search.host -}}:{{ .Values.search.port }}" {{- end }} {{- if not $appMemoryEnv }} - name: FUSIONAUTH_APP_MEMORY diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 60fe66e..b89ef44 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,25 +1,27 @@ -{{- $fusionauthUserGeneratedSecretEnabled := eq (include "fusionauth.database.fusionauthUser.generatedSecret.enabled" .) "true" -}} +{{- include "fusionauth.database.dbUser.validate" . }} +{{- include "fusionauth.database.rootUser.validate" . }} +{{- $dbUserGeneratedSecretEnabled := eq (include "fusionauth.database.dbUser.generatedSecret.enabled" .) "true" -}} {{- $rootUserGeneratedSecretEnabled := eq (include "fusionauth.database.rootUser.generatedSecret.enabled" .) "true" -}} -{{- if $fusionauthUserGeneratedSecretEnabled -}} -{{- if not (include "fusionauth.database.fusionauthUser.password" .) -}} -{{- fail "database.fusionauthUser.password is required when database.fusionauthUser.existingSecret.enabled is false" }} +{{- if $dbUserGeneratedSecretEnabled -}} +{{- if not (include "fusionauth.database.dbUser.password" .) -}} +{{- fail "database.dbUser.password is required when database.dbUser.existingSecret.enabled is false and DATABASE_PASSWORD is not set in environment; legacy database.password is also accepted" }} {{- end -}} apiVersion: v1 data: - password: {{ include "fusionauth.database.fusionauthUser.password" . | b64enc }} + password: {{ include "fusionauth.database.dbUser.password" . | b64enc }} kind: Secret metadata: labels: {{- include "fusionauth.labels" . | nindent 4 }} - name: {{ include "fusionauth.database.fusionauthUser.secretName" . }} + name: {{ include "fusionauth.database.dbUser.secretName" . }} type: Opaque {{- end }} {{ if $rootUserGeneratedSecretEnabled }} -{{ if $fusionauthUserGeneratedSecretEnabled }} +{{ if $dbUserGeneratedSecretEnabled }} --- {{ end }} {{- if not (include "fusionauth.database.rootUser.password" .) -}} -{{- fail "database.rootUser.password is required when database.rootUser.username is set and database.rootUser.existingSecret.enabled is false" }} +{{- fail "database.rootUser.password is required when database.rootUser.username is set, database.rootUser.existingSecret.enabled is false, and DATABASE_ROOT_PASSWORD is not set in environment; legacy database.root.password is also accepted" }} {{- end -}} apiVersion: v1 data: diff --git a/chart/values.schema.json b/chart/values.schema.json index 04b6277..6d0780f 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -62,7 +62,7 @@ "database": { "type": "object", "properties": { - "fusionauthUser": { + "dbUser": { "type": "object", "properties": { "existingSecret": { @@ -76,9 +76,6 @@ }, "passwordKey": { "type": "string" - }, - "usernameKey": { - "type": "string" } } }, @@ -91,28 +88,7 @@ } }, "existingSecret": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "passwordKey": { - "type": "string" - }, - "rootPasswordKey": { - "type": "string" - } - } - } - ] + "type": "string" }, "host": { "type": "string" @@ -160,9 +136,6 @@ }, "passwordKey": { "type": "string" - }, - "usernameKey": { - "type": "string" } } }, diff --git a/chart/values.yaml b/chart/values.yaml index c1c8b94..9d464ad 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -1,10 +1,9 @@ # Default values for fusionauth. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. # replicaCount -- The number of fusionauth-app instances to run replicaCount: 1 +# image -- Configures the docker image to use for fusionauth-app image: # image.repository -- The name of the docker repository for fusionauth-app repository: docker.io/fusionauth/fusionauth-app @@ -13,35 +12,27 @@ image: # image.pullPolicy -- Kubernetes image pullPolicy to use for fusionauth-app pullPolicy: IfNotPresent -# imagePullSecrets -- Configures kubernetes secrets to use for pulling private images +# image.imagePullSecrets -- Configures kubernetes secrets to use for pulling private images imagePullSecrets: [] # nameOverride -- Overrides resource names nameOverride: "" # fullnameOverride -- Overrides full resource names fullnameOverride: "" -# - spec for Container: -# kubectl explain pod.spec.initContainers -# kubectl explain pod.spec.initContainers --recursive -extraInitContainers: [] - +# initContainers -- Configures init containers for fusionauth pods. Init containers are used to wait for the database and search engine to be ready before starting fusionauth. initContainers: - # initContainers.waitForDatabase -- Create an init container that waits for the database to be ready. - # waitForDatabase: true - # initContainers.waitForSearch -- Create an init container that waits for search to be ready. - # waitForSearch: true - # This image should contain `nc`, `wget` and a shell of some kind to do a simple loop. + # initContainers.waitForDatabase -- waits for the database to be ready. Setting this to `false` is not recommended. + waitForDatabase: true + # initContainers.waitForSearch -- waits for the search engine to be ready. Setting this to `false` is not recommended. + waitForSearch: true + # initContainers.image -- Configures the docker image to use for init containers. image: - # initContainers.image.repository -- Docker image to use for initContainers + # initContainers.image.repository -- Docker image to use for initContainers. This image must contain `nc`, `wget` and a shell of some kind to do a simple loop. repository: docker.io/library/busybox # initContainers.image.tag -- Tag to use for initContainers docker image tag: 1.36.1 - # initContainers.resources -- Resource requests and limits to use for initContainers + # initContainers.resources -- It is recommended to set these values when you understand FusionAuth's resource usage in your specific environment. resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi @@ -49,9 +40,10 @@ initContainers: # cpu: 100m # memory: 128Mi -# - spec for Container: -# kubectl explain pod.spec.containers -# kubectl explain pod.spec.containers --recursive +# extraInitContainers -- Add specs for additional init containers if needed. +extraInitContainers: [] + +# extraContainers -- Add specs for additional containers if needed. extraContainers: [] service: @@ -62,8 +54,9 @@ service: # service.annotations -- Extra annotations to add to service object annotations: {} +# database -- Configures the database connection for fusionauth database: - # database.protocol -- Should either be postgresql or mysql. Protocol for jdbc connection to database + # database.protocol -- Protocol for jdbc connection to database [`postgresql|mysql`]. protocol: postgresql # database.host -- Hostname or ip of the database instance host: "" @@ -75,40 +68,40 @@ database: tlsMode: require # database.name -- Name of the fusionauth database name: fusionauth + # database.username -- Username for the fusionauth database - # database.fusionauthUser -- Database credentials for fusionauth to use in normal operation - fusionauthUser: - # database.fusionauthUser.username -- Database username for fusionauth to use in normal operation + # database.dbUser -- Database credentials for fusionauth to use in normal operation + dbUser: + # database.dbUser.username -- Database username for fusionauth to use in normal operation. username: "" - # database.fusionauthUser.password -- Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true - password: "" - # database.fusionauthUser.existingSecret -- Configures an existing secret that contains the normal database user credentials. + # database.dbUser.password -- Database password for fusionauth to use in normal operation. + # It is not recommended to set the password in clear text here. Use an existing secret instead. + # password: "" + # database.dbUser.existingSecret -- Configures an existing secret that contains the normal database user password. existingSecret: - # database.fusionauthUser.existingSecret.enabled -- Use an existing secret for the normal database user credentials. + # database.dbUser.existingSecret.enabled -- Use an existing secret for the normal database user password. enabled: false - # database.fusionauthUser.existingSecret.name -- The name of an existing secret that contains the normal database user credentials. + # database.dbUser.existingSecret.name -- The name of an existing secret that contains the normal database user password. name: "" - # database.fusionauthUser.existingSecret.usernameKey -- The key in database.fusionauthUser.existingSecret.name that contains the database username. - usernameKey: username - # database.fusionauthUser.existingSecret.passwordKey -- The key in database.fusionauthUser.existingSecret.name that contains the database password. + # database.dbUser.existingSecret.passwordKey -- The key in the existing secret that contains the database password. passwordKey: password # database.rootUser -- Database credentials for fusionauth to use during initial bootstrap rootUser: - # database.rootUser.username -- Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database + # database.rootUser.username -- Database username for fusionauth to use during initial bootstrap username: "" - # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true - password: "" - # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user credentials. + # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap + # It is not recommended to set the password in clear text here. Use an existing secret instead. + # password: "" + # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user password. existingSecret: - # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user credentials. + # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user password. enabled: false - # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user credentials. + # database.rootUser.existingSecret.name -- The name of an existing secret that contains the root database user password. name: "" - # database.rootUser.existingSecret.usernameKey -- The key in database.rootUser.existingSecret.name that contains the root database username. - usernameKey: username - # database.rootUser.existingSecret.passwordKey -- The key in database.rootUser.existingSecret.name that contains the root database password. + # database.rootUser.existingSecret.passwordKey -- The key in the existing secret that contains the root database password. passwordKey: password +# search -- Configures the search engine for fusionauth search: # search.engine -- Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. engine: elasticsearch @@ -134,9 +127,10 @@ search: name: "" # search.basicAuth.existingSecret.userKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch username. userKey: username - # search.basicAuth.existingSecret.passwordKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. + # -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. passwordKey: password +# app -- Configures general settings for the fusionauth application app: # app.memory -- Configures the amount of memory Java can use memory: 256M @@ -161,6 +155,7 @@ environment: [] # - name: FUSIONAUTH_APP_KICKSTART_FILE # value: /kickstart/kickstart.json +# kickstart -- Configures kickstart for initial application setup kickstart: enabled: false # kickstart.file -- File path FusionAuth should use for kickstart configuration. @@ -246,12 +241,8 @@ ingress: # hosts: # - chart-example.local -# resources -- Define resource requests and limits for fusionauth-app. +# resources -- Define resource requests and limits for fusionauth-app. It is recommended to set these values when you understand FusionAuth's resource usage in your specific environment. resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi From 95df720b107eab335cecf96ed4d248789bdbccd9 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Thu, 21 May 2026 17:21:15 -0600 Subject: [PATCH 19/28] update secret names --- chart/__test-values.yaml | 44 +++++++++++++++++------------------ chart/templates/_database.tpl | 4 ++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/chart/__test-values.yaml b/chart/__test-values.yaml index 3e50196..bcb7877 100644 --- a/chart/__test-values.yaml +++ b/chart/__test-values.yaml @@ -1,27 +1,27 @@ search: host: localhost -# database: -# host: localhost -# user: fa-test -# password: test123 -# root: -# user: asdfasdfasdfasdfasd -# password: asdfasdf - database: host: localhost - dbUser: - username: test - password: test123 - # existingSecret: - # enabled: false - # name: fa-db-user-creds - # passwordKey: fa-password - # rootUser: - # username: testroot - # password: testroot123 - # existingSecret: - # enabled: false - # name: fa-db-root-creds - # passwordKey: root-password + user: fa-test + password: test123 + root: + user: asdfasdfasdfasdfasd + password: asdfasdf + +# database: +# host: localhost +# dbUser: +# username: test +# password: test123 +# existingSecret: +# enabled: false +# name: fa-db-user-creds +# passwordKey: fa-password +# rootUser: +# username: testroot +# password: testroot123 +# existingSecret: +# enabled: false +# name: fa-db-root-creds +# passwordKey: root-password diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl index 16fbe91..882ad3c 100644 --- a/chart/templates/_database.tpl +++ b/chart/templates/_database.tpl @@ -91,7 +91,7 @@ Resolve the FusionAuth database Secret name. {{- required "database.dbUser.existingSecret.name is required when database.dbUser.existingSecret.enabled is true" $dbUserExistingSecret.name -}} {{- end -}} {{- else -}} -{{ .Release.Name }}-credentials +{{ .Release.Name }}-db-credentials {{- end -}} {{- end -}} @@ -207,7 +207,7 @@ Resolve the root database Secret name. {{- required "database.rootUser.existingSecret.name is required when database.rootUser.existingSecret.enabled is true" $rootUserExistingSecret.name -}} {{- end -}} {{- else -}} -{{ .Release.Name }}-root-credentials +{{ .Release.Name }}-db-root-credentials {{- end -}} {{- end -}} From 52d9b147322a69dd80a94f84ce652c48923eb2ce Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 11:37:14 -0600 Subject: [PATCH 20/28] update readme, roll back some changes for simplicity --- chart/README.md | 131 +++++++++++++++------------ chart/__test-values.yaml | 56 ++++++++---- chart/examples/minikube/values.yaml | 4 +- chart/templates/_database.tpl | 19 ++++ chart/templates/_deployment.tpl | 21 +---- chart/templates/_init_containers.tpl | 9 +- chart/templates/deployment.yaml | 11 +-- chart/templates/secret.yaml | 3 +- chart/values.schema.json | 6 -- chart/values.yaml | 8 +- 10 files changed, 148 insertions(+), 120 deletions(-) diff --git a/chart/README.md b/chart/README.md index dd50651..c01efbc 100644 --- a/chart/README.md +++ b/chart/README.md @@ -4,85 +4,104 @@ [FusionAuth](https://fusionauth.io/) is a modern platform for Customer Identity and Access Management (CIAM). FusionAuth provides APIs and a responsive web user interface to support login, registration, localized email, multi-factor authentication, reporting, and much more. -## Breaking Changes +## Important Changes ### 1.67.0 -- **The minimum supported Kubernetes version is now 1.23.0.** This is due - to removing support for long-deprecated beta APIs for `HorizontalPodAutoscaler`, - `Ingress`, and `PodDisruptionBudget`. +⚠️ This release contains several breaking changes, as well as recommended changes. +Review your values file carefully against these notes! + +#### Breaking Changes + +The following changes were made to simplify the chart: + +- **The minimum supported Kubernetes version is now 1.23.0.** This removes support + for long-deprecated beta APIs for `HorizontalPodAutoscaler`, `Ingress`, and + `PodDisruptionBudget`. - **`service.spec` has been removed from the chart** to eliminate the risk of - overwriting valid service configurations. + overwriting valid service configurations. If you used `service.spec` in a way + that is not supported by the standard chart values, please open an issue + describing your use case. - **`service.type` no longer supports `ExternalName`.** `ExternalName` support - is not relevant for this chart. + should not be required in this chart. If you used `ExternalName`, please open + an issue describing your use case. + +#### Recommended Migrations + +There are additional changes to the values format, but there are compatibility shims +in place to give you time to migrate. It's recommended to migrate to the new values +as soon as possible, as the compatibility shims will be removed in a future chart release. -- **`database.existingSecret` has been replaced.** The legacy scalar Secret - name is still accepted for compatibility, but new installs should use the - following password-only existing Secret configuration. Database usernames - continue to come from `database.dbUser.username` and - `database.rootUser.username`. +- **Values for `database` credentials have been updated.** + + Replace the previous values with the new values. + + If you are using `existingSecret` to store the database passwords (recommended): ```yaml + # Old values + database: + user: fusionauth # name of the database user + existingSecret: fusionauth-db-creds # name of the k8s Secret + root: + user: postgres # name of the root user + + # New values database: dbUser: - username: fusionauth + username: fusionauth # name of the database user existingSecret: enabled: true name: fusionauth-db-creds # name of the k8s Secret passwordKey: password # name of the key that stores the password rootUser: - username: postgres + username: postgres # name of the root user existingSecret: enabled: true name: fusionauth-root-creds # name of the k8s Secret - passwordKey: password # name of the key that stores the password + passwordKey: password # name of the key that stores the root password ``` -#### Recommended value migrations - -The following values have been updated, but compatibility shims are in -place in `_helpers.tpl`. It is recommended to migrate to the new values, -as the compatibility shims may be removed in a future release. - -| Previous value | New value | -| ----------------------------------- | --------------------------------------------------------------------------------- | -| `annotations` | `deploymentAnnotations` | -| `initContainers.waitForDb` | `initContainers.waitForDatabase` | -| `initContainers.waitForEs` | `initContainers.waitForSearch` | -| `database.existingSecret` | `database.dbUser.existingSecret` and `database.rootUser.existingSecret` if needed | -| `database.user` | `database.dbUser.username` | -| `database.password` | `database.dbUser.password` | -| `database.root.user` | `database.rootUser.username` | -| `database.root.password` | `database.rootUser.password` | -| `search.user` | `search.basicAuth.username` | -| `search.password` | `search.basicAuth.password` | -| `search.existingSecret` | `search.basicAuth.existingSecret.name: secret-name` | -| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | -| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | -| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | -| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | - -When migrating existing database Secrets, keep database usernames in `database.dbUser.username` and `database.rootUser.username`, set `database.dbUser.existingSecret.enabled: true`, and, if you use root bootstrap credentials, set `database.rootUser.existingSecret.enabled: true`. Database existing Secrets only need password keys. When migrating inline search credentials, set `search.basicAuth.enabled: true`. When migrating search credentials from an existing Secret, set `search.basicAuth.existingSecret.enabled: true`. - -Prefer the following structure for database credentials: + If you are storing the database passwords in clear text (NOT recommended): -```yaml -database: - dbUser: - username: fusionauth - existingSecret: - enabled: true - name: database-user-secret - passwordKey: password - rootUser: - username: postgres - existingSecret: - enabled: true - name: database-root-secret - passwordKey: password -``` + ```yaml + # Old values + database: + user: fusionauth + password: password + root: + user: postgres + password: password + + # New values + database: + dbUser: + username: fusionauth + password: password + rootUser: + username: postgres + password: password + ``` + + 📝 Whether you use the new shape or not, the chart will now create separate + Secrets for the database user and the root user, instead of putting both + passwords into a single secret. + +- `initContainers.waitForEs` renamed to `initContainers.waitForSearch` + + This was renamed because FusionAuth supports both ElasticSearch and OpenSearch. + +| Previous value | New value | +| ----------------------------------- | --------------------------------------------------- | +| `search.user` | `search.basicAuth.username` | +| `search.password` | `search.basicAuth.password` | +| `search.existingSecret` | `search.basicAuth.existingSecret.name: secret-name` | +| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | +| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | +| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | +| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | Prefer the following structure for search basic auth credentials: diff --git a/chart/__test-values.yaml b/chart/__test-values.yaml index bcb7877..df24da9 100644 --- a/chart/__test-values.yaml +++ b/chart/__test-values.yaml @@ -1,27 +1,45 @@ search: host: localhost -database: - host: localhost - user: fa-test - password: test123 - root: - user: asdfasdfasdfasdfasd - password: asdfasdf +# Old values +# database: +# host: localhost +# user: fusionauth +# password: password123 +# root: +# user: postgres +# password: password234 +# New values # database: # host: localhost # dbUser: -# username: test -# password: test123 -# existingSecret: -# enabled: false -# name: fa-db-user-creds -# passwordKey: fa-password +# username: fusionauth +# password: password # rootUser: -# username: testroot -# password: testroot123 -# existingSecret: -# enabled: false -# name: fa-db-root-creds -# passwordKey: root-password +# username: postgres +# password: password + +# Old values +# database: +# host: localhost +# user: fusionauth +# existingSecret: fusionauth-db-creds +# root: +# user: postgres + +# New values +database: + host: localhost + dbUser: + username: fusionauth # name of the database user + existingSecret: + enabled: true + name: fusionauth-db-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the password + rootUser: + username: postgres # name of the root user + existingSecret: + enabled: true + name: fusionauth-root-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the root password diff --git a/chart/examples/minikube/values.yaml b/chart/examples/minikube/values.yaml index e8bbbc9..15056a6 100644 --- a/chart/examples/minikube/values.yaml +++ b/chart/examples/minikube/values.yaml @@ -252,8 +252,8 @@ dnsConfig: {} # dnsPolicy -- Define dnsPolicy for fusionauth pods. dnsPolicy: ClusterFirst -# deploymentAnnotations -- Define annotations for fusionauth deployment. -deploymentAnnotations: {} +# annotations -- Define annotations for fusionauth deployment. +annotations: {} # podAnnotations -- Define annotations for fusionauth pods. podAnnotations: {} diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl index 882ad3c..315e914 100644 --- a/chart/templates/_database.tpl +++ b/chart/templates/_database.tpl @@ -169,6 +169,25 @@ Validate root database credential combinations. {{- end -}} {{- end -}} +{{/* +Validate database credential combinations that span the dbUser and rootUser. +*/}} +{{- define "fusionauth.database.validate" -}} +{{- include "fusionauth.database.dbUser.validate" . }} +{{- include "fusionauth.database.rootUser.validate" . }} +{{- $dbUserExistingSecretEnabled := eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} +{{- $rootUserExistingSecretEnabled := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} +{{- if and $dbUserExistingSecretEnabled $rootUserExistingSecretEnabled -}} +{{- $dbUserSecretName := include "fusionauth.database.dbUser.secretName" . -}} +{{- $rootUserSecretName := include "fusionauth.database.rootUser.secretName" . -}} +{{- $dbUserPasswordKey := include "fusionauth.database.dbUser.passwordKey" . -}} +{{- $rootUserPasswordKey := include "fusionauth.database.rootUser.passwordKey" . -}} +{{- if and (eq $dbUserSecretName $rootUserSecretName) (eq $dbUserPasswordKey $rootUserPasswordKey) -}} +{{- fail "passwordKey values must be different when database.dbUser.existingSecret.name and database.rootUser.existingSecret.name reference the same Secret" -}} +{{- end -}} +{{- end -}} +{{- end -}} + {{/* Resolve whether root database credentials should come from an existing secret. - Current: database.rootUser.existingSecret.enabled diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl index ab60c31..fe06ea5 100644 --- a/chart/templates/_deployment.tpl +++ b/chart/templates/_deployment.tpl @@ -6,28 +6,14 @@ Resolve the reserved kickstart config volume name. {{- printf "%s-config-volume" (include "fusionauth.fullname" .) -}} {{- end -}} -{{/* -Resolve deployment annotations. -Current value: deploymentAnnotations. -Backward compatibility: top-level annotations is deprecated but still accepted. -When both are set, deploymentAnnotations wins. -*/}} -{{- define "fusionauth.deploymentAnnotations" -}} -{{- if .Values.deploymentAnnotations -}} -{{- toYaml .Values.deploymentAnnotations -}} -{{- else if and (hasKey .Values "annotations") .Values.annotations -}} -{{- toYaml .Values.annotations -}} -{{- end -}} -{{- end -}} - {{/* Resolve whether the database wait init container should be rendered. DATABASE_URL supplied through .Values.environment takes precedence over the chart database values, so the chart does not wait on database.host in that mode. */}} -{{- define "fusionauth.deployment.waitForDatabase.enabled" -}} +{{- define "fusionauth.deployment.waitForDb.enabled" -}} {{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} -{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDatabase" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} +{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDb" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* @@ -41,8 +27,7 @@ Resolve whether the search wait init container should be rendered. Validate deployment-only conflicts before rendering the Deployment manifest. */}} {{- define "fusionauth.deployment.validate" -}} -{{- include "fusionauth.database.dbUser.validate" . }} -{{- include "fusionauth.database.rootUser.validate" . }} +{{- include "fusionauth.database.validate" . }} {{- if hasKey .Values.podLabels "app.kubernetes.io/name" }} {{- fail "podLabels cannot override reserved selector label app.kubernetes.io/name" }} {{- end }} diff --git a/chart/templates/_init_containers.tpl b/chart/templates/_init_containers.tpl index 19c2b25..ed3b968 100644 --- a/chart/templates/_init_containers.tpl +++ b/chart/templates/_init_containers.tpl @@ -1,14 +1,9 @@ {{/* vim: set filetype=mustache: */}} {{/* Resolve the database wait init-container flag. -Current value: initContainers.waitForDatabase. -Backward compatibility: deprecated initContainers.waitForDb is still accepted. -When both are set, waitForDatabase wins. */}} -{{- define "fusionauth.initContainers.waitForDatabase" -}} -{{- if hasKey .Values.initContainers "waitForDatabase" -}} -{{- .Values.initContainers.waitForDatabase -}} -{{- else if hasKey .Values.initContainers "waitForDb" -}} +{{- define "fusionauth.initContainers.waitForDb" -}} +{{- if hasKey .Values.initContainers "waitForDb" -}} {{- .Values.initContainers.waitForDb -}} {{- else -}} true diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 5917fec..097cbc5 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,17 +1,16 @@ {{- include "fusionauth.deployment.validate" . }} -{{- $waitForDatabase := eq (include "fusionauth.deployment.waitForDatabase.enabled" .) "true" }} +{{- $waitForDb := eq (include "fusionauth.deployment.waitForDb.enabled" .) "true" }} {{- $waitForSearch := eq (include "fusionauth.deployment.waitForSearch.enabled" .) "true" }} {{- $kickstartVolumeName := include "fusionauth.kickstart.volumeName" . }} -{{- $deploymentAnnotations := include "fusionauth.deploymentAnnotations" . }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "fusionauth.fullname" . }} labels: {{- include "fusionauth.labels" . | nindent 4 }} - {{- with $deploymentAnnotations }} + {{- with .Values.annotations }} annotations: - {{- . | nindent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if not .Values.autoscaling.enabled }} @@ -37,10 +36,10 @@ spec: imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }} {{- end }} - {{- if or (.Values.extraInitContainers) (or $waitForDatabase $waitForSearch) }} + {{- if or (.Values.extraInitContainers) (or $waitForDb $waitForSearch) }} initContainers: {{- end }} - {{- if $waitForDatabase }} + {{- if $waitForDb }} - name: wait-for-db image: "{{ .Values.initContainers.image.repository }}:{{ .Values.initContainers.image.tag }}" args: diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index b89ef44..05f8255 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -1,5 +1,4 @@ -{{- include "fusionauth.database.dbUser.validate" . }} -{{- include "fusionauth.database.rootUser.validate" . }} +{{- include "fusionauth.database.validate" . }} {{- $dbUserGeneratedSecretEnabled := eq (include "fusionauth.database.dbUser.generatedSecret.enabled" .) "true" -}} {{- $rootUserGeneratedSecretEnabled := eq (include "fusionauth.database.rootUser.generatedSecret.enabled" .) "true" -}} {{- if $dbUserGeneratedSecretEnabled -}} diff --git a/chart/values.schema.json b/chart/values.schema.json index 6d0780f..c6b6dc9 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -8,9 +8,6 @@ "annotations": { "type": "object" }, - "deploymentAnnotations": { - "type": "object" - }, "app": { "type": "object", "properties": { @@ -357,9 +354,6 @@ "resources": { "type": "object" }, - "waitForDatabase": { - "type": "boolean" - }, "waitForDb": { "type": "boolean" }, diff --git a/chart/values.yaml b/chart/values.yaml index 9d464ad..81a24e0 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -21,8 +21,8 @@ fullnameOverride: "" # initContainers -- Configures init containers for fusionauth pods. Init containers are used to wait for the database and search engine to be ready before starting fusionauth. initContainers: - # initContainers.waitForDatabase -- waits for the database to be ready. Setting this to `false` is not recommended. - waitForDatabase: true + # initContainers.waitForDb -- waits for the database to be ready. Setting this to `false` is not recommended. + waitForDb: true # initContainers.waitForSearch -- waits for the search engine to be ready. Setting this to `false` is not recommended. waitForSearch: true # initContainers.image -- Configures the docker image to use for init containers. @@ -289,8 +289,8 @@ dnsConfig: {} # dnsPolicy -- Define dnsPolicy for fusionauth pods. dnsPolicy: ClusterFirst -# deploymentAnnotations -- Define annotations for fusionauth deployment. -deploymentAnnotations: {} +# annotations -- Define annotations for fusionauth deployment. +annotations: {} # podAnnotations -- Define annotations for fusionauth pods. podAnnotations: {} From 88353b74741aaa8533e3115339fc94096b0aa06b Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 14:16:49 -0600 Subject: [PATCH 21/28] readme update --- chart/README.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/chart/README.md b/chart/README.md index c01efbc..06f65b1 100644 --- a/chart/README.md +++ b/chart/README.md @@ -85,35 +85,31 @@ as soon as possible, as the compatibility shims will be removed in a future char password: password ``` - 📝 Whether you use the new shape or not, the chart will now create separate - Secrets for the database user and the root user, instead of putting both - passwords into a single secret. + 📝 Whether you use the new shape or not, if you are not using `existingSecrets`, + the chart will now create separate Secrets for the database user and the root + user, instead of putting both passwords into a single secret. - `initContainers.waitForEs` renamed to `initContainers.waitForSearch` - This was renamed because FusionAuth supports both ElasticSearch and OpenSearch. - -| Previous value | New value | -| ----------------------------------- | --------------------------------------------------- | -| `search.user` | `search.basicAuth.username` | -| `search.password` | `search.basicAuth.password` | -| `search.existingSecret` | `search.basicAuth.existingSecret.name: secret-name` | -| `search.existingSecret.enabled` | `search.basicAuth.existingSecret.enabled` | -| `search.existingSecret.name` | `search.basicAuth.existingSecret.name` | -| `search.existingSecret.userKey` | `search.basicAuth.existingSecret.userKey` | -| `search.existingSecret.passwordKey` | `search.basicAuth.existingSecret.passwordKey` | - -Prefer the following structure for search basic auth credentials: - -```yaml -search: - basicAuth: - existingSecret: - enabled: true - name: secret-name - userKey: username - passwordKey: password -``` +- Values for `search` credentials have changed. + - A `basicAuth` key was added to prepare for support of other credential types in the future. + - It now supports using `existingSecret`. + + ```yaml + # Old values + search: + user: username # name of the search user + password: password # password for the search user + + # New values + search: + basicAuth: + username: username + existingSecret: + enabled: true + name: fusionauth-search-creds + passwordKey: password + ``` ### 1.57.1 From fbf3938dcb15b27d071e32461b8a27d260e062f6 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 14:58:06 -0600 Subject: [PATCH 22/28] readme update, remove unused values --- chart/README.md | 82 +++++++++++++++++++++++++------- chart/templates/_deployment.tpl | 10 ++-- chart/templates/_search.tpl | 84 +++------------------------------ chart/values.schema.json | 25 ---------- 4 files changed, 75 insertions(+), 126 deletions(-) diff --git a/chart/README.md b/chart/README.md index 06f65b1..40af2ee 100644 --- a/chart/README.md +++ b/chart/README.md @@ -13,8 +13,6 @@ Review your values file carefully against these notes! #### Breaking Changes -The following changes were made to simplify the chart: - - **The minimum supported Kubernetes version is now 1.23.0.** This removes support for long-deprecated beta APIs for `HorizontalPodAutoscaler`, `Ingress`, and `PodDisruptionBudget`. @@ -28,6 +26,13 @@ The following changes were made to simplify the chart: should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. +- **Chart-managed database password Secrets are now split.** The chart creates + one Secret for `database.dbUser` and one Secret for `database.rootUser` instead + of putting both passwords into one Secret. + - This is not a breaking change for the chart itself, but if you have any external + consumers of the previous secret, they may require updates. + - If you are using `existingSecret` to store passwords, this does not affect you. + #### Recommended Migrations There are additional changes to the values format, but there are compatibility shims @@ -36,8 +41,6 @@ as soon as possible, as the compatibility shims will be removed in a future char - **Values for `database` credentials have been updated.** - Replace the previous values with the new values. - If you are using `existingSecret` to store the database passwords (recommended): ```yaml @@ -93,7 +96,7 @@ as soon as possible, as the compatibility shims will be removed in a future char - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - - It now supports using `existingSecret`. + - `search.basicAuth` supports using `existingSecret`. ```yaml # Old values @@ -104,10 +107,17 @@ as soon as possible, as the compatibility shims will be removed in a future char # New values search: basicAuth: + enabled: true username: username + password: password + + # New values with existingSecret + search: + basicAuth: existingSecret: enabled: true name: fusionauth-search-creds + userKey: username passwordKey: password ``` @@ -304,7 +314,7 @@ You should now be able to connect to the FusionAuth application at http://localh - + @@ -334,7 +344,7 @@ You should now be able to connect to the FusionAuth application at http://localh - + @@ -430,7 +440,7 @@ You should now be able to connect to the FusionAuth application at http://localh - + @@ -494,15 +504,15 @@ You should now be able to connect to the FusionAuth application at http://localh - + - + - + @@ -610,11 +620,53 @@ You should now be able to connect to the FusionAuth application at http://localh + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -652,12 +704,6 @@ You should now be able to connect to the FusionAuth application at http://localh - - - - - - diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl index fe06ea5..82a967b 100644 --- a/chart/templates/_deployment.tpl +++ b/chart/templates/_deployment.tpl @@ -82,7 +82,7 @@ entries with the same name. {{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} {{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} {{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} -{{- $searchExistingSecretEnabled := eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} +{{- $searchExistingSecretEnabled := .Values.search.basicAuth.existingSecret.enabled -}} {{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} {{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} {{- if not $databaseUsernameEnv }} @@ -122,15 +122,15 @@ entries with the same name. - name: SEARCH_USERNAME valueFrom: secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.userKey" . | quote }} + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" .Values.search.basicAuth.existingSecret.name | quote }} + key: {{ .Values.search.basicAuth.existingSecret.userKey | default "username" | quote }} {{- end }} {{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} - name: SEARCH_PASSWORD valueFrom: secretKeyRef: - name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" (include "fusionauth.search.existingSecret.name" .) | quote }} - key: {{ include "fusionauth.search.existingSecret.passwordKey" . | quote }} + name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" .Values.search.basicAuth.existingSecret.name | quote }} + key: {{ .Values.search.basicAuth.existingSecret.passwordKey | default "password" | quote }} {{- end }} {{- end }} {{- if and $chartSearchEnabled (not $searchServersEnv) }} diff --git a/chart/templates/_search.tpl b/chart/templates/_search.tpl index 617e96c..587f61c 100644 --- a/chart/templates/_search.tpl +++ b/chart/templates/_search.tpl @@ -2,98 +2,26 @@ {{/* Resolve whether search basic auth is enabled. Current value: search.basicAuth.enabled. -Backward compatibility: deprecated search.user/search.password and deprecated -search.existingSecret still imply that basic auth is enabled. +Backward compatibility: deprecated search.user/search.password still imply that +basic auth is enabled. */}} {{- define "fusionauth.search.basicAuth.enabled" -}} {{- if .Values.search.basicAuth.enabled -}} true -{{- else if or .Values.search.user .Values.search.password (eq (include "fusionauth.search.existingSecret.enabled" .) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} +{{- else if or .Values.search.user .Values.search.password .Values.search.basicAuth.existingSecret.enabled (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} true {{- else -}} false {{- end -}} {{- end -}} -{{/* -Resolve whether search basic auth credentials should come from an existing -secret. -Current value: search.basicAuth.existingSecret.enabled. -Backward compatibility: deprecated search.existingSecret is still accepted as -either an object or a scalar secret name. -*/}} -{{- define "fusionauth.search.existingSecret.enabled" -}} -{{- if .Values.search.basicAuth.existingSecret.enabled -}} -true -{{- else if .Values.search.existingSecret -}} -{{- if kindIs "string" .Values.search.existingSecret -}} -{{- if .Values.search.existingSecret -}}true{{- else -}}false{{- end -}} -{{- else -}} -{{- .Values.search.existingSecret.enabled | default false -}} -{{- end -}} -{{- else -}} -false -{{- end -}} -{{- end -}} - -{{/* -Resolve the search existing secret name. -Current value: search.basicAuth.existingSecret.name. -Backward compatibility: deprecated search.existingSecret string values are used -directly, and deprecated search.existingSecret.name values are also accepted. -*/}} -{{- define "fusionauth.search.existingSecret.name" -}} -{{- if .Values.search.basicAuth.existingSecret.name -}} -{{- .Values.search.basicAuth.existingSecret.name -}} -{{- else if .Values.search.existingSecret -}} -{{- if kindIs "string" .Values.search.existingSecret -}} -{{- .Values.search.existingSecret -}} -{{- else -}} -{{- .Values.search.existingSecret.name -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Resolve the search username key. -Current value: search.basicAuth.existingSecret.userKey. -Backward compatibility: deprecated search.existingSecret.userKey is still -accepted. -*/}} -{{- define "fusionauth.search.existingSecret.userKey" -}} -{{- if .Values.search.basicAuth.existingSecret.userKey -}} -{{- .Values.search.basicAuth.existingSecret.userKey -}} -{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.userKey -}} -{{- .Values.search.existingSecret.userKey -}} -{{- else -}} -username -{{- end -}} -{{- end -}} - -{{/* -Resolve the search password key. -Current value: search.basicAuth.existingSecret.passwordKey. -Backward compatibility: deprecated search.existingSecret.passwordKey is still -accepted. -*/}} -{{- define "fusionauth.search.existingSecret.passwordKey" -}} -{{- if .Values.search.basicAuth.existingSecret.passwordKey -}} -{{- .Values.search.basicAuth.existingSecret.passwordKey -}} -{{- else if and .Values.search.existingSecret (kindIs "map" .Values.search.existingSecret) .Values.search.existingSecret.passwordKey -}} -{{- .Values.search.existingSecret.passwordKey -}} -{{- else -}} -password -{{- end -}} -{{- end -}} - {{/* Build the search login prefix for SEARCH_SERVERS. -Backward compatibility: this uses the search compatibility helpers above, so -deprecated search.user/search.password and search.existingSecret shapes still -render the same URL prefix. +Backward compatibility: deprecated search.user/search.password still render the +same URL prefix. */}} {{- define "fusionauth.searchLogin" -}} -{{- if eq (include "fusionauth.search.existingSecret.enabled" .) "true" -}} +{{- if .Values.search.basicAuth.existingSecret.enabled -}} $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{- else if eq (include "fusionauth.search.basicAuth.enabled" .) "true" -}} {{- $username := "" -}} diff --git a/chart/values.schema.json b/chart/values.schema.json index c6b6dc9..1fd524f 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -603,31 +603,6 @@ "host": { "type": "string" }, - "existingSecret": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "passwordKey": { - "type": "string" - }, - "userKey": { - "type": "string" - } - } - } - ] - }, "password": { "type": "string" }, From 9a12b3d27d45155043978abfd683a0691dc1496a Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 15:27:46 -0600 Subject: [PATCH 23/28] add tests --- .github/workflows/chart-test-matrix.yml | 32 ++ README.md | 16 + chart/.helmignore | 1 + chart/__test-values.yaml | 45 --- chart/templates/_init_containers.tpl | 7 +- chart/tests/database_test.yaml | 481 ++++++++++++++++++++++++ chart/tests/init_test.yaml | 63 ++++ chart/tests/negative_test.yaml | 71 ++++ chart/tests/search_test.yaml | 138 +++++++ 9 files changed, 805 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/chart-test-matrix.yml delete mode 100644 chart/__test-values.yaml create mode 100644 chart/tests/database_test.yaml create mode 100644 chart/tests/init_test.yaml create mode 100644 chart/tests/negative_test.yaml create mode 100644 chart/tests/search_test.yaml diff --git a/.github/workflows/chart-test-matrix.yml b/.github/workflows/chart-test-matrix.yml new file mode 100644 index 0000000..924a0a5 --- /dev/null +++ b/.github/workflows/chart-test-matrix.yml @@ -0,0 +1,32 @@ +name: Chart test matrix + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + chart-unit-test-matrix: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + suite: + - database + - search + - init + - negative + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Helm + uses: azure/setup-helm@v4 + + - name: Install helm-unittest + run: helm plugin install https://github.com/helm-unittest/helm-unittest.git --verify=false + + - name: Run chart matrix suite + run: helm unittest --strict -f "tests/${{ matrix.suite }}_test.yaml" chart diff --git a/README.md b/README.md index 3fe9091..ad55f46 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,22 @@ Beginning with 1.57.1, the Helm chart version is the same as the FusionAuth app We'll typically release any changes to the chart alongside new FusionAuth app versions. Changes will be called out in the release notes. If changes must be made to the chart outside of the FusionAuth app release cycle, we'll indicate that with a SemVer pre-release tag. For example, `1.57.1-1` would indicate the 1st revision of the chart after the `1.57.1` release, before the next FusionAuth app release. +## Testing Changes + +Install the Helm unit test plugin: + +```sh +helm plugin install https://github.com/helm-unittest/helm-unittest.git --verify=false +``` + +Run the chart test matrix locally: + +```sh +helm unittest --strict chart +``` + +Changes to the chart should have corresponding tests, and the tests must pass prior to release. + ## Releasing the Chart Release the chart by pushing a new tag. diff --git a/chart/.helmignore b/chart/.helmignore index 50af031..f85b0ee 100644 --- a/chart/.helmignore +++ b/chart/.helmignore @@ -20,3 +20,4 @@ .idea/ *.tmproj .vscode/ +tests/ diff --git a/chart/__test-values.yaml b/chart/__test-values.yaml deleted file mode 100644 index df24da9..0000000 --- a/chart/__test-values.yaml +++ /dev/null @@ -1,45 +0,0 @@ -search: - host: localhost - -# Old values -# database: -# host: localhost -# user: fusionauth -# password: password123 -# root: -# user: postgres -# password: password234 - -# New values -# database: -# host: localhost -# dbUser: -# username: fusionauth -# password: password -# rootUser: -# username: postgres -# password: password - -# Old values -# database: -# host: localhost -# user: fusionauth -# existingSecret: fusionauth-db-creds -# root: -# user: postgres - -# New values -database: - host: localhost - dbUser: - username: fusionauth # name of the database user - existingSecret: - enabled: true - name: fusionauth-db-creds # name of the k8s Secret - passwordKey: password # name of the key that stores the password - rootUser: - username: postgres # name of the root user - existingSecret: - enabled: true - name: fusionauth-root-creds # name of the k8s Secret - passwordKey: password # name of the key that stores the root password diff --git a/chart/templates/_init_containers.tpl b/chart/templates/_init_containers.tpl index ed3b968..4a475aa 100644 --- a/chart/templates/_init_containers.tpl +++ b/chart/templates/_init_containers.tpl @@ -14,13 +14,12 @@ true Resolve the search wait init-container flag. Current value: initContainers.waitForSearch. Backward compatibility: deprecated initContainers.waitForEs is still accepted. -When both are set, waitForSearch wins. */}} {{- define "fusionauth.initContainers.waitForSearch" -}} -{{- if hasKey .Values.initContainers "waitForSearch" -}} -{{- .Values.initContainers.waitForSearch -}} -{{- else if hasKey .Values.initContainers "waitForEs" -}} +{{- if hasKey .Values.initContainers "waitForEs" -}} {{- .Values.initContainers.waitForEs -}} +{{- else if hasKey .Values.initContainers "waitForSearch" -}} +{{- .Values.initContainers.waitForSearch -}} {{- else -}} true {{- end -}} diff --git a/chart/tests/database_test.yaml b/chart/tests/database_test.yaml new file mode 100644 index 0000000..c384608 --- /dev/null +++ b/chart/tests/database_test.yaml @@ -0,0 +1,481 @@ +suite: database credential matrix +templates: + - secret.yaml + - deployment.yaml +release: + name: test +tests: + - it: renders legacy inline database credentials without root credentials + set: + database.host: db + database.user: legacy_user + database.password: legacy_pass + search.engine: database + asserts: + - hasDocuments: + count: 1 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-credentials + template: secret.yaml + - equal: + path: data.password + value: bGVnYWN5X3Bhc3M= + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: legacy_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-credentials + key: password + template: deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: legacy_root + template: deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-root-credentials + key: password + template: deployment.yaml + + - it: renders legacy inline database credentials with root credentials + set: + database.host: db + database.user: legacy_user + database.password: legacy_pass + database.root.user: legacy_root + database.root.password: legacy_root_pass + search.engine: database + asserts: + - hasDocuments: + count: 2 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-credentials + documentIndex: 0 + template: secret.yaml + - equal: + path: data.password + value: bGVnYWN5X3Bhc3M= + documentIndex: 0 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-root-credentials + documentIndex: 1 + template: secret.yaml + - equal: + path: data.password + value: bGVnYWN5X3Jvb3RfcGFzcw== + documentIndex: 1 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: legacy_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-credentials + key: password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: legacy_root + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-root-credentials + key: password + template: deployment.yaml + + - it: uses legacy existing database secret for database and root credentials + set: + database.host: db + database.user: legacy_user + database.existingSecret: legacy-db-creds + database.root.user: legacy_root + search.engine: database + asserts: + - hasDocuments: + count: 0 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: legacy_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: legacy-db-creds + key: password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: legacy_root + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: legacy-db-creds + key: rootpassword + template: deployment.yaml + + - it: uses legacy existing database secret without root credentials + set: + database.host: db + database.user: legacy_user + database.existingSecret: legacy-db-creds + search.engine: database + asserts: + - hasDocuments: + count: 0 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: legacy_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: legacy-db-creds + key: password + template: deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: legacy_root + template: deployment.yaml + + - it: renders new inline database credentials without root credentials + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + asserts: + - hasDocuments: + count: 1 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-credentials + template: secret.yaml + - equal: + path: data.password + value: ZGJfcGFzcw== + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: db_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-credentials + key: password + template: deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: root_user + template: deployment.yaml + + - it: renders new inline database credentials with root credentials + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + database.rootUser.username: root_user + database.rootUser.password: root_pass + search.engine: database + asserts: + - hasDocuments: + count: 2 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-credentials + documentIndex: 0 + template: secret.yaml + - equal: + path: data.password + value: ZGJfcGFzcw== + documentIndex: 0 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-root-credentials + documentIndex: 1 + template: secret.yaml + - equal: + path: data.password + value: cm9vdF9wYXNz + documentIndex: 1 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: db_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-credentials + key: password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: root_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-root-credentials + key: password + template: deployment.yaml + + - it: uses a new existing secret for database credentials + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.existingSecret.enabled: true + database.dbUser.existingSecret.name: db-creds + database.dbUser.existingSecret.passwordKey: db-password + search.engine: database + asserts: + - hasDocuments: + count: 0 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USERNAME + value: db_user + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: db-creds + key: db-password + template: deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_USERNAME + value: root_user + template: deployment.yaml + + - it: uses separate new existing secrets for database and root credentials + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.existingSecret.enabled: true + database.dbUser.existingSecret.name: db-creds + database.dbUser.existingSecret.passwordKey: db-password + database.rootUser.username: root_user + database.rootUser.existingSecret.enabled: true + database.rootUser.existingSecret.name: root-creds + database.rootUser.existingSecret.passwordKey: root-password + search.engine: database + asserts: + - hasDocuments: + count: 0 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: db-creds + key: db-password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: root-creds + key: root-password + template: deployment.yaml + + - it: uses existing database secret with generated root secret + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.existingSecret.enabled: true + database.dbUser.existingSecret.name: db-creds + database.dbUser.existingSecret.passwordKey: db-password + database.rootUser.username: root_user + database.rootUser.password: root_pass + search.engine: database + asserts: + - hasDocuments: + count: 1 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-root-credentials + template: secret.yaml + - equal: + path: data.password + value: cm9vdF9wYXNz + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: db-creds + key: db-password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-root-credentials + key: password + template: deployment.yaml + + - it: uses generated database secret with existing root secret + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + database.rootUser.username: root_user + database.rootUser.existingSecret.enabled: true + database.rootUser.existingSecret.name: root-creds + database.rootUser.existingSecret.passwordKey: root-password + search.engine: database + asserts: + - hasDocuments: + count: 1 + template: secret.yaml + - equal: + path: metadata.name + value: test-db-credentials + template: secret.yaml + - equal: + path: data.password + value: ZGJfcGFzcw== + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: test-db-credentials + key: password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: root-creds + key: root-password + template: deployment.yaml + + - it: uses shared new existing database secret when keys are different + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.existingSecret.enabled: true + database.dbUser.existingSecret.name: shared-db-creds + database.dbUser.existingSecret.passwordKey: password + database.rootUser.username: root_user + database.rootUser.existingSecret.enabled: true + database.rootUser.existingSecret.name: shared-db-creds + database.rootUser.existingSecret.passwordKey: rootpassword + search.engine: database + asserts: + - hasDocuments: + count: 0 + template: secret.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: shared-db-creds + key: password + template: deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: shared-db-creds + key: rootpassword + template: deployment.yaml diff --git a/chart/tests/init_test.yaml b/chart/tests/init_test.yaml new file mode 100644 index 0000000..5057533 --- /dev/null +++ b/chart/tests/init_test.yaml @@ -0,0 +1,63 @@ +suite: init container matrix +templates: + - deployment.yaml +release: + name: test +tests: + - it: renders default database and search wait containers + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-db + - equal: + path: spec.template.spec.initContainers[1].name + value: wait-for-search + + - it: disables database wait using waitForDb + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + initContainers.waitForDb: false + asserts: + - notExists: + path: spec.template.spec.initContainers + + - it: disables search wait using legacy waitForEs + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + initContainers.waitForEs: false + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-db + - lengthEqual: + path: spec.template.spec.initContainers + count: 1 + + - it: disables search wait using waitForSearch + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + initContainers.waitForSearch: false + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-db + - lengthEqual: + path: spec.template.spec.initContainers + count: 1 diff --git a/chart/tests/negative_test.yaml b/chart/tests/negative_test.yaml new file mode 100644 index 0000000..1a6b135 --- /dev/null +++ b/chart/tests/negative_test.yaml @@ -0,0 +1,71 @@ +suite: validation matrix +templates: + - secret.yaml + - deployment.yaml +release: + name: test +tests: + - it: fails when new database and root existing secrets use the same Secret and key + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.existingSecret.enabled: true + database.dbUser.existingSecret.name: shared-db-creds + database.dbUser.existingSecret.passwordKey: password + database.rootUser.username: root_user + database.rootUser.existingSecret.enabled: true + database.rootUser.existingSecret.name: shared-db-creds + database.rootUser.existingSecret.passwordKey: password + search.engine: database + asserts: + - failedTemplate: + errorMessage: passwordKey values must be different when database.dbUser.existingSecret.name and database.rootUser.existingSecret.name reference the same Secret + template: secret.yaml + + - it: fails when legacy database password is set without legacy database user + set: + database.host: db + database.password: legacy_pass + database.dbUser.username: "" + search.engine: database + asserts: + - failedTemplate: + errorMessage: database.user is required when database.password is set + template: secret.yaml + + - it: fails when new database password is set without new database username + set: + database.host: db + database.dbUser.username: "" + database.dbUser.password: db_pass + search.engine: database + asserts: + - failedTemplate: + errorMessage: database.dbUser.username is required when database.dbUser.password is set + template: secret.yaml + + - it: fails when legacy root password is set without legacy root user + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + database.root.password: root_pass + database.rootUser.username: "" + search.engine: database + asserts: + - failedTemplate: + errorMessage: database.root.user is required when database.root.password is set + template: secret.yaml + + - it: fails when new root password is set without new root username + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + database.rootUser.username: "" + database.rootUser.password: root_pass + search.engine: database + asserts: + - failedTemplate: + errorMessage: database.rootUser.username is required when database.rootUser.password is set + template: secret.yaml diff --git a/chart/tests/search_test.yaml b/chart/tests/search_test.yaml new file mode 100644 index 0000000..4913f3b --- /dev/null +++ b/chart/tests/search_test.yaml @@ -0,0 +1,138 @@ +suite: search credential matrix +templates: + - deployment.yaml +release: + name: test +tests: + - it: renders search without basic auth + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_TYPE + value: elasticsearch + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_SERVERS + value: http://search:9200 + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_USERNAME + value: search_user + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_PASSWORD + value: search_pass + + - it: renders legacy search inline credentials in SEARCH_SERVERS + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.user: legacy_search_user + search.password: legacy_search_pass + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_SERVERS + value: http://legacy_search_user:legacy_search_pass@search:9200 + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_USERNAME + value: legacy_search_user + + - it: renders new search inline credentials in SEARCH_SERVERS + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.basicAuth.enabled: true + search.basicAuth.username: search_user + search.basicAuth.password: search_pass + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_SERVERS + value: http://search_user:search_pass@search:9200 + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_USERNAME + value: search_user + + - it: uses new existing search secret with custom keys + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.basicAuth.existingSecret.enabled: true + search.basicAuth.existingSecret.name: search-creds + search.basicAuth.existingSecret.userKey: search-username + search.basicAuth.existingSecret.passwordKey: search-password + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_USERNAME + valueFrom: + secretKeyRef: + name: search-creds + key: search-username + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: search-creds + key: search-password + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_SERVERS + value: http://$(SEARCH_USERNAME):$(SEARCH_PASSWORD)@search:9200 + + - it: uses new existing search secret with default keys + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.basicAuth.existingSecret.enabled: true + search.basicAuth.existingSecret.name: search-creds + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_USERNAME + valueFrom: + secretKeyRef: + name: search-creds + key: username + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: search-creds + key: password From df5bff608cc3b980345fab677bba4b523eb0d38a Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 16:19:05 -0600 Subject: [PATCH 24/28] generate README from template --- .github/workflows/chart-docs.yml | 30 ++ .github/workflows/chart-test-matrix.yml | 14 +- .github/workflows/release.yml | 26 +- README.md | 25 + chart/README.md | 673 +++++------------------- chart/README.md.gotmpl | 226 ++++++++ chart/values.yaml | 74 ++- scripts/validate-chart.sh | 24 + 8 files changed, 512 insertions(+), 580 deletions(-) create mode 100644 .github/workflows/chart-docs.yml create mode 100644 chart/README.md.gotmpl create mode 100755 scripts/validate-chart.sh diff --git a/.github/workflows/chart-docs.yml b/.github/workflows/chart-docs.yml new file mode 100644 index 0000000..7164419 --- /dev/null +++ b/.github/workflows/chart-docs.yml @@ -0,0 +1,30 @@ +name: Generate Chart README + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + chart-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: stable + + - name: Install helm-docs + run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2 + + - name: Generate chart docs + run: | + "$(go env GOPATH)/bin/helm-docs" -x --chart-search-root . --chart-to-generate chart + + - name: Verify chart docs are current + run: git diff --exit-code chart/README.md diff --git a/.github/workflows/chart-test-matrix.yml b/.github/workflows/chart-test-matrix.yml index 924a0a5..f7c450e 100644 --- a/.github/workflows/chart-test-matrix.yml +++ b/.github/workflows/chart-test-matrix.yml @@ -1,4 +1,4 @@ -name: Chart test matrix +name: Chart Test Matrix on: pull_request: @@ -8,6 +8,18 @@ on: workflow_dispatch: jobs: + chart-smoke-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Helm + uses: azure/setup-helm@v4 + + - name: Smoke-test chart + run: sh scripts/validate-chart.sh chart + chart-unit-test-matrix: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2f3170..6566691 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,5 @@ +name: Release + on: push: tags: @@ -40,32 +42,12 @@ jobs: "$RELEASE_CHART/$file" done - helm lint "$RELEASE_CHART" - echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV echo "CHART_VERSION=$CHART_VERSION" >> $GITHUB_ENV echo "RELEASE_CHART=$RELEASE_CHART" >> $GITHUB_ENV - - name: lint and test the release chart - run: | - helm lint "$RELEASE_CHART" \ - --set database.host=postgres \ - --set database.dbUser.username=fusionauth \ - --set database.dbUser.password=password \ - --set search.host=opensearch - - helm template test "$RELEASE_CHART" \ - --set database.host=postgres \ - --set database.dbUser.username=fusionauth \ - --set database.dbUser.password=password \ - --set search.engine=database \ - --set ingress.enabled=true \ - --set ingress.hosts[0]=example.com \ - --set ingress.paths[0].path=/ \ - --set ingress.paths[0].pathType=Prefix \ - --set autoscaling.enabled=true \ - --set podDisruptionBudget.enabled=true \ - --set serviceMonitor.enabled=true + - name: Smoke-test release chart + run: sh scripts/validate-chart.sh "$RELEASE_CHART" - name: configure git run: | diff --git a/README.md b/README.md index ad55f46..e41481f 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,37 @@ Run the chart test matrix locally: ```sh helm unittest --strict chart +sh scripts/validate-chart.sh chart ``` Changes to the chart should have corresponding tests, and the tests must pass prior to release. +## Updating Chart Documentation + +The chart README is generated from README.md.gotpml and `helm-docs`. Do not manually update `chart/README.md`. Update the template and regenerate it. + +Install `helm-docs` with homebrew: + +```sh +brew install norwoodj/tap/helm-docs +``` + +Install `helm-docs` with `go install`: + +```sh +go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2 +``` + +Regenerate the chart README after changing `chart/values.yaml`, `chart/Chart.yaml`, or `chart/README.md.gotmpl`: + +```sh +helm-docs -x --chart-search-root . --chart-to-generate chart +``` + ## Releasing the Chart +Make sure you've run tests and generated docs before releasing. The release could fail if these are not done. + Release the chart by pushing a new tag. ``` diff --git a/chart/README.md b/chart/README.md index 40af2ee..f04d08f 100644 --- a/chart/README.md +++ b/chart/README.md @@ -223,542 +223,137 @@ You should now be able to connect to the FusionAuth application at http://localh ## Chart Values -
50 CPU use % threshold to trigger a HPA scale up. Ignored when autoscaling.enabled is false.
database.existingSecretstring""The name of an existing Kubernetes Secret that contains the database passwords.
database.host string"fusionauth" Name of the fusionauth database.
database.passwordstring""Database password for fusionauth to use in normal operation - not required if database.existingSecret is configured.
database.port intShould either be postgresql or mysql. Protocol for jdbc connection to database.
database.root.passworddatabase.fusionauthUser.username string ""Database password for fusionauth to use during initial bootstrap - not required if database.existingSecret is configured.Database username for fusionauth to use in normal operation.
database.fusionauthUser.passwordstring""Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true.
database.fusionauthUser.existingSecret.enabledboolfalseUse an existing Secret for the normal database user credentials.
database.fusionauthUser.existingSecret.namestring""The name of an existing Secret that contains the normal database user credentials.
database.fusionauthUser.existingSecret.usernameKeystring"username"The key in database.fusionauthUser.existingSecret.name that contains the database username.
database.root.userdatabase.fusionauthUser.existingSecret.passwordKeystring"password"The key in database.fusionauthUser.existingSecret.name that contains the database password.
database.rootUser.username string "" Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database.
database.rootUser.passwordstring""Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true.
database.rootUser.existingSecret.enabledboolfalseUse an existing Secret for the root database user credentials.
database.rootUser.existingSecret.namestring""The name of an existing Secret that contains the root database user credentials.
database.rootUser.existingSecret.usernameKeystring"username"The key in database.rootUser.existingSecret.name that contains the root database username.
database.rootUser.existingSecret.passwordKeystring"password"The key in database.rootUser.existingSecret.name that contains the root database password.
database.tls bool"require" If tls is enabled, this configures the mode.
database.userstring""Database username for fusionauth to use in normal operation.
dnsConfig objectShould either be postgresql or mysql. Protocol for jdbc connection to database.
database.fusionauthUser.usernamedatabase.dbUser.username string"""fusionauth" Database username for fusionauth to use in normal operation.
database.fusionauthUser.passworddatabase.dbUser.password string ""Database password for fusionauth to use in normal operation - not required if database.fusionauthUser.existingSecret.enabled is true.Database password for fusionauth to use in normal operation - not required if database.dbUser.existingSecret.enabled is true.
database.fusionauthUser.existingSecret.enableddatabase.dbUser.existingSecret.enabled bool falseUse an existing Secret for the normal database user credentials.Use an existing Secret for the normal database user password.
database.fusionauthUser.existingSecret.namedatabase.dbUser.existingSecret.name string ""The name of an existing Secret that contains the normal database user credentials.
database.fusionauthUser.existingSecret.usernameKeystring"username"The key in database.fusionauthUser.existingSecret.name that contains the database username.The name of an existing Secret that contains the normal database user password.
database.fusionauthUser.existingSecret.passwordKeydatabase.dbUser.existingSecret.passwordKey string "password"The key in database.fusionauthUser.existingSecret.name that contains the database password.The key in database.dbUser.existingSecret.name that contains the database password.
database.rootUser.username string"""postgres" Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database.
database.rootUser.existingSecret.enabled bool falseUse an existing Secret for the root database user credentials.Use an existing Secret for the root database user password.
database.rootUser.existingSecret.name string ""The name of an existing Secret that contains the root database user credentials.
database.rootUser.existingSecret.usernameKeystring"username"The key in database.rootUser.existingSecret.name that contains the root database username.The name of an existing Secret that contains the root database user password.
database.rootUser.existingSecret.passwordKey
database.dbUser.username string"fusionauth""" Database username for fusionauth to use in normal operation.
database.rootUser.username string"postgres""" Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database.
image.repository string"fusionauth/fusionauth-app""docker.io/fusionauth/fusionauth-app" The image repository to use for fusionauth-app.
Create an init container which waits for the database to be ready.
initContainers.waitForEsinitContainers.waitForSearch bool trueCreate an init container which waits for elasticsearch to be ready.Create an init container which waits for the search engine to be ready.
initContainers.image.repository string"busybox""docker.io/library/busybox" Image to use for initContainers docker image.
{} Define resource requests and limits for fusionauth-app.
search.basicAuth.enabledboolfalseEnables elasticsearch basic auth using inline username/password. Not required when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.usernamestring""Username to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.passwordstring""Password to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.existingSecret.enabledboolfalseUse an existing Secret for elasticsearch basic auth credentials.
search.basicAuth.existingSecret.namestring""The name of an existing Secret that contains elasticsearch basic auth credentials.
search.basicAuth.existingSecret.userKeystring"username"The key in search.basicAuth.existingSecret.name that contains the elasticsearch username.
search.basicAuth.existingSecret.passwordKeystring"password"The key in search.basicAuth.existingSecret.name that contains the elasticsearch password.
search.engine string "elasticsearch"Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch.Defines backend for fusionauth search capabilities. Valid values are elasticsearch or database.
search.host9011 Port for the Kubernetes service to expose.
service.specobject{}Any extra fields to add to the service object spec.
service.type string
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDefaultDescription
affinityobject{}Configure affinity rules for the fusionauth Deployment.
annotationsobject{}Define annotations for fusionauth Deployment.
app.memorystring"256M"Configures the amount of memory to allocate to the Java VM (sets FUSIONAUTH_APP_MEMORY).
app.runtimeModestring"development"Configures runtime mode (sets FUSIONAUTH_APP_RUNTIME_MODE). Must be development or production.
app.silentModeboolfalseConfigures silent mode (sets FUSIONAUTH_APP_SILENT_MODE). Must be true or false.
autoscaling.enabledboolfalseEnable Horizontal Pod Autoscaling. See the values file for more HPA parameters.
autoscaling.minReplicasint2Minimum number of running instances when HPA is enabled. Ignored when autoscaling.enabled is false.
autoscaling.maxReplicasint5Maximum number of running instances when HPA is enabled. Ignored when autoscaling.enabled is false.
autoscaling.targetCPUint50CPU use % threshold to trigger a HPA scale up. Ignored when autoscaling.enabled is false.
database.hoststring""Hostname or IP address of the fusionauth database.
database.namestring"fusionauth"Name of the fusionauth database.
database.portint5432Port used by the fusionauth database.
database.protocolstring"postgresql"Should either be postgresql or mysql. Protocol for jdbc connection to database.
database.dbUser.usernamestring""Database username for fusionauth to use in normal operation.
database.dbUser.passwordstring""Database password for fusionauth to use in normal operation - not required if database.dbUser.existingSecret.enabled is true.
database.dbUser.existingSecret.enabledboolfalseUse an existing Secret for the normal database user password.
database.dbUser.existingSecret.namestring""The name of an existing Secret that contains the normal database user password.
database.dbUser.existingSecret.passwordKeystring"password"The key in database.dbUser.existingSecret.name that contains the database password.
database.rootUser.usernamestring""Database username for fusionauth to use during initial bootstrap - not required if you have manually bootstrapped your database.
database.rootUser.passwordstring""Database password for fusionauth to use during initial bootstrap - not required if database.rootUser.existingSecret.enabled is true.
database.rootUser.existingSecret.enabledboolfalseUse an existing Secret for the root database user password.
database.rootUser.existingSecret.namestring""The name of an existing Secret that contains the root database user password.
database.rootUser.existingSecret.passwordKeystring"password"The key in database.rootUser.existingSecret.name that contains the root database password.
database.tlsboolfalseConfigures whether or not to use tls when connecting to the database.
database.tlsModestring"require"If tls is enabled, this configures the mode.
dnsConfigobject{}Define dnsConfig for fusionauth pods.
dnsPolicystring"ClusterFirst"Define dnsPolicy for fusionauth pods.
environmentlist[]Configure additional environment variables.
extraVolumeMountslist[]Define mount paths for extraVolumes.
extraContainerslist[]Create containers for the pods. Can be used for sidecars, ambassador, and adapter patterns.
extraInitContainerslist[]Add extra init containers. Can be used for setup or wait for other dependent services.
extraVolumeslist[]Define extra volumes to mount in the deployment.
fullnameOverridestring""Overrides full resource names.
image.pullPolicystring"IfNotPresent"Kubernetes image pullPolicy to use for fusionauth-app.
image.repositorystring"docker.io/fusionauth/fusionauth-app"The image repository to use for fusionauth-app.
image.tagstring"0.0.0-app-dev"The image tag to pull for fusionauth-app (this is the fusionauth-app version).
imagePullSecretslist[]Configures Kubernetes secrets to use for pulling images from private repositories.
ingress.annotationsobject{}Configure annotations to add to the ingress object.
ingress.enabledboolfalseEnables ingress creation for fusionauth.
ingress.extraPathslist[]Define path objects which will be inserted before regular paths. Can be useful for things like ALB Ingress Controller actions.
ingress.hostslist[]List of hostnames to configure the ingress with.
ingress.ingressClassNamestring""Specify the ingressClass to be used by the Ingress.
ingress.pathslist[]Paths to be used by the Ingress.
ingress.tlslist[]List of secrets used to configure TLS for the ingress.
initContainers.waitForDbbooltrueCreate an init container which waits for the database to be ready.
initContainers.waitForSearchbooltrueCreate an init container which waits for the search engine to be ready.
initContainers.image.repositorystring"docker.io/library/busybox"Image to use for initContainers docker image.
initContainers.image.tagstring"1.36.1"Tag to use for initContainers docker image.
initContainers.resourcesobject{}Resource requests and limits to use for initContainers.
kickstart.dataobject{}Fusionauth kickstart settings.
kickstart.enabledboolfalseEnable fusionauth kickstart settings.
lifecycleobject{}Define custom lifecycle settings for the deployment.
livenessProbeobject -
-livenessProbe:
-  httpGet:
-    path: /
-    port: http
-  failureThreshold: 3
-  periodSeconds: 30
-  timeoutSeconds: 5
Configures a livenessProbe to ensure fusionauth is running.
nameOverridestring""Overrides resource names.
nodeSelectorobject{}Define nodeSelector for kubernetes to use when scheduling fusionauth pods.
podAnnotationsobject{}Define annotations for fusionauth pods.
podDisruptionBudget.enabledboolfalseEnables creation of a PodDisruptionBudget.
podLabelsobject{}Define labels for fusionauth Deployment.
podSecurityContextobject{}Security context for the pod. Ref: Kubernetes docs.
readinessProbeobject -
-readinessProbe:
-  httpGet:
-    path: /
-    port: http
-  failureThreshold: 5
-  timeoutSeconds: 5
Configures a readinessProbe to ensure fusionauth is ready for requests.
replicaCountint1The number of fusionauth-app instances to run.
resourcesobject{}Define resource requests and limits for fusionauth-app.
search.basicAuth.enabledboolfalseEnables elasticsearch basic auth using inline username/password. Not required when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.usernamestring""Username to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.passwordstring""Password to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true.
search.basicAuth.existingSecret.enabledboolfalseUse an existing Secret for elasticsearch basic auth credentials.
search.basicAuth.existingSecret.namestring""The name of an existing Secret that contains elasticsearch basic auth credentials.
search.basicAuth.existingSecret.userKeystring"username"The key in search.basicAuth.existingSecret.name that contains the elasticsearch username.
search.basicAuth.existingSecret.passwordKeystring"password"The key in search.basicAuth.existingSecret.name that contains the elasticsearch password.
search.enginestring"elasticsearch"Defines backend for fusionauth search capabilities. Valid values are elasticsearch or database.
search.hoststring""Hostname or ip to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch.
search.portint9200Port to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch.
search.protocolstring"http"Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch.
securityContextobject{}Security context for the fusionauth container. Ref: Kubernetes docs.
service.annotationsobject{}Extra annotations to add to the service object.
service.portint9011Port for the Kubernetes service to expose.
service.typestring"ClusterIP"Type of Kubernetes service to create.
serviceAccount.annotationsobject{}Extra annotations to add to the service account object.
serviceAccount.automountboolfalseAutomatically mount a service account's API credentials.
serviceAccount.createboolfalseIf set to true, service account will be created. Otherwise, the default serviceaccount will be used.
serviceAccount.namestring""The name of the service account to use. If not set and create is true, a name is generated using the fullname template.
startupProbeobject -
-startupProbe:
-  httpGet:
-    path: /
-    port: http
-  failureThreshold: 20
-  periodSeconds: 10
-  timeoutSeconds: 5
Configures a startupProbe to ensure fusionauth has finished starting up.
tolerationslist[]Define tolerations for kubernetes to use when scheduling fusionauth pods.
topologySpreadConstraintslist[]Define topologySpreadConstraints for kubernetes to use when scheduling fusionauth pods.
+| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Define affinity for kubernetes to use when scheduling fusionauth pods. | +| annotations | object | `{}` | Define annotations for fusionauth deployment. | +| app | object | `{"memory":"256M","runtimeMode":"development","silentMode":false}` | Configures general settings for the fusionauth application | +| app.memory | string | `"256M"` | Configures the amount of memory Java can use | +| app.runtimeMode | string | `"development"` | Configures runtime mode for fusionauth. Should be 'development' or 'production' learn more about the difference here: https://fusionauth.io/docs/v1/tech/reference/configuration | +| app.silentMode | bool | `false` | Configures silent mode for fusionauth. Should be 'true' or 'false' learn more about silent mode here: https://fusionauth.io/docs/get-started/download-and-install/silent-mode silent-mode minimizes downtime during upgrades: https://fusionauth.io/docs/operate/deploy/upgrade#downtime-and-database-migrations | +| autoscaling | object | `{"enabled":false,"maxReplicas":5,"minReplicas":2,"targetCPU":50}` | Configures Horizontal Pod Autoscaling. | +| autoscaling.enabled | bool | `false` | Enable Horizontal Pod Autoscaling. | +| autoscaling.maxReplicas | int | `5` | Maximum number of running instances when HPA is enabled. | +| autoscaling.minReplicas | int | `2` | Minimum number of running instances when HPA is enabled. | +| autoscaling.targetCPU | int | `50` | CPU use % threshold to trigger a HPA scale up. | +| database | object | `{"dbUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"host":"","name":"fusionauth","port":5432,"protocol":"postgresql","rootUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"tls":false,"tlsMode":"require"}` | Configures the database connection for fusionauth | +| database.dbUser | object | `{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""}` | Database credentials for fusionauth to use in normal operation | +| database.dbUser.existingSecret | object | `{"enabled":false,"name":"","passwordKey":"password"}` | Configures an existing secret that contains the normal database user password. | +| database.dbUser.existingSecret.enabled | bool | `false` | Use an existing secret for the normal database user password. | +| database.dbUser.existingSecret.name | string | `""` | The name of an existing secret that contains the normal database user password. | +| database.dbUser.existingSecret.passwordKey | string | `"password"` | The key in the existing secret that contains the database password. | +| database.dbUser.password | string | `""` | Database password for fusionauth to use in normal operation. It is not recommended to set the password in clear text here. Use an existing secret instead. | +| database.dbUser.username | string | `""` | Database username for fusionauth to use in normal operation. | +| database.host | string | `""` | Hostname or ip of the database instance | +| database.name | string | `"fusionauth"` | Name of the fusionauth database | +| database.port | int | `5432` | Port of the database instance | +| database.protocol | string | `"postgresql"` | Protocol for jdbc connection to database [`postgresql|mysql`]. | +| database.rootUser | object | `{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""}` | Database credentials for fusionauth to use during initial bootstrap | +| database.rootUser.existingSecret | object | `{"enabled":false,"name":"","passwordKey":"password"}` | Configures an existing secret that contains the root database user password. | +| database.rootUser.existingSecret.enabled | bool | `false` | Use an existing secret for the root database user password. | +| database.rootUser.existingSecret.name | string | `""` | The name of an existing secret that contains the root database user password. | +| database.rootUser.existingSecret.passwordKey | string | `"password"` | The key in the existing secret that contains the root database password. | +| database.rootUser.password | string | `""` | Database password for fusionauth to use during initial bootstrap It is not recommended to set the password in clear text here. Use an existing secret instead. | +| database.rootUser.username | string | `""` | Database username for fusionauth to use during initial bootstrap | +| database.tls | bool | `false` | Configures whether or not to use tls when connecting to the database | +| database.tlsMode | string | `"require"` | If tls is enabled, this configures the mode | +| dnsConfig | object | `{}` | Define dnsConfig for fusionauth pods. | +| dnsPolicy | string | `"ClusterFirst"` | Define dnsPolicy for fusionauth pods. | +| environment | list | `[]` | Configure additional environment variables. Should only be used for things that are not explicitly set elsewhere in the chart. | +| extraContainers | list | `[]` | Add specs for additional containers if needed. | +| extraInitContainers | list | `[]` | Add specs for additional init containers if needed. | +| extraVolumeMounts | list | `[]` | Associate mountPath for each extraVolumes | +| extraVolumes | list | `[]` | Define extra Volumes. Allow to add existing claimName | +| fullnameOverride | string | `""` | Overrides full resource names | +| image | object | `{"pullPolicy":"IfNotPresent","repository":"docker.io/fusionauth/fusionauth-app","tag":"0.0.0-app-dev"}` | Configures the docker image to use for fusionauth-app | +| image.pullPolicy | string | `"IfNotPresent"` | Kubernetes image pullPolicy to use for fusionauth-app | +| image.repository | string | `"docker.io/fusionauth/fusionauth-app"` | The name of the docker repository for fusionauth-app | +| image.tag | string | `"0.0.0-app-dev"` | The docker tag to pull for fusionauth-app | +| imagePullSecrets | list | `[]` | Configures kubernetes secrets to use for pulling private images | +| ingress | object | `{"annotations":{},"enabled":false,"extraPaths":[],"hosts":[],"ingressClassName":null,"paths":[],"tls":[]}` | Configures ingress for FusionAuth. | +| ingress.annotations | object | `{}` | Configure annotations to add to the ingress object | +| ingress.enabled | bool | `false` | Enables ingress creation for fusionauth. | +| ingress.extraPaths | list | `[]` | Define complete path objects, will be inserted before regular paths. Can be useful for things like ALB Ingress Controller actions | +| ingress.hosts | list | `[]` | List of hostnames to configure the ingress with | +| ingress.ingressClassName | string/null | null | Specify the ingressClass to be used by the Ingress. The kubernetes.io/ingress.class annotation is deprecated as of networking.k8s.io/v1 or Kubernetes 1.22+. | +| ingress.paths | list | `[]` | Paths to be used by the Ingress. | +| ingress.tls | list | `[]` | List of secrets used to configure TLS for the ingress. | +| initContainers | object | `{"image":{"repository":"docker.io/library/busybox","tag":"1.36.1"},"resources":{},"waitForDb":true,"waitForSearch":true}` | Configures init containers for fusionauth pods. Init containers are used to wait for the database and search engine to be ready before starting fusionauth. | +| initContainers.image | object | `{"repository":"docker.io/library/busybox","tag":"1.36.1"}` | Configures the docker image to use for init containers. | +| initContainers.image.repository | string | `"docker.io/library/busybox"` | Docker image to use for initContainers. This image must contain `nc`, `wget` and a shell of some kind to do a simple loop. | +| initContainers.image.tag | string | `"1.36.1"` | Tag to use for initContainers docker image | +| initContainers.resources | object | `{}` | It is recommended to set these values when you understand FusionAuth's resource usage in your specific environment. | +| initContainers.waitForDb | bool | `true` | waits for the database to be ready. Setting this to `false` is not recommended. | +| initContainers.waitForSearch | bool | `true` | waits for the search engine to be ready. Setting this to `false` is not recommended. | +| kickstart | object | `{"data":{},"enabled":false,"file":"/kickstart/kickstart.json"}` | Configures kickstart for initial application setup | +| kickstart.data | object | `{}` | FusionAuth kickstart settings. | +| kickstart.enabled | bool | `false` | Enable kickstart for initial application setup. | +| kickstart.file | string | `"/kickstart/kickstart.json"` | File path FusionAuth should use for kickstart configuration. | +| lifecycle | object | `{}` | Define custom lifecycle settings for the deployment. | +| livenessProbe | object | `{"failureThreshold":3,"httpGet":{"path":"/","port":"http"},"periodSeconds":30,"timeoutSeconds":5}` | Configures a livenessProbe to ensure fusionauth is running | +| livenessProbe.failureThreshold | int | `3` | Failure threshold for the liveness probe. | +| livenessProbe.httpGet | object | `{"path":"/","port":"http"}` | Configures the liveness probe HTTP endpoint. | +| livenessProbe.httpGet.path | string | `"/"` | Path used for the liveness probe. | +| livenessProbe.httpGet.port | string | `"http"` | Port used for the liveness probe. | +| livenessProbe.periodSeconds | int | `30` | Period in seconds between liveness probe checks. | +| livenessProbe.timeoutSeconds | int | `5` | Timeout in seconds for the liveness probe. | +| nameOverride | string | `""` | Overrides resource names | +| nodeSelector | object | `{}` | Define nodeSelector for kubernetes to use when scheduling fusionauth pods. | +| podAnnotations | object | `{}` | Define annotations for fusionauth pods. | +| podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":null}` | Configures the PodDisruptionBudget for FusionAuth pods. | +| podDisruptionBudget.enabled | bool | `false` | Enables creation of a PodDisruptionBudget | +| podDisruptionBudget.maxUnavailable | int/string/null | null | Maximum number of unavailable pods. Cannot be used with minAvailable. Defaults to replicaCount - 1. | +| podDisruptionBudget.minAvailable | int/string/null | null | Minimum number of available pods. Cannot be used with maxUnavailable. | +| podLabels | object | `{}` | Define labels for fusionauth pods. | +| podSecurityContext | object | `{}` | Security context for the pod. Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ | +| readinessProbe | object | `{"failureThreshold":5,"httpGet":{"path":"/","port":"http"},"timeoutSeconds":5}` | Configures a readinessProbe to ensure fusionauth is ready for requests | +| readinessProbe.failureThreshold | int | `5` | Failure threshold for the readiness probe. | +| readinessProbe.httpGet | object | `{"path":"/","port":"http"}` | Configures the readiness probe HTTP endpoint. | +| readinessProbe.httpGet.path | string | `"/"` | Path used for the readiness probe. | +| readinessProbe.httpGet.port | string | `"http"` | Port used for the readiness probe. | +| readinessProbe.timeoutSeconds | int | `5` | Timeout in seconds for the readiness probe. | +| replicaCount | int | `1` | The number of fusionauth-app instances to run | +| resources | object | `{}` | Define resource requests and limits for fusionauth-app. It is recommended to set these values when you understand FusionAuth's resource usage in your specific environment. | +| search | object | `{"basicAuth":{"enabled":false,"existingSecret":{"enabled":false,"name":"","passwordKey":"password","userKey":"username"},"password":"","username":""},"engine":"elasticsearch","host":"","port":9200,"protocol":"http"}` | Configures the search engine for fusionauth | +| search.basicAuth | object | `{"enabled":false,"existingSecret":{"enabled":false,"name":"","passwordKey":"password","userKey":"username"},"password":"","username":""}` | Configures elasticsearch basic auth credentials. Ignored when search.engine is NOT elasticsearch. | +| search.basicAuth.enabled | bool | `false` | Enables elasticsearch basic auth using inline username/password. Not required when search.basicAuth.existingSecret.enabled is true. | +| search.basicAuth.existingSecret | object | `{"enabled":false,"name":"","passwordKey":"password","userKey":"username"}` | Configures an existing secret that contains elasticsearch basic auth credentials. | +| search.basicAuth.existingSecret.enabled | bool | `false` | Use an existing secret for elasticsearch basic auth credentials. | +| search.basicAuth.existingSecret.name | string | `""` | The name of an existing secret that contains elasticsearch basic auth credentials. | +| search.basicAuth.existingSecret.passwordKey | string | `"password"` | The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. | +| search.basicAuth.existingSecret.userKey | string | `"username"` | The key in search.basicAuth.existingSecret.name that contains the elasticsearch username. | +| search.basicAuth.password | string | `""` | Password to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. | +| search.basicAuth.username | string | `""` | Username to use with elasticsearch basic auth. Ignored when search.basicAuth.existingSecret.enabled is true. | +| search.engine | string | `"elasticsearch"` | Defines backend for fusionauth search capabilities. Valid values for engine are 'elasticsearch' or 'database'. | +| search.host | string | `""` | Hostname or ip to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch | +| search.port | int | `9200` | Port to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch | +| search.protocol | string | `"http"` | Protocol to use when connecting to elasticsearch. Ignored when search.engine is NOT elasticsearch | +| securityContext | object | `{}` | Security context for the fusionauth container. Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ | +| service | object | `{"annotations":{},"port":9011,"type":"ClusterIP"}` | Configures the Kubernetes service for FusionAuth. | +| service.annotations | object | `{}` | Extra annotations to add to service object | +| service.port | int | `9011` | Port for the Kubernetes service to expose | +| service.type | string | `"ClusterIP"` | Type of Kubernetes service to create | +| serviceAccount | object | `{"annotations":{},"automount":true,"create":false,"name":""}` | Configures the Kubernetes service account for FusionAuth pods. | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.automount | bool | `true` | Automatically mount a ServiceAccount's API credentials? | +| serviceAccount.create | bool | `false` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| serviceMonitor | object | `{"annotations":{},"basicAuth":{},"enabled":false,"interval":null,"labels":{},"namespaceSelector":{},"path":"/api/prometheus/metrics","relabelings":[],"scrapeTimeout":null}` | Configures a Prometheus operator ServiceMonitor custom resource Ref: https://fusionauth.io/docs/v1/tech/tutorials/prometheus | +| serviceMonitor.annotations | object | `{}` | Annotations to add to the ServiceMonitor object. | +| serviceMonitor.basicAuth | object | `{}` | Configures basic auth for prometheus, this is required for the serviceMonitor to work with FusionAuth because metrics sit behind an authenticated endpoint | +| serviceMonitor.enabled | bool | `false` | Enables creation of a ServiceMonitor | +| serviceMonitor.interval | string/null | null | Interval at which Prometheus should scrape metrics. | +| serviceMonitor.labels | object | `{}` | Labels to add to the ServiceMonitor object. | +| serviceMonitor.namespaceSelector | object | `{}` | Namespace selector for the ServiceMonitor. | +| serviceMonitor.path | string | `"/api/prometheus/metrics"` | Configures path to metrics, defaults to FusionAuth's prometheus metrics API endpoint | +| serviceMonitor.relabelings | list | `[]` | Relabeling rules for the ServiceMonitor endpoint. | +| serviceMonitor.scrapeTimeout | string/null | null | Timeout for Prometheus metric scrapes. | +| startupProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/","port":"http"},"periodSeconds":10,"timeoutSeconds":5}` | Configures a startupProbe to ensure fusionauth has finished starting up | +| startupProbe.failureThreshold | int | `20` | Failure threshold for the startup probe. | +| startupProbe.httpGet | object | `{"path":"/","port":"http"}` | Configures the startup probe HTTP endpoint. | +| startupProbe.httpGet.path | string | `"/"` | Path used for the startup probe. | +| startupProbe.httpGet.port | string | `"http"` | Port used for the startup probe. | +| startupProbe.periodSeconds | int | `10` | Period in seconds between startup probe checks. | +| startupProbe.timeoutSeconds | int | `5` | Timeout in seconds for the startup probe. | +| tolerations | list | `[]` | Define tolerations for kubernetes to use when scheduling fusionauth pods. | +| topologySpreadConstraints | list | `[]` | Define topologySpreadConstraints for kubernetes to use when scheduling fusionauth pods. | diff --git a/chart/README.md.gotmpl b/chart/README.md.gotmpl new file mode 100644 index 0000000..66dd662 --- /dev/null +++ b/chart/README.md.gotmpl @@ -0,0 +1,226 @@ +# FusionAuth Helm Chart + +![Build Status](https://github.com/FusionAuth/charts/actions/workflows/release.yml/badge.svg) + +[FusionAuth](https://fusionauth.io/) is a modern platform for Customer Identity and Access Management (CIAM). FusionAuth provides APIs and a responsive web user interface to support login, registration, localized email, multi-factor authentication, reporting, and much more. + +## Important Changes + +### 1.67.0 + +⚠️ This release contains several breaking changes, as well as recommended changes. +Review your values file carefully against these notes! + +#### Breaking Changes + +- **The minimum supported Kubernetes version is now 1.23.0.** This removes support + for long-deprecated beta APIs for `HorizontalPodAutoscaler`, `Ingress`, and + `PodDisruptionBudget`. + +- **`service.spec` has been removed from the chart** to eliminate the risk of + overwriting valid service configurations. If you used `service.spec` in a way + that is not supported by the standard chart values, please open an issue + describing your use case. + +- **`service.type` no longer supports `ExternalName`.** `ExternalName` support + should not be required in this chart. If you used `ExternalName`, please open + an issue describing your use case. + +- **Chart-managed database password Secrets are now split.** The chart creates + one Secret for `database.dbUser` and one Secret for `database.rootUser` instead + of putting both passwords into one Secret. + - This is not a breaking change for the chart itself, but if you have any external + consumers of the previous secret, they may require updates. + - If you are using `existingSecret` to store passwords, this does not affect you. + +#### Recommended Migrations + +There are additional changes to the values format, but there are compatibility shims +in place to give you time to migrate. It's recommended to migrate to the new values +as soon as possible, as the compatibility shims will be removed in a future chart release. + +- **Values for `database` credentials have been updated.** + + If you are using `existingSecret` to store the database passwords (recommended): + + ```yaml + # Old values + database: + user: fusionauth # name of the database user + existingSecret: fusionauth-db-creds # name of the k8s Secret + root: + user: postgres # name of the root user + + # New values + database: + dbUser: + username: fusionauth # name of the database user + existingSecret: + enabled: true + name: fusionauth-db-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the password + rootUser: + username: postgres # name of the root user + existingSecret: + enabled: true + name: fusionauth-root-creds # name of the k8s Secret + passwordKey: password # name of the key that stores the root password + ``` + + If you are storing the database passwords in clear text (NOT recommended): + + ```yaml + # Old values + database: + user: fusionauth + password: password + root: + user: postgres + password: password + + # New values + database: + dbUser: + username: fusionauth + password: password + rootUser: + username: postgres + password: password + ``` + + 📝 Whether you use the new shape or not, if you are not using `existingSecrets`, + the chart will now create separate Secrets for the database user and the root + user, instead of putting both passwords into a single secret. + +- `initContainers.waitForEs` renamed to `initContainers.waitForSearch` + +- Values for `search` credentials have changed. + - A `basicAuth` key was added to prepare for support of other credential types in the future. + - `search.basicAuth` supports using `existingSecret`. + + ```yaml + # Old values + search: + user: username # name of the search user + password: password # password for the search user + + # New values + search: + basicAuth: + enabled: true + username: username + password: password + + # New values with existingSecret + search: + basicAuth: + existingSecret: + enabled: true + name: fusionauth-search-creds + userKey: username + passwordKey: password + ``` + +### 1.57.1 + +- **The chart version now matches the FusionAuth app version.** + + ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. + +### 1.0.0 + +- **The FusionAuth app version will now default to the latest available at the time of the chart's release.** Release notes will indicate the FusionAuth version included in the chart. + + ⚠️ You can (and probably should) override the `image.tag` field in `values.yaml` to pin the desired version of the FusionAuth application. This ensures that upgrading the helm chart doesn't unexpectedly upgrade the FusionAuth version. + +### 0.8.0 + +- **The `environment` value is now an array instead of an object.** Make sure to reformat your values when you update. + +### 0.4.0 + +- **The external postgresql and elasticsearch charts were dropped.** You will need to maintain those dependencies on your own. + +## Installing the Chart + +You can read the official instructions, including install steps for AWS, GCP, and Azure, in the [FusionAuth Kubernetes installation guide](https://fusionauth.io/docs/get-started/download-and-install/kubernetes/fusionauth-deployment). + +### Prerequisites + +- PostgreSQL or MySQL database +- ElasticSearch or OpenSearch instance (optional) + +⚠️ Though an ElasticSearch or OpenSearch instance is optional, it is strongly recommended for most use cases. + +### Installation + +To install the chart with the release name `fusionauth`: + +```shell +helm repo add fusionauth https://fusionauth.github.io/charts +helm install fusionauth fusionauth/fusionauth \ + --set database.host=[database host] \ + --set database.dbUser.username=[database username] \ + --set database.dbUser.password=[database password] \ + --set search.host=[elasticsearch host] +``` + +## Setting Up a Test Deployment + +This will install FusionAuth and its prerequisites in a single kubernetes namespace, with a configuration suitable for evaluation and testing. **This configuration is not suitable for production.** + +Create and switch to the test namespace. + +```shell +kubectl create namespace fusionauth-test +kubectl config set-context --current --namespace=fusionauth-test +``` + +### Install PostgreSQL + +```shell +helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql +``` + +### Install Opensearch + +Opensearch is optional, but highly recommended. See the note below. + +```shell +helm repo add opensearch https://opensearch-project.github.io/helm-charts/ +helm install opensearch opensearch/opensearch \ +--set singleNode=true \ +--set-json 'extraEnvs=[{"name":"DISABLE_SECURITY_PLUGIN","value":"true"}]' +``` + +### Install FusionAuth + +Wait for the Postgres and Opensearch pods to be ready, then install FusionAuth. + +```shell +export FA_PSQL_PASS=$(kubectl get secret postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d) +helm repo add fusionauth https://fusionauth.github.io/charts +helm install fusionauth fusionauth/fusionauth \ +--set database.host=postgres-postgresql \ +--set database.dbUser.username=fusionauth \ +--set database.dbUser.password=$FA_PSQL_PASS \ +--set search.host=opensearch-cluster-master +``` + +📝 For test deployments, you can remove `--set search.host` and add `--set search.engine=database` to configure FusionAuth to use the database for search instead of a dedicated search host. This is **not recommended** for real-world use, as search performance will be greatly reduced. + +### Connect to FusionAuth + +Create a port forward to connect to the FusionAuth app. + +```shell +kubectl port-forward svc/fusionauth 9011:9011 +``` + +You should now be able to connect to the FusionAuth application at http://localhost:9011 to start the initial setup. + +📝 You may wish to set up an ingress instead of using a port forward. See the table below for how to configure the FusionAuth chart values to add an ingress. + +## Chart Values + +{{ template "chart.valuesTable" . }} diff --git a/chart/values.yaml b/chart/values.yaml index 81a24e0..7907439 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -12,7 +12,7 @@ image: # image.pullPolicy -- Kubernetes image pullPolicy to use for fusionauth-app pullPolicy: IfNotPresent -# image.imagePullSecrets -- Configures kubernetes secrets to use for pulling private images +# imagePullSecrets -- Configures kubernetes secrets to use for pulling private images imagePullSecrets: [] # nameOverride -- Overrides resource names nameOverride: "" @@ -46,6 +46,7 @@ extraInitContainers: [] # extraContainers -- Add specs for additional containers if needed. extraContainers: [] +# service -- Configures the Kubernetes service for FusionAuth. service: # service.type -- Type of Kubernetes service to create type: ClusterIP @@ -68,7 +69,6 @@ database: tlsMode: require # database.name -- Name of the fusionauth database name: fusionauth - # database.username -- Username for the fusionauth database # database.dbUser -- Database credentials for fusionauth to use in normal operation dbUser: @@ -76,7 +76,7 @@ database: username: "" # database.dbUser.password -- Database password for fusionauth to use in normal operation. # It is not recommended to set the password in clear text here. Use an existing secret instead. - # password: "" + password: "" # database.dbUser.existingSecret -- Configures an existing secret that contains the normal database user password. existingSecret: # database.dbUser.existingSecret.enabled -- Use an existing secret for the normal database user password. @@ -91,7 +91,7 @@ database: username: "" # database.rootUser.password -- Database password for fusionauth to use during initial bootstrap # It is not recommended to set the password in clear text here. Use an existing secret instead. - # password: "" + password: "" # database.rootUser.existingSecret -- Configures an existing secret that contains the root database user password. existingSecret: # database.rootUser.existingSecret.enabled -- Use an existing secret for the root database user password. @@ -127,7 +127,7 @@ search: name: "" # search.basicAuth.existingSecret.userKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch username. userKey: username - # -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. + # search.basicAuth.existingSecret.passwordKey -- The key in search.basicAuth.existingSecret.name that contains the elasticsearch password. passwordKey: password # app -- Configures general settings for the fusionauth application @@ -142,7 +142,7 @@ app: # silent-mode minimizes downtime during upgrades: https://fusionauth.io/docs/operate/deploy/upgrade#downtime-and-database-migrations silentMode: false -# environment - Configure additional environment variables. Should only be used for things that are not explicitly set elsewhere in the chart. +# environment -- Configure additional environment variables. Should only be used for things that are not explicitly set elsewhere in the chart. environment: [] # - name: POD_IP # valueFrom: @@ -157,9 +157,11 @@ environment: [] # kickstart -- Configures kickstart for initial application setup kickstart: + # kickstart.enabled -- Enable kickstart for initial application setup. enabled: false # kickstart.file -- File path FusionAuth should use for kickstart configuration. file: /kickstart/kickstart.json + # kickstart.data -- FusionAuth kickstart settings. data: {} # kickstart.json: | # { @@ -198,6 +200,7 @@ kickstart: # setup-password.txt: | # Hallo +# lifecycle -- Define custom lifecycle settings for the deployment. lifecycle: {} # # lifecycle.postStart -- postStart lifecycle command for fusionauth container # postStart: @@ -208,14 +211,18 @@ lifecycle: {} # exec: # command: ["/bin/bash","-c","kill -3 1"] +# podDisruptionBudget -- Configures the PodDisruptionBudget for FusionAuth pods. podDisruptionBudget: # podDisruptionBudget.enabled -- Enables creation of a PodDisruptionBudget enabled: false - # podDisruptionBudget.minAvailable -- Minimum number of available pods. Cannot be used with maxUnavailable. - # minAvailable: 1 - # podDisruptionBudget.maxUnavailable -- Maximum number of unavailable pods. Cannot be used with minAvailable. Defaults to replicaCount - 1. - # maxUnavailable: 1 - + # podDisruptionBudget.minAvailable -- (int/string/null) Minimum number of available pods. Cannot be used with maxUnavailable. + # @default -- null + minAvailable: null + # podDisruptionBudget.maxUnavailable -- (int/string/null) Maximum number of unavailable pods. Cannot be used with minAvailable. Defaults to replicaCount - 1. + # @default -- null + maxUnavailable: null + +# ingress -- Configures ingress for FusionAuth. ingress: # ingress.enabled -- Enables ingress creation for fusionauth. enabled: false @@ -223,10 +230,11 @@ ingress: annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" - # ingress.ingressClassName since the kubernetes.io/ingress.class annotation - # is deprecated as of networking.k8s.io/v1 or kubernetes 1.22+ + # ingress.ingressClassName -- (string/null) Specify the ingressClass to be used by the Ingress. The kubernetes.io/ingress.class annotation is deprecated as of networking.k8s.io/v1 or Kubernetes 1.22+. + # @default -- null ingressClassName: ~ # ingressClassName: nginx + # ingress.paths -- Paths to be used by the Ingress. paths: [] # - path: /* # pathType: Prefix @@ -250,11 +258,15 @@ resources: {} # cpu: 100m # memory: 128Mi -## Autoscaling parameters +# autoscaling -- Configures Horizontal Pod Autoscaling. autoscaling: + # autoscaling.enabled -- Enable Horizontal Pod Autoscaling. enabled: false + # autoscaling.minReplicas -- Minimum number of running instances when HPA is enabled. minReplicas: 2 + # autoscaling.maxReplicas -- Maximum number of running instances when HPA is enabled. maxReplicas: 5 + # autoscaling.targetCPU -- CPU use % threshold to trigger a HPA scale up. targetCPU: 50 # targetMemory: 50 @@ -299,26 +311,43 @@ podLabels: {} # livenessProbe -- Configures a livenessProbe to ensure fusionauth is running livenessProbe: + # livenessProbe.httpGet -- Configures the liveness probe HTTP endpoint. httpGet: + # livenessProbe.httpGet.path -- Path used for the liveness probe. path: / + # livenessProbe.httpGet.port -- Port used for the liveness probe. port: http + # livenessProbe.failureThreshold -- Failure threshold for the liveness probe. failureThreshold: 3 + # livenessProbe.periodSeconds -- Period in seconds between liveness probe checks. periodSeconds: 30 + # livenessProbe.timeoutSeconds -- Timeout in seconds for the liveness probe. timeoutSeconds: 5 # readinessProbe -- Configures a readinessProbe to ensure fusionauth is ready for requests readinessProbe: + # readinessProbe.httpGet -- Configures the readiness probe HTTP endpoint. httpGet: + # readinessProbe.httpGet.path -- Path used for the readiness probe. path: / + # readinessProbe.httpGet.port -- Port used for the readiness probe. port: http + # readinessProbe.failureThreshold -- Failure threshold for the readiness probe. failureThreshold: 5 + # readinessProbe.timeoutSeconds -- Timeout in seconds for the readiness probe. timeoutSeconds: 5 # startupProbe -- Configures a startupProbe to ensure fusionauth has finished starting up startupProbe: + # startupProbe.httpGet -- Configures the startup probe HTTP endpoint. httpGet: + # startupProbe.httpGet.path -- Path used for the startup probe. path: / + # startupProbe.httpGet.port -- Port used for the startup probe. port: http + # startupProbe.failureThreshold -- Failure threshold for the startup probe. failureThreshold: 20 + # startupProbe.periodSeconds -- Period in seconds between startup probe checks. periodSeconds: 10 + # startupProbe.timeoutSeconds -- Timeout in seconds for the startup probe. timeoutSeconds: 5 # extraVolumes -- Define extra Volumes. Allow to add existing claimName @@ -332,14 +361,15 @@ extraVolumeMounts: [] # - name: custom-css-data # mountPath: /usr/local/fusionauth/fusionauth-app/web/custom +# serviceAccount -- Configures the Kubernetes service account for FusionAuth pods. serviceAccount: - # serviceAccount.create - Specifies whether a service account should be created + # serviceAccount.create -- Specifies whether a service account should be created create: false - # serviceAccount.automount - Automatically mount a ServiceAccount's API credentials? + # serviceAccount.automount -- Automatically mount a ServiceAccount's API credentials? automount: true - # serviceAccount.annotations - Annotations to add to the service account + # serviceAccount.annotations -- Annotations to add to the service account annotations: {} - # serviceAccount.name - The name of the service account to use. + # serviceAccount.name -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" @@ -358,9 +388,17 @@ serviceMonitor: # key: user # serviceMonitor.path -- Configures path to metrics, defaults to FusionAuth's prometheus metrics API endpoint path: /api/prometheus/metrics + # serviceMonitor.namespaceSelector -- Namespace selector for the ServiceMonitor. namespaceSelector: {} + # serviceMonitor.annotations -- Annotations to add to the ServiceMonitor object. annotations: {} + # serviceMonitor.labels -- Labels to add to the ServiceMonitor object. labels: {} + # serviceMonitor.interval -- (string/null) Interval at which Prometheus should scrape metrics. + # @default -- null interval: null + # serviceMonitor.scrapeTimeout -- (string/null) Timeout for Prometheus metric scrapes. + # @default -- null scrapeTimeout: null + # serviceMonitor.relabelings -- Relabeling rules for the ServiceMonitor endpoint. relabelings: [] diff --git a/scripts/validate-chart.sh b/scripts/validate-chart.sh new file mode 100755 index 0000000..d518507 --- /dev/null +++ b/scripts/validate-chart.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +set -eu + +chart="${1:-chart}" + +helm lint "$chart" \ + --set database.host=postgres \ + --set database.dbUser.username=fusionauth \ + --set database.dbUser.password=password \ + --set search.host=opensearch + +helm template test "$chart" \ + --set database.host=postgres \ + --set database.dbUser.username=fusionauth \ + --set database.dbUser.password=password \ + --set search.engine=database \ + --set ingress.enabled=true \ + --set ingress.hosts[0]=example.com \ + --set ingress.paths[0].path=/ \ + --set ingress.paths[0].pathType=Prefix \ + --set autoscaling.enabled=true \ + --set podDisruptionBudget.enabled=true \ + --set serviceMonitor.enabled=true \ + >/dev/null From 2d231f8db4565ebdf7dda984bbd6d45e59b07f98 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 16:53:40 -0600 Subject: [PATCH 25/28] do not allow env var orderrides for chart values --- README.md | 2 +- chart/README.md | 15 ++++++--- chart/README.md.gotmpl | 15 ++++++--- chart/templates/_database.tpl | 22 +++++++------ chart/templates/_deployment.tpl | 56 +++++---------------------------- chart/templates/_helpers.tpl | 31 ++++++++++++------ chart/templates/_search.tpl | 14 ++------- chart/templates/secret.yaml | 4 +-- chart/tests/negative_test.yaml | 29 +++++++++++++++++ chart/values.yaml | 4 --- release-notes.md | 2 +- 11 files changed, 97 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index e41481f..a773895 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Changes to the chart should have corresponding tests, and the tests must pass pr ## Updating Chart Documentation -The chart README is generated from README.md.gotpml and `helm-docs`. Do not manually update `chart/README.md`. Update the template and regenerate it. +The chart README is generated from README.md.gotmpl and `helm-docs`. Do not manually update `chart/README.md`. Update the template and regenerate it. Install `helm-docs` with homebrew: diff --git a/chart/README.md b/chart/README.md index f04d08f..8cffea1 100644 --- a/chart/README.md +++ b/chart/README.md @@ -26,12 +26,12 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. -- **Chart-managed database password Secrets are now split.** The chart creates - one Secret for `database.dbUser` and one Secret for `database.rootUser` instead - of putting both passwords into one Secret. +- **Chart-managed database password Secrets are now split.** + - If you are using `existingSecret` to store passwords, this does not affect you. + - `-db-credentials` contains the password from `database.dbUser.password`. + - `-db-root-credentials` contains the password from `database.rootUser.password`. - This is not a breaking change for the chart itself, but if you have any external consumers of the previous secret, they may require updates. - - If you are using `existingSecret` to store passwords, this does not affect you. #### Recommended Migrations @@ -88,12 +88,17 @@ as soon as possible, as the compatibility shims will be removed in a future char password: password ``` - 📝 Whether you use the new shape or not, if you are not using `existingSecrets`, + 📝 Whether you use the new shape or not, if you are not using `existingSecret`, the chart will now create separate Secrets for the database user and the root user, instead of putting both passwords into a single secret. - `initContainers.waitForEs` renamed to `initContainers.waitForSearch` +- `environment` can no longer override variables managed by chart values, such + as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, + `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or + `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. + - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - `search.basicAuth` supports using `existingSecret`. diff --git a/chart/README.md.gotmpl b/chart/README.md.gotmpl index 66dd662..6918d69 100644 --- a/chart/README.md.gotmpl +++ b/chart/README.md.gotmpl @@ -26,12 +26,12 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. -- **Chart-managed database password Secrets are now split.** The chart creates - one Secret for `database.dbUser` and one Secret for `database.rootUser` instead - of putting both passwords into one Secret. +- **Chart-managed database password Secrets are now split.** + - If you are using `existingSecret` to store passwords, this does not affect you. + - `-db-credentials` contains the password from `database.dbUser.password`. + - `-db-root-credentials` contains the password from `database.rootUser.password`. - This is not a breaking change for the chart itself, but if you have any external consumers of the previous secret, they may require updates. - - If you are using `existingSecret` to store passwords, this does not affect you. #### Recommended Migrations @@ -88,12 +88,17 @@ as soon as possible, as the compatibility shims will be removed in a future char password: password ``` - 📝 Whether you use the new shape or not, if you are not using `existingSecrets`, + 📝 Whether you use the new shape or not, if you are not using `existingSecret`, the chart will now create separate Secrets for the database user and the root user, instead of putting both passwords into a single secret. - `initContainers.waitForEs` renamed to `initContainers.waitForSearch` +- `environment` can no longer override variables managed by chart values, such + as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, + `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or + `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. + - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - `search.basicAuth` supports using `existingSecret`. diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl index 315e914..15453df 100644 --- a/chart/templates/_database.tpl +++ b/chart/templates/_database.tpl @@ -8,6 +8,13 @@ Configure TLS if enabled {{- end -}} {{- end -}} +{{/* +Build DATABASE_URL from chart database values. +*/}} +{{- define "fusionauth.database.url" -}} +jdbc:{{ .Values.database.protocol }}://{{- required "database.host is required" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }} +{{- end -}} + {{/* Resolve FusionAuth database username. - Current: database.dbUser.username @@ -113,13 +120,11 @@ password {{/* Resolve whether the chart should create the FusionAuth database credentials -Secret. If DATABASE_PASSWORD is supplied through .Values.environment, that env -var takes precedence and the generated Secret is not needed. +Secret. */}} {{- define "fusionauth.database.dbUser.generatedSecret.enabled" -}} {{- $existingSecret := eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- if and (not $existingSecret) (not $databasePasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- if not $existingSecret -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* @@ -173,6 +178,7 @@ Validate root database credential combinations. Validate database credential combinations that span the dbUser and rootUser. */}} {{- define "fusionauth.database.validate" -}} +{{- include "fusionauth.environment.validate" . }} {{- include "fusionauth.database.dbUser.validate" . }} {{- include "fusionauth.database.rootUser.validate" . }} {{- $dbUserExistingSecretEnabled := eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} @@ -253,17 +259,13 @@ password Resolve whether root database credentials are configured. */}} {{- define "fusionauth.database.rootUser.configured" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- if or (include "fusionauth.database.rootUser.username" .) $databaseRootUsernameEnv -}}true{{- else -}}false{{- end -}} +{{- if include "fusionauth.database.rootUser.username" . -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* Resolve whether the chart should create the root database credentials Secret. -If DATABASE_ROOT_PASSWORD is supplied through .Values.environment, that env var -takes precedence and the generated Secret is not needed. */}} {{- define "fusionauth.database.rootUser.generatedSecret.enabled" -}} {{- $existingSecret := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- if and (eq (include "fusionauth.database.rootUser.configured" .) "true") (not $existingSecret) (not $databaseRootPasswordEnv) -}}true{{- else -}}false{{- end -}} +{{- if and (eq (include "fusionauth.database.rootUser.configured" .) "true") (not $existingSecret) -}}true{{- else -}}false{{- end -}} {{- end -}} diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl index 82a967b..7771116 100644 --- a/chart/templates/_deployment.tpl +++ b/chart/templates/_deployment.tpl @@ -8,12 +8,9 @@ Resolve the reserved kickstart config volume name. {{/* Resolve whether the database wait init container should be rendered. -DATABASE_URL supplied through .Values.environment takes precedence over the -chart database values, so the chart does not wait on database.host in that mode. */}} {{- define "fusionauth.deployment.waitForDb.enabled" -}} -{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} -{{- if and (not $databaseUrlEnv) (eq (include "fusionauth.initContainers.waitForDb" .) "true") .Values.database.host -}}true{{- else -}}false{{- end -}} +{{- if eq (include "fusionauth.initContainers.waitForDb" .) "true" -}}true{{- else -}}false{{- end -}} {{- end -}} {{/* @@ -63,93 +60,56 @@ Validate deployment-only conflicts before rendering the Deployment manifest. {{- end -}} {{/* -Render FusionAuth container environment variables. User-supplied entries in -.Values.environment are rendered first and take precedence over chart-managed -entries with the same name. +Render FusionAuth container environment variables. */}} {{- define "fusionauth.deployment.env" -}} -{{- $databaseUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_USERNAME")) "true" -}} -{{- $databasePasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_PASSWORD")) "true" -}} -{{- $databaseRootUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_USERNAME")) "true" -}} -{{- $databaseRootPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_ROOT_PASSWORD")) "true" -}} -{{- $databaseUrlEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "DATABASE_URL")) "true" -}} -{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} -{{- $searchUsernameEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} -{{- $searchPasswordEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} -{{- $searchServersEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_SERVERS")) "true" -}} -{{- $appMemoryEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_MEMORY")) "true" -}} -{{- $appRuntimeModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_RUNTIME_MODE")) "true" -}} -{{- $appSilentModeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_SILENT_MODE")) "true" -}} -{{- $appKickstartFileEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "FUSIONAUTH_APP_KICKSTART_FILE")) "true" -}} {{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} {{- $searchExistingSecretEnabled := .Values.search.basicAuth.existingSecret.enabled -}} {{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} {{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} -{{- if not $databaseUsernameEnv }} - name: DATABASE_USERNAME - value: {{ required "database.dbUser.username is required unless DATABASE_USERNAME is set in environment; legacy database.user is also accepted" (include "fusionauth.database.dbUser.username" .) | quote }} -{{- end }} -{{- if not $databasePasswordEnv }} + value: {{ required "database.dbUser.username is required; legacy database.user is also accepted" (include "fusionauth.database.dbUser.username" .) | quote }} - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: {{ include "fusionauth.database.dbUser.secretName" . }} key: {{ include "fusionauth.database.dbUser.passwordKey" . | quote }} -{{- end }} {{- if $databaseRootUserConfigured }} -{{- if not $databaseRootUsernameEnv }} - name: DATABASE_ROOT_USERNAME value: {{ include "fusionauth.database.rootUser.username" . | quote }} -{{- end }} -{{- if not $databaseRootPasswordEnv }} - name: DATABASE_ROOT_PASSWORD valueFrom: secretKeyRef: name: {{ include "fusionauth.database.rootUser.secretName" . }} key: {{ include "fusionauth.database.rootUser.passwordKey" . | quote }} {{- end }} -{{- end }} -{{- if not $databaseUrlEnv }} - name: DATABASE_URL - value: "jdbc:{{ .Values.database.protocol }}://{{- required "database.host is required unless DATABASE_URL is set in environment" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }}" -{{- end }} -{{- if not $searchTypeEnv }} + value: {{ include "fusionauth.database.url" . | quote }} - name: SEARCH_TYPE value: {{ .Values.search.engine | quote }} -{{- end }} -{{- if or $chartSearchEnabled $searchServersEnv }} -{{- if and $searchExistingSecretEnabled (not $searchUsernameEnv) }} +{{- if $chartSearchEnabled }} +{{- if $searchExistingSecretEnabled }} - name: SEARCH_USERNAME valueFrom: secretKeyRef: name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" .Values.search.basicAuth.existingSecret.name | quote }} key: {{ .Values.search.basicAuth.existingSecret.userKey | default "username" | quote }} -{{- end }} -{{- if and $searchExistingSecretEnabled (not $searchPasswordEnv) }} - name: SEARCH_PASSWORD valueFrom: secretKeyRef: name: {{ required "search.basicAuth.existingSecret.name is required when search basic auth uses an existing secret" .Values.search.basicAuth.existingSecret.name | quote }} key: {{ .Values.search.basicAuth.existingSecret.passwordKey | default "password" | quote }} {{- end }} -{{- end }} -{{- if and $chartSearchEnabled (not $searchServersEnv) }} - name: SEARCH_SERVERS - value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "search.host is required when search.engine is elasticsearch unless SEARCH_SERVERS is set in environment" .Values.search.host -}}:{{ .Values.search.port }}" + value: "{{ .Values.search.protocol }}://{{ include "fusionauth.searchLogin" . }}{{- required "search.host is required when search.engine is elasticsearch" .Values.search.host -}}:{{ .Values.search.port }}" {{- end }} -{{- if not $appMemoryEnv }} - name: FUSIONAUTH_APP_MEMORY value: {{ .Values.app.memory | quote }} -{{- end }} -{{- if not $appRuntimeModeEnv }} - name: FUSIONAUTH_APP_RUNTIME_MODE value: {{ .Values.app.runtimeMode | quote }} -{{- end }} -{{- if not $appSilentModeEnv }} - name: FUSIONAUTH_APP_SILENT_MODE value: {{ .Values.app.silentMode | quote }} -{{- end }} -{{- if and .Values.kickstart.enabled (not $appKickstartFileEnv) }} +{{- if .Values.kickstart.enabled }} - name: FUSIONAUTH_APP_KICKSTART_FILE value: {{ .Values.kickstart.file | quote }} {{- end }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index aaad475..8da67b8 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -25,17 +25,28 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{/* -Return true when .Values.environment contains an entry with the given name. -Used so user-provided environment variables can take precedence over chart -values without rendering duplicate env names. +Fail when .Values.environment attempts to override chart-managed FusionAuth +environment variables. Use the corresponding chart values instead. */}} -{{- define "fusionauth.environment.has" -}} -{{- $name := .name -}} -{{- $found := false -}} -{{- range .context.Values.environment -}} -{{- if eq .name $name -}} -{{- $found = true -}} +{{- define "fusionauth.environment.validate" -}} +{{- $reserved := list + "DATABASE_USERNAME" + "DATABASE_PASSWORD" + "DATABASE_ROOT_USERNAME" + "DATABASE_ROOT_PASSWORD" + "DATABASE_URL" + "SEARCH_TYPE" + "SEARCH_USERNAME" + "SEARCH_PASSWORD" + "SEARCH_SERVERS" + "FUSIONAUTH_APP_MEMORY" + "FUSIONAUTH_APP_RUNTIME_MODE" + "FUSIONAUTH_APP_SILENT_MODE" + "FUSIONAUTH_APP_KICKSTART_FILE" +-}} +{{- range .Values.environment -}} +{{- if has .name $reserved -}} +{{- fail (printf "environment cannot override chart-managed variable %s; use the corresponding chart value instead" .name) -}} {{- end -}} {{- end -}} -{{- if $found -}}true{{- else -}}false{{- end -}} {{- end -}} diff --git a/chart/templates/_search.tpl b/chart/templates/_search.tpl index 587f61c..996bc98 100644 --- a/chart/templates/_search.tpl +++ b/chart/templates/_search.tpl @@ -8,7 +8,7 @@ basic auth is enabled. {{- define "fusionauth.search.basicAuth.enabled" -}} {{- if .Values.search.basicAuth.enabled -}} true -{{- else if or .Values.search.user .Values.search.password .Values.search.basicAuth.existingSecret.enabled (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true") (eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true") -}} +{{- else if or .Values.search.user .Values.search.password .Values.search.basicAuth.existingSecret.enabled -}} true {{- else -}} false @@ -33,12 +33,6 @@ $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{- $username = .Values.search.user -}} {{- $password = .Values.search.password -}} {{- end -}} -{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_USERNAME")) "true" -}} -{{- $username = "$(SEARCH_USERNAME)" -}} -{{- end -}} -{{- if eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_PASSWORD")) "true" -}} -{{- $password = "$(SEARCH_PASSWORD)" -}} -{{- end -}} {{- printf "%s:%s@" $username $password -}} {{- else -}} {{- printf "" -}} @@ -47,10 +41,8 @@ $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{/* Resolve whether chart-managed Elasticsearch/OpenSearch env and wait behavior -should be rendered. SEARCH_TYPE supplied through .Values.environment takes -precedence over the chart search values. +should be rendered. */}} {{- define "fusionauth.search.chartEnabled" -}} -{{- $searchTypeEnv := eq (include "fusionauth.environment.has" (dict "context" . "name" "SEARCH_TYPE")) "true" -}} -{{- if and (not $searchTypeEnv) (eq .Values.search.engine "elasticsearch") -}}true{{- else -}}false{{- end -}} +{{- if eq .Values.search.engine "elasticsearch" -}}true{{- else -}}false{{- end -}} {{- end -}} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 05f8255..9ff1a6f 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -3,7 +3,7 @@ {{- $rootUserGeneratedSecretEnabled := eq (include "fusionauth.database.rootUser.generatedSecret.enabled" .) "true" -}} {{- if $dbUserGeneratedSecretEnabled -}} {{- if not (include "fusionauth.database.dbUser.password" .) -}} -{{- fail "database.dbUser.password is required when database.dbUser.existingSecret.enabled is false and DATABASE_PASSWORD is not set in environment; legacy database.password is also accepted" }} +{{- fail "database.dbUser.password is required when database.dbUser.existingSecret.enabled is false; legacy database.password is also accepted" }} {{- end -}} apiVersion: v1 data: @@ -20,7 +20,7 @@ type: Opaque --- {{ end }} {{- if not (include "fusionauth.database.rootUser.password" .) -}} -{{- fail "database.rootUser.password is required when database.rootUser.username is set, database.rootUser.existingSecret.enabled is false, and DATABASE_ROOT_PASSWORD is not set in environment; legacy database.root.password is also accepted" }} +{{- fail "database.rootUser.password is required when database.rootUser.username is set and database.rootUser.existingSecret.enabled is false; legacy database.root.password is also accepted" }} {{- end -}} apiVersion: v1 data: diff --git a/chart/tests/negative_test.yaml b/chart/tests/negative_test.yaml index 1a6b135..4baa803 100644 --- a/chart/tests/negative_test.yaml +++ b/chart/tests/negative_test.yaml @@ -69,3 +69,32 @@ tests: - failedTemplate: errorMessage: database.rootUser.username is required when database.rootUser.password is set template: secret.yaml + + - it: fails when environment overrides DATABASE_URL + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + environment: + - name: DATABASE_URL + value: jdbc:postgresql://db:5432/fusionauth + asserts: + - failedTemplate: + errorMessage: environment cannot override chart-managed variable DATABASE_URL; use the corresponding chart value instead + template: secret.yaml + + - it: fails when environment overrides SEARCH_SERVERS + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + environment: + - name: SEARCH_SERVERS + value: http://search:9200 + asserts: + - failedTemplate: + errorMessage: environment cannot override chart-managed variable SEARCH_SERVERS; use the corresponding chart value instead + template: secret.yaml diff --git a/chart/values.yaml b/chart/values.yaml index 7907439..3dffd23 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -150,10 +150,6 @@ environment: [] # fieldPath: status.podIP # - name: FUSIONAUTH_API_KEY # value: test - # Its important to add /kickstart/ as prefix to your kickstart file else it won't work! All other files will be mounted below /kickstart/ - # => Use this environment variable to override the default location '/kickstart/kickstart.json' which is autom. set when kickstart.enabled is set - # - name: FUSIONAUTH_APP_KICKSTART_FILE - # value: /kickstart/kickstart.json # kickstart -- Configures kickstart for initial application setup kickstart: diff --git a/release-notes.md b/release-notes.md index 7e15207..5817197 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,6 @@ ### FusionAuth Helm Chart ${CHART_VERSION} -**⚠️ The FusionAuth app version matches the chart version.** The means that, by default, upgrading the chart will also upgrade the FusionAuth app version. +**⚠️ The FusionAuth app version matches the chart version.** This means that, by default, upgrading the chart will also upgrade the FusionAuth app version. If you do not want a chart upgrade to modify the app version, set the `image.tag` value in the chart. You can set this in a custom values file, or by passing `--set image.tag=[version]` to the helm install/upgrade command, where `[version]` is the FusionAuth app version that you wish to use. From 6b39e100fcd827cf2cd2ae2aec440c73014609f9 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 17:03:45 -0600 Subject: [PATCH 26/28] handle building DATABASE_URL --- chart/README.md | 19 +++++++++++-------- chart/README.md.gotmpl | 12 +++++++----- chart/templates/_database.tpl | 7 +++++++ chart/tests/database_test.yaml | 20 ++++++++++++++++++++ chart/tests/negative_test.yaml | 11 +++++++++++ chart/values.schema.json | 3 +++ chart/values.yaml | 10 ++++++---- 7 files changed, 65 insertions(+), 17 deletions(-) diff --git a/chart/README.md b/chart/README.md index 8cffea1..421eafe 100644 --- a/chart/README.md +++ b/chart/README.md @@ -26,6 +26,13 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. +- `environment` can no longer override variables managed by chart values, such + as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, + `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or + `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. + If you previously set `DATABASE_URL` through `environment`, use `database.url` + instead. + - **Chart-managed database password Secrets are now split.** - If you are using `existingSecret` to store passwords, this does not affect you. - `-db-credentials` contains the password from `database.dbUser.password`. @@ -94,11 +101,6 @@ as soon as possible, as the compatibility shims will be removed in a future char - `initContainers.waitForEs` renamed to `initContainers.waitForSearch` -- `environment` can no longer override variables managed by chart values, such - as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, - `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or - `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. - - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - `search.basicAuth` supports using `existingSecret`. @@ -241,7 +243,7 @@ You should now be able to connect to the FusionAuth application at http://localh | autoscaling.maxReplicas | int | `5` | Maximum number of running instances when HPA is enabled. | | autoscaling.minReplicas | int | `2` | Minimum number of running instances when HPA is enabled. | | autoscaling.targetCPU | int | `50` | CPU use % threshold to trigger a HPA scale up. | -| database | object | `{"dbUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"host":"","name":"fusionauth","port":5432,"protocol":"postgresql","rootUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"tls":false,"tlsMode":"require"}` | Configures the database connection for fusionauth | +| database | object | `{"dbUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"host":"","name":"fusionauth","port":5432,"protocol":"postgresql","rootUser":{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""},"tls":false,"tlsMode":"require","url":""}` | Configures the database connection for fusionauth | | database.dbUser | object | `{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""}` | Database credentials for fusionauth to use in normal operation | | database.dbUser.existingSecret | object | `{"enabled":false,"name":"","passwordKey":"password"}` | Configures an existing secret that contains the normal database user password. | | database.dbUser.existingSecret.enabled | bool | `false` | Use an existing secret for the normal database user password. | @@ -249,9 +251,9 @@ You should now be able to connect to the FusionAuth application at http://localh | database.dbUser.existingSecret.passwordKey | string | `"password"` | The key in the existing secret that contains the database password. | | database.dbUser.password | string | `""` | Database password for fusionauth to use in normal operation. It is not recommended to set the password in clear text here. Use an existing secret instead. | | database.dbUser.username | string | `""` | Database username for fusionauth to use in normal operation. | -| database.host | string | `""` | Hostname or ip of the database instance | +| database.host | string | `""` | Hostname or ip of the database instance. Required by the wait-for-db init container even when database.url is set. | | database.name | string | `"fusionauth"` | Name of the fusionauth database | -| database.port | int | `5432` | Port of the database instance | +| database.port | int | `5432` | Port of the database instance. Required by the wait-for-db init container even when database.url is set. | | database.protocol | string | `"postgresql"` | Protocol for jdbc connection to database [`postgresql|mysql`]. | | database.rootUser | object | `{"existingSecret":{"enabled":false,"name":"","passwordKey":"password"},"password":"","username":""}` | Database credentials for fusionauth to use during initial bootstrap | | database.rootUser.existingSecret | object | `{"enabled":false,"name":"","passwordKey":"password"}` | Configures an existing secret that contains the root database user password. | @@ -262,6 +264,7 @@ You should now be able to connect to the FusionAuth application at http://localh | database.rootUser.username | string | `""` | Database username for fusionauth to use during initial bootstrap | | database.tls | bool | `false` | Configures whether or not to use tls when connecting to the database | | database.tlsMode | string | `"require"` | If tls is enabled, this configures the mode | +| database.url | string | `""` | Optional full JDBC URL. When set, this value is used for DATABASE_URL instead of building it from protocol, host, port, name, tls, and tlsMode. | | dnsConfig | object | `{}` | Define dnsConfig for fusionauth pods. | | dnsPolicy | string | `"ClusterFirst"` | Define dnsPolicy for fusionauth pods. | | environment | list | `[]` | Configure additional environment variables. Should only be used for things that are not explicitly set elsewhere in the chart. | diff --git a/chart/README.md.gotmpl b/chart/README.md.gotmpl index 6918d69..aef10eb 100644 --- a/chart/README.md.gotmpl +++ b/chart/README.md.gotmpl @@ -26,6 +26,13 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. +- `environment` can no longer override variables managed by chart values, such + as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, + `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or + `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. + If you previously set `DATABASE_URL` through `environment`, use `database.url` + instead. + - **Chart-managed database password Secrets are now split.** - If you are using `existingSecret` to store passwords, this does not affect you. - `-db-credentials` contains the password from `database.dbUser.password`. @@ -94,11 +101,6 @@ as soon as possible, as the compatibility shims will be removed in a future char - `initContainers.waitForEs` renamed to `initContainers.waitForSearch` -- `environment` can no longer override variables managed by chart values, such - as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, - `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or - `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. - - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - `search.basicAuth` supports using `existingSecret`. diff --git a/chart/templates/_database.tpl b/chart/templates/_database.tpl index 15453df..ff74570 100644 --- a/chart/templates/_database.tpl +++ b/chart/templates/_database.tpl @@ -12,8 +12,12 @@ Configure TLS if enabled Build DATABASE_URL from chart database values. */}} {{- define "fusionauth.database.url" -}} +{{- if .Values.database.url -}} +{{- .Values.database.url -}} +{{- else -}} jdbc:{{ .Values.database.protocol }}://{{- required "database.host is required" .Values.database.host -}}:{{ .Values.database.port }}/{{ .Values.database.name }}{{ include "fusionauth.databaseTLS" . }} {{- end -}} +{{- end -}} {{/* Resolve FusionAuth database username. @@ -181,6 +185,9 @@ Validate database credential combinations that span the dbUser and rootUser. {{- include "fusionauth.environment.validate" . }} {{- include "fusionauth.database.dbUser.validate" . }} {{- include "fusionauth.database.rootUser.validate" . }} +{{- if and .Values.database.url (eq (include "fusionauth.initContainers.waitForDb" .) "true") (not .Values.database.host) -}} +{{- fail "database.host is required when database.url is set and initContainers.waitForDb is true" -}} +{{- end -}} {{- $dbUserExistingSecretEnabled := eq (include "fusionauth.database.dbUser.existingSecret.enabled" .) "true" -}} {{- $rootUserExistingSecretEnabled := eq (include "fusionauth.database.rootUser.existingSecret.enabled" .) "true" -}} {{- if and $dbUserExistingSecretEnabled $rootUserExistingSecretEnabled -}} diff --git a/chart/tests/database_test.yaml b/chart/tests/database_test.yaml index c384608..b557e76 100644 --- a/chart/tests/database_test.yaml +++ b/chart/tests/database_test.yaml @@ -5,6 +5,26 @@ templates: release: name: test tests: + - it: renders database.url as DATABASE_URL while keeping wait host from database.host + set: + database.url: jdbc:postgresql://db-primary:5432/fusionauth?sslmode=verify-full&connectTimeout=10 + database.host: db-proxy + database.port: 15432 + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL + value: jdbc:postgresql://db-primary:5432/fusionauth?sslmode=verify-full&connectTimeout=10 + template: deployment.yaml + - matchRegex: + path: spec.template.spec.initContainers[0].args[2] + pattern: "nc -zv 'db-proxy' 15432" + template: deployment.yaml + - it: renders legacy inline database credentials without root credentials set: database.host: db diff --git a/chart/tests/negative_test.yaml b/chart/tests/negative_test.yaml index 4baa803..5cd564e 100644 --- a/chart/tests/negative_test.yaml +++ b/chart/tests/negative_test.yaml @@ -84,6 +84,17 @@ tests: errorMessage: environment cannot override chart-managed variable DATABASE_URL; use the corresponding chart value instead template: secret.yaml + - it: fails when database.url is set without a wait host + set: + database.url: jdbc:postgresql://db:5432/fusionauth?sslmode=verify-full + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + asserts: + - failedTemplate: + errorMessage: database.host is required when database.url is set and initContainers.waitForDb is true + template: secret.yaml + - it: fails when environment overrides SEARCH_SERVERS set: database.host: db diff --git a/chart/values.schema.json b/chart/values.schema.json index 1fd524f..b5bdc54 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -101,6 +101,9 @@ "minimum": 1, "maximum": 65535 }, + "url": { + "type": "string" + }, "protocol": { "type": "string", "enum": [ diff --git a/chart/values.yaml b/chart/values.yaml index 3dffd23..88a1325 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -57,12 +57,14 @@ service: # database -- Configures the database connection for fusionauth database: - # database.protocol -- Protocol for jdbc connection to database [`postgresql|mysql`]. - protocol: postgresql - # database.host -- Hostname or ip of the database instance + # database.url -- Optional full JDBC URL. When set, this value is used for DATABASE_URL instead of building it from protocol, host, port, name, tls, and tlsMode. + url: "" + # database.host -- Hostname or ip of the database instance. Required by the wait-for-db init container even when database.url is set. host: "" - # database.port -- Port of the database instance + # database.port -- Port of the database instance. Required by the wait-for-db init container even when database.url is set. port: 5432 + # database.protocol -- Protocol for jdbc connection to database [`postgresql|mysql`]. + protocol: postgresql # database.tls -- Configures whether or not to use tls when connecting to the database tls: false # database.tlsMode -- If tls is enabled, this configures the mode From 911e45e21f48a1dedf57ad930f6872badf58a5c7 Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 22:22:42 -0600 Subject: [PATCH 27/28] validate search basicAuth --- chart/README.md | 2 +- chart/templates/_search.tpl | 6 ++++++ chart/tests/negative_test.yaml | 28 ++++++++++++++++++++++++++++ chart/values.yaml | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/chart/README.md b/chart/README.md index 421eafe..5d2951f 100644 --- a/chart/README.md +++ b/chart/README.md @@ -238,7 +238,7 @@ You should now be able to connect to the FusionAuth application at http://localh | app.memory | string | `"256M"` | Configures the amount of memory Java can use | | app.runtimeMode | string | `"development"` | Configures runtime mode for fusionauth. Should be 'development' or 'production' learn more about the difference here: https://fusionauth.io/docs/v1/tech/reference/configuration | | app.silentMode | bool | `false` | Configures silent mode for fusionauth. Should be 'true' or 'false' learn more about silent mode here: https://fusionauth.io/docs/get-started/download-and-install/silent-mode silent-mode minimizes downtime during upgrades: https://fusionauth.io/docs/operate/deploy/upgrade#downtime-and-database-migrations | -| autoscaling | object | `{"enabled":false,"maxReplicas":5,"minReplicas":2,"targetCPU":50}` | Configures Horizontal Pod Autoscaling. | +| autoscaling | object | `{"enabled":false,"maxReplicas":5,"minReplicas":2,"targetCPU":50}` | Configures Horizontal Pod Autoscaling. If you enable autoscaling, you will need to also set resource requests for the corresponding targets. | | autoscaling.enabled | bool | `false` | Enable Horizontal Pod Autoscaling. | | autoscaling.maxReplicas | int | `5` | Maximum number of running instances when HPA is enabled. | | autoscaling.minReplicas | int | `2` | Minimum number of running instances when HPA is enabled. | diff --git a/chart/templates/_search.tpl b/chart/templates/_search.tpl index 996bc98..abef65a 100644 --- a/chart/templates/_search.tpl +++ b/chart/templates/_search.tpl @@ -29,6 +29,12 @@ $(SEARCH_USERNAME):$(SEARCH_PASSWORD)@ {{- if .Values.search.basicAuth.enabled -}} {{- $username = .Values.search.basicAuth.username -}} {{- $password = .Values.search.basicAuth.password -}} +{{- if not $username -}} +{{- fail "search.basicAuth.username is required when search.basicAuth.enabled is true and search.basicAuth.existingSecret.enabled is false" -}} +{{- end -}} +{{- if not $password -}} +{{- fail "search.basicAuth.password is required when search.basicAuth.enabled is true and search.basicAuth.existingSecret.enabled is false" -}} +{{- end -}} {{- else -}} {{- $username = .Values.search.user -}} {{- $password = .Values.search.password -}} diff --git a/chart/tests/negative_test.yaml b/chart/tests/negative_test.yaml index 5cd564e..d233ec6 100644 --- a/chart/tests/negative_test.yaml +++ b/chart/tests/negative_test.yaml @@ -109,3 +109,31 @@ tests: - failedTemplate: errorMessage: environment cannot override chart-managed variable SEARCH_SERVERS; use the corresponding chart value instead template: secret.yaml + + - it: fails when search basic auth is enabled without a username + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.basicAuth.enabled: true + search.basicAuth.password: search_pass + asserts: + - failedTemplate: + errorMessage: search.basicAuth.username is required when search.basicAuth.enabled is true and search.basicAuth.existingSecret.enabled is false + template: deployment.yaml + + - it: fails when search basic auth is enabled without a password + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: elasticsearch + search.host: search + search.basicAuth.enabled: true + search.basicAuth.username: search_user + asserts: + - failedTemplate: + errorMessage: search.basicAuth.password is required when search.basicAuth.enabled is true and search.basicAuth.existingSecret.enabled is false + template: deployment.yaml diff --git a/chart/values.yaml b/chart/values.yaml index 88a1325..a8a8778 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -256,7 +256,7 @@ resources: {} # cpu: 100m # memory: 128Mi -# autoscaling -- Configures Horizontal Pod Autoscaling. +# autoscaling -- Configures Horizontal Pod Autoscaling. If you enable autoscaling, you will need to also set resource requests for the corresponding targets. autoscaling: # autoscaling.enabled -- Enable Horizontal Pod Autoscaling. enabled: false From a3f069ed852894123fb6515ce096017bf1fb80bf Mon Sep 17 00:00:00 2001 From: John Jeffers Date: Fri, 22 May 2026 23:42:50 -0600 Subject: [PATCH 28/28] readme update, parse env properly --- chart/README.md | 45 ++++++++++++++-------- chart/README.md.gotmpl | 45 ++++++++++++++-------- chart/templates/_deployment.tpl | 4 +- chart/templates/tests/test-connection.yaml | 15 -------- chart/tests/database_test.yaml | 23 +++++++++++ 5 files changed, 86 insertions(+), 46 deletions(-) delete mode 100644 chart/templates/tests/test-connection.yaml diff --git a/chart/README.md b/chart/README.md index 5d2951f..cb08d6a 100644 --- a/chart/README.md +++ b/chart/README.md @@ -26,24 +26,39 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. -- `environment` can no longer override variables managed by chart values, such - as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, - `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or - `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. - If you previously set `DATABASE_URL` through `environment`, use `database.url` - instead. - -- **Chart-managed database password Secrets are now split.** +- **`environment` can no longer override variables managed by chart values.** + If you have set any of the following variables in the `environment` section, + you must remove them and use the corresponding chart values instead. + | Env Var | Chart Value | + | --- | --- | + | `DATABASE_USERNAME` | `database.dbUser.username` | + | `DATABASE_PASSWORD` | `database.dbUser.password` | + | `DATABASE_ROOT_USERNAME` | `database.rootUser.username` | + | `DATABASE_ROOT_PASSWORD` | `database.rootUser.password` | + | `DATABASE_URL` | `database.url` | + | `FUSIONAUTH_APP_MEMORY` | `fusionauth.app.memory` | + | `FUSIONAUTH_APP_RUNTIME_MODE` | `fusionauth.app.runtimeMode` | + | `FUSIONAUTH_APP_SILENT_MODE` | `fusionauth.app.silentMode` | + | `FUSIONAUTH_APP_KICKSTART_FILE` | `kickstart.file` | + | `SEARCH_TYPE` | `search.engine` | + | `SEARCH_SERVERS` | `search.host`
`search.protocol`
`search.port` | + | `SEARCH_USERNAME` | `search.basicAuth.username` | + | `SEARCH_PASSWORD` | `search.basicAuth.password` | + +- **Chart-managed database secrets have changed.** - If you are using `existingSecret` to store passwords, this does not affect you. - - `-db-credentials` contains the password from `database.dbUser.password`. - - `-db-root-credentials` contains the password from `database.rootUser.password`. - - This is not a breaking change for the chart itself, but if you have any external - consumers of the previous secret, they may require updates. + - If you are not using `existingSecret`, we recommend that you do, as storing passwords + in clear text in the values file is not secure. If you continue to store passwords in + the values file, you will be impacted by these changes. + - The previous secret `-credentials` is no longer created or used by + the chart. Instead, the chart creates two new secrets: + - `-db-credentials` contains the password from `database.dbUser.password`. + - `-db-root-credentials` contains the password from `database.rootUser.password`. #### Recommended Migrations -There are additional changes to the values format, but there are compatibility shims -in place to give you time to migrate. It's recommended to migrate to the new values +There are additional changes to the values, but these changes include compatibility +shims to give you time to migrate. It's recommended to migrate to the new values as soon as possible, as the compatibility shims will be removed in a future chart release. - **Values for `database` credentials have been updated.** @@ -103,7 +118,7 @@ as soon as possible, as the compatibility shims will be removed in a future char - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - - `search.basicAuth` supports using `existingSecret`. + - `search.basicAuth` now supports `existingSecret`. ```yaml # Old values diff --git a/chart/README.md.gotmpl b/chart/README.md.gotmpl index aef10eb..69c3ce5 100644 --- a/chart/README.md.gotmpl +++ b/chart/README.md.gotmpl @@ -26,24 +26,39 @@ Review your values file carefully against these notes! should not be required in this chart. If you used `ExternalName`, please open an issue describing your use case. -- `environment` can no longer override variables managed by chart values, such - as `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `SEARCH_TYPE`, - `SEARCH_SERVERS`, `FUSIONAUTH_APP_MEMORY`, or - `FUSIONAUTH_APP_KICKSTART_FILE`. Use the corresponding chart values instead. - If you previously set `DATABASE_URL` through `environment`, use `database.url` - instead. - -- **Chart-managed database password Secrets are now split.** +- **`environment` can no longer override variables managed by chart values.** + If you have set any of the following variables in the `environment` section, + you must remove them and use the corresponding chart values instead. + | Env Var | Chart Value | + | --- | --- | + | `DATABASE_USERNAME` | `database.dbUser.username` | + | `DATABASE_PASSWORD` | `database.dbUser.password` | + | `DATABASE_ROOT_USERNAME` | `database.rootUser.username` | + | `DATABASE_ROOT_PASSWORD` | `database.rootUser.password` | + | `DATABASE_URL` | `database.url` | + | `FUSIONAUTH_APP_MEMORY` | `fusionauth.app.memory` | + | `FUSIONAUTH_APP_RUNTIME_MODE` | `fusionauth.app.runtimeMode` | + | `FUSIONAUTH_APP_SILENT_MODE` | `fusionauth.app.silentMode` | + | `FUSIONAUTH_APP_KICKSTART_FILE` | `kickstart.file` | + | `SEARCH_TYPE` | `search.engine` | + | `SEARCH_SERVERS` | `search.host`
`search.protocol`
`search.port` | + | `SEARCH_USERNAME` | `search.basicAuth.username` | + | `SEARCH_PASSWORD` | `search.basicAuth.password` | + +- **Chart-managed database secrets have changed.** - If you are using `existingSecret` to store passwords, this does not affect you. - - `-db-credentials` contains the password from `database.dbUser.password`. - - `-db-root-credentials` contains the password from `database.rootUser.password`. - - This is not a breaking change for the chart itself, but if you have any external - consumers of the previous secret, they may require updates. + - If you are not using `existingSecret`, we recommend that you do, as storing passwords + in clear text in the values file is not secure. If you continue to store passwords in + the values file, you will be impacted by these changes. + - The previous secret `-credentials` is no longer created or used by + the chart. Instead, the chart creates two new secrets: + - `-db-credentials` contains the password from `database.dbUser.password`. + - `-db-root-credentials` contains the password from `database.rootUser.password`. #### Recommended Migrations -There are additional changes to the values format, but there are compatibility shims -in place to give you time to migrate. It's recommended to migrate to the new values +There are additional changes to the values, but these changes include compatibility +shims to give you time to migrate. It's recommended to migrate to the new values as soon as possible, as the compatibility shims will be removed in a future chart release. - **Values for `database` credentials have been updated.** @@ -103,7 +118,7 @@ as soon as possible, as the compatibility shims will be removed in a future char - Values for `search` credentials have changed. - A `basicAuth` key was added to prepare for support of other credential types in the future. - - `search.basicAuth` supports using `existingSecret`. + - `search.basicAuth` now supports `existingSecret`. ```yaml # Old values diff --git a/chart/templates/_deployment.tpl b/chart/templates/_deployment.tpl index 7771116..28c49fb 100644 --- a/chart/templates/_deployment.tpl +++ b/chart/templates/_deployment.tpl @@ -66,7 +66,9 @@ Render FusionAuth container environment variables. {{- $databaseRootUserConfigured := eq (include "fusionauth.database.rootUser.configured" .) "true" -}} {{- $searchExistingSecretEnabled := .Values.search.basicAuth.existingSecret.enabled -}} {{- $chartSearchEnabled := eq (include "fusionauth.search.chartEnabled" .) "true" -}} -{{- if .Values.environment }}{{ toYaml .Values.environment }}{{ end -}} +{{- if .Values.environment }} +{{ toYaml .Values.environment }} +{{- end }} - name: DATABASE_USERNAME value: {{ required "database.dbUser.username is required; legacy database.user is also accepted" (include "fusionauth.database.dbUser.username" .) | quote }} - name: DATABASE_PASSWORD diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml deleted file mode 100644 index 5b1d515..0000000 --- a/chart/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "fusionauth.fullname" . }}-test-connection" - labels: - {{- include "fusionauth.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: wget - image: "{{ .Values.initContainers.image.repository }}:{{ .Values.initContainers.image.tag }}" - command: ['wget'] - args: ['{{ include "fusionauth.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/chart/tests/database_test.yaml b/chart/tests/database_test.yaml index b557e76..ce2eae2 100644 --- a/chart/tests/database_test.yaml +++ b/chart/tests/database_test.yaml @@ -25,6 +25,29 @@ tests: pattern: "nc -zv 'db-proxy' 15432" template: deployment.yaml + - it: renders custom environment entries before chart-managed entries + set: + database.host: db + database.dbUser.username: db_user + database.dbUser.password: db_pass + search.engine: database + environment: + - name: SOME_VAR + value: some_value + asserts: + - equal: + path: spec.template.spec.containers[0].env[0] + value: + name: SOME_VAR + value: some_value + template: deployment.yaml + - equal: + path: spec.template.spec.containers[0].env[1] + value: + name: DATABASE_USERNAME + value: db_user + template: deployment.yaml + - it: renders legacy inline database credentials without root credentials set: database.host: db