From c88ce8075025159963a20db2b157b6f6f0f3df53 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 22 Aug 2026 20:10:06 +0200 Subject: [PATCH 1/3] Conventions batch 4: env blocks, homeassistant className default, strict clusterIssuer - apache-tika, gotenberg, cyberchef, homeassistant, patchmon: additive env value blocks rendered through tpl (value, secretKeyRef and configMapKeyRef forms), following the template-chart/outline pattern; patchmon gets separate blocks for the server container and the guacd sidecar. - homeassistant: ingress.className now defaults to nginx; an empty className omits ingressClassName from the rendered Ingress, so the flexible upstream ingress style stays intact. - outline, paperless-ngx: remove the ingress.clusterIssuer defaults (letsencrypt / letsencrypt-issuer) - the value is now strictly required, consistent with the repo-wide ingress standard; CI test-values set it explicitly. Chart bumps: apache-tika 7.2.0, gotenberg 4.2.0, cyberchef 5.2.0, patchmon 5.2.0, homeassistant 4.2.0 (minor); outline 5.0.0, paperless-ngx 10.0.0 (major). --- apache-tika/Chart.yaml | 2 +- .../templates/apache-tika.deployment.yaml | 23 ++++++++++ apache-tika/values.yaml | 3 ++ ci/outline/test-values.yaml | 3 ++ ci/paperless-ngx/test-values.yaml | 3 ++ cyberchef/Chart.yaml | 2 +- cyberchef/templates/cyberchef.deployment.yaml | 23 ++++++++++ cyberchef/values.yaml | 3 ++ gotenberg/Chart.yaml | 2 +- gotenberg/templates/gotenberg.deployment.yaml | 23 ++++++++++ gotenberg/values.yaml | 3 ++ homeassistant/Chart.yaml | 2 +- .../templates/homeassistant.ingress.yaml | 4 +- .../templates/homeassistant.sfs.yaml | 20 +++++++++ homeassistant/values.yaml | 8 +++- outline/Chart.yaml | 2 +- outline/values.yaml | 2 +- paperless-ngx/Chart.yaml | 2 +- paperless-ngx/values.yaml | 2 +- patchmon/Chart.yaml | 2 +- patchmon/templates/patchmon.deployment.yaml | 43 +++++++++++++++++++ patchmon/values.yaml | 8 ++++ 22 files changed, 174 insertions(+), 11 deletions(-) diff --git a/apache-tika/Chart.yaml b/apache-tika/Chart.yaml index 0f6bfb0..695a4c3 100644 --- a/apache-tika/Chart.yaml +++ b/apache-tika/Chart.yaml @@ -5,5 +5,5 @@ icon: https://www.apache.org/logos/res/tika/default.png type: application -version: 7.1.2+up3.3.1.0.full +version: 7.2.0+up3.3.1.0.full appVersion: "3.3.1.0-full" diff --git a/apache-tika/templates/apache-tika.deployment.yaml b/apache-tika/templates/apache-tika.deployment.yaml index cc595e8..dabd39f 100644 --- a/apache-tika/templates/apache-tika.deployment.yaml +++ b/apache-tika/templates/apache-tika.deployment.yaml @@ -22,6 +22,29 @@ spec: - name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment image: "apache/tika:{{ .Chart.AppVersion }}" imagePullPolicy: IfNotPresent + {{- if .Values.tika.env }} + env: + {{- range $value := .Values.tika.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} securityContext: {{- toYaml .Values.tika.securityContext.container | nindent 12 }} volumeMounts: diff --git a/apache-tika/values.yaml b/apache-tika/values.yaml index 5517bd5..2a7022b 100644 --- a/apache-tika/values.yaml +++ b/apache-tika/values.yaml @@ -45,6 +45,9 @@ tika: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 + # Extra environment variables. Values are rendered through tpl; value, + # secretKeyRef and configMapKeyRef forms are supported. + env: [] resources: # Limits are optional: memory and ephemeral-storage limits default to # limitFactor x the request, an explicitly set limit always wins, and a diff --git a/ci/outline/test-values.yaml b/ci/outline/test-values.yaml index e75b9f2..b9397ee 100644 --- a/ci/outline/test-values.yaml +++ b/ci/outline/test-values.yaml @@ -5,3 +5,6 @@ secrets: ingress: domain: outline.ci.local + # clusterIssuer is strictly required (no default); the Ingress just has to + # apply - there is no cert-manager in the CI cluster. + clusterIssuer: ci diff --git a/ci/paperless-ngx/test-values.yaml b/ci/paperless-ngx/test-values.yaml index ad87c80..3769628 100644 --- a/ci/paperless-ngx/test-values.yaml +++ b/ci/paperless-ngx/test-values.yaml @@ -5,6 +5,9 @@ secrets: ingress: domain: paperless.ci.local + # clusterIssuer is strictly required (no default); the Ingress just has to + # apply - there is no cert-manager in the CI cluster. + clusterIssuer: ci # smbConnection and oidc stay disabled (defaults): the SMB CSI driver and a # real OIDC login flow are out of scope for the CI install-test. diff --git a/cyberchef/Chart.yaml b/cyberchef/Chart.yaml index 7cd0cf0..59bc61a 100644 --- a/cyberchef/Chart.yaml +++ b/cyberchef/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/gchq/CyberChef/master/src/web/static/ima type: application -version: 5.1.0+up11.4.0 +version: 5.2.0+up11.4.0 appVersion: "11.4.0" maintainers: diff --git a/cyberchef/templates/cyberchef.deployment.yaml b/cyberchef/templates/cyberchef.deployment.yaml index 25cb41c..aac23d8 100644 --- a/cyberchef/templates/cyberchef.deployment.yaml +++ b/cyberchef/templates/cyberchef.deployment.yaml @@ -22,6 +22,29 @@ spec: - name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment image: "ghcr.io/gchq/cyberchef:{{ .Chart.AppVersion }}" imagePullPolicy: IfNotPresent + {{- if .Values.cyberchef.env }} + env: + {{- range $value := .Values.cyberchef.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} securityContext: {{- toYaml .Values.cyberchef.securityContext.container | nindent 12 }} volumeMounts: diff --git a/cyberchef/values.yaml b/cyberchef/values.yaml index 2d47606..0796648 100644 --- a/cyberchef/values.yaml +++ b/cyberchef/values.yaml @@ -45,6 +45,9 @@ cyberchef: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 + # Extra environment variables. Values are rendered through tpl; value, + # secretKeyRef and configMapKeyRef forms are supported. + env: [] resources: # Limits are optional: memory and ephemeral-storage limits default to # limitFactor x the request, an explicitly set limit always wins, and a diff --git a/gotenberg/Chart.yaml b/gotenberg/Chart.yaml index 2460bca..a9df3af 100644 --- a/gotenberg/Chart.yaml +++ b/gotenberg/Chart.yaml @@ -5,7 +5,7 @@ icon: https://gotenberg.dev/img/logo.png type: application -version: 4.1.2+up8.36.0 +version: 4.2.0+up8.36.0 appVersion: "8.36.0" maintainers: diff --git a/gotenberg/templates/gotenberg.deployment.yaml b/gotenberg/templates/gotenberg.deployment.yaml index 341bcc7..481ce38 100644 --- a/gotenberg/templates/gotenberg.deployment.yaml +++ b/gotenberg/templates/gotenberg.deployment.yaml @@ -22,6 +22,29 @@ spec: - name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment image: "gotenberg/gotenberg:{{ .Chart.AppVersion }}" imagePullPolicy: IfNotPresent + {{- if .Values.gotenberg.env }} + env: + {{- range $value := .Values.gotenberg.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} securityContext: {{- toYaml .Values.gotenberg.securityContext.container | nindent 12 }} volumeMounts: diff --git a/gotenberg/values.yaml b/gotenberg/values.yaml index 758183a..c24d95b 100644 --- a/gotenberg/values.yaml +++ b/gotenberg/values.yaml @@ -46,6 +46,9 @@ gotenberg: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 + # Extra environment variables. Values are rendered through tpl; value, + # secretKeyRef and configMapKeyRef forms are supported. + env: [] resources: # Limits are optional: memory and ephemeral-storage limits default to # limitFactor x the request, an explicitly set limit always wins, and a diff --git a/homeassistant/Chart.yaml b/homeassistant/Chart.yaml index dc5f109..fcc96cb 100644 --- a/homeassistant/Chart.yaml +++ b/homeassistant/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart to deploy Home Assistant on Kubernetes icon: https://www.home-assistant.io/images/home-assistant-logo.svg type: application -version: 4.1.2+up2026.8.3 +version: 4.2.0+up2026.8.3 appVersion: "2026.8.3" diff --git a/homeassistant/templates/homeassistant.ingress.yaml b/homeassistant/templates/homeassistant.ingress.yaml index 7bff858..598d899 100644 --- a/homeassistant/templates/homeassistant.ingress.yaml +++ b/homeassistant/templates/homeassistant.ingress.yaml @@ -10,7 +10,9 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} spec: - ingressClassName: {{ .Values.ingress.className }} + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} diff --git a/homeassistant/templates/homeassistant.sfs.yaml b/homeassistant/templates/homeassistant.sfs.yaml index 6e7d7b9..42aa274 100644 --- a/homeassistant/templates/homeassistant.sfs.yaml +++ b/homeassistant/templates/homeassistant.sfs.yaml @@ -28,6 +28,26 @@ spec: env: - name: TZ value: "{{ .Values.core.timezone }}" + {{- range $value := .Values.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} ports: - name: http containerPort: 8123 diff --git a/homeassistant/values.yaml b/homeassistant/values.yaml index dbf5796..c252504 100644 --- a/homeassistant/values.yaml +++ b/homeassistant/values.yaml @@ -18,7 +18,8 @@ service: # This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ ingress: enabled: false - className: "" + # Ingress class; set to "" to omit ingressClassName from the rendered Ingress. + className: nginx annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" @@ -87,6 +88,11 @@ startupProbe: timeoutSeconds: 5 failureThreshold: 30 +# Extra environment variables, appended to the ones the chart always sets. +# Values are rendered through tpl; value, secretKeyRef and configMapKeyRef +# forms are supported. +env: [] + # Additional volumes on the output Deployment definition. volumes: [] # - name: foo diff --git a/outline/Chart.yaml b/outline/Chart.yaml index 560bc7b..07a792a 100644 --- a/outline/Chart.yaml +++ b/outline/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/outline/outline/main/public/images/icon- type: application -version: 4.0.0+up1.9.2 +version: 5.0.0+up1.9.2 appVersion: 1.9.2 maintainers: diff --git a/outline/values.yaml b/outline/values.yaml index dd2e7a9..fdc56d4 100644 --- a/outline/values.yaml +++ b/outline/values.yaml @@ -81,7 +81,7 @@ outline: ingress: enabled: true maxUploadFileSize: 25m - clusterIssuer: letsencrypt + clusterIssuer: null domain: null className: nginx # Extra annotations for the Ingress; merged with the cert-manager diff --git a/paperless-ngx/Chart.yaml b/paperless-ngx/Chart.yaml index c8acbb4..d87db47 100644 --- a/paperless-ngx/Chart.yaml +++ b/paperless-ngx/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/dev/src-ui/s type: application -version: 9.0.0+up3.0.5 +version: 10.0.0+up3.0.5 appVersion: 3.0.5 diff --git a/paperless-ngx/values.yaml b/paperless-ngx/values.yaml index 5d41c59..08ae837 100644 --- a/paperless-ngx/values.yaml +++ b/paperless-ngx/values.yaml @@ -12,7 +12,7 @@ secrets: oidcSecretRef: null ingress: - clusterIssuer: "letsencrypt-issuer" + clusterIssuer: null domain: null className: nginx # Extra annotations for the Ingress; merged with the cert-manager diff --git a/patchmon/Chart.yaml b/patchmon/Chart.yaml index 229fd99..8c77df2 100644 --- a/patchmon/Chart.yaml +++ b/patchmon/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/PatchMon/PatchMon/main/frontend/public/a type: application -version: 5.1.0+up2.1.3 +version: 5.2.0+up2.1.3 appVersion: "2.1.3" maintainers: diff --git a/patchmon/templates/patchmon.deployment.yaml b/patchmon/templates/patchmon.deployment.yaml index 669298b..1f8436e 100644 --- a/patchmon/templates/patchmon.deployment.yaml +++ b/patchmon/templates/patchmon.deployment.yaml @@ -143,6 +143,26 @@ spec: - name: GUACD_ADDRESS value: "127.0.0.1:4822" {{- end }} + {{- range $value := .Values.patchmon.server.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} # Probe values are rendered through tpl (see values.yaml for the # defaults and why the probes send a Host header). livenessProbe: @@ -162,6 +182,29 @@ spec: - name: {{ .Release.Name }}-{{ .Chart.Name }}-guacd image: {{ .Values.patchmon.guacd.image }} imagePullPolicy: IfNotPresent + {{- if .Values.patchmon.guacd.env }} + env: + {{- range $value := .Values.patchmon.guacd.env }} + {{- if $value.value }} + - name: {{ $value.name | quote }} + value: {{ tpl $value.value $ | quote }} + {{- else if $value.valueFrom }} + {{- if $value.valueFrom.secretKeyRef }} + - name: {{ $value.name }} + valueFrom: + secretKeyRef: + name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }} + {{- else if $value.valueFrom.configMapKeyRef }} + - name: {{ $value.name }} + valueFrom: + configMapKeyRef: + name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }} + key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} securityContext: {{- toYaml .Values.patchmon.guacd.securityContext.container | nindent 12 }} ports: diff --git a/patchmon/values.yaml b/patchmon/values.yaml index 3423e3d..30530ec 100644 --- a/patchmon/values.yaml +++ b/patchmon/values.yaml @@ -77,6 +77,10 @@ patchmon: periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 + # Extra environment variables, appended to the ones the chart always sets. + # Values are rendered through tpl; value, secretKeyRef and configMapKeyRef + # forms are supported. + env: [] guacd: # In-browser RDP for Windows hosts (beta) via the Apache Guacamole daemon, # run as a sidecar container in the server pod. Disabled by default; when @@ -126,6 +130,10 @@ patchmon: periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 30 + # Extra environment variables for the guacd sidecar (e.g. GUACD_LOG_LEVEL). + # Values are rendered through tpl; value, secretKeyRef and configMapKeyRef + # forms are supported. + env: [] ingress: clusterIssuer: null From 22fdb58396eb1efc56647c1d7b83f205b3a210e1 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 22 Aug 2026 23:13:23 +0200 Subject: [PATCH 2/3] Strict clusterIssuer also for cyberchef and urlaubsverwaltung Per the maintainer decision (option a: ingress.clusterIssuer strictly required in ALL charts) the two remaining defaults are removed as well: - cyberchef: drop the letsencrypt default; bump raised from minor to major (6.0.0+up11.4.0) as this is a breaking values change. - urlaubsverwaltung: drop the letsencrypt-issuer default; major bump to 6.0.0+up6.7.0. - CI test-values for both charts now set clusterIssuer explicitly. A repo-wide grep confirms no chart carries a clusterIssuer default anymore. --- ci/cyberchef/test-values.yaml | 7 ++++--- ci/urlaubsverwaltung/test-values.yaml | 3 +++ cyberchef/Chart.yaml | 2 +- cyberchef/values.yaml | 2 +- urlaubsverwaltung/Chart.yaml | 2 +- urlaubsverwaltung/values.yaml | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ci/cyberchef/test-values.yaml b/ci/cyberchef/test-values.yaml index 64aa16e..354205a 100644 --- a/ci/cyberchef/test-values.yaml +++ b/ci/cyberchef/test-values.yaml @@ -1,5 +1,6 @@ -# cyberchef: stateless, no secrets. Only the ingress host is required -# (clusterIssuer already has a default). The Ingress just has to apply - -# there is no ingress-nginx/cert-manager in the CI cluster. +# cyberchef: stateless, no secrets. Only the required ingress values are set. +# The Ingress just has to apply - there is no ingress-nginx/cert-manager in +# the CI cluster. ingress: domain: cyberchef.ci.local + clusterIssuer: ci diff --git a/ci/urlaubsverwaltung/test-values.yaml b/ci/urlaubsverwaltung/test-values.yaml index 25a6351..8b2dd38 100644 --- a/ci/urlaubsverwaltung/test-values.yaml +++ b/ci/urlaubsverwaltung/test-values.yaml @@ -4,3 +4,6 @@ secrets: ingress: domain: uv.ci.local + # clusterIssuer is strictly required (no default); the Ingress just has to + # apply - there is no cert-manager in the CI cluster. + clusterIssuer: ci diff --git a/cyberchef/Chart.yaml b/cyberchef/Chart.yaml index 59bc61a..ad80563 100644 --- a/cyberchef/Chart.yaml +++ b/cyberchef/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/gchq/CyberChef/master/src/web/static/ima type: application -version: 5.2.0+up11.4.0 +version: 6.0.0+up11.4.0 appVersion: "11.4.0" maintainers: diff --git a/cyberchef/values.yaml b/cyberchef/values.yaml index 0796648..c891758 100644 --- a/cyberchef/values.yaml +++ b/cyberchef/values.yaml @@ -60,7 +60,7 @@ cyberchef: ingress: enabled: true - clusterIssuer: letsencrypt + clusterIssuer: null domain: null className: nginx # Extra annotations for the Ingress; merged with the cert-manager diff --git a/urlaubsverwaltung/Chart.yaml b/urlaubsverwaltung/Chart.yaml index 6220576..98c02f9 100644 --- a/urlaubsverwaltung/Chart.yaml +++ b/urlaubsverwaltung/Chart.yaml @@ -4,5 +4,5 @@ description: A Helm chart to deploy urlaubsverwaltung on Kubernetes icon: https://raw.githubusercontent.com/urlaubsverwaltung/urlaubsverwaltung/main/src/main/resources/static/favicons/apple-touch-icon.png type: application -version: 5.0.0+up6.7.0 +version: 6.0.0+up6.7.0 appVersion: 6.7.0 diff --git a/urlaubsverwaltung/values.yaml b/urlaubsverwaltung/values.yaml index 76a5cc4..f6ca655 100644 --- a/urlaubsverwaltung/values.yaml +++ b/urlaubsverwaltung/values.yaml @@ -10,7 +10,7 @@ secrets: ingress: enabled: true - clusterIssuer: "letsencrypt-issuer" + clusterIssuer: null domain: null className: nginx # Extra annotations for the Ingress; merged with the cert-manager From 408232a36ffe31b5b3f71a51ace05aded56a3ba8 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 22 Aug 2026 23:16:41 +0200 Subject: [PATCH 3/3] Document the consolidated chart style guidelines README.md gains a comprehensive 'Chart style guidelines' section - the binding end state of the conventions overhaul (#32, batches 0-4) for all current and future charts: naming (incl. the PVC-name exception and the StatefulSet migration pattern), versioning scheme and semver rules, image pinning, the ingress standard (domain + clusterIssuer strictly required without defaults, className default nginx, annotations merge, TLS secret name, homeassistant exception), security hardening with mandatory documentation of deviations, probes, the auto-limits scheme, the secrets.SecretRef convention, tpl-rendered env blocks, scaling, and the icon/lint/CI-fixture/no-dependencies rules. The Versioning section now points there and documents that app-version updates go through PRs. CLAUDE.md's conventions section is aligned as a compact summary referencing the README guide. --- CLAUDE.md | 24 +++++++++------- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 23 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 90fe5c3..6d379ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,19 +47,23 @@ Copy `template-chart/` and replace every `xxx` placeholder — in file names (`t Semver conventions for the chart part: -- **Patch**: bug fixes without behavior or values-schema change. +- **Patch**: bug fixes without behavior or values-schema change; plain app-version updates. - **Minor**: additive, backward-compatible changes (new optional values with unchanged defaults). -- **Major**: runtime behavior changes or breaking values changes (e.g. probes, security hardening, computed limits). +- **Major**: runtime behavior changes or breaking values changes (e.g. probes, security hardening, computed limits, values that become required). App-version updates go through PRs (so the validate and install-test pipelines run), not through the `change-app-version.yml` dispatch workflow (which commits directly to main and bypasses both). For app **major** jumps, update stepwise: first to the last release of the old major line (own PR, merge, publish), then to the newest major. -## Chart conventions (from template-chart) +## Chart conventions -- All resources are named `{{ .Release.Name }}-{{ .Chart.Name }}-` (e.g. `...-deployment`, `...-service`, `...-secret`). -- Image tags come from `{{ .Chart.AppVersion }}`. +The binding style guide for all current and future charts lives in **README.md ("Chart style guidelines")**; `template-chart/` is the reference implementation. Compact summary: + +- Resources named `{{ .Release.Name }}-{{ .Chart.Name }}-`; template files `..yaml`; stateful apps use a StatefulSet (`.sfs.yaml`). Documented exception: historical PVC names stay (renames would orphan data); StatefulSet renames only with a `--cascade=orphan` migration note in a major PR. +- Main image tag from `{{ .Chart.AppVersion }}`; auxiliary/sidecar/init images pinned via values and reviewed on app-update rounds. +- Ingress: `ingress.domain` and `ingress.clusterIssuer` `required` **without defaults**; `ingress.className` defaults to `nginx`; optional `ingress.annotations` merged with the cert-manager annotation; TLS secret `tls---ingress`. Documented exception: homeassistant keeps its flexible upstream ingress style (className default `nginx`). - Secrets are 1Password `OnePasswordItem` CRs: values expose `secrets.SecretRef` holding an `op://` item path; the operator materializes the Kubernetes Secret. -- Ingress standard: `ingress.domain` and `ingress.clusterIssuer` (cert-manager) are `required` values; `ingress.className` defaults to `nginx`; optional `ingress.annotations` are merged with the cert-manager annotation; TLS secret named `tls---ingress`. -- `env` entries in values support Helm templating — the deployment renders them through `tpl`, and supports `value`, `secretKeyRef`, and `configMapKeyRef` forms. -- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped; probes and resource requests/limits always set. Security contexts are values-configurable (`securityContext.pod` / `securityContext.container`); image-forced deviations are documented with a comment in the chart's `values.yaml`. -- Stateful apps use a StatefulSet in a `.sfs.yaml` template instead of a deployment. -- No chart has dependencies; `charts/` directories are empty. +- Every container offers an additive `env` block rendered through `tpl` with `value`, `secretKeyRef`, and `configMapKeyRef` forms. +- Hardening: `automountServiceAccountToken: false`, non-root, `readOnlyRootFilesystem: true`, caps dropped, `allowPrivilegeEscalation: false`; contexts values-configurable (`securityContext.pod` / `securityContext.container`); writable paths as emptyDirs; image-forced deviations MUST be documented with a comment in the chart's `values.yaml`. +- Probes: liveness/readiness/startup on every workload (exception: paperless consume CronJob), values-configurable with calibrated defaults. +- Resources: requests as values defaults; limits auto-computed by the `_helpers.tpl` helper (`limitFactor` default 3; no CPU limits unless set explicitly). +- Replicas per workload (explicit `0` allowed) plus a chart-global `scaleDown` flag that overrides them. +- `icon:` mandatory in every `Chart.yaml`; `helm lint --strict` without findings; CI fixtures under `ci//` or a justified exclusion; no chart dependencies (`charts/` directories are empty). diff --git a/README.md b/README.md index 36b816d..a538d72 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo ## Versioning -`Chart.yaml` uses the scheme `version: +up` (e.g. `1.4.0+up1.9.2`), where `appVersion` matches the `+up` suffix (dashes in the app version are normalized to dots in the suffix, e.g. `0.0.36-beta` → `+up0.0.36.beta`). +`Chart.yaml` uses the scheme `version: +up` (e.g. `1.4.0+up1.9.2`) — see the [chart style guidelines](#chart-style-guidelines) for the full rules (suffix normalization, semver bump levels). -- Bump the **semver part** when the chart itself (templates/values) changes. -- The **app version** is bumped via the `Change App-Version` workflow, which rewrites `version:`/`appVersion:` and commits directly to `main`. +- Bump the **chart version** in every change that touches a chart directory — a push to `main` publishes the chart. +- **App-version updates go through PRs** (so the validate and install-test pipelines run). The manually dispatched `Change App-Version` workflow (rewrites `version:`/`appVersion:` and commits directly to `main`) exists but bypasses both pipelines. For app **major** jumps, update stepwise: first to the last release of the old major line (own PR, merge, publish), then to the newest major. ## CI/CD (GitHub Actions) @@ -69,16 +69,75 @@ helm template --set ingress.domain=example.com --set ingress.cluste For runtime verification (do the workloads actually start, do probes/security settings hold?), use a throwaway local [k3d](https://k3d.io) cluster: `k3d cluster create `, apply a stub CRD for `OnePasswordItem` plus dummy secrets (and throwaway postgres/redis pods where the app needs them), `helm install` the chart, watch pod readiness/events, then `k3d cluster delete `. -## Chart conventions +## Chart style guidelines + +These guidelines are **binding for every current and future chart** in this repo. They are the consolidated end state of the conventions overhaul (#32, batches 0–4); `template-chart/` is the reference implementation. + +### 1. Naming + +- Resources are named `{{ .Release.Name }}-{{ .Chart.Name }}-` (e.g. `...-deployment`, `...-sfs`, `...-service`, `...-secret`, `...-configmap`, `...-ingress`, `...-pvc`). +- Template files are named `..yaml`; stateful apps use a StatefulSet in a `.sfs.yaml` template instead of a deployment. +- **Documented exception — existing PVC names**: PVCs of already-published charts keep their historical names even where they deviate from the scheme. Renaming a PVC makes Kubernetes provision a new, empty volume, so a rename would orphan the existing data. New charts name PVCs `{{ .Release.Name }}-{{ .Chart.Name }}-pvc` per the scheme. +- StatefulSet renames are possible (data survives because the charts use standalone PVC templates, not `volumeClaimTemplates`) but only ship in a major-bump PR with a one-time migration note: `kubectl delete statefulset --cascade=orphan` (and delete the leftover old pod) before upgrading. + +### 2. Versioning + +- `Chart.yaml` uses `version: +up`; the `+up` suffix matches `appVersion`, with dashes normalized to dots (e.g. `0.0.36-beta` → `+up0.0.36.beta`). `appVersion` is the **exact image tag** of the main app image. +- Semver rules for the chart part: + - **Patch**: bug fixes without behavior or values-schema change; plain app-version updates. + - **Minor**: additive, backward-compatible changes (new optional values with unchanged defaults). + - **Major**: runtime behavior changes or breaking values changes (e.g. resource renames, probes, security hardening, values that become required). +- Any push to `main` touching a chart directory publishes that chart, so the version must be bumped in the same change. + +### 3. Images + +- The main app image tag comes from `{{ .Chart.AppVersion }}` — never hardcoded. +- Auxiliary images (sidecars, init containers, secondary workloads — e.g. the delegation nginx in matrix-synapse, guacd in patchmon, busybox init/consume images in paperless-ngx) are pinned via values instead of being hardcoded in templates, and their pinned versions are reviewed alongside every app-version update round. + +### 4. Ingress + +- `ingress.domain` and `ingress.clusterIssuer` (cert-manager) are `required` values **without defaults** — every installation sets both explicitly; they are the only values needed for a default install. +- `ingress.className` selects the ingress class and defaults to `nginx`. +- `ingress.annotations` takes optional extra annotations that are merged with the cert-manager `cluster-issuer` annotation the chart always sets. +- The TLS secret is named `tls---ingress`. +- **Documented exception — homeassistant**: keeps its upstream-style flexible ingress values (host/tls lists, no required values); only its `ingress.className` default follows the standard (`nginx`, clearable to omit the field). + +### 5. Security hardening + +- Defaults on every workload: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped, `allowPrivilegeEscalation: false`. +- Pod and container contexts are values-configurable via `securityContext.pod` / `securityContext.container`. +- Writable paths (e.g. `/tmp`, nginx cache dirs, s6-overlay's `/run`) are emptyDirs so the root filesystem stays read-only. +- Where an image cannot satisfy the full standard (e.g. Home Assistant must start as root for s6-overlay, paperless needs a root init container to hand over `/run`, samba needs its capabilities), the deviation is minimal and **must** be justified with a comment in that chart's `values.yaml`. + +### 6. Probes + +- Every workload has liveness, readiness and startup probes (documented exception: the paperless-ngx consume CronJob). +- Probes are values-configurable with calibrated defaults (the startup probe gates liveness/readiness; its budget is documented in a values comment per chart). + +### 7. Resources (auto-limits) + +- Resource requests ship as chart defaults in `values.yaml` and are user-overridable. +- Limits are computed by a per-chart `_helpers.tpl` helper (reference implementation in `template-chart/templates/_helpers.tpl`): an explicitly set limit always wins, missing memory and ephemeral-storage limits default to `resources.limitFactor` × the request (factor default `3`), and a CPU limit is only rendered when set explicitly — otherwise CPU stays unlimited (it is compressible, throttling instead of OOM-killing). The helper is duplicated into every chart because charts have no dependencies. + +### 8. Secrets -- Resources are named `{{ .Release.Name }}-{{ .Chart.Name }}-`; template files are named `..yaml` (StatefulSets use `.sfs.yaml`). - - **Deliberate exception — existing PVC names**: PVCs of already-published charts keep their historical names even where they deviate from the scheme. Renaming a PVC makes Kubernetes provision a new, empty volume, so a rename would orphan the existing data. New charts name PVCs `{{ .Release.Name }}-{{ .Chart.Name }}-pvc` per the scheme. StatefulSet names *are* being aligned (data survives because the charts use standalone PVC templates, not `volumeClaimTemplates`); each rename ships in its own major-bump PR with a one-time migration note (`kubectl delete statefulset --cascade=orphan` before upgrading). -- Image tags come from `{{ .Chart.AppVersion }}`. -- Auxiliary/sidecar images (images other than the app image, currently only the delegation nginx in matrix-synapse) are pinned via values (`.image.repository` / `.image.tag`) instead of being hardcoded in templates, and their pinned versions are reviewed alongside every app-version update round. - Secrets are **1Password `OnePasswordItem` CRs**: values expose `secrets.SecretRef` holding an `op://` item path; the 1Password operator materializes the Kubernetes Secret in-cluster. -- Ingress standard: `ingress.domain` and `ingress.clusterIssuer` (cert-manager) are the only required values. `ingress.className` selects the ingress class and defaults to `nginx`; `ingress.annotations` takes optional extra annotations that are merged with the cert-manager `cluster-issuer` annotation the chart always sets. The TLS secret is named `tls---ingress`. (homeassistant deliberately keeps its upstream-style flexible ingress values instead.) -- `env` entries in `values.yaml` are rendered through `tpl`, so values may contain Helm templating; `value`, `secretKeyRef` and `configMapKeyRef` forms are supported. -- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped, probes and resource requests always set. Pod and container contexts are configurable via `securityContext.pod` / `securityContext.container` in each chart's values; writable paths (e.g. `/tmp`, nginx cache dirs, s6-overlay's `/run`) are emptyDirs. Where an image cannot satisfy the full standard (e.g. Home Assistant must start as root for s6-overlay, paperless needs a root init container to hand over `/run`), the deviation is minimal and justified with a comment in that chart's `values.yaml`. -- Resource requests ship as chart defaults and are user-overridable; limits are computed by a per-chart `_helpers.tpl` helper (reference implementation in `template-chart/templates/_helpers.tpl`): an explicitly set limit always wins, missing memory and ephemeral-storage limits default to `resources.limitFactor` × the request (factor default `3`), and a CPU limit is only rendered when set explicitly — otherwise CPU stays unlimited (it is compressible, throttling instead of OOM-killing). The helper is duplicated into every chart because charts have no dependencies. -- Replica counts and scale-to-zero: every Deployment/StatefulSet has a `replicas` value (default `1`, the values path per workload matches its section, e.g. `patchmon.frontend.replicas`) that also accepts an explicit `0` for per-workload fine control. In addition, every chart has a global `scaleDown` flag (default `false`): `scaleDown: true` takes precedence over all `replicas` values, renders every Deployment/StatefulSet with `replicas: 0` and suspends CronJobs (paperless-ngx consume job). `scaleDown` **stops** the app but does not uninstall it — PVCs, Secrets, Services and Ingress stay in place, and setting it back to `false` brings the workloads back up. Implemented via a per-chart `.replicas` helper in `_helpers.tpl` (reference in `template-chart`); the helper uses an explicit nil check (`kindIs "invalid"`) because Helm's `default` would swallow an explicit `0`. + +### 9. Environment variables + +- Every workload container offers an additive `env` value block, appended to the env entries the chart always sets. +- Entries are rendered through `tpl` (so values may contain Helm templating) and support the `value`, `secretKeyRef` and `configMapKeyRef` forms (pattern: `template-chart/templates/xxx.deployment.yaml`). + +### 10. Scaling + +- Every Deployment/StatefulSet has a `replicas` value (default `1`, the values path per workload matches its section) that also accepts an explicit `0` for per-workload fine control. +- Every chart has a global `scaleDown` flag (default `false`): `scaleDown: true` takes precedence over all `replicas` values, renders every Deployment/StatefulSet with `replicas: 0` and suspends CronJobs. It **stops** the app but does not uninstall it — PVCs, Secrets, Services and Ingress stay in place; setting it back to `false` brings the workloads back up. +- Implemented via a per-chart `.replicas` helper in `_helpers.tpl` (reference in `template-chart`); the helper uses an explicit nil check (`kindIs "invalid"`) because Helm's `default` would swallow an explicit `0`. + +### 11. Everything else + +- `icon:` is mandatory in every `Chart.yaml`. +- `helm lint --strict` must pass without findings. +- `NOTES.txt` for every chart is planned (#43); currently only homeassistant ships one. +- CI: every installable chart has fixtures under `ci//` (`test-values.yaml`, optionally `fixtures.yaml`/`settings.env`) or a justified entry in `ci/excluded-charts.txt` — the install-test fails if both are missing. - Charts have no dependencies (`charts/` directories are empty).