From 66b98c70fe7ce7380f13b6e08122178f019e0682 Mon Sep 17 00:00:00 2001 From: Vyacheslav Klimov Date: Thu, 23 Oct 2025 20:33:07 +0700 Subject: [PATCH] feat: add support TPL in fields Signed-off-by: Vyacheslav Klimov --- .../examples/subchart-with-globals.yaml | 75 +++++++++++++++++++ charts/cluster/templates/_backup.tpl | 2 +- .../templates/_barman_object_store.tpl | 35 +++++---- charts/cluster/templates/_bootstrap.tpl | 9 ++- .../templates/_external_source_cluster.tpl | 18 ++--- charts/cluster/templates/_helpers.tpl | 17 +++++ charts/cluster/templates/cluster.yaml | 12 +-- 7 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 charts/cluster/examples/subchart-with-globals.yaml diff --git a/charts/cluster/examples/subchart-with-globals.yaml b/charts/cluster/examples/subchart-with-globals.yaml new file mode 100644 index 0000000000..5eeea848a5 --- /dev/null +++ b/charts/cluster/examples/subchart-with-globals.yaml @@ -0,0 +1,75 @@ +# Example: Using CloudNative PG Chart as a Subchart with Global Values and TPL supporting +# +# This example demonstrates how to use this chart as a subchart in a parent chart, +# leveraging global values for dynamic configuration. This is useful when multiple +# charts need to share common values like database names, backup buckets, AWS regions, etc. +# + +# Global values (typically defined in parent chart's values.yaml) +# These are shared across multiple subcharts +global: + accountId: "123456789012" + region: us-east-1 + resourcePrefix: prod- + + # Database configuration (used across subcharts) + dbName: application_db + dbOwner: app_owner + backupBucket: postgresql-backups + iamRole: ekes-postgres-role + environment: production + project: mobile-app + +# PostgreSQL Cluster Configuration (subchart-specific) +type: postgresql +mode: standalone + +cluster: + instances: 3 + + # Initialize database with dynamic name from globals + initdb: + database: "{{ .Values.global.dbName }}" + owner: "{{ .Values.global.dbOwner }}" + encoding: UTF8 + + storage: + size: 10Gi + storageClass: gp3 + + walStorage: + enabled: true + size: 10Gi + storageClass: gp3 + + primaryUpdateMethod: switchover + primaryUpdateStrategy: unsupervised + + postgresql: {} + + # Service account with IAM role annotation from globals + serviceAccountTemplate: + metadata: + annotations: + iamRoleArn: "arn:aws:iam::{{ .Values.global.accountId }}:role/{{ .Values.global.iamRole }}" + labels: + environment: "{{ .Values.global.environment }}" + project: "{{ .Values.global.project }}" + +# Backups configuration with dynamic bucket name from globals +backups: + enabled: true + provider: s3 + s3: + region: "{{ .Values.global.region }}" + bucket: "{{ .Values.global.resourcePrefix }}{{ .Values.global.backupBucket }}" + path: "/{{ .Release.Name }}" + inheritFromIAMRole: true + + scheduledBackups: + - name: daily-full + schedule: "0 6 * * *" + method: barmanObjectStore + retentionPolicy: "14d" + secret: + name: "{{ .Release.Name }}-backup-s3-creds" diff --git a/charts/cluster/templates/_backup.tpl b/charts/cluster/templates/_backup.tpl index 60270ea699..607d7412b7 100644 --- a/charts/cluster/templates/_backup.tpl +++ b/charts/cluster/templates/_backup.tpl @@ -17,7 +17,7 @@ backup: {{- end }} jobs: {{ .Values.backups.data.jobs }} - {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.backups "secretPrefix" "backup" }} + {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.backups "secretPrefix" "backup" "context" $ }} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 2 }} {{- end }} {{- end }} diff --git a/charts/cluster/templates/_barman_object_store.tpl b/charts/cluster/templates/_barman_object_store.tpl index c00e412131..7a39f16f94 100644 --- a/charts/cluster/templates/_barman_object_store.tpl +++ b/charts/cluster/templates/_barman_object_store.tpl @@ -1,25 +1,28 @@ {{- define "cluster.barmanObjectStoreConfig" -}} {{- if .scope.endpointURL }} - endpointURL: {{ .scope.endpointURL | quote }} + endpointURL: {{ include "tpl" (dict "value" .scope.endpointURL "context" .context) | quote }} {{- end }} {{- if or (.scope.endpointCA.create) (.scope.endpointCA.name) }} endpointCA: - name: {{.scope.endpointCA.name }} + name: {{ include "tpl" (dict "value" .scope.endpointCA.name "context" .context) }} key: {{ .scope.endpointCA.key }} {{- end }} {{- if .scope.destinationPath }} - destinationPath: {{ .scope.destinationPath }} + destinationPath: {{ include "tpl" (dict "value" .scope.destinationPath "context" .context) | quote }} {{- end }} {{- if eq .scope.provider "s3" }} {{- if empty .scope.endpointURL }} - endpointURL: "https://s3.{{ required "You need to specify S3 region if endpointURL is not specified." .scope.s3.region }}.amazonaws.com" + {{- $region := include "tpl" (dict "value" (required "You need to specify S3 region if endpointURL is not specified." .scope.s3.region) "context" .context) }} + endpointURL: {{ printf "https://s3.%s.amazonaws.com" $region | quote }} {{- end }} {{- if empty .scope.destinationPath }} - destinationPath: "s3://{{ required "You need to specify S3 bucket if destinationPath is not specified." .scope.s3.bucket }}{{ .scope.s3.path }}" + {{- $bucket := include "tpl" (dict "value" (required "You need to specify S3 bucket if destinationPath is not specified." .scope.s3.bucket) "context" .context) }} + {{- $path := include "tpl" (dict "value" .scope.s3.path "context" .context) }} + destinationPath: {{ printf "s3://%s%s" $bucket $path | quote }} {{- end }} {{- $secretName := coalesce .scope.secret.name (printf "%s-%s-s3-creds" .chartFullname .secretPrefix) }} s3Credentials: @@ -27,15 +30,18 @@ inheritFromIAMRole: true {{- else }} accessKeyId: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: ACCESS_KEY_ID secretAccessKey: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: ACCESS_SECRET_KEY {{- end }} {{- else if eq .scope.provider "azure" }} + {{- if empty .scope.destinationPath }} - destinationPath: "https://{{ required "You need to specify Azure storageAccount if destinationPath is not specified." .scope.azure.storageAccount }}.{{ .scope.azure.serviceName }}.core.windows.net/{{ .scope.azure.containerName }}{{ .scope.azure.path }}" + {{- $storageAccount := include "tpl" (dict "value" (required "You need to specify Azure storageAccount if destinationPath is not specified." .scope.azure.storageAccount) "context" .context) }} + {{- $containerName := include "tpl" (dict "value" .scope.azure.containerName "context" .context) }} + destinationPath: {{ printf "https://%s.%s.core.windows.net/%s%s" $storageAccount .scope.azure.serviceName $containerName .scope.azure.path | quote }} {{- end }} {{- $secretName := coalesce .scope.secret.name (printf "%s-%s-azure-creds" .chartFullname .secretPrefix) }} azureCredentials: @@ -43,32 +49,33 @@ inheritFromAzureAD: true {{- else if .scope.azure.connectionString }} connectionString: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: AZURE_CONNECTION_STRING {{- else }} storageAccount: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: AZURE_STORAGE_ACCOUNT {{- if .scope.azure.storageKey }} storageKey: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: AZURE_STORAGE_KEY {{- else }} storageSasToken: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: AZURE_STORAGE_SAS_TOKEN {{- end }} {{- end }} {{- else if eq .scope.provider "google" }} {{- if empty .scope.destinationPath }} - destinationPath: "gs://{{ required "You need to specify Google storage bucket if destinationPath is not specified." .scope.google.bucket }}{{ .scope.google.path }}" + {{- $bucket := include "tpl" (dict "value" (required "You need to specify Google storage bucket if destinationPath is not specified." .scope.google.bucket) "context" .context) }} + destinationPath: {{ printf "gs://%s%s" $bucket .scope.google.path | quote }} {{- end }} {{- $secretName := coalesce .scope.secret.name (printf "%s-%s-google-creds" .chartFullname .secretPrefix) }} googleCredentials: gkeEnvironment: {{ .scope.google.gkeEnvironment }} {{- if not .scope.google.gkeEnvironment }} applicationCredentials: - name: {{ $secretName }} + name: {{ include "tpl" (dict "value" $secretName "context" .context) }} key: APPLICATION_CREDENTIALS {{- end }} {{- end -}} diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 95bedd214f..b42e5c39f0 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -3,12 +3,15 @@ bootstrap: initdb: {{- with .Values.cluster.initdb }} - {{- with (omit . "postInitApplicationSQL" "owner" "import") }} + {{- with (omit . "postInitApplicationSQL" "owner" "import" "database") }} {{- . | toYaml | nindent 4 }} {{- end }} {{- end }} + {{- if .Values.cluster.initdb.database }} + database: {{ include "tpl" (dict "value" .Values.cluster.initdb.database "context" $) | quote }} + {{- end }} {{- if .Values.cluster.initdb.owner }} - owner: {{ tpl .Values.cluster.initdb.owner . }} + owner: {{ include "tpl" (dict "value" .Values.cluster.initdb.owner "context" $) }} {{- end }} {{- if or (eq .Values.type "postgis") (eq .Values.type "timescaledb") (not (empty .Values.cluster.initdb.postInitApplicationSQL)) }} postInitApplicationSQL: @@ -102,7 +105,7 @@ externalClusters: - name: objectStoreRecoveryCluster barmanObjectStore: serverName: {{ .Values.recovery.clusterName }} - {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretPrefix" "recovery" -}} + {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretPrefix" "recovery" "context" $ -}} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 4 }} {{- end }} {{- end }} diff --git a/charts/cluster/templates/_external_source_cluster.tpl b/charts/cluster/templates/_external_source_cluster.tpl index 6d21e205e8..2de14ce702 100644 --- a/charts/cluster/templates/_external_source_cluster.tpl +++ b/charts/cluster/templates/_external_source_cluster.tpl @@ -3,31 +3,31 @@ {{- $config := last . -}} - name: {{ first . }} connectionParameters: - host: {{ $config.host | quote }} - port: {{ $config.port | quote }} - user: {{ $config.username | quote }} + host: {{ include "tpl" (dict "value" $config.host "context" $) | quote }} + port: {{ include "tpl" (dict "value" $config.port "context" $) | quote }} + user: {{ include "tpl" (dict "value" $config.username "context" $) | quote }} {{- with $config.database }} - dbname: {{ . | quote }} + dbname: {{ include "tpl" (dict "value" . "context" $) | quote }} {{- end }} - sslmode: {{ $config.sslMode | quote }} + sslmode: {{ include "tpl" (dict "value" $config.sslMode "context" $) | quote }} {{- if $config.passwordSecret.name }} password: - name: {{ $config.passwordSecret.name }} + name: {{ include "tpl" (dict "value" $config.passwordSecret.name "context" $) }} key: {{ $config.passwordSecret.key }} {{- end }} {{- if $config.sslKeySecret.name }} sslKey: - name: {{ $config.sslKeySecret.name }} + name: {{ include "tpl" (dict "value" $config.sslKeySecret.name "context" $) }} key: {{ $config.sslKeySecret.key }} {{- end }} {{- if $config.sslCertSecret.name }} sslCert: - name: {{ $config.sslCertSecret.name }} + name: {{ include "tpl" (dict "value" $config.sslCertSecret.name "context" $) }} key: {{ $config.sslCertSecret.key }} {{- end }} {{- if $config.sslRootCertSecret.name }} sslRootCert: - name: {{ $config.sslRootCertSecret.name }} + name: {{ include "tpl" (dict "value" $config.sslRootCertSecret.name "context" $) }} key: {{ $config.sslRootCertSecret.key }} {{- end }} {{- end }} diff --git a/charts/cluster/templates/_helpers.tpl b/charts/cluster/templates/_helpers.tpl index 2bae419493..2d0c8a65c1 100644 --- a/charts/cluster/templates/_helpers.tpl +++ b/charts/cluster/templates/_helpers.tpl @@ -144,3 +144,20 @@ Postgres GID {{- 26 -}} {{- end -}} {{- end -}} + +{{/* +Renders a value that contains template expressions. + +This helper processes values through the Helm template engine, allowing dynamic values +to be used in configuration. It handles both string values and complex objects. + +Usage: + {{ include "tpl" (dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "tpl" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index bb9ea770cc..5f16f949e7 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -79,11 +79,11 @@ spec: {{- end }} {{- with .Values.cluster.postgresql.pg_hba }} pg_hba: - {{- toYaml . | nindent 6 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 6 }} {{- end }} {{- with .Values.cluster.postgresql.pg_ident }} pg_ident: - {{- toYaml . | nindent 6 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 6 }} {{- end }} {{- with .Values.cluster.postgresql.ldap }} ldap: @@ -95,24 +95,24 @@ spec: {{ end }} {{- with .Values.cluster.postgresql.parameters }} parameters: - {{- toYaml . | nindent 6 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 6 }} {{- end }} {{- if not (and (empty .Values.cluster.roles) (empty .Values.cluster.services)) }} managed: {{- with .Values.cluster.services }} services: - {{- toYaml . | nindent 6 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 6 }} {{ end }} {{- with .Values.cluster.roles }} roles: - {{- toYaml . | nindent 6 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 6 }} {{ end }} {{- end }} {{- with .Values.cluster.serviceAccountTemplate }} serviceAccountTemplate: - {{- toYaml . | nindent 4 }} + {{- include "tpl" (dict "value" . "context" $) | nindent 4 }} {{- end }} monitoring: