diff --git a/apps/charts/tandoor/Chart.yaml b/apps/charts/tandoor/Chart.yaml new file mode 100644 index 0000000..89ad062 --- /dev/null +++ b/apps/charts/tandoor/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +version: 1.0.0 +name: tandoor +dependencies: + - name: common + version: 1.0.0 + repository: file://../../common diff --git a/apps/charts/tandoor/templates/common.yaml b/apps/charts/tandoor/templates/common.yaml new file mode 100644 index 0000000..a6613c2 --- /dev/null +++ b/apps/charts/tandoor/templates/common.yaml @@ -0,0 +1 @@ +{{ include "common.all" . }} diff --git a/apps/charts/tandoor/templates/social-providers-template.yaml b/apps/charts/tandoor/templates/social-providers-template.yaml new file mode 100644 index 0000000..00a324e --- /dev/null +++ b/apps/charts/tandoor/templates/social-providers-template.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.fullname" . }}-social-providers-template + labels: + {{- include "common.labels" . | nindent 4 }} +data: + socialaccount_providers.json.template: | + { + "authentik": { + "APPS": [ + { + "client_id": "${CLIENT_ID}", + "secret": "${CLIENT_SECRET}", + "settings": { + "server_url": "${ISSUER}/.well-known/openid-configuration" + } + } + ] + } + } diff --git a/apps/charts/tandoor/values.yaml b/apps/charts/tandoor/values.yaml new file mode 100644 index 0000000..c55251e --- /dev/null +++ b/apps/charts/tandoor/values.yaml @@ -0,0 +1,142 @@ +image: + repository: vabene1111/recipes + tag: latest + pullPolicy: IfNotPresent + +subdomain: recipes + +deployment: + strategy: Recreate + replicas: 1 + +container: + port: 80 + +service: + port: 80 + type: ClusterIP + +volumes: + - name: media + mountPath: /opt/recipes/mediafiles + persistentVolumeClaim: tandoor-media + - name: static + mountPath: /opt/recipes/staticfiles + persistentVolumeClaim: tandoor-static + - name: social-providers-vol + mountPath: /run/secrets + emptyDir: {} + - name: config-template + mountPath: /config-template + configMap: "{release}-social-providers-template" + +initContainers: + - name: create-social-providers + image: alpine:latest + command: ["/bin/sh", "-c"] + args: + - | + sed -e "s|\${CLIENT_ID}|$CLIENT_ID|g" \ + -e "s|\${CLIENT_SECRET}|$CLIENT_SECRET|g" \ + -e "s|\${ISSUER}|$ISSUER|g" \ + /config-template/socialaccount_providers.json.template > /run/secrets/socialaccount_providers.txt + env: + - name: CLIENT_ID + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: clientId + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: clientSecret + - name: ISSUER + valueFrom: + secretKeyRef: + name: "{release}-oidc-credentials" + key: issuer + volumeMounts: + - name: social-providers-vol + mountPath: /run/secrets + - name: config-template + mountPath: /config-template + +persistentVolumeClaims: + - name: media + size: 10Gi + - name: static + size: 1Gi + +virtualService: + enabled: true + gateways: + public: true + private: true + +oidc: + enabled: true + redirectUris: + - "/accounts/authentik/login/callback/" + subjectMode: user_username + +database: + enabled: true + +externalSecrets: + - name: "{release}-secrets" + passwords: + - name: secret-key + length: 64 + encoding: base64 + allowRepeat: true + secretKeys: + - secret-key + +env: + TZ: + value: "{timezone}" + + DEBUG: "0" + TANDOOR_PORT: "80" + # ALLOWED_HOSTS: "*" + SECRET_KEY: + valueFrom: + secretKeyRef: + name: "{release}-secrets" + key: secret-key + + # Database + POSTGRES_HOST: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: host + POSTGRES_PORT: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: port + POSTGRES_DB: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: database + POSTGRES_USER: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: user + POSTGRES_PASSWORD: + valueFrom: + secretKeyRef: + name: "{release}-connection" + key: password + + # Authentication + SOCIALACCOUNT_PROVIDERS_FILE: "/run/secrets/socialaccount_providers.txt" + ENABLE_OIDC: "1" + + # Gunicorn configuration to serve files without nginx + TANDOOR_GUNICORN_MEDIA: "1" + TANDOOR_GUNICORN_STATIC: "1" diff --git a/apps/common/README.md b/apps/common/README.md index b117f9c..7b56b1b 100644 --- a/apps/common/README.md +++ b/apps/common/README.md @@ -525,54 +525,66 @@ externalSecrets: The secret will contain a key named `mySecretKey` (not `my-password-generator`). -## Secret-based Configurations +## Secret-based Configurations (via Init Container) -When an application requires a configuration file (like `config.yaml` or `.env`) that contains sensitive data, you should generate a `Secret` using External Secrets templating instead of a `ConfigMap`. This keeps the sensitive data encrypted in ETCD while allowing you to merge static and dynamic values. +When an application requires a configuration file (like `config.yaml` or `.env`) that contains sensitive data from multiple sources (e.g., merging static config with secrets), you can use an `initContainer` to generate the file from a template. This is especially useful when a `SecretStore` is not available for External Secrets templating. -### 1. Create an External Secret with a Template -Create a custom template in your chart (e.g., `templates/external-config.yaml`): +### 1. Create a ConfigMap Template +Create a custom template in your chart (e.g., `templates/config-template.yaml`): ```yaml -apiVersion: external-secrets.io/v1beta1 -kind: ExternalSecret +apiVersion: v1 +kind: ConfigMap metadata: - name: {{ include "common.fullname" . }}-config -spec: - refreshInterval: 1h - secretStoreRef: - name: vault-backend # Replace with your SecretStore name - kind: SecretStore - target: - name: {{ include "common.fullname" . }}-config - template: - engineVersion: v2 - data: - config.yaml: | - server: - port: 8080 - database: - user: {{ "{{" }} .db_user | toString {{ "}}" }} - password: {{ "{{" }} .db_pass | toString {{ "}}" }} - data: - - secretKey: db_user - remoteRef: - key: database/credentials - property: username - - secretKey: db_pass - remoteRef: - key: database/credentials - property: password + name: {{ include "common.fullname" . }}-config-template + labels: + {{- include "common.labels" . | nindent 4 }} +data: + config.yaml.template: | + server: + port: 8080 + database: + user: "${DB_USER}" + password: "${DB_PASS}" ``` -### 2. Mount the Secret in values.yaml -Reference the generated secret in your `volumes` configuration using the `{release}` placeholder: +### 2. Configure Init Container and Volumes in values.yaml +Reference the template and secrets in your `values.yaml`: ```yaml volumes: - - name: config - mountPath: /app/config.yaml - subPath: config.yaml - secret: "{release}-config" + - name: config-vol + mountPath: /app/config + emptyDir: {} + - name: config-template + mountPath: /config-template + configMap: "{release}-config-template" + +initContainers: + - name: generate-config + image: alpine:latest + command: ["/bin/sh", "-c"] + args: + - | + sed -e "s|\${DB_USER}|$DB_USER|g" \ + -e "s|\${DB_PASS}|$DB_PASS|g" \ + /config-template/config.yaml.template > /app/config/config.yaml + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: "{release}-db-credentials" + key: username + - name: DB_PASS + valueFrom: + secretKeyRef: + name: "{release}-db-credentials" + key: password + volumeMounts: + - name: config-vol + mountPath: /app/config + - name: config-template + mountPath: /config-template ``` ## Placeholders diff --git a/apps/common/templates/_helpers.tpl b/apps/common/templates/_helpers.tpl index 13feeba..103e662 100644 --- a/apps/common/templates/_helpers.tpl +++ b/apps/common/templates/_helpers.tpl @@ -347,17 +347,58 @@ spec: {{- include "common.dnsConfig" . | nindent 6 }} {{- if .Values.initContainers }} initContainers: - {{- toYaml .Values.initContainers | nindent 8 }} + {{- $initContainers := toYaml .Values.initContainers -}} + {{- $initContainers = $initContainers | replace "{release}" .Release.Name -}} + {{- $initContainers = $initContainers | replace "{namespace}" .Release.Namespace -}} + {{- $initContainers = $initContainers | replace "{fullname}" (include "common.fullname" .) -}} + {{- if .Values.subdomain -}} + {{- $initContainers = $initContainers | replace "{subdomain}" .Values.subdomain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.domain -}} + {{- $initContainers = $initContainers | replace "{domain}" .Values.globals.domain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.timezone -}} + {{- $initContainers = $initContainers | replace "{timezone}" .Values.globals.timezone -}} + {{- end -}} + {{- $initContainers | nindent 8 }} {{- end }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }} {{- if .Values.command }} - command: {{- toYaml .Values.command | nindent 12 }} + command: + {{- $command := toYaml .Values.command -}} + {{- $command = $command | replace "{release}" .Release.Name -}} + {{- $command = $command | replace "{namespace}" .Release.Namespace -}} + {{- $command = $command | replace "{fullname}" (include "common.fullname" .) -}} + {{- if .Values.subdomain -}} + {{- $command = $command | replace "{subdomain}" .Values.subdomain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.domain -}} + {{- $command = $command | replace "{domain}" .Values.globals.domain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.timezone -}} + {{- $command = $command | replace "{timezone}" .Values.globals.timezone -}} + {{- end -}} + {{- $command | nindent 12 }} {{- end }} {{- if .Values.args }} - args: {{- toYaml .Values.args | nindent 12 }} + args: + {{- $args := toYaml .Values.args -}} + {{- $args = $args | replace "{release}" .Release.Name -}} + {{- $args = $args | replace "{namespace}" .Release.Namespace -}} + {{- $args = $args | replace "{fullname}" (include "common.fullname" .) -}} + {{- if .Values.subdomain -}} + {{- $args = $args | replace "{subdomain}" .Values.subdomain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.domain -}} + {{- $args = $args | replace "{domain}" .Values.globals.domain -}} + {{- end -}} + {{- if and .Values.globals .Values.globals.timezone -}} + {{- $args = $args | replace "{timezone}" .Values.globals.timezone -}} + {{- end -}} + {{- $args | nindent 12 }} {{- end }} ports: {{ include "common.containerPorts" . | indent 12 }}