diff --git a/apps/charts/common/TEMPLATING.md b/apps/charts/common/TEMPLATING.md new file mode 100644 index 0000000..a0f4f3a --- /dev/null +++ b/apps/charts/common/TEMPLATING.md @@ -0,0 +1,149 @@ +# Values Templating Guide + +This document explains how templating works in `values.yaml` files and what placeholders are available. + +## Current Placeholders + +The templating system supports the following placeholders in `values.yaml`: + +| Placeholder | Maps To | Example | +|------------|---------|---------| +| `{release}` | `.Release.Name` | `forgejo`, `audiobookshelf` | +| `{namespace}` | `.Release.Namespace` | `prod`, `default` | +| `{fullname}` | `common.fullname` helper | `audiobookshelf`, `forgejo` | +| `{subdomain}` | `.Values.subdomain` | `code`, `audiobookshelf` | +| `{domain}` | `.Values.globals.domain` | `olsen.cloud` | +| `{timezone}` | `.Values.globals.timezone` | `Europe/Amsterdam` | + +## Available Values + +### Release Object (`.Release.*`) +- `.Release.Name` - The release name (chart instance name) +- `.Release.Namespace` - The namespace the release will be installed into +- `.Release.Service` - The service that rendered the chart (usually "Helm") +- `.Release.Revision` - The revision number of this release + +### Chart Object (`.Chart.*`) +- `.Chart.Name` - The name of the chart +- `.Chart.Version` - The version of the chart +- `.Chart.AppVersion` - The app version of the chart + +### Values Object (`.Values.*`) +- `.Values.subdomain` - The subdomain for this application +- `.Values.globals.environment` - The environment (e.g., `prod`) +- `.Values.globals.domain` - The domain (e.g., `olsen.cloud`) +- `.Values.globals.timezone` - The timezone (e.g., `Europe/Amsterdam`) +- `.Values.globals.istio.gateways.public` - Public Istio gateway +- `.Values.globals.istio.gateways.private` - Private Istio gateway +- `.Values.globals.authentik.ref.name` - Authentik server name +- `.Values.globals.authentik.ref.namespace` - Authentik server namespace +- `.Values.globals.networking.private.ip` - Private network IP + +## Usage Examples + +### Simple String Replacement + +```yaml +env: + BASE_URL: + value: "https://{subdomain}.{domain}" + # Renders to: "https://audiobookshelf.olsen.cloud" +``` + +### Secret Reference with Release Name + +```yaml +env: + DATABASE_URL: + valueFrom: + secretKeyRef: + name: "{release}-database" + key: url + # Renders to: name: "audiobookshelf-database" +``` + +### Complex String with Multiple Placeholders + +```yaml +env: + SSH_DOMAIN: + value: "ssh-{subdomain}.{domain}" + # Renders to: "ssh-code.olsen.cloud" +``` + +## Extending Placeholders + +To add more placeholders, edit `apps/charts/common/templates/_helpers.tpl` in the `common.env` helper: + +### Current Implementation + +```go +value: {{ $value + | replace "{release}" $.Release.Name + | replace "{namespace}" $.Release.Namespace + | replace "{fullname}" (include "common.fullname" $) + | replace "{subdomain}" $.Values.subdomain + | replace "{domain}" $.Values.globals.domain + | replace "{timezone}" $.Values.globals.timezone + | quote }} +``` + +**Note:** `{fullname}` uses the `common.fullname` helper which: +- Returns `.Release.Name` if it contains the chart name +- Otherwise returns `{release}-{chart-name}` +- Respects `.Values.fullnameOverride` if set + +**Important:** Update both locations: +1. Line ~245: For `value:` entries (when `$value.value` exists) +2. Line ~248: For simple string values + +### Example Usage + +```yaml +env: + NAMESPACE: + value: "{namespace}" + # Renders to: "prod" (or whatever namespace the release is in) + + TIMEZONE: + value: "{timezone}" + # Renders to: "Europe/Amsterdam" + + APP_NAME: + value: "{fullname}" + # Renders to: "audiobookshelf" (or "release-chartname" if different) + + FULL_URL: + value: "https://{subdomain}.{domain}" + # Renders to: "https://audiobookshelf.olsen.cloud" + + SECRET_NAME: + valueFrom: + secretKeyRef: + name: "{fullname}-secrets" + key: apiKey + # Renders to: name: "audiobookshelf-secrets" +``` + +## Limitations + +1. **No nested placeholders**: Placeholders cannot reference other placeholders +2. **No conditional logic**: Placeholders are simple string replacements +3. **No functions**: Cannot use Helm template functions in values.yaml +4. **Order matters**: Replacements happen in order, so `{release}` is replaced before `{subdomain}` + +## Best Practices + +1. **Use placeholders for dynamic values**: Release names, domains, subdomains +2. **Keep it simple**: Use placeholders for common values, not complex logic +3. **Document custom placeholders**: If you add new ones, document them +4. **Test thoroughly**: Verify placeholders render correctly in your environment + +## Troubleshooting + +If a placeholder isn't being replaced: + +1. Check the placeholder name matches exactly (case-sensitive) +2. Verify the value exists in the context (`.Release.*` or `.Values.*`) +3. Check the replacement chain in `_helpers.tpl` +4. Use `helm template --debug` to see the rendered output diff --git a/apps/charts/common/common-1.0.0.tgz b/apps/charts/common/common-1.0.0.tgz new file mode 100644 index 0000000..5103967 Binary files /dev/null and b/apps/charts/common/common-1.0.0.tgz differ diff --git a/apps/charts/common/templates/_helpers.tpl b/apps/charts/common/templates/_helpers.tpl index 13250ec..c89da53 100644 --- a/apps/charts/common/templates/_helpers.tpl +++ b/apps/charts/common/templates/_helpers.tpl @@ -60,10 +60,13 @@ Recreate {{- end }} {{/* -Standard container port +Standard container port (for backward compatibility) */}} {{- define "common.containerPort" -}} -{{- if .Values.container.port }} +{{- if .Values.container.ports }} +{{- $primaryPort := first .Values.container.ports }} +{{- $primaryPort.port }} +{{- else if .Values.container.port }} {{- .Values.container.port }} {{- else }} 80 @@ -71,28 +74,85 @@ Standard container port {{- end }} {{/* -Standard service port +Container ports list +*/}} +{{- define "common.containerPorts" -}} +{{- if .Values.container.ports }} +{{- range .Values.container.ports }} +- name: {{ .name }} + containerPort: {{ .port }} + protocol: {{ .protocol | default "TCP" }} +{{- end }} +{{- else if .Values.container.port }} +- name: http + containerPort: {{ .Values.container.port }} + protocol: TCP +{{- else }} +- name: http + containerPort: 80 + protocol: TCP +{{- end }} +{{- end }} + +{{/* +Standard service port (for backward compatibility) */}} {{- define "common.servicePort" -}} -{{- if .Values.service.port }} +{{- if .Values.service.ports }} +{{- $primaryService := first .Values.service.ports }} +{{- $primaryService.port }} +{{- else if .Values.service.port }} {{- .Values.service.port }} {{- else }} 80 {{- end }} {{- end }} +{{/* +Service ports list +*/}} +{{- define "common.servicePorts" -}} +{{- if .Values.service.ports }} +{{- range .Values.service.ports }} +- port: {{ .port }} + targetPort: {{ .targetPort | default .port }} + protocol: {{ .protocol | default "TCP" }} + name: {{ .name }} +{{- end }} +{{- else if .Values.service.port }} +- port: {{ .Values.service.port }} + targetPort: {{ include "common.containerPort" . }} + protocol: TCP + name: http +{{- else }} +- port: 80 + targetPort: {{ include "common.containerPort" . }} + protocol: TCP + name: http +{{- end }} +{{- end }} + {{/* Standard health probe */}} {{- define "common.healthProbe" -}} {{- if .Values.container.healthProbe }} +{{- $probePort := .Values.container.healthProbe.port | default (include "common.containerPort" .) }} {{- if eq .Values.container.healthProbe.type "httpGet" }} httpGet: path: {{ .Values.container.healthProbe.path | default "/" }} - port: {{ include "common.containerPort" . }} + {{- if regexMatch "^[0-9]+$" $probePort }} + port: {{ $probePort }} + {{- else }} + port: {{ $probePort }} + {{- end }} {{- else if eq .Values.container.healthProbe.type "tcpSocket" }} tcpSocket: - port: {{ include "common.containerPort" . }} + {{- if regexMatch "^[0-9]+$" $probePort }} + port: {{ $probePort }} + {{- else }} + port: {{ $probePort }} + {{- end }} {{- end }} {{- if .Values.container.healthProbe.initialDelaySeconds }} initialDelaySeconds: {{ .Values.container.healthProbe.initialDelaySeconds }} @@ -174,16 +234,18 @@ Standard environment variables valueFrom: {{- if $value.valueFrom.secretKeyRef }} secretKeyRef: - name: {{ $value.valueFrom.secretKeyRef.name }} + name: {{ $value.valueFrom.secretKeyRef.name | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) }} key: {{ $value.valueFrom.secretKeyRef.key }} {{- else if $value.valueFrom.configMapKeyRef }} configMapKeyRef: - name: {{ $value.valueFrom.configMapKeyRef.name }} + name: {{ $value.valueFrom.configMapKeyRef.name | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) }} key: {{ $value.valueFrom.configMapKeyRef.key }} {{- end }} + {{- else if $value.value }} + value: {{ $value.value | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) | replace "{subdomain}" $.Values.subdomain | replace "{domain}" $.Values.globals.domain | replace "{timezone}" $.Values.globals.timezone | quote }} {{- end }} {{- else }} - value: {{ $value | quote }} + value: {{ $value | replace "{release}" $.Release.Name | replace "{namespace}" $.Release.Namespace | replace "{fullname}" (include "common.fullname" $) | replace "{subdomain}" $.Values.subdomain | replace "{domain}" $.Values.globals.domain | replace "{timezone}" $.Values.globals.timezone | quote }} {{- end }} {{- end }} {{- end }} @@ -240,9 +302,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }} ports: - - name: http - containerPort: {{ include "common.containerPort" . }} - protocol: TCP +{{ include "common.containerPorts" . | indent 12 }} {{- if .Values.container.healthProbe }} livenessProbe: {{ include "common.healthProbe" . | indent 12 }} @@ -265,10 +325,31 @@ spec: {{- end }} {{/* -Full Service resource +Full Service resource(s) - supports multiple services */}} {{- define "common.service" -}} {{- if .Values.service }} +{{- if .Values.service.ports }} +{{- $firstPort := index .Values.service.ports 0 }} +{{- range $index, $port := .Values.service.ports }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ if $port.serviceName }}{{ include "common.fullname" $ }}-{{ $port.serviceName }}{{ else }}{{ include "common.fullname" $ }}{{ if and (gt $index 0) }}-{{ $port.name }}{{ end }}{{ end }} + labels: + {{- include "common.labels" $ | nindent 4 }} +spec: + type: {{ $port.type | default $.Values.service.type | default "ClusterIP" }} + ports: + - port: {{ $port.port }} + targetPort: {{ $port.targetPort | default $port.port }} + protocol: {{ $port.protocol | default "TCP" }} + name: {{ $port.name }} + selector: + {{- include "common.selectorLabels" $ | nindent 4 }} +{{- end }} +{{- else }} apiVersion: v1 kind: Service metadata: @@ -278,14 +359,12 @@ metadata: spec: type: {{ .Values.service.type | default "ClusterIP" }} ports: - - port: {{ include "common.servicePort" . }} - targetPort: {{ include "common.containerPort" . }} - protocol: TCP - name: http +{{ include "common.servicePorts" . | indent 4 }} selector: {{- include "common.selectorLabels" . | nindent 4 }} {{- end }} {{- end }} +{{- end }} {{/* Full PVC resources @@ -337,7 +416,11 @@ spec: - destination: host: {{ include "common.fullname" . }} port: + {{- if .Values.virtualService.servicePort }} + number: {{ .Values.virtualService.servicePort }} + {{- else }} number: {{ include "common.servicePort" . }} + {{- end }} --- {{- end }} @@ -360,7 +443,11 @@ spec: - destination: host: {{ include "common.fullname" . }} port: + {{- if .Values.virtualService.servicePort }} + number: {{ .Values.virtualService.servicePort }} + {{- else }} number: {{ include "common.servicePort" . }} + {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/apps/charts/forgejo/Chart.yaml b/apps/charts/forgejo/Chart.yaml index fa3f030..fbd26a7 100644 --- a/apps/charts/forgejo/Chart.yaml +++ b/apps/charts/forgejo/Chart.yaml @@ -1,3 +1,7 @@ apiVersion: v2 version: 1.0.0 name: forgejo +dependencies: + - name: common + version: 1.0.0 + repository: file://../common diff --git a/apps/charts/forgejo/templates/deployment.yaml b/apps/charts/forgejo/templates/deployment.yaml index 57a96ed..4508e33 100644 --- a/apps/charts/forgejo/templates/deployment.yaml +++ b/apps/charts/forgejo/templates/deployment.yaml @@ -1,106 +1 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: "{{ .Release.Name }}" -spec: - strategy: - type: Recreate - replicas: 1 - revisionHistoryLimit: 0 - selector: - matchLabels: - app: "{{ .Release.Name }}" - template: - metadata: - labels: - app: "{{ .Release.Name }}" - spec: - containers: - - name: "{{ .Release.Name }}" - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: "{{ .Values.image.pullPolicy }}" - ports: - - name: http - containerPort: 3000 - protocol: TCP - - name: ssh - containerPort: 22 - protocol: TCP - livenessProbe: - tcpSocket: - port: http - readinessProbe: - tcpSocket: - port: http - volumeMounts: - - mountPath: /data - name: data - env: - - name: TZ - value: "{{ .Values.globals.timezone }}" - - name: USER_UID - value: "1000" - - name: USER_GID - value: "1000" - - name: FORGEJO__server__SSH_DOMAIN - value: "ssh-{{ .Values.subdomain }}.{{ .Values.globals.domain }}" - - name: FORGEJO__server__SSH_PORT - value: "2206" - - name: FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD - value: "true" - #- name: FORGEJO__service__ENABLE_BASIC_AUTHENTICATION - # value: 'true' - - name: FORGEJO__service__ENABLE_PASSWORD_SIGNIN_FORM - value: "false" - - name: FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE - value: "true" - - name: FORGEJO__service__DEFAULT_USER_IS_RESTRICTED - value: "true" - - name: FORGEJO__service__DEFAULT_USER_VISIBILITY - value: "private" - - name: FORGEJO__service__DEFAULT_ORG_VISIBILITY - value: "private" - - name: FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION - value: "true" - - name: FORGEJO__other__SHOW_FOOTER_POWERED_BY - value: "false" - - name: FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME - value: "false" - - name: FORGEJO__other__SHOW_FOOTER_VERSION - value: "false" - - name: FORGEJO__repository__ENABLE_PUSH_CREATE_USER - value: "true" - - name: FORGEJO__repository__ENABLE_PUSH_CREATE_ORG - value: "true" - - name: FORGEJO__openid__ENABLE_OPENID_SIGNIN - value: "false" - - name: FORGEJO__openid__ENABLE_OPENID_SIGNUP - value: "false" - - name: FORGEJO__database__DB_TYPE - value: postgres - - name: FORGEJO__database__NAME - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-pg-connection" - key: database - - name: FORGEJO__database__HOST - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-pg-connection" - key: host - - name: FORGEJO__database__DB_PORT - value: "5432" - - name: FORGEJO__database__USER - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-pg-connection" - key: user - - name: FORGEJO__database__PASSWD - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-pg-connection" - key: password - volumes: - - name: data - persistentVolumeClaim: - claimName: "{{ .Release.Name }}-data" +{{ include "common.deployment" . }} diff --git a/apps/charts/forgejo/templates/pvc.yaml b/apps/charts/forgejo/templates/pvc.yaml index bc1d0a6..379bad9 100644 --- a/apps/charts/forgejo/templates/pvc.yaml +++ b/apps/charts/forgejo/templates/pvc.yaml @@ -1,11 +1 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: '{{ .Release.Name }}-data' -spec: - accessModes: - - 'ReadWriteOnce' - resources: - requests: - storage: '1Gi' - storageClassName: '{{ .Values.globals.environment }}' +{{ include "common.pvc" . }} diff --git a/apps/charts/forgejo/templates/service.yaml b/apps/charts/forgejo/templates/service.yaml index bfade59..f024c64 100644 --- a/apps/charts/forgejo/templates/service.yaml +++ b/apps/charts/forgejo/templates/service.yaml @@ -1,32 +1 @@ -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Release.Name }}" - labels: - app: "{{ .Release.Name }}" -spec: - type: ClusterIP - ports: - - port: 80 - targetPort: 3000 - protocol: TCP - name: http - selector: - app: "{{ .Release.Name }}" - ---- -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Release.Name }}-ssh" - labels: - app: "{{ .Release.Name }}" -spec: - type: LoadBalancer - ports: - - port: 2206 - targetPort: 22 - protocol: TCP - name: ssh - selector: - app: "{{ .Release.Name }}" +{{ include "common.service" . }} diff --git a/apps/charts/forgejo/templates/virtual-service.yaml b/apps/charts/forgejo/templates/virtual-service.yaml index 36e9d43..766f6b9 100644 --- a/apps/charts/forgejo/templates/virtual-service.yaml +++ b/apps/charts/forgejo/templates/virtual-service.yaml @@ -1,39 +1 @@ -apiVersion: networking.istio.io/v1 -kind: VirtualService -metadata: - name: "{{ .Release.Name }}-public" - namespace: "{{ .Release.Namespace }}" -spec: - gateways: - - "{{ .Values.globals.istio.gateways.public }}" - - mesh - hosts: - - "{{ .Values.subdomain }}.{{ .Values.globals.domain }}" - - mesh - http: - - route: - - destination: - host: "{{ .Release.Name }}" - port: - number: 80 - ---- -apiVersion: networking.istio.io/v1 -kind: VirtualService -metadata: - name: "{{ .Release.Name }}-private" - namespace: "{{ .Release.Namespace }}" -spec: - gateways: - - "{{ .Values.globals.istio.gateways.private }}" - - mesh - hosts: - - "{{ .Values.subdomain }}.{{ .Values.globals.domain }}" - - mesh - http: - - route: - - destination: - host: "{{ .Release.Name }}" - port: - number: 80 - +{{ include "common.virtualService" . }} diff --git a/apps/charts/forgejo/values.yaml b/apps/charts/forgejo/values.yaml index 857496d..c01e181 100644 --- a/apps/charts/forgejo/values.yaml +++ b/apps/charts/forgejo/values.yaml @@ -2,6 +2,102 @@ image: repository: codeberg.org/forgejo/forgejo tag: 13@sha256:88858e7f592f82d4f650713c7bed8c0cd792d7f71475a7467c5650a31cd2eda9 pullPolicy: IfNotPresent + subdomain: code -globals: - environment: prod + +# Deployment configuration +deployment: + strategy: Recreate + replicas: 1 + revisionHistoryLimit: 0 + +# Container configuration - multiple ports +container: + ports: + - name: http + port: 3000 + protocol: TCP + - name: ssh + port: 22 + protocol: TCP + healthProbe: + type: tcpSocket + port: http # Use named port + +# Service configuration - multiple services +service: + ports: + - name: http + port: 80 + targetPort: 3000 + protocol: TCP + type: ClusterIP + - name: ssh + port: 2206 + targetPort: 22 + protocol: TCP + type: LoadBalancer + serviceName: ssh # Will be prefixed with release name: {release}-ssh + +# Volume configuration +volumes: + - name: data + mountPath: /data + persistentVolumeClaim: data + +# Persistent volume claims +persistentVolumeClaims: + - name: data + size: 1Gi + +# VirtualService configuration +virtualService: + enabled: true + gateways: + public: true + private: true + servicePort: 80 # Route to the http service port + +# Environment variables +env: + USER_UID: "1000" + USER_GID: "1000" + FORGEJO__server__SSH_DOMAIN: + value: "ssh-{subdomain}.{domain}" # Will be templated: ssh-{subdomain}.{domain} + FORGEJO__server__SSH_PORT: "2206" + FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD: "true" + FORGEJO__service__ENABLE_PASSWORD_SIGNIN_FORM: "false" + FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE: "true" + FORGEJO__service__DEFAULT_USER_IS_RESTRICTED: "true" + FORGEJO__service__DEFAULT_USER_VISIBILITY: "private" + FORGEJO__service__DEFAULT_ORG_VISIBILITY: "private" + FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true" + FORGEJO__other__SHOW_FOOTER_POWERED_BY: "false" + FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME: "false" + FORGEJO__other__SHOW_FOOTER_VERSION: "false" + FORGEJO__repository__ENABLE_PUSH_CREATE_USER: "true" + FORGEJO__repository__ENABLE_PUSH_CREATE_ORG: "true" + FORGEJO__openid__ENABLE_OPENID_SIGNIN: "false" + FORGEJO__openid__ENABLE_OPENID_SIGNUP: "false" + FORGEJO__database__DB_TYPE: postgres + FORGEJO__database__DB_PORT: "5432" + FORGEJO__database__NAME: + valueFrom: + secretKeyRef: + name: "{release}-pg-connection" + key: database + FORGEJO__database__HOST: + valueFrom: + secretKeyRef: + name: "{release}-pg-connection" + key: host + FORGEJO__database__USER: + valueFrom: + secretKeyRef: + name: "{release}-pg-connection" + key: user + FORGEJO__database__PASSWD: + valueFrom: + secretKeyRef: + name: "{release}-pg-connection" + key: password