Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 84 additions & 14 deletions charts/ckan/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,37 +1,107 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
CKAN {{ .Chart.AppVersion }} has been installed.

1. Installation summary
-----------------------
Release: {{ .Release.Name }}
Namespace: {{ .Release.Namespace }}
Chart version: {{ .Chart.Version }}
Application version: {{ .Chart.AppVersion }}
Image: {{ include "ckan.image" . }}
Service name: {{ include "ckan.fullname" . }}
Service type: {{ .Values.service.type }}
Service port: {{ .Values.service.port }}

2. Access
---------
{{- if .Values.ingress.enabled }}
Ingress routes:
{{- range .Values.ingress.hosts }}
Web UI: http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
{{- if .host }}
- http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}/
{{- else }}
- catch-all host on the configured ingress controller
{{- end }}
{{- end }}
{{- else }}
kubectl port-forward svc/{{ include "ckan.fullname" . }} 5000:{{ .Values.service.port }}
Web UI: http://localhost:5000
Port-forward the Service:

kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "ckan.fullname" . }} 5000:{{ .Values.service.port }}

Then open:

http://localhost:5000/
{{- end }}

Default credentials: {{ .Values.ckan.sysadminName }} / (check secret {{ include "ckan.secretName" . }})
Cluster-local endpoint:

http://{{ include "ckan.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}/

3. Credentials
--------------
Sysadmin username: {{ .Values.ckan.sysadminName }}
Sysadmin password secret: {{ include "ckan.secretName" . }}
Sysadmin password key: {{ .Values.ckan.existingSecretPasswordKey | default "sysadmin-password" }}

Inspect generated credentials:

kubectl -n {{ .Release.Namespace }} get secret {{ include "ckan.secretName" . }}

4. Runtime components
---------------------
PostgreSQL subchart: {{ ternary "enabled" "disabled" .Values.postgresql.enabled }}
Redis subchart: {{ ternary "enabled" "disabled" .Values.redis.enabled }}
Solr: {{ ternary "enabled" "external" .Values.solr.enabled }}
DataPusher: {{ ternary "enabled" "disabled" .Values.datapusher.enabled }}
Backup CronJob: {{ ternary "enabled" "disabled" .Values.backup.enabled }}

Runtime:
Image: {{ include "ckan.image" . }}
PostgreSQL subchart: {{ ternary "enabled" "disabled" .Values.postgresql.enabled }}
Redis subchart: {{ ternary "enabled" "disabled" .Values.redis.enabled }}
Solr: {{ ternary "enabled" "external" .Values.solr.enabled }}
DataPusher: {{ ternary "enabled" "disabled" .Values.datapusher.enabled }}
Backup CronJob: {{ ternary "enabled" "disabled" .Values.backup.enabled }}
5. Operational checks
---------------------
Wait for CKAN:

kubectl -n {{ .Release.Namespace }} rollout status deploy/{{ include "ckan.fullname" . }} --timeout=300s

Inspect workload status:

Checks:
kubectl -n {{ .Release.Namespace }} get pods -l app.kubernetes.io/instance={{ .Release.Name }}
kubectl -n {{ .Release.Namespace }} logs deploy/{{ include "ckan.fullname" . }} --tail=100
kubectl -n {{ .Release.Namespace }} get events --sort-by=.lastTimestamp

6. Storage and backup
---------------------
{{- if .Values.persistence.enabled }}
CKAN file storage persistence is enabled.
PVC: {{ .Values.persistence.existingClaim | default (printf "%s-storage" (include "ckan.fullname" .)) }}
{{- else }}
CKAN file storage persistence is disabled and uses emptyDir.
{{- end }}

{{- if .Values.backup.enabled }}
Scheduled PostgreSQL backups are enabled.
Backup CronJob: {{ include "ckan.fullname" . }}-backup
{{- else }}
Scheduled PostgreSQL backups are disabled.
{{- end }}

7. Troubleshooting
------------------
Inspect CKAN and dependencies:

kubectl -n {{ .Release.Namespace }} describe deploy/{{ include "ckan.fullname" . }}
{{- if .Values.postgresql.enabled }}
kubectl -n {{ .Release.Namespace }} logs statefulset/{{ .Release.Name }}-postgresql --tail=80
{{- end }}
{{- if .Values.redis.enabled }}
kubectl -n {{ .Release.Namespace }} logs statefulset/{{ .Release.Name }}-redis --tail=80
{{- end }}
{{- if .Values.solr.enabled }}
kubectl -n {{ .Release.Namespace }} logs statefulset/{{ include "ckan.fullname" . }}-solr --tail=80
{{- end }}

Upgrade reminder:
Take a PostgreSQL backup and confirm CKAN plugins before production upgrades.
8. Resources
------------
Chart docs: https://helmforge.dev/docs/charts/ckan
Chart source: https://github.com/helmforgedev/charts/tree/main/charts/ckan
Upstream project: https://ckan.org

Happy Helmforging :)
54 changes: 44 additions & 10 deletions charts/ckan/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,50 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{- end -}}

{{- define "ckan.validate" -}}
{{- if and (eq .Values.database.mode "external") (not .Values.database.external.host) -}}
{{- fail "database.external.host is required when database.mode is external" -}}
{{- end -}}
{{- if and (eq .Values.database.mode "external") (not .Values.database.external.existingSecret) (not .Values.database.external.password) -}}
{{- fail "database.external.password or database.external.existingSecret is required when database.mode is external" -}}
{{- end -}}
{{- if and (eq .Values.redisConfig.mode "external") (not .Values.redisConfig.external.url) -}}
{{- fail "redisConfig.external.url is required when redisConfig.mode is external" -}}
{{- end -}}
{{- if and (not .Values.solr.enabled) (not .Values.solr.externalUrl) -}}
{{- fail "solr.externalUrl is required when solr.enabled is false" -}}
{{- end -}}
{{- if and .Values.ingress.enabled (not .Values.ingress.hosts) -}}
{{- fail "ingress.enabled requires ingress.hosts to contain at least one rule" -}}
{{- end -}}
{{- if .Values.backup.enabled -}}
{{- if not .Values.backup.s3.endpoint -}}
{{- fail "backup.s3.endpoint is required when backup.enabled is true" -}}
{{- end -}}
{{- if not .Values.backup.s3.bucket -}}
{{- fail "backup.s3.bucket is required when backup.enabled is true" -}}
{{- end -}}
{{- if and (not .Values.backup.s3.existingSecret) (not .Values.backup.s3.accessKey) -}}
{{- fail "backup.s3.accessKey or backup.s3.existingSecret is required when backup.enabled is true" -}}
{{- end -}}
{{- if and (not .Values.backup.s3.existingSecret) (not .Values.backup.s3.secretKey) -}}
{{- fail "backup.s3.secretKey or backup.s3.existingSecret is required when backup.enabled is true" -}}
{{- end -}}
{{- end -}}
{{- range $key, $_ := .Values.podLabels -}}
{{- if or (eq $key "app.kubernetes.io/name") (eq $key "app.kubernetes.io/instance") (eq $key "app.kubernetes.io/component") -}}
{{- fail (printf "podLabels must not override selector label %q" $key) -}}
{{- end -}}
{{- end -}}
{{- if .Values.datapusher.enabled -}}
{{- range $key, $_ := .Values.datapusher.podLabels -}}
{{- if or (eq $key "app.kubernetes.io/name") (eq $key "app.kubernetes.io/instance") (eq $key "app.kubernetes.io/component") -}}
{{- fail (printf "datapusher.podLabels must not override selector label %q" $key) -}}
Comment thread
mberlofa marked this conversation as resolved.
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}

# =============================================================================
# Secret
# =============================================================================
Expand Down Expand Up @@ -286,18 +330,8 @@ exec /srv/app/start_ckan.sh
{{- end -}}
{{- end -}}

{{/* Backup — validate required fields */}}
{{- define "ckan.backupEnabled" -}}
{{- if .Values.backup.enabled -}}
{{- if not .Values.backup.s3.endpoint -}}
{{- fail "backup.s3.endpoint is required when backup.enabled is true" -}}
{{- end -}}
{{- if not .Values.backup.s3.bucket -}}
{{- fail "backup.s3.bucket is required when backup.enabled is true" -}}
{{- end -}}
{{- if and (not .Values.backup.s3.existingSecret) (not .Values.backup.s3.accessKey) -}}
{{- fail "backup.s3.accessKey or backup.s3.existingSecret is required when backup.enabled is true" -}}
{{- end -}}
true
{{- end -}}
{{- end -}}
Expand Down
4 changes: 3 additions & 1 deletion charts/ckan/templates/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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"))) }}
Expand Down
2 changes: 2 additions & 0 deletions charts/ckan/templates/validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
{{- include "ckan.validate" . -}}
11 changes: 0 additions & 11 deletions charts/ckan/tests/backup_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,3 @@ tests:
content:
name: DB_HOST
value: test-postgresql

- it: should fail without s3 endpoint
set:
backup.enabled: true
backup.s3.bucket: backups
backup.s3.accessKey: admin
backup.s3.secretKey: admin123
template: backup-cronjob.yaml
asserts:
- failedTemplate:
errorMessage: "backup.s3.endpoint is required when backup.enabled is true"
14 changes: 14 additions & 0 deletions charts/ckan/tests/ingress_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,17 @@ tests:
- equal:
path: spec.rules[0].host
value: ckan.example.com

- it: should allow catch-all ingress rules without host
set:
ingress:
enabled: true
hosts:
- paths:
- path: /
pathType: Prefix
asserts:
- isKind:
of: Ingress
- notExists:
path: spec.rules[0].host
112 changes: 112 additions & 0 deletions charts/ckan/tests/validation_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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 external database host is missing
set:
database.mode: external
database.external.password: secret
asserts:
- failedTemplate:
errorMessage: "database.external.host is required when database.mode is external"

- it: should fail when external database credentials are missing
set:
database.mode: external
database.external.host: postgresql.example.com
asserts:
- failedTemplate:
errorMessage: "database.external.password or database.external.existingSecret is required when database.mode is external"

- it: should fail when external redis url is missing
set:
redisConfig.mode: external
asserts:
- failedTemplate:
errorMessage: "redisConfig.external.url is required when redisConfig.mode is external"

- it: should fail when external solr url is missing
set:
solr.enabled: false
asserts:
- failedTemplate:
errorMessage: "solr.externalUrl is required when solr.enabled is false"

- it: should fail when ingress is enabled without rules
set:
ingress.enabled: true
ingress.hosts: []
asserts:
- failedTemplate:
errorMessage: "ingress.enabled requires ingress.hosts to contain at least one rule"

- it: should fail when backup endpoint is missing
set:
backup.enabled: true
backup.s3.bucket: backups
backup.s3.accessKey: access
backup.s3.secretKey: secret
asserts:
- failedTemplate:
errorMessage: "backup.s3.endpoint is required when backup.enabled is true"

- it: should fail when backup bucket is missing
set:
backup.enabled: true
backup.s3.endpoint: http://minio:9000
backup.s3.accessKey: access
backup.s3.secretKey: secret
asserts:
- failedTemplate:
errorMessage: "backup.s3.bucket is required when backup.enabled is true"

- it: should fail when backup access key is missing
set:
backup.enabled: true
backup.s3.endpoint: http://minio:9000
backup.s3.bucket: backups
backup.s3.secretKey: secret
asserts:
- failedTemplate:
errorMessage: "backup.s3.accessKey or backup.s3.existingSecret is required when backup.enabled is true"

- it: should fail when backup secret key is missing
set:
backup.enabled: true
backup.s3.endpoint: http://minio:9000
backup.s3.bucket: backups
backup.s3.accessKey: access
asserts:
- failedTemplate:
errorMessage: "backup.s3.secretKey or backup.s3.existingSecret is required when backup.enabled is true"

- it: should fail when podLabels override selector labels
set:
podLabels:
app.kubernetes.io/component: custom
asserts:
- failedTemplate:
errorMessage: 'podLabels must not override selector label "app.kubernetes.io/component"'

- it: should fail when datapusher podLabels override selector labels
set:
datapusher.podLabels:
app.kubernetes.io/name: custom
asserts:
- failedTemplate:
errorMessage: 'datapusher.podLabels must not override selector label "app.kubernetes.io/name"'

- it: should allow datapusher podLabels when datapusher is disabled
set:
datapusher.enabled: false
datapusher.podLabels:
app.kubernetes.io/name: custom
asserts:
- hasDocuments:
count: 0