From a287a1961ef174465e76bf90fd948173efb9292f Mon Sep 17 00:00:00 2001 From: MergeCheck Date: Fri, 3 Jul 2026 01:27:36 -0300 Subject: [PATCH 1/2] fix(changedetection): add template validation gate --- charts/changedetection/templates/NOTES.txt | 40 ++++++------ charts/changedetection/templates/_helpers.tpl | 27 ++++++++ charts/changedetection/templates/ingress.yaml | 4 +- .../changedetection/templates/validate.yaml | 2 + .../changedetection/tests/ingress_test.yaml | 13 ++++ .../tests/validation_test.yaml | 61 +++++++++++++++++++ 6 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 charts/changedetection/templates/validate.yaml create mode 100644 charts/changedetection/tests/validation_test.yaml diff --git a/charts/changedetection/templates/NOTES.txt b/charts/changedetection/templates/NOTES.txt index 44fc93f0f..ca54344e0 100644 --- a/charts/changedetection/templates/NOTES.txt +++ b/charts/changedetection/templates/NOTES.txt @@ -2,8 +2,8 @@ changedetection.io has been installed ===================================== -Installation summary --------------------- +1. Installation summary +----------------------- Release: {{ .Release.Name }} Namespace: {{ .Release.Namespace }} Chart version: {{ .Chart.Version }} @@ -14,12 +14,16 @@ Service type: {{ .Values.service.type }} Service port: {{ .Values.service.port }} Container port: {{ .Values.changedetection.port }} -Access changedetection.io -------------------------- +2. Access changedetection.io +---------------------------- {{- if .Values.ingress.enabled }} Ingress routes: {{- range .Values.ingress.hosts }} +{{- if .host }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}/ +{{- else }} + - catch-all host on the configured ingress controller +{{- end }} {{- end }} {{- else if .Values.gateway.enabled }} Gateway API routes: @@ -42,8 +46,8 @@ Cluster-local endpoint: http://{{ include "changedetection.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}/ -Operational checks ------------------- +3. Operational checks +--------------------- Wait for the workload: kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=changedetection -n {{ .Release.Namespace }} --timeout=300s @@ -54,8 +58,8 @@ Inspect status: kubectl logs -l app.kubernetes.io/name=changedetection -n {{ .Release.Namespace }} --all-containers --tail=50 kubectl get events -n {{ .Release.Namespace }} --sort-by=.lastTimestamp -Persistence ------------ +4. Persistence +-------------- {{- if .Values.persistence.enabled }} Datastore persistence is enabled. PVC: {{ include "changedetection.dataClaimName" . }} @@ -68,8 +72,8 @@ Datastore persistence is disabled. /datastore uses emptyDir and all watches, history, and snapshots are ephemeral. {{- end }} -Browser rendering ------------------ +5. Browser rendering +-------------------- {{- if .Values.browser.enabled }} Playwright browser rendering is enabled. Browser image: {{ .Values.browser.image.repository }}:{{ .Values.browser.image.tag }} @@ -80,8 +84,8 @@ Enable browser.enabled=true for JavaScript-heavy pages, SPAs, and rendered price or inventory checks. {{- end }} -Routing summary ---------------- +6. Routing summary +------------------ Ingress: {{ ternary "enabled" "disabled" .Values.ingress.enabled }} Gateway API HTTPRoute: {{ ternary "enabled" "disabled" .Values.gateway.enabled }} Service IP family policy: {{ default "cluster default" .Values.service.ipFamilyPolicy }} @@ -89,8 +93,8 @@ Service IP family policy: {{ default "cluster default" .Values.service.ipFamilyP Service IP families: {{ join ", " . }} {{- end }} -External Secrets ----------------- +7. External Secrets +------------------- {{- if .Values.externalSecrets.enabled }} ExternalSecret is enabled. Target Secret: {{ include "changedetection.externalSecretName" . }} @@ -106,8 +110,8 @@ externalSecrets.enabled to materialize environment-backed credentials through External Secrets Operator. {{- end }} -Common operations ------------------ +8. Common operations +-------------------- Open a shell in the main container: kubectl exec -n {{ .Release.Namespace }} deploy/{{ include "changedetection.fullname" . }} -c changedetection -- sh @@ -116,8 +120,8 @@ List rendered resources: kubectl get all,pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} -Documentation -------------- +9. Documentation +---------------- Chart docs: https://helmforge.dev/docs/charts/changedetection Chart source: https://github.com/helmforgedev/charts/tree/main/charts/changedetection Upstream project: https://changedetection.io diff --git a/charts/changedetection/templates/_helpers.tpl b/charts/changedetection/templates/_helpers.tpl index c073d5f62..6927cfd92 100644 --- a/charts/changedetection/templates/_helpers.tpl +++ b/charts/changedetection/templates/_helpers.tpl @@ -39,6 +39,33 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- end -}} +{{- define "changedetection.validate" -}} +{{- if and .Values.ingress.enabled (not .Values.ingress.hosts) -}} +{{- fail "ingress.enabled requires ingress.hosts to contain at least one host" -}} +{{- end -}} +{{- if and .Values.gateway.enabled (not .Values.gateway.parentRefs) -}} +{{- fail "gateway.enabled requires gateway.parentRefs to be populated to create a valid HTTPRoute." -}} +{{- end -}} +{{- if .Values.gateway.enabled -}} +{{- range $index, $parentRef := .Values.gateway.parentRefs -}} +{{- if not $parentRef.name -}} +{{- fail (printf "gateway.parentRefs[%d].name is required when gateway.enabled is true" $index) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- if and .Values.externalSecrets.enabled (not .Values.externalSecrets.secretStoreRef.name) -}} +{{- fail "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" -}} +{{- end -}} +{{- if and .Values.externalSecrets.enabled (not .Values.externalSecrets.data) (not .Values.externalSecrets.dataFrom) -}} +{{- fail "externalSecrets.data or externalSecrets.dataFrom is required when externalSecrets.enabled=true" -}} +{{- end -}} +{{- range $key, $_ := .Values.podLabels -}} +{{- if or (eq $key "app.kubernetes.io/name") (eq $key "app.kubernetes.io/instance") -}} +{{- fail (printf "podLabels must not override selector label %q" $key) -}} +{{- end -}} +{{- end -}} +{{- end -}} + {{- define "changedetection.image" -}} {{- printf "%s:%s" .Values.image.repository .Values.image.tag -}} {{- end -}} diff --git a/charts/changedetection/templates/ingress.yaml b/charts/changedetection/templates/ingress.yaml index 7ef8aa22e..d11cfef1a 100644 --- a/charts/changedetection/templates/ingress.yaml +++ b/charts/changedetection/templates/ingress.yaml @@ -26,7 +26,9 @@ spec: {{- end }} rules: {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} + - {{- with .host }} + host: {{ . | quote }} + {{- end }} http: paths: {{- range (.paths | default (list (dict "path" "/" "pathType" "Prefix"))) }} diff --git a/charts/changedetection/templates/validate.yaml b/charts/changedetection/templates/validate.yaml new file mode 100644 index 000000000..a28b07fe9 --- /dev/null +++ b/charts/changedetection/templates/validate.yaml @@ -0,0 +1,2 @@ +{{/* SPDX-License-Identifier: Apache-2.0 */}} +{{- include "changedetection.validate" . -}} diff --git a/charts/changedetection/tests/ingress_test.yaml b/charts/changedetection/tests/ingress_test.yaml index e8005677e..cd265bba9 100644 --- a/charts/changedetection/tests/ingress_test.yaml +++ b/charts/changedetection/tests/ingress_test.yaml @@ -22,3 +22,16 @@ tests: asserts: - isKind: of: Ingress + + - it: should allow catch-all ingress rules without host + set: + ingress.enabled: true + ingress.hosts: + - paths: + - path: / + pathType: Prefix + asserts: + - isKind: + of: Ingress + - notExists: + path: spec.rules[0].host diff --git a/charts/changedetection/tests/validation_test.yaml b/charts/changedetection/tests/validation_test.yaml new file mode 100644 index 000000000..025384361 --- /dev/null +++ b/charts/changedetection/tests/validation_test.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: Apache-2.0 +suite: Validation +templates: + - templates/validate.yaml +tests: + - it: should render no validation manifest by default + asserts: + - hasDocuments: + count: 0 + + - it: should fail when ingress is enabled without hosts + set: + ingress.enabled: true + ingress.hosts: [] + asserts: + - failedTemplate: + errorMessage: "ingress.enabled requires ingress.hosts to contain at least one host" + + - it: should fail when gateway is enabled without parentRefs + set: + gateway.enabled: true + asserts: + - failedTemplate: + errorMessage: "gateway.enabled requires gateway.parentRefs to be populated to create a valid HTTPRoute." + + - it: should fail when gateway parentRef has no name + set: + gateway.enabled: true + gateway.parentRefs: + - namespace: ingress + asserts: + - failedTemplate: + errorMessage: "gateway.parentRefs[0].name is required when gateway.enabled is true" + + - it: should fail when external secret store reference is missing + set: + externalSecrets.enabled: true + externalSecrets.data: + - secretKey: LOGGER_LEVEL + remoteRef: + key: changedetection/app + property: loggerLevel + asserts: + - failedTemplate: + errorMessage: "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" + + - it: should fail when external secret has no data mappings + set: + externalSecrets.enabled: true + externalSecrets.secretStoreRef.name: helmforge-fake-store + asserts: + - failedTemplate: + errorMessage: "externalSecrets.data or externalSecrets.dataFrom is required when externalSecrets.enabled=true" + + - it: should fail when podLabels override selector labels + set: + podLabels: + app.kubernetes.io/instance: custom + asserts: + - failedTemplate: + errorMessage: 'podLabels must not override selector label "app.kubernetes.io/instance"' From 5a5ff8d647a0437f61dfc1b57763d0700060da7e Mon Sep 17 00:00:00 2001 From: MergeCheck Date: Sun, 5 Jul 2026 19:36:59 -0300 Subject: [PATCH 2/2] fix(changedetection): derive protected pod label keys --- charts/changedetection/templates/_helpers.tpl | 3 ++- charts/changedetection/tests/validation_test.yaml | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/charts/changedetection/templates/_helpers.tpl b/charts/changedetection/templates/_helpers.tpl index 6927cfd92..252f1b0c5 100644 --- a/charts/changedetection/templates/_helpers.tpl +++ b/charts/changedetection/templates/_helpers.tpl @@ -59,8 +59,9 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- if and .Values.externalSecrets.enabled (not .Values.externalSecrets.data) (not .Values.externalSecrets.dataFrom) -}} {{- fail "externalSecrets.data or externalSecrets.dataFrom is required when externalSecrets.enabled=true" -}} {{- end -}} +{{- $selectorLabels := include "changedetection.selectorLabels" . | fromYaml -}} {{- range $key, $_ := .Values.podLabels -}} -{{- if or (eq $key "app.kubernetes.io/name") (eq $key "app.kubernetes.io/instance") -}} +{{- if hasKey $selectorLabels $key -}} {{- fail (printf "podLabels must not override selector label %q" $key) -}} {{- end -}} {{- end -}} diff --git a/charts/changedetection/tests/validation_test.yaml b/charts/changedetection/tests/validation_test.yaml index 025384361..9b48f8579 100644 --- a/charts/changedetection/tests/validation_test.yaml +++ b/charts/changedetection/tests/validation_test.yaml @@ -59,3 +59,11 @@ tests: asserts: - failedTemplate: errorMessage: 'podLabels must not override selector label "app.kubernetes.io/instance"' + + - it: should fail when podLabels override chart name selector label + set: + podLabels: + app.kubernetes.io/name: custom + asserts: + - failedTemplate: + errorMessage: 'podLabels must not override selector label "app.kubernetes.io/name"'