diff --git a/library/common-test/tests/container/image_test.yaml b/library/common-test/tests/container/image_test.yaml new file mode 100644 index 0000000000..e0ef1dfd3c --- /dev/null +++ b/library/common-test/tests/container/image_test.yaml @@ -0,0 +1,128 @@ +suite: container image test +templates: + - common.yaml +tests: + - it: should generate correct image + set: + imageDictToUse: image + image: &image + repository: nginx + tag: 1.19.0 + pullPolicy: IfNotPresent + imageGPU: + repository: some-gpu-image + tag: 5.20.0 + pullPolicy: Always + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: "{{ .Values.imageDictToUse }}" + container-name2: + enabled: true + primary: false + imageSelector: imageGPU + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + image: nginx:1.19.0 + imagePullPolicy: IfNotPresent + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[1] + content: + image: some-gpu-image:5.20.0 + imagePullPolicy: Always + + # Failures + - it: should fail with imageSelector trying to access non-existent image + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image-dict-that-does-not-exist + asserts: + - failedTemplate: + errorMessage: Container - Expected <.Values.image-dict-that-does-not-exist> to exist + + - it: should fail with empty repository in selected image + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty <.Values.image.repository> + + - it: should fail with empty tag in selected image + set: + image: + repository: nginx + tag: "" + pullPolicy: IfNotPresent + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty <.Values.image.tag> + + - it: should fail with invalid pullPolicy selected image + set: + image: + repository: nginx + tag: 1.19.0 + pullPolicy: invalid + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + asserts: + - failedTemplate: + errorMessage: Container - Expected <.Values.image.pullPolicy> to be one of [IfNotPresent, Always, Never], but got [invalid] diff --git a/library/common-test/tests/container/name_test.yaml b/library/common-test/tests/container/name_test.yaml new file mode 100644 index 0000000000..b89e03c111 --- /dev/null +++ b/library/common-test/tests/container/name_test.yaml @@ -0,0 +1,42 @@ +suite: container name test +templates: + - common.yaml +tests: + - it: should generate correct container name + set: + image: &image + repository: nginx + tag: 1.19.0 + pullPolicy: IfNotPresent + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + container-name2: + enabled: true + primary: false + imageSelector: image + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + name: release-name-common-test + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[1] + content: + name: release-name-common-test-container-name2 diff --git a/library/common-test/tests/container/tty_stdin_test.yaml b/library/common-test/tests/container/tty_stdin_test.yaml new file mode 100644 index 0000000000..7bfb1fefb3 --- /dev/null +++ b/library/common-test/tests/container/tty_stdin_test.yaml @@ -0,0 +1,64 @@ +suite: container tty and stdin test +templates: + - common.yaml +tests: + - it: should pass without tty and stdin + set: + image: &image + repository: nginx + tag: 1.19.0 + pullPolicy: IfNotPresent + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + tty: false + stdin: false + + - it: should pass with tty and stdin enabled + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + tty: true + stdin: true + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + tty: true + stdin: true diff --git a/library/common-test/tests/container/validation_test.yaml b/library/common-test/tests/container/validation_test.yaml new file mode 100644 index 0000000000..e6929065f8 --- /dev/null +++ b/library/common-test/tests/container/validation_test.yaml @@ -0,0 +1,41 @@ +suite: container validation test +templates: + - common.yaml +tests: + - it: should fail with more than one primary container on a workload + set: + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + container-name2: + enabled: true + primary: true + asserts: + - failedTemplate: + errorMessage: Container - Only one container can be primary per workload + + - it: should fail with no primary container on a workload + set: + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: false + container-name2: + enabled: true + primary: false + asserts: + - failedTemplate: + errorMessage: Container - At least one enabled container must be primary per workload diff --git a/library/common/1.0.0/docs/container/README.md b/library/common/1.0.0/docs/container/README.md new file mode 100644 index 0000000000..add5796264 --- /dev/null +++ b/library/common/1.0.0/docs/container/README.md @@ -0,0 +1,46 @@ +# Container + +Assume every key bellow has a prefix of `workload.[workload-name].podSpec`. + +| Key | Type | Required | Helm Template | Default | Description | +| :---------------------------------- | :-------: | :------: | :-----------: | :-----: | :-------------------------------- | +| containers.[container-name] | `dict` | ✅ | ❌ | `{}` | Define the container as dict | +| containers.[container-name].enabled | `boolean` | ✅ | ❌ | `false` | Enables or Disables the container | +| containers.[container-name].primary | `boolean` | ✅ | ❌ | `false` | Sets the container as primary | +| containers.[container-name].stdin | `boolean` | ❌ | ❌ | `false` | whether to enable stdin or not | +| containers.[container-name].tty | `boolean` | ❌ | ❌ | `false` | whether to enable tty or not | + +--- + +Appears in: + +- `.Values.workload.[workload-name].podSpec.containers` + +--- + +Naming scheme: + +- Primary: `$FullName` (release-name-chart-name) +- Non-Primary: `$FullName-$ContainerName` (release-name-chart-name-container-name) + +--- + +Examples: + +```yaml +workload: + workload-name: + enabled: true + primary: true + labels: + key: value + annotations: + key: value + podSpec: + containers: + container-name: + enabled: true + primary: true + stdin: true + tty: true +``` diff --git a/library/common/1.0.0/docs/workload/README.md b/library/common/1.0.0/docs/workload/README.md index 2b0ea19acb..fcdac50b9a 100644 --- a/library/common/1.0.0/docs/workload/README.md +++ b/library/common/1.0.0/docs/workload/README.md @@ -40,6 +40,8 @@ | workload.[workload-name].podSpec.securityContext.sysctls | `list` | ❌ | ❌ | `[]` | Pod's sysctls | | workload.[workload-name].podSpec.securityContext.sysctls.name | `string` | ✅ | ✅ | `""` | `name` of the sysctl | | workload.[workload-name].podSpec.securityContext.sysctls.value | `string` | ✅ | ✅ | `""` | `value` of the sysctl | +| workload.[workload-name].podSpec.containers | `dict` | ❌ | ❌ | `{}` | Define container(s) | +| workload.[workload-name].podSpec.initContainers | `dict` | ❌ | ❌ | `{}` | Define initContainer(s) | --- @@ -75,6 +77,10 @@ Naming scheme: - [CronJob](cronjob.md) - [Job](job.md) +> Additional keys, information and examples for `workload.[workload-name].podSpec.containers`. + +- [Container](../container/README.md) + --- Examples: diff --git a/library/common/1.0.0/templates/lib/container/_imageSelector.tpl b/library/common/1.0.0/templates/lib/container/_imageSelector.tpl new file mode 100644 index 0000000000..58a0e3eeb6 --- /dev/null +++ b/library/common/1.0.0/templates/lib/container/_imageSelector.tpl @@ -0,0 +1,42 @@ +{{/* Returns the image dictionary */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.container.imageSelector" (dict "rootCtx" $ "objectData" $objectData) }} +rootCtx: The root context of the template. It is used to access the global context. +objectData: The object data to be used to render the container. +*/}} +{{- define "ix.v1.common.lib.container.imageSelector" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $imageObj := dict -}} + + {{- $selector := "image" -}} + {{- with $objectData.imageSelector -}} + {{- $selector = tpl . $rootCtx -}} + {{- end -}} + + {{- if hasKey $rootCtx.Values $selector -}} + {{- $imageObj = get $rootCtx.Values $selector -}} + {{- else -}} + {{- fail (printf "Container - Expected <.Values.%s> to exist" $selector) -}} + {{- end -}} + + {{- if not $imageObj.repository -}} + {{- fail (printf "Container - Expected non-empty <.Values.%s.repository>" $selector) -}} + {{- end -}} + + {{- if not $imageObj.tag -}} + {{- fail (printf "Container - Expected non-empty <.Values.%s.tag>" $selector) -}} + {{- end -}} + + {{- if not $imageObj.pullPolicy -}} + {{- $_ := set $imageObj "pullPolicy" "IfNotPresent" -}} + {{- end -}} + + {{- $policies := (list "IfNotPresent" "Always" "Never") -}} + {{- if not (mustHas $imageObj.pullPolicy $policies) -}} + {{- fail (printf "Container - Expected <.Values.%s.pullPolicy> to be one of [%s], but got [%s]" $selector (join ", " $policies) $imageObj.pullPolicy) -}} + {{- end -}} + + {{- $imageObj | toJson -}} +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/container/_primaryValidation.tpl b/library/common/1.0.0/templates/lib/container/_primaryValidation.tpl new file mode 100644 index 0000000000..484edd2a01 --- /dev/null +++ b/library/common/1.0.0/templates/lib/container/_primaryValidation.tpl @@ -0,0 +1,40 @@ +{{/* Containers Basic Validation */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.container.primaryValidation" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} +*/}} +{{- define "ix.v1.common.lib.container.primaryValidation" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{/* Initialize values */}} + {{- $hasPrimary := false -}} + {{- $hasEnabled := false -}} + + {{/* Go over the contaienrs */}} + {{- range $name, $container := $objectData.podSpec.containers -}} + + {{/* If container is enabled */}} + {{- if $container.enabled -}} + {{- $hasEnabled = true -}} + + {{/* And container is primary */}} + {{- if and (hasKey $container "primary") ($container.primary) -}} + + {{/* Fail if there is already a primary container */}} + {{- if $hasPrimary -}} + {{- fail "Container - Only one container can be primary per workload" -}} + {{- end -}} + + {{- $hasPrimary = true -}} + + {{- end -}} + {{- end -}} + + {{- end -}} + + {{/* Require at least one primary container, if any enabled */}} + {{- if and $hasEnabled (not $hasPrimary) -}} + {{- fail "Container - At least one enabled container must be primary per workload" -}} + {{- end -}} + +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/pod/_container.tpl b/library/common/1.0.0/templates/lib/pod/_container.tpl index 5581c703ab..921b83f46c 100644 --- a/library/common/1.0.0/templates/lib/pod/_container.tpl +++ b/library/common/1.0.0/templates/lib/pod/_container.tpl @@ -8,4 +8,10 @@ objectData: The object data to be used to render the Pod. {{- $rootCtx := .rootCtx -}} {{- $objectData := .objectData -}} + {{- $imageObj := fromJson (include "ix.v1.common.lib.container.imageSelector" (dict "rootCtx" $rootCtx "objectData" $objectData)) }} +- name: {{ $objectData.name }} + image: {{ printf "%s:%s" $imageObj.repository $imageObj.tag }} + imagePullPolicy: {{ $imageObj.pullPolicy }} + tty: {{ $objectData.tty | default false }} + stdin: {{ $objectData.stdin | default false }} {{- end -}} diff --git a/library/common/1.0.0/templates/lib/workload/_pod.tpl b/library/common/1.0.0/templates/lib/workload/_pod.tpl index 1a49a6fc02..2930f11fea 100644 --- a/library/common/1.0.0/templates/lib/workload/_pod.tpl +++ b/library/common/1.0.0/templates/lib/workload/_pod.tpl @@ -44,12 +44,23 @@ tolerations: {{- end }} securityContext: {{- include "ix.v1.common.lib.pod.securityContext" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} - {{- if $objectData.podSpec.contaienrs }} + {{- if $objectData.podSpec.containers }} containers: - {{- range $name, $containerValues := $objectData.podSpec.containers -}} - {{- include "ix.v1.common.lib.pod.container" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} + {{- include "ix.v1.common.lib.container.primaryValidation" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} + {{- range $containerName, $containerValues := $objectData.podSpec.containers -}} + {{- if $containerValues.enabled -}} + {{- $container := (mustDeepCopy $containerValues) -}} + {{- $name := include "ix.v1.common.lib.chart.names.fullname" $rootCtx -}} + {{- if not $container.primary -}} + {{- $name = printf "%s-%s" $name $containerName -}} + {{- end -}} + + {{- $_ := set $container "name" $name -}} + {{- $_ := set $container "shortName" $containerName -}} + {{- include "ix.v1.common.lib.pod.container" (dict "rootCtx" $rootCtx "objectData" $container) | trim | nindent 2 }} + {{- end -}} {{- end -}} - {{- end -}} + {{- end }} #TODO:initContainers: {{- with (include "ix.v1.common.lib.pod.volumes" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim) }} volumes: diff --git a/library/common/1.0.0/values.yaml b/library/common/1.0.0/values.yaml index 22ea0feb68..d2f4c31549 100644 --- a/library/common/1.0.0/values.yaml +++ b/library/common/1.0.0/values.yaml @@ -77,15 +77,27 @@ securityContext: supplementalGroups: [] sysctls: [] -# -- Resources -# Can be overruled per container -resources: - limits: - cpu: 4000m - memory: 8Gi - requests: - cpu: 10m - memory: 50Mi +containerOptions: + # -- Resources + # Can be overruled per container + resources: + limits: + cpu: 4000m + memory: 8Gi + requests: + cpu: 10m + memory: 50Mi + # -- Timezone for all containers + # Can be overruled per container + TZ: UTC + # -- PUID for all containers + # Can be overruled per container + PUID: 568 + # -- UMASK for all containers + # Can be overruled per container + UMASK: "002" + NVIDIA_CAPS: + - all # -- Options for all pods # Can be overruled per pod @@ -109,18 +121,6 @@ podOptions: # TODO: Add affinity # TODO: Add topologySpreadConstraints -# -- Timezone for all containers -# Can be overruled per container -TZ: UTC -# -- PUID for all containers -# Can be overruled per container -PUID: 568 -# -- UMASK for all containers -# Can be overruled per container -UMASK: "002" -NVIDIA_CAPS: - - all - # -- Persistence persistence: shared: