diff --git a/library/common-test/tests/container/lifecycle_test.yaml b/library/common-test/tests/container/lifecycle_test.yaml new file mode 100644 index 0000000000..35bf215796 --- /dev/null +++ b/library/common-test/tests/container/lifecycle_test.yaml @@ -0,0 +1,242 @@ +suite: container lifecycle test +templates: + - common.yaml +tests: + - it: should pass with lifecycle + 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 + lifecycle: + postStart: + type: exec + command: + - echo + - hello + preStop: + type: http + port: 80 + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + lifecycle: + postStart: + exec: + command: + - echo + - hello + preStop: + httpGet: + port: 80 + path: / + scheme: HTTP + + - it: should pass with lifecycle + set: + image: *image + some_command: ./some_command + some_port: 80 + some_host: some_host + some_scheme: HTTPS + some_path: /api/v1 + some_value: some_value + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + postStart: + type: http + port: "{{ .Values.some_port }}" + host: "{{ .Values.some_host }}" + scheme: "{{ .Values.some_scheme }}" + path: "{{ .Values.some_path }}" + httpHeaders: + key: "{{ .Values.some_value }}" + preStop: + type: exec + command: "{{ .Values.some_command }}" + asserts: + - documentIndex: &deploymentDoc 0 + isKind: + of: Deployment + - documentIndex: *deploymentDoc + isAPIVersion: + of: apps/v1 + - documentIndex: *deploymentDoc + isSubset: + path: spec.template.spec.containers[0] + content: + lifecycle: + postStart: + httpGet: + port: 80 + path: /api/v1 + scheme: HTTPS + host: some_host + httpHeaders: + - name: key + value: some_value + preStop: + exec: + command: + - ./some_command + + # Failures + - it: should fail with invalid lifecycle hook + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + invalid: {} + asserts: + - failedTemplate: + errorMessage: Container - Expected to be one of [preStop, postStart], but got [invalid] + + - it: should fail with empty lifecycle type + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + preStop: + command: [] + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty + + - it: should fail with invalid lifecycle type + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + preStop: + type: invalid + command: [] + asserts: + - failedTemplate: + errorMessage: Container - Expected to be one of [exec, http], but got [invalid] + + - it: should fail with empty command on exec lifecycle type + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + preStop: + type: exec + command: [] + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty on [exec] type + + - it: should fail with empty port on http lifecycle type + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + preStop: + type: http + port: "" + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty on [http] type + + - it: should fail with empty value on httpHeaders on http lifecycle type + set: + image: *image + workload: + workload-name: + enabled: true + primary: true + type: Deployment + podSpec: + containers: + container-name1: + enabled: true + primary: true + imageSelector: image + lifecycle: + preStop: + type: http + port: 80 + httpHeaders: + key: "" + asserts: + - failedTemplate: + errorMessage: Container - Expected non-empty on diff --git a/library/common/1.0.0/docs/container/README.md b/library/common/1.0.0/docs/container/README.md index eb89ec8cb9..e003e77ed5 100644 --- a/library/common/1.0.0/docs/container/README.md +++ b/library/common/1.0.0/docs/container/README.md @@ -2,19 +2,29 @@ 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 | -| containers.[container-name].command | `list/string` | ❌ | ✅ | `[]` | Define command(s). If it's single, can be defined as string | -| containers.[container-name].args | `list/string` | ❌ | ✅ | `[]` | Define arg(s). If it's single, can be defined as string | -| containers.[container-name].extraArgs | `list/string` | ❌ | ✅ | `[]` | Define extraArg(s). Those are appended after the `args`. Useful for user defined args from GUI | -| containers.[container-name].termination | `dict` | ❌ | ❌ | `{}` | Define termination for the container | -| containers.[container-name].termination.messagePath | `string` | ❌ | ✅ | `""` | Define termination message path for the container | -| containers.[container-name].termination.messagePolicy | `string` | ❌ | ✅ | `""` | Define termination message policy for the container | +| 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 | +| containers.[container-name].command | `list/string` | ❌ | ✅ | `[]` | Define command(s). If it's single, can be defined as string | +| containers.[container-name].args | `list/string` | ❌ | ✅ | `[]` | Define arg(s). If it's single, can be defined as string | +| containers.[container-name].extraArgs | `list/string` | ❌ | ✅ | `[]` | Define extraArg(s). Those are appended after the `args`. Useful for user defined args from GUI | +| containers.[container-name].termination | `dict` | ❌ | ❌ | `{}` | Define termination for the container | +| containers.[container-name].termination.messagePath | `string` | ❌ | ✅ | `""` | Define termination message path for the container | +| containers.[container-name].termination.messagePolicy | `string` | ❌ | ✅ | `""` | Define termination message policy for the container | +| containers.[container-name].lifecycle | `dict` | ❌ | ❌ | `{}` | Define lifecycle for the container | +| containers.[container-name].lifecycle.preStop | `dict` | ❌ | ❌ | `{}` | Define preStop lifecycle | +| containers.[container-name].lifecycle.postStart | `dict` | ❌ | ❌ | `{}` | Define preStop lifecycle | +| containers.[container-name].lifecycle.[hook].type | `string` | ❌ | ❌ | `""` | Define hook type (exec, http) | +| containers.[container-name].lifecycle.[hook].command | `list/string` | ✅ (On exec type) | ✅ | `""` | Define command(s). If it's single, can be defined as string (Only when exec type is used) | | +| containers.[container-name].lifecycle.[hook].port | `int` | ✅ (On http type) | ✅ | `""` | Define the port, (Only when http type is used) | | +| containers.[container-name].lifecycle.[hook].host | `string` | ❌ | ✅ | | Define the host, k8s defaults to POD IP (Only when http type is used) | | +| containers.[container-name].lifecycle.[hook].path | `string` | ❌ | ✅ | `/` | Define the path (Only when http type is used) | | +| containers.[container-name].lifecycle.[hook].scheme | `string` | ❌ | ✅ | `HTTP` | Define the scheme (Only when http type is used) | | +| containers.[container-name].lifecycle.[hook].httpHeaders | `dict` | ❌ | ✅ (On value only) | `{}` | Define the httpHeaders in key-value pairs (Only when http type is used) | | --- @@ -57,4 +67,17 @@ workload: termination: messagePath: /dev/termination-log messagePolicy: File + lifecycle: + preStop: + type: exec + command: + - command + postStart: + type: http + port: 8080 + host: localhost + path: /path + scheme: HTTP + httpHeaders: + key: value ``` diff --git a/library/common/1.0.0/templates/lib/container/_lifecycle.tpl b/library/common/1.0.0/templates/lib/container/_lifecycle.tpl new file mode 100644 index 0000000000..48b11e9de0 --- /dev/null +++ b/library/common/1.0.0/templates/lib/container/_lifecycle.tpl @@ -0,0 +1,43 @@ +{{/* Returns lifecycle */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.container.lifecycle" (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.lifecycle" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $hooks := (list "preStop" "postStart") -}} + {{- $types := (list "exec" "http") -}} + {{- with $objectData.lifecycle -}} + {{- range $hook, $hookValues := . -}} + {{- if not (mustHas $hook $hooks) -}} + {{- fail (printf "Container - Expected to be one of [%s], but got [%s]" (join ", " $hooks) $hook) -}} + {{- end -}} + + {{- if not $hookValues.type -}} + {{- fail "Container - Expected non-empty " -}} + {{- end -}} + + {{- if not (mustHas $hookValues.type $types) -}} + {{- fail (printf "Container - Expected to be one of [%s], but got [%s]" (join ", " $types) $hookValues.type) -}} + {{- end }} +{{ $hook }}: + {{- if eq $hookValues.type "exec" -}} + {{- if not $hookValues.command -}} + {{- fail "Container - Expected non-empty on [exec] type" -}} + {{- end }} + {{- include "ix.v1.common.lib.container.actions.exec" (dict "rootCtx" $rootCtx "objectData" $hookValues) | trim | nindent 2 }} + {{- else if eq $hookValues.type "http" }} + {{- if not $hookValues.port -}} + {{- fail "Container - Expected non-empty on [http] type" -}} + {{- end }} + {{- include "ix.v1.common.lib.container.actions.httpGet" (dict "rootCtx" $rootCtx "objectData" $hookValues) | trim | nindent 2 }} + {{- end -}} + + {{- end -}} + {{- end -}} + + +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/container/actions/_exec.tpl b/library/common/1.0.0/templates/lib/container/actions/_exec.tpl new file mode 100644 index 0000000000..92a56e1a74 --- /dev/null +++ b/library/common/1.0.0/templates/lib/container/actions/_exec.tpl @@ -0,0 +1,13 @@ +{{/* Returns exec action */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.container.actions.exec" (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.actions.exec" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData }} +exec: + command: + {{- include "ix.v1.common.lib.container.command" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 4}} +{{- end -}} diff --git a/library/common/1.0.0/templates/lib/container/actions/_httpGet.tpl b/library/common/1.0.0/templates/lib/container/actions/_httpGet.tpl new file mode 100644 index 0000000000..72446f4220 --- /dev/null +++ b/library/common/1.0.0/templates/lib/container/actions/_httpGet.tpl @@ -0,0 +1,42 @@ +{{/* Returns httpGet action */}} +{{/* Call this template: +{{ include "ix.v1.common.lib.container.actions.httpGet" (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.actions.httpGet" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $port := $objectData.port -}} + {{- $path := "/" -}} + {{- $scheme := "HTTP" -}} + + {{- if kindIs "string" $port -}} + {{- $port = tpl $port $rootCtx -}} + {{- end -}} + {{- with $objectData.path -}} + {{- $path = tpl . $rootCtx -}} + {{- end -}} + {{- with $objectData.scheme -}} + {{- $scheme = tpl . $rootCtx -}} + {{- end }} +httpGet: + {{- with $objectData.host }} + host: {{ tpl . $rootCtx }} + {{- end }} + port: {{ $port }} + path: {{ $path }} + scheme: {{ $scheme }} + {{- with $objectData.httpHeaders }} + httpHeaders: + {{- range $name, $value := . }} + {{- if not $value -}} + {{- fail "Container - Expected non-empty on " -}} + {{- end }} + - name: {{ $name }} + value: {{ tpl $value $rootCtx }} + {{- end -}} + {{- 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 3aea6fb164..ee64f37ec4 100644 --- a/library/common/1.0.0/templates/lib/pod/_container.tpl +++ b/library/common/1.0.0/templates/lib/pod/_container.tpl @@ -29,9 +29,12 @@ objectData: The object data to be used to render the Pod. {{- with $termination.messagePolicy }} terminationMessagePolicy: {{ . }} {{- end -}} + {{- with (include "ix.v1.common.lib.container.lifecycle" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim) }} + lifecycle: + {{- . | nindent 4 }} + {{- end -}} {{- end -}} {{/* TODO: -lifecycle securityContext resources probes diff --git a/library/common/1.0.0/values.yaml b/library/common/1.0.0/values.yaml index 4681e4d546..59b5e3079e 100644 --- a/library/common/1.0.0/values.yaml +++ b/library/common/1.0.0/values.yaml @@ -476,9 +476,19 @@ workload: - ALL lifecycle: postStart: - command: [] + type: http + port: 123 + path: /asdf + httpHeaders: + key: value + key2: value2 + host: asdf + scheme: HTTPS preStop: - command: [] + type: exec + command: + - asdf + - asdf termination: messagePath: "" messagePolicy: ""