diff --git a/charts/library/common-test/tests/container/validations_test.yaml b/charts/library/common-test/tests/container/validations_test.yaml new file mode 100644 index 00000000..107fd55e --- /dev/null +++ b/charts/library/common-test/tests/container/validations_test.yaml @@ -0,0 +1,42 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json +suite: Container validations +templates: + - common.yaml +tests: + - it: image required to be a dictionary + set: + controllers: + main: + containers: + test: + image: "test:latest" + asserts: + - failedTemplate: + errorMessage: "Image required to be a dictionary with repository and tag fields. (controller main, container test)" + + - it: image repository is required + set: + controllers: + main: + containers: + test: + image: + repository: "" + tag: "test" + asserts: + - failedTemplate: + errorMessage: "No image repository specified for container. (controller main, container test)" + + - it: image tag is required + set: + controllers: + main: + containers: + test: + image: + repository: test + tag: "" + asserts: + - failedTemplate: + errorMessage: "No image tag specified for container. (controller main, container test)" diff --git a/charts/library/common-test/tests/container/volumemounts_test.yaml b/charts/library/common-test/tests/container/volumemounts_test.yaml index 111afdec..6278c5ac 100644 --- a/charts/library/common-test/tests/container/volumemounts_test.yaml +++ b/charts/library/common-test/tests/container/volumemounts_test.yaml @@ -183,3 +183,93 @@ tests: name: config mountPath: /dev subPath: mySubPath + + - it: volumeClaimTemplates with default mounts should pass + set: + controllers.main: + type: statefulset + statefulset: + volumeClaimTemplates: + - name: "storage" + accessMode: "ReadWriteOnce" + size: "10Gi" + storageClass: "storage" + asserts: + - documentIndex: &StatefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *StatefulSetDoc + equal: + path: spec.template.spec.containers[0].volumeMounts[0] + value: + name: storage + mountPath: /storage + + - it: volumeClaimTemplates with globalMounts should pass + set: + controllers.main: + type: statefulset + statefulset: + volumeClaimTemplates: + - name: "storage" + accessMode: "ReadWriteOnce" + size: "10Gi" + storageClass: "storage" + globalMounts: + - path: /tmp/storage + - path: /tmp/secondMountPoint + asserts: + - documentIndex: &StatefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *StatefulSetDoc + equal: + path: spec.template.spec.containers[0].volumeMounts[0] + value: + name: storage + mountPath: /tmp/storage + - documentIndex: *StatefulSetDoc + equal: + path: spec.template.spec.containers[0].volumeMounts[1] + value: + name: storage + mountPath: /tmp/secondMountPoint + + - it: volumeClaimTemplates with advancedMounts should pass + set: + controllers.main: + type: statefulset + statefulset: + volumeClaimTemplates: + - name: "storage" + accessMode: "ReadWriteOnce" + size: "10Gi" + storageClass: "storage" + advancedMounts: + second-container: + - path: /tmp/storage + - path: /tmp/secondMountPoint + containers: + second-container: + image: + repository: ghcr.io/mendhak/http-https-echo + tag: 30 + asserts: + - documentIndex: &StatefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *StatefulSetDoc + notExists: + path: spec.template.spec.containers[0].volumeMounts + - documentIndex: *StatefulSetDoc + equal: + path: spec.template.spec.containers[1].volumeMounts[0] + value: + name: storage + mountPath: /tmp/storage + - documentIndex: *StatefulSetDoc + equal: + path: spec.template.spec.containers[1].volumeMounts[1] + value: + name: storage + mountPath: /tmp/secondMountPoint diff --git a/charts/library/common-test/tests/persistence/volumeclaimtemplates_test.yaml b/charts/library/common-test/tests/persistence/volumeclaimtemplates_test.yaml index 30ea8c91..54b129d2 100644 --- a/charts/library/common-test/tests/persistence/volumeclaimtemplates_test.yaml +++ b/charts/library/common-test/tests/persistence/volumeclaimtemplates_test.yaml @@ -4,38 +4,14 @@ suite: persistence volumeclaimtemplates templates: - common.yaml tests: - - it: volumeClaimTemplates should pass + - it: default should pass set: controllers.main: type: statefulset - statefulset: - volumeClaimTemplates: - - name: "storage" - accessMode: "ReadWriteOnce" - size: "10Gi" - storageClass: "storage" - labels: - test: "label" - annotations: - test: "annotation" asserts: - - documentIndex: 0 + - documentIndex: &StatefulSetDoc 0 isKind: of: StatefulSet - - documentIndex: 0 - equal: - path: spec.volumeClaimTemplates[0] - value: - metadata: - name: storage - labels: - test: "label" - annotations: - test: "annotation" - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - storageClassName: storage + - documentIndex: *StatefulSetDoc + notExists: + path: spec.volumeClaimTemplates diff --git a/charts/library/common-test/tests/pod/initcontainers_test.yaml b/charts/library/common-test/tests/pod/initcontainers_test.yaml index 0ecc0923..62a8bfc1 100644 --- a/charts/library/common-test/tests/pod/initcontainers_test.yaml +++ b/charts/library/common-test/tests/pod/initcontainers_test.yaml @@ -10,6 +10,7 @@ tests: init1: image: repository: ghcr.io/mendhak/http-https-echo + tag: latest env: int: 1 float: 1.5 @@ -66,10 +67,12 @@ tests: order: 2 image: repository: ghcr.io/mendhak/http-https-echo + tag: latest init2: order: 1 image: repository: ghcr.io/mendhak/http-https-echo + tag: latest asserts: - documentIndex: &DeploymentDocument 0 isKind: diff --git a/charts/library/common/Chart.yaml b/charts/library/common/Chart.yaml index 493bb240..8e6c82e1 100644 --- a/charts/library/common/Chart.yaml +++ b/charts/library/common/Chart.yaml @@ -3,7 +3,7 @@ apiVersion: v2 name: common description: Function library for Helm charts type: library -version: 2.0.0 +version: 2.0.1 kubeVersion: ">=1.22.0-0" keywords: - common @@ -14,15 +14,12 @@ maintainers: email: me@bjw-s.dev annotations: artifacthub.io/changes: |- - - kind: removed + - kind: fixed description: |- - **BREAKING CHANGE** Removed support for add-ons. These can be configured through other means nowadays. - - kind: added + Mounting of volumeClaimTemplates was not implemented properly. + - kind: fixed description: |- - **BREAKING CHANGE** Added support for managing multiple controllers / containers. This has introduced a number of breaking changes. Please see updated values.yaml - - kind: added - description: Added support for ordering (init)Containers within a controller. - - kind: added - description: Added support for creating Network Policies. - - kind: changed - description: Automatically detect `gateway.networking.k8s.io` API version + Using advancedMounts for initContainers resulted in an error. + - kind: fixed + description: |- + Validations for container images have been improved. diff --git a/charts/library/common/templates/lib/chart/_notes.tpl b/charts/library/common/templates/lib/chart/_notes.tpl deleted file mode 100644 index ccd2d2c7..00000000 --- a/charts/library/common/templates/lib/chart/_notes.tpl +++ /dev/null @@ -1,56 +0,0 @@ -{{/* -Default NOTES.txt content. -*/}} -{{- define "bjw-s.common.lib.chart.notes" -}} - -{{- $primaryIngress := get .Values.ingress (include "bjw-s.common.lib.ingress.primary" .) -}} -{{- $primaryService := get .Values.service (include "bjw-s.common.lib.service.primary" .) -}} -{{- $primaryPort := "" -}} -{{- if $primaryService -}} - {{- $primaryPort = get $primaryService.ports (include "bjw-s.common.lib.service.primaryPort" (dict "serviceName" (include "bjw-s.common.lib.service.primary" .) "values" $primaryService)) -}} -{{- end -}} - -{{- $prefix := "http" -}} -{{- if $primaryPort }} - {{- if hasKey $primaryPort "protocol" }} - {{- if eq $primaryPort.protocol "HTTPS" }} - {{- $prefix = "https" }} - {{- end }} - {{- end }} -{{- end }} - -{{- if $primaryIngress }} -1. Access the application by visiting one of these URL's: -{{ range $primaryIngress.hosts }} - {{- $prefix = "http" -}} - {{ if $primaryIngress.tls -}} - {{- $prefix = "https" -}} - {{ end -}} - {{- $host := .host -}} - {{ if .hostTpl -}} - {{- $host = tpl .hostTpl $ -}} - {{ end }} - {{- $path := (first .paths).path | default "/" -}} - {{ if (first .paths).pathTpl -}} - {{- $path = tpl (first .paths).pathTpl $ -}} - {{ end }} - - {{ $prefix }}://{{- $host }}{{- $path }} -{{- end }} -{{- else if and $primaryService $primaryPort }} -1. Get the application URL by running these commands: -{{- if contains "NodePort" $primaryService.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "bjw-s.common.lib.chart.names.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo {{ $prefix }}://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" $primaryService.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ include "bjw-s.common.lib.chart.names.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "bjw-s.common.lib.chart.names.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo {{ $prefix }}://$SERVICE_IP:{{ $primaryPort.port | toString | atoi }} -{{- else if contains "ClusterIP" $primaryService.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "bjw-s.common.lib.chart.names.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit {{ $prefix }}://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ $primaryPort.port | toString | atoi }} -{{- end }} -{{- end }} -{{- end -}} diff --git a/charts/library/common/templates/lib/container/_validate.tpl b/charts/library/common/templates/lib/container/_validate.tpl index c8cbbf27..9d1ab3db 100644 --- a/charts/library/common/templates/lib/container/_validate.tpl +++ b/charts/library/common/templates/lib/container/_validate.tpl @@ -3,9 +3,18 @@ Validate container values */}} {{- define "bjw-s.common.lib.container.validate" -}} {{- $rootContext := .rootContext -}} - {{- $containerValues := .object -}} + {{- $controllerObject := .controllerObject -}} + {{- $containerObject := .containerObject -}} - {{- if eq (dig "image" "repository" "" $containerValues) "" -}} - {{- fail (printf "No image repository specified for container. (controller: %s, container: %s)" $containerValues.controller $containerValues.identifier) }} + {{- if not (kindIs "map" $containerObject.image) -}} + {{- fail (printf "Image required to be a dictionary with repository and tag fields. (controller %s, container %s)" $controllerObject.identifier $containerObject.identifier) }} + {{- end -}} + + {{- if eq (dig "image" "repository" "" $containerObject) "" -}} + {{- fail (printf "No image repository specified for container. (controller %s, container %s)" $controllerObject.identifier $containerObject.identifier) }} + {{- end -}} + + {{- if eq (dig "image" "tag" "" $containerObject) "" -}} + {{- fail (printf "No image tag specified for container. (controller %s, container %s)" $controllerObject.identifier $containerObject.identifier) }} {{- end -}} {{- end -}} diff --git a/charts/library/common/templates/lib/container/_valuesToObject.tpl b/charts/library/common/templates/lib/container/_valuesToObject.tpl index e13104de..044ad0a9 100644 --- a/charts/library/common/templates/lib/container/_valuesToObject.tpl +++ b/charts/library/common/templates/lib/container/_valuesToObject.tpl @@ -8,6 +8,14 @@ Convert container values to an object {{- $_ := set $objectValues "identifier" $identifier -}} + {{- /* Convert float64 image tags to string */ -}} + {{- if kindIs "map" $objectValues.image -}} + {{- $imageTag := dig "image" "tag" "" $objectValues -}} + {{- if kindIs "float64" $imageTag -}} + {{- $_ := set $objectValues.image "tag" ($imageTag | toString) -}} + {{- end -}} + {{- end -}} + {{- /* Return the container object */ -}} {{- $objectValues | toYaml -}} {{- end -}} diff --git a/charts/library/common/templates/lib/container/fields/_image.tpl b/charts/library/common/templates/lib/container/fields/_image.tpl index 0dc989b5..88d93c2a 100644 --- a/charts/library/common/templates/lib/container/fields/_image.tpl +++ b/charts/library/common/templates/lib/container/fields/_image.tpl @@ -9,10 +9,6 @@ Image used by the container. {{- $imageRepo := $containerObject.image.repository -}} {{- $imageTag := default $rootContext.Chart.AppVersion $containerObject.image.tag -}} - {{- if kindIs "float64" $imageTag -}} - {{- $imageTag = $imageTag | toString -}} - {{- end -}} - {{- if and $imageRepo $imageTag -}} {{- printf "%s:%s" $imageRepo $imageTag -}} {{- end -}} diff --git a/charts/library/common/templates/lib/container/fields/_volumeMounts.tpl b/charts/library/common/templates/lib/container/fields/_volumeMounts.tpl index f728abde..f74f413c 100644 --- a/charts/library/common/templates/lib/container/fields/_volumeMounts.tpl +++ b/charts/library/common/templates/lib/container/fields/_volumeMounts.tpl @@ -11,6 +11,7 @@ volumeMounts used by the container. {{- $persistenceItemsToProcess := dict -}} {{- $enabledVolumeMounts := list -}} + {{- /* Collect regular persistence items */ -}} {{- range $identifier, $persistenceValues := $rootContext.Values.persistence -}} {{- /* Enable persistence item by default, but allow override */ -}} {{- $persistenceEnabled := true -}} @@ -19,60 +20,86 @@ volumeMounts used by the container. {{- end -}} {{- if $persistenceEnabled -}} - {{- /* Set some default values */ -}} + {{- $_ := set $persistenceItemsToProcess $identifier $persistenceValues -}} + {{- end -}} + {{- end -}} - {{- /* Set the default mountPath to / */ -}} - {{- $mountPath := (printf "/%v" $identifier) -}} - {{- if eq "hostPath" (default "pvc" $persistenceValues.type) -}} - {{- $mountPath = $persistenceValues.hostPath -}} + {{- /* Collect volumeClaimTemplates */ -}} + {{- if not (eq (dig "statefulset" "volumeClaimTemplates" nil $controllerObject) nil) -}} + {{- range $persistenceValues := $controllerObject.statefulset.volumeClaimTemplates -}} + {{- /* Enable persistence item by default, but allow override */ -}} + {{- $persistenceEnabled := true -}} + {{- if hasKey $persistenceValues "enabled" -}} + {{- $persistenceEnabled = $persistenceValues.enabled -}} {{- end -}} - {{- /* Process configured mounts */ -}} - {{- if or .globalMounts .advancedMounts -}} - {{- $mounts := list -}} - {{- if hasKey . "globalMounts" -}} - {{- $mounts = .globalMounts -}} + {{- if $persistenceEnabled -}} + {{- $mountValues := dict -}} + {{- if not (eq (dig "globalMounts" nil $persistenceValues) nil) -}} + {{- $_ := set $mountValues "globalMounts" $persistenceValues.globalMounts -}} {{- end -}} - - {{- if hasKey . "advancedMounts" -}} - {{- $advancedMounts := dig $controllerObject.identifier $containerObject.identifier list .advancedMounts -}} - {{- range $advancedMounts -}} - {{- $mounts = append $mounts . -}} - {{- end -}} + {{- if not (eq (dig "advancedMounts" nil $persistenceValues) nil) -}} + {{- $_ := set $mountValues "advancedMounts" (dict $controllerObject.identifier $persistenceValues.advancedMounts) -}} {{- end -}} + {{- $_ := set $persistenceItemsToProcess $persistenceValues.name $mountValues -}} + {{- end -}} + {{- end -}} + {{- end -}} - {{- range $mounts -}} - {{- $volumeMount := dict -}} - {{- $_ := set $volumeMount "name" $identifier -}} + {{- range $identifier, $persistenceValues := $persistenceItemsToProcess -}} + {{- /* Set some default values */ -}} - {{- /* Use the specified mountPath if provided */ -}} - {{- with .path -}} - {{- $mountPath = . -}} - {{- end -}} - {{- $_ := set $volumeMount "mountPath" $mountPath -}} + {{- /* Set the default mountPath to / */ -}} + {{- $mountPath := (printf "/%v" $identifier) -}} + {{- if eq "hostPath" (default "pvc" $persistenceValues.type) -}} + {{- $mountPath = $persistenceValues.hostPath -}} + {{- end -}} - {{- /* Use the specified subPath if provided */ -}} - {{- with .subPath -}} - {{- $subPath := . -}} - {{- $_ := set $volumeMount "subPath" $subPath -}} - {{- end -}} + {{- /* Process configured mounts */ -}} + {{- if or .globalMounts .advancedMounts -}} + {{- $mounts := list -}} + {{- if hasKey . "globalMounts" -}} + {{- $mounts = .globalMounts -}} + {{- end -}} - {{- /* Use the specified readOnly setting if provided */ -}} - {{- with .readOnly -}} - {{- $readOnly := . -}} - {{- $_ := set $volumeMount "readOnly" $readOnly -}} - {{- end -}} - - {{- $enabledVolumeMounts = append $enabledVolumeMounts $volumeMount -}} + {{- if hasKey . "advancedMounts" -}} + {{- $advancedMounts := dig $controllerObject.identifier $containerObject.identifier list .advancedMounts -}} + {{- range $advancedMounts -}} + {{- $mounts = append $mounts . -}} {{- end -}} + {{- end -}} - {{- /* Mount to default path if no mounts are configured */ -}} - {{- else -}} + {{- range $mounts -}} {{- $volumeMount := dict -}} {{- $_ := set $volumeMount "name" $identifier -}} + + {{- /* Use the specified mountPath if provided */ -}} + {{- with .path -}} + {{- $mountPath = . -}} + {{- end -}} {{- $_ := set $volumeMount "mountPath" $mountPath -}} + + {{- /* Use the specified subPath if provided */ -}} + {{- with .subPath -}} + {{- $subPath := . -}} + {{- $_ := set $volumeMount "subPath" $subPath -}} + {{- end -}} + + {{- /* Use the specified readOnly setting if provided */ -}} + {{- with .readOnly -}} + {{- $readOnly := . -}} + {{- $_ := set $volumeMount "readOnly" $readOnly -}} + {{- end -}} + {{- $enabledVolumeMounts = append $enabledVolumeMounts $volumeMount -}} {{- end -}} + + {{- /* Mount to default path if no mounts are configured */ -}} + {{- else -}} + {{- $volumeMount := dict -}} + {{- $_ := set $volumeMount "name" $identifier -}} + {{- $_ := set $volumeMount "mountPath" $mountPath -}} + {{- $enabledVolumeMounts = append $enabledVolumeMounts $volumeMount -}} {{- end -}} {{- end -}} diff --git a/charts/library/common/templates/lib/pod/fields/_containers.tpl b/charts/library/common/templates/lib/pod/fields/_containers.tpl index a2783d27..6fb99539 100644 --- a/charts/library/common/templates/lib/pod/fields/_containers.tpl +++ b/charts/library/common/templates/lib/pod/fields/_containers.tpl @@ -16,7 +16,7 @@ Returns the value for containers {{- $containerObject := (include "bjw-s.common.lib.container.valuesToObject" (dict "rootContext" $ "id" $key "values" $containerValues)) | fromYaml -}} {{- /* Perform validations on the Container before rendering */ -}} - {{- include "bjw-s.common.lib.container.validate" (dict "rootContext" $ "object" $containerObject) -}} + {{- include "bjw-s.common.lib.container.validate" (dict "rootContext" $ "controllerObject" $controllerObject "containerObject" $containerObject) -}} {{- /* Generate the Container spec */ -}} {{- $renderedContainer := include "bjw-s.common.lib.container.spec" (dict "rootContext" $rootContext "controllerObject" $controllerObject "containerObject" $containerObject) | fromYaml -}} diff --git a/charts/library/common/templates/lib/pod/fields/_initContainers.tpl b/charts/library/common/templates/lib/pod/fields/_initContainers.tpl index 7da5a3a7..fb737df2 100644 --- a/charts/library/common/templates/lib/pod/fields/_initContainers.tpl +++ b/charts/library/common/templates/lib/pod/fields/_initContainers.tpl @@ -22,10 +22,10 @@ Returns the value for initContainers {{- $containerObject := (include "bjw-s.common.lib.container.valuesToObject" (dict "rootContext" $ "id" $key "values" $containerValues)) | fromYaml -}} {{- /* Perform validations on the Container before rendering */ -}} - {{- include "bjw-s.common.lib.container.validate" (dict "rootContext" $ "object" $containerObject) -}} + {{- include "bjw-s.common.lib.container.validate" (dict "rootContext" $ "controllerObject" $controllerObject "containerObject" $containerObject) -}} {{- /* Generate the Container spec */ -}} - {{- $renderedContainer := include "bjw-s.common.lib.container.spec" (dict "rootContext" $rootContext "containerObject" $containerObject) | fromYaml -}} + {{- $renderedContainer := include "bjw-s.common.lib.container.spec" (dict "rootContext" $rootContext "controllerObject" $controllerObject "containerObject" $containerObject) | fromYaml -}} {{- $containerOrder := (dig "order" 99 $containerValues) -}} {{- $_ := set $orderedContainers (printf "%v-%s" $containerOrder $key) $renderedContainer -}} diff --git a/charts/other/app-template/Chart.yaml b/charts/other/app-template/Chart.yaml index e021e6b2..bd22ebbb 100644 --- a/charts/other/app-template/Chart.yaml +++ b/charts/other/app-template/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 description: A common powered chart template. This can be useful for small projects that don't have their own chart. name: app-template -version: 2.0.0 +version: 2.0.1 kubeVersion: ">=1.22.0-0" maintainers: - name: bjw-s @@ -10,14 +10,14 @@ maintainers: dependencies: - name: common repository: https://bjw-s.github.io/helm-charts - version: 2.0.0 + version: 2.0.1 annotations: artifacthub.io/changes: |- - kind: changed description: | - Updated library version to 2.0.0. + Updated library version to 2.0.1. links: - - name: Upgrade instructions + - name: Upgrade instructions from v1.x url: https://github.com/bjw-s/helm-charts/tree/main/charts/other/app-template#from-1xx-to-20x - name: Common library chart definition url: https://github.com/bjw-s/helm-charts/blob/main/charts/library/common/Chart.yaml