diff --git a/library/common-test/Chart.yaml b/library/common-test/Chart.yaml index c9ec900d..0edafd8d 100644 --- a/library/common-test/Chart.yaml +++ b/library/common-test/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "" dependencies: - name: common repository: file://../common - version: ~14.3.0 + version: ~14.4.0 deprecated: false description: Helper chart to test different use cases of the common library home: https://github.com/truecharts/apps/tree/master/charts/library/common-test diff --git a/library/common-test/ci/storageclass-values.yaml b/library/common-test/ci/storageclass-values.yaml new file mode 100644 index 00000000..3edbc43d --- /dev/null +++ b/library/common-test/ci/storageclass-values.yaml @@ -0,0 +1,65 @@ +service: + main: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + protocol: http + port: 8080 + +workload: + main: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + main: + enabled: true + primary: true + args: + - --port + - "8080" + probes: + liveness: + enabled: true + type: http + port: "{{ .Values.service.main.ports.main.port }}" + readiness: + enabled: true + type: http + port: "{{ .Values.service.main.ports.main.port }}" + startup: + enabled: true + type: http + port: "{{ .Values.service.main.ports.main.port }}" + +# -- create storageClasses on demand +storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + example2: + enabled: true + provisioner: some.provisioner.io + reclaimPolicy: retain + allowVolumeExpansion: true + volumeBindingMode: Immediate + example3: + enabled: true + provisioner: some.provisioner.io + parameters: {} + mountOptions: [] + example4: + enabled: true + provisioner: some.provisioner.io + parameters: {} + reclaimPolicy: retain + allowVolumeExpansion: true + volumeBindingMode: Immediate + mountOptions: [] + +manifestManager: + enabled: false diff --git a/library/common-test/tests/storageClass/metadata_test.yaml b/library/common-test/tests/storageClass/metadata_test.yaml new file mode 100644 index 00000000..fdfb1fdf --- /dev/null +++ b/library/common-test/tests/storageClass/metadata_test.yaml @@ -0,0 +1,60 @@ +suite: storageClass metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with storageClass created with labels and annotations + set: + label1: label1 + label2: global_label2 + annotation1: annotation1 + annotation2: global_annotation2 + global: + labels: + g_label1: global_label1 + g_label2: "{{ .Values.label2 }}" + annotations: + g_annotation1: global_annotation1 + g_annotation2: "{{ .Values.annotation2 }}" + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + labels: + label1: "{{ .Values.label1 }}" + label2: label2 + annotations: + annotation1: "{{ .Values.annotation1 }}" + annotation2: annotation2 + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: metadata.annotations + value: + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *storageClassDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + release: test-release-name + helm-revision: "0" + helm.sh/chart: common-test-1.0.0 + app.kubernetes.io/name: common-test + app.kubernetes.io/instance: test-release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 diff --git a/library/common-test/tests/storageClass/names_test.yaml b/library/common-test/tests/storageClass/names_test.yaml new file mode 100644 index 00000000..834e222e --- /dev/null +++ b/library/common-test/tests/storageClass/names_test.yaml @@ -0,0 +1,37 @@ +suite: storageClass name test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should generate correct name + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + example2: + enabled: true + provisioner: some.provisioner.io + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + isAPIVersion: + of: storage.k8s.io/v1 + - documentIndex: *storageClassDoc + equal: + path: metadata.name + value: test-release-name-common-test-example1 + - documentIndex: &otherStorageClassDoc 1 + isKind: + of: StorageClass + - documentIndex: *otherStorageClassDoc + isAPIVersion: + of: storage.k8s.io/v1 + - documentIndex: *otherStorageClassDoc + equal: + path: metadata.name + value: test-release-name-common-test-example2 diff --git a/library/common-test/tests/storageClass/spec_test.yaml b/library/common-test/tests/storageClass/spec_test.yaml new file mode 100644 index 00000000..fd897236 --- /dev/null +++ b/library/common-test/tests/storageClass/spec_test.yaml @@ -0,0 +1,187 @@ +suite: storageClass name test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should generate correct spec with provisioner provided + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + isAPIVersion: + of: storage.k8s.io/v1 + - documentIndex: *storageClassDoc + equal: + path: provisioner + value: some.provisioner.io + - documentIndex: *storageClassDoc + equal: + path: reclaimPolicy + value: Retain + - documentIndex: *storageClassDoc + equal: + path: allowVolumeExpansion + value: true + - documentIndex: *storageClassDoc + equal: + path: volumeBindingMode + value: Immediate + + - it: should generate correct spec with non-default reclaim policy + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + reclaimPolicy: Delete + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: reclaimPolicy + value: Delete + + - it: should generate correct spec with non-default allowVolumeExpansion + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + allowVolumeExpansion: false + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: allowVolumeExpansion + value: false + + - it: should generate correct spec with parameters provided from tpl + set: + test: + key1: value1 + key2: value2 + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + parameters: + param1: "{{ .Values.test.key1 }}" + param2: "{{ .Values.test.key2 }}" + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: parameters + value: + param1: value1 + param2: value2 + + - it: should generate correct spec with parameters provided + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + parameters: + param1: value1 + param2: value2 + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: parameters + value: + param1: value1 + param2: value2 + + - it: should generate correct spec with mountOptions provided + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + mountOptions: + - option1 + - option2=value2 + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: mountOptions + value: + - option1 + - option2=value2 + + - it: should generate correct spec with mountOptions provided from tpl + set: + test: + key1: option1 + key2: value2 + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + mountOptions: + - "{{ .Values.test.key1 }}" + - option2={{ .Values.test.key2 }} + asserts: + - documentIndex: &storageClassDoc 0 + isKind: + of: StorageClass + - documentIndex: *storageClassDoc + equal: + path: mountOptions + value: + - option1 + - option2=value2 + +# Failures + - it: should fail without provisioner + set: + storageClass: + example1: + enabled: true + provisioner: "" + asserts: + - failedTemplate: + errorMessage: Storage Class - Expected non-empty [provisioner] + + - it: should fail with invalid reclaimPolicy + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + reclaimPolicy: invalid + asserts: + - failedTemplate: + errorMessage: Storage Class - Expected [reclaimPolicy] to be one of [Retain, Delete], but got [invalid] + + - it: should fail with invalid volumeBindingMode + set: + storageClass: + example1: + enabled: true + provisioner: some.provisioner.io + volumeBindingMode: invalid + asserts: + - failedTemplate: + errorMessage: Storage Class - Expected [volumeBindingMode] to be one of [WaitForFirstConsumer, Immediate], but got [invalid] diff --git a/library/common/Chart.yaml b/library/common/Chart.yaml index 97eb65dd..abf5e5bb 100644 --- a/library/common/Chart.yaml +++ b/library/common/Chart.yaml @@ -15,4 +15,4 @@ maintainers: name: common sources: null type: library -version: 14.3.5 +version: 14.4.0 diff --git a/library/common/templates/class/_storageClass.tpl b/library/common/templates/class/_storageClass.tpl new file mode 100644 index 00000000..49e0754a --- /dev/null +++ b/library/common/templates/class/_storageClass.tpl @@ -0,0 +1,53 @@ +{{/* Configmap Class */}} +{{/* Call this template: +{{ include "tc.v1.common.class.storageclass" (dict "rootCtx" $ "objectData" $objectData) }} + +rootCtx: The root context of the chart. +objectData: + name: The name of the storageclass. + labels: The labels of the storageclass. + annotations: The annotations of the storageclass. +*/}} + +{{- define "tc.v1.common.class.storageclass" -}} + + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $allowVolExpand := true -}} + {{- if not (kindIs "invalid" $objectData.allowVolumeExpansion) -}} + {{- $allowVolExpand = $objectData.allowVolumeExpansion -}} + {{- end }} +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: {{ $objectData.name }} + {{- $labels := (mustMerge ($objectData.labels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} + labels: + {{- . | nindent 4 }} + {{- end -}} + {{- $annotations := (mustMerge ($objectData.annotations | default dict) (include "tc.v1.common.lib.metadata.allAnnotations" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "annotations" $annotations) | trim) }} + annotations: + {{- . | nindent 4 }} + {{- end }} +provisioner: {{ $objectData.provisioner }} +{{- with $objectData.parameters }} +parameters: {{/* TODO: */}} + {{- range $k, $v := . -}} + {{- $val := tpl $v $rootCtx }} + {{ $k }}: {{ include "tc.v1.common.helper.makeIntOrNoop" $val | quote }} + {{- end -}} +{{- end }} +reclaimPolicy: {{ $objectData.reclaimPolicy | default "Retain" }} +allowVolumeExpansion: {{ $allowVolExpand }} +{{- with $objectData.mountOptions }} +mountOptions: + {{- range $opt := . }} + - {{ tpl $opt $rootCtx }} + {{- end -}} +{{- end }} +volumeBindingMode: {{ $objectData.volumeBindingMode | default "Immediate" }} +{{- end -}} diff --git a/library/common/templates/lib/storage/_storageClassValidation.tpl b/library/common/templates/lib/storage/_storageClassValidation.tpl new file mode 100644 index 00000000..b8c5b49f --- /dev/null +++ b/library/common/templates/lib/storage/_storageClassValidation.tpl @@ -0,0 +1,22 @@ +{{- define "tc.v1.common.lib.storageclass.validation" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- if not $objectData.provisioner -}} + {{- fail "Storage Class - Expected non-empty [provisioner]" -}} + {{- end -}} + + {{- $validPolicies := (list "Retain" "Delete") -}} + {{- if $objectData.reclaimPolicy -}} + {{- if not (mustHas $objectData.reclaimPolicy $validPolicies) -}} + {{- fail (printf "Storage Class - Expected [reclaimPolicy] to be one of [%s], but got [%s]" (join ", " $validPolicies) $objectData.reclaimPolicy) -}} + {{- end -}} + {{- end -}} + + {{- $validBindModes := (list "WaitForFirstConsumer" "Immediate") -}} + {{- if $objectData.volumeBindingMode -}} + {{- if not (mustHas $objectData.volumeBindingMode $validBindModes) -}} + {{- fail (printf "Storage Class - Expected [volumeBindingMode] to be one of [%s], but got [%s]" (join ", " $validBindModes) $objectData.volumeBindingMode) -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/library/common/templates/loader/_apply.tpl b/library/common/templates/loader/_apply.tpl index 3094272e..86f4e27d 100644 --- a/library/common/templates/loader/_apply.tpl +++ b/library/common/templates/loader/_apply.tpl @@ -37,6 +37,9 @@ {{/* Render Services(s) */}} {{- include "tc.v1.common.spawner.service" . | nindent 0 -}} + {{/* Render storageClass(s) */}} + {{- include "tc.v1.common.spawner.storageclass" . | nindent 0 -}} + {{/* Render PVC(s) */}} {{- include "tc.v1.common.spawner.pvc" . | nindent 0 -}} diff --git a/library/common/templates/spawner/_storageClass.tpl b/library/common/templates/spawner/_storageClass.tpl new file mode 100644 index 00000000..c62133db --- /dev/null +++ b/library/common/templates/spawner/_storageClass.tpl @@ -0,0 +1,61 @@ +{{/* Configmap Spawwner */}} +{{/* Call this template: +{{ include "tc.v1.common.spawner.storageclass" $ -}} +*/}} + +{{- define "tc.v1.common.spawner.storageclass" -}} + {{- $fullname := include "tc.v1.common.lib.chart.names.fullname" $ -}} + + {{- range $name, $storageclass := .Values.storageClass -}} + + {{- $enabled := false -}} + {{- if hasKey $storageclass "enabled" -}} + {{- if not (kindIs "invalid" $storageclass.enabled) -}} + {{- $enabled = $storageclass.enabled -}} + {{- else -}} + {{- fail (printf "StorageClass - Expected the defined key [enabled] in to not be empty" $name) -}} + {{- end -}} + {{- end -}} + + + {{- if kindIs "string" $enabled -}} + {{- $enabled = tpl $enabled $ -}} + + {{/* After tpl it becomes a string, not a bool */}} + {{- if eq $enabled "true" -}} + {{- $enabled = true -}} + {{- else if eq $enabled "false" -}} + {{- $enabled = false -}} + {{- end -}} + {{- end -}} + + {{- if $enabled -}} + + {{/* Create a copy of the storageclass */}} + {{- $objectData := (mustDeepCopy $storageclass) -}} + + {{- $objectName := (printf "%s-%s" $fullname $name) -}} + {{- if hasKey $objectData "expandObjectName" -}} + {{- if not $objectData.expandObjectName -}} + {{- $objectName = $name -}} + {{- end -}} + {{- end -}} + + {{/* Perform validations */}} {{/* Configmaps have a max name length of 253 */}} + {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $objectName "length" 253) -}} + {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $objectData "caller" "StorageClass") -}} + + {{/* Set the name of the storageclass */}} + {{- $_ := set $objectData "name" $objectName -}} + {{- $_ := set $objectData "shortName" $name -}} + + {{/* Validate */}} + {{- include "tc.v1.common.lib.storageclass.validation" (dict "rootCtx" $ "objectData" $objectData) -}} + {{/* Call class to create the object */}} + {{- include "tc.v1.common.class.storageclass" (dict "rootCtx" $ "objectData" $objectData) -}} + + {{- end -}} + + {{- end -}} + +{{- end -}} diff --git a/library/common/values.yaml b/library/common/values.yaml index b82c4ef0..c0d9da65 100644 --- a/library/common/values.yaml +++ b/library/common/values.yaml @@ -561,6 +561,17 @@ webhook: type: mutating webhooks: [] +# # -- create storageClasses on demand +# storageClass: +# example: +# provisioner: some.provisioner.io +# enabled: true +# parameters: {} +# reclaimPolicy: retain +# allowVolumeExpansion: true +# volumeBindingMode: Immediate +# mountOptions: [] + metrics: main: enabled: false