transform forgejo

This commit is contained in:
Morten Olsen
2026-01-01 13:28:58 +01:00
parent f4ff0a20da
commit f03dbea746
9 changed files with 359 additions and 207 deletions

View File

@@ -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

Binary file not shown.

View File

@@ -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,10 +443,14 @@ spec:
- destination:
host: {{ include "common.fullname" . }}
port:
{{- if .Values.virtualService.servicePort }}
number: {{ .Values.virtualService.servicePort }}
{{- else }}
number: {{ include "common.servicePort" . }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Full DNS resource

View File

@@ -1,3 +1,7 @@
apiVersion: v2
version: 1.0.0
name: forgejo
dependencies:
- name: common
version: 1.0.0
repository: file://../common

View File

@@ -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" . }}

View File

@@ -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" . }}

View File

@@ -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" . }}

View File

@@ -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" . }}

View File

@@ -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