diff --git a/changelog/v0.37.3/conditional-strategy.yaml b/changelog/v0.37.3/conditional-strategy.yaml new file mode 100644 index 000000000..d92beb7a4 --- /dev/null +++ b/changelog/v0.37.3/conditional-strategy.yaml @@ -0,0 +1,7 @@ +changelog: + - type: NEW_FEATURE + issueLink: https://github.com/solo-io/gloo-mesh-enterprise/issues/15710 + resolvesIssue: false + description: | + Adds support for conditional deployment strategy. + skipCI: false diff --git a/codegen/cmd_test.go b/codegen/cmd_test.go index 6efa48315..2a0ce0a8d 100644 --- a/codegen/cmd_test.go +++ b/codegen/cmd_test.go @@ -1927,6 +1927,118 @@ roleRef: }), ) + DescribeTable("rendering conditional deployment strategy", + func(values map[string]any, conditionalStrategy []model.ConditionalStrategy, expectedStrategy appsv1.DeploymentStrategy) { + cmd := &Command{ + Chart: &Chart{ + Operators: []Operator{ + { + Name: "painter", + Deployment: Deployment{ + ConditionalStrategy: conditionalStrategy, + Container: Container{ + Image: Image{ + Tag: "v0.0.0", + Repository: "painter", + Registry: "quay.io/solo-io", + PullPolicy: "IfNotPresent", + }, + }, + }, + }, + }, + + Values: nil, + Data: Data{ + ApiVersion: "v1", + Description: "", + Name: "Painting Operator", + Version: "v0.0.1", + Home: "https://docs.solo.io/skv2/latest", + Sources: []string{ + "https://github.com/solo-io/skv2", + }, + }, + }, + + ManifestRoot: "codegen/test/chart-conditional-deployment-strategy", + } + + err := cmd.Execute() + Expect(err).NotTo(HaveOccurred()) + + helmValues := map[string]interface{}{"painter": values} + renderedManifests := helmTemplate("codegen/test/chart-conditional-deployment-strategy", helmValues) + + var renderedDeployment *appsv1.Deployment + decoder := kubeyaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(renderedManifests), 4096) + for { + obj := &unstructured.Unstructured{} + err := decoder.Decode(obj) + if err != nil { + break + } + if obj.GetName() != "painter" || obj.GetKind() != "Deployment" { + continue + } + + bytes, err := obj.MarshalJSON() + Expect(err).NotTo(HaveOccurred()) + renderedDeployment = &appsv1.Deployment{} + err = json.Unmarshal(bytes, renderedDeployment) + Expect(err).NotTo(HaveOccurred()) + } + Expect(renderedDeployment).NotTo(BeNil()) + renderedDeploymentStrategy := renderedDeployment.Spec.Strategy + Expect(renderedDeploymentStrategy).To(Equal(expectedStrategy)) + }, + Entry("when the conditional strategy is not defined", + map[string]any{"enabled": true}, + nil, + appsv1.DeploymentStrategy{}, + ), + Entry("when the condition is true", + map[string]any{"enabled": true, "condition": true}, + []model.ConditionalStrategy{ + { + Condition: "$.Values.painter.condition", + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + }, + }, + { + Condition: "not $.Values.painter.condition", + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + }, + }, + appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + }, + ), + Entry("when the condition is false", + map[string]any{"enabled": true, "condition": false}, + []model.ConditionalStrategy{ + { + Condition: "$.Values.painter.condition", + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + }, + }, + { + Condition: "not $.Values.painter.condition", + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + }, + }, + appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + ), + ) + DescribeTable("rendering pod security context", func(podSecurityContextValues map[string]interface{}, podSecurityContext *v1.PodSecurityContext, expectedPodSecurityContext *v1.PodSecurityContext) { cmd := &Command{ diff --git a/codegen/model/chart.go b/codegen/model/chart.go index 7997a8d5f..78f53cdce 100644 --- a/codegen/model/chart.go +++ b/codegen/model/chart.go @@ -114,6 +114,7 @@ type Deployment struct { UseDaemonSet bool Container Strategy *appsv1.DeploymentStrategy + ConditionalStrategy []ConditionalStrategy Sidecars []Sidecar PodSecurityContext *corev1.PodSecurityContext Volumes []corev1.Volume @@ -124,6 +125,11 @@ type Deployment struct { CustomDeploymentAnnotations map[string]string } +type ConditionalStrategy struct { + Condition string + Strategy appsv1.DeploymentStrategy +} + type ConditionalVolume struct { Condition string Volume corev1.Volume diff --git a/codegen/templates/chart/operator-deployment.yamltmpl b/codegen/templates/chart/operator-deployment.yamltmpl index a7034e0c2..965053d30 100644 --- a/codegen/templates/chart/operator-deployment.yamltmpl +++ b/codegen/templates/chart/operator-deployment.yamltmpl @@ -9,7 +9,8 @@ Expressions evaluating SKv2 Config use "[[" and "]]" [[/* custom values defined in codegen model */]] [[- $containers := containerConfigs $operator -]] -[[- $deploymentStrategy := $operator.Deployment.Strategy -]] +[[- $strategy := $operator.Deployment.Strategy -]] +[[- $conditionalStrategy := $operator.Deployment.ConditionalStrategy -]] [[- $podSecurityContext := $operator.Deployment.PodSecurityContext -]] [[- $volumes := $operator.Deployment.Volumes -]] [[- $conditionalVolumes := $operator.Deployment.ConditionalVolumes -]] @@ -60,14 +61,15 @@ spec: [[- range $key, $value := $customPodLabels ]] [[ $key ]]: [[ $value ]] [[- end ]] -[[- if $deploymentStrategy ]] +[[- if $strategy ]] strategy: -[[- if $deploymentStrategy.Type ]] - type: [[ $deploymentStrategy.Type ]] -[[- end ]] -[[- if $deploymentStrategy.RollingUpdate ]] - rollingUpdate: -[[ toYaml $deploymentStrategy.RollingUpdate | indent 6 ]] +[[ toYaml $strategy | indent 4 ]] +[[- else if $conditionalStrategy ]] +[[- range $s := $conditionalStrategy ]] +{{- if [[ $s.Condition ]] }} + strategy: +[[ toYaml $s.Strategy | indent 4 ]] +{{- end }} [[- end ]] [[- end ]] template: diff --git a/codegen/test/chart-conditional-deployment-strategy/Chart.yaml b/codegen/test/chart-conditional-deployment-strategy/Chart.yaml new file mode 100644 index 000000000..01037b07a --- /dev/null +++ b/codegen/test/chart-conditional-deployment-strategy/Chart.yaml @@ -0,0 +1,8 @@ +# Code generated by skv2. DO NOT EDIT. + +apiVersion: v1 +home: https://docs.solo.io/skv2/latest +name: Painting Operator +sources: +- https://github.com/solo-io/skv2 +version: v0.0.1 diff --git a/codegen/test/chart-conditional-deployment-strategy/templates/_helpers.tpl b/codegen/test/chart-conditional-deployment-strategy/templates/_helpers.tpl new file mode 100644 index 000000000..0c155a127 --- /dev/null +++ b/codegen/test/chart-conditional-deployment-strategy/templates/_helpers.tpl @@ -0,0 +1,54 @@ +# Code generated by skv2. DO NOT EDIT. + + + +{{/* Below are library functions provided by skv2 */}} + +{{- /* + +"skv2.utils.merge" takes an array of three values: +- the top context +- the yaml block that will be merged in (override) +- the name of the base template (source) + +note: the source must be a named template (helm partial). This is necessary for the merging logic. + +The behaviour is as follows, to align with already existing helm behaviour: +- If no source is found (template is empty), the merged output will be empty +- If no overrides are specified, the source is rendered as is +- If overrides are specified and source is not empty, overrides will be merged in to the source. + +Overrides can replace / add to deeply nested dictionaries, but will completely replace lists. +Examples: + +┌─────────────────────┬───────────────────────┬────────────────────────┐ +│ Source (template) │ Overrides │ Result │ +├─────────────────────┼───────────────────────┼────────────────────────┤ +│ metadata: │ metadata: │ metadata: │ +│ labels: │ labels: │ labels: │ +│ app: gloo │ app: gloo1 │ app: gloo1 │ +│ cluster: useast │ author: infra-team │ author: infra-team │ +│ │ │ cluster: useast │ +├─────────────────────┼───────────────────────┼────────────────────────┤ +│ lists: │ lists: │ lists: │ +│ groceries: │ groceries: │ groceries: │ +│ - apple │ - grapes │ - grapes │ +│ - banana │ │ │ +└─────────────────────┴───────────────────────┴────────────────────────┘ + +skv2.utils.merge is a fork of a helm library chart function (https://github.com/helm/charts/blob/master/incubator/common/templates/_util.tpl). +This includes some optimizations to speed up chart rendering time, and merges in a value (overrides) with a named template, unlike the upstream +version, which merges two named templates. + +*/ -}} +{{- define "skv2.utils.merge" -}} +{{- $top := first . -}} +{{- $overrides := (index . 1) -}} +{{- $tpl := fromYaml (include (index . 2) $top) -}} +{{- if or (empty $overrides) (empty $tpl) -}} +{{ include (index . 2) $top }} {{/* render source as is */}} +{{- else -}} +{{- $merged := merge $overrides $tpl -}} +{{- toYaml $merged -}} {{/* render source with overrides as YAML */}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/codegen/test/chart-conditional-deployment-strategy/templates/deployment.yaml b/codegen/test/chart-conditional-deployment-strategy/templates/deployment.yaml new file mode 100644 index 000000000..9c9ab1ae6 --- /dev/null +++ b/codegen/test/chart-conditional-deployment-strategy/templates/deployment.yaml @@ -0,0 +1,140 @@ +# Code generated by skv2. DO NOT EDIT. + + + +{{- $painter := $.Values.painter }} +--- + +{{- define "painter.deploymentSpec" }} +# Deployment manifest for painter + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: painter + annotations: + app.kubernetes.io/name: painter + name: painter + namespace: {{ default .Release.Namespace $.Values.painter.namespace }} +spec: + selector: + matchLabels: + app: painter +{{- if $.Values.painter.condition }} + strategy: + type: RollingUpdate +{{- end }} +{{- if not $.Values.painter.condition }} + strategy: + type: Recreate +{{- end }} + template: + metadata: + labels: + app: painter + annotations: + app.kubernetes.io/name: painter + spec: + serviceAccountName: painter + {{- /* Override the default podSecurityContext config if it is set. */}} +{{- if or ($.Values.painter.podSecurityContext) (eq "map[]" (printf "%v" $.Values.painter.podSecurityContext)) }} + securityContext: +{{ toYaml $.Values.painter.podSecurityContext | indent 8 }} +{{- end }} + containers: +{{- $painter := $.Values.painter }} +{{- $painterImage := $painter.image }} + - name: painter + image: {{ $painterImage.registry }}/{{ $painterImage.repository }}:{{ $painterImage.tag }} + imagePullPolicy: {{ $painterImage.pullPolicy }} +{{- if $painter.env }} + env: +{{ toYaml $painter.env | indent 10 }} +{{- else if $painter.extraEnvs }} + env: +{{- end }} +{{- range $name, $item := $painter.extraEnvs }} + - name: {{ $name }} +{{- $item | toYaml | nindent 12 }} +{{- end }} + resources: +{{- if $painter.resources }} +{{ toYaml $painter.resources | indent 10}} +{{- else}} + requests: + cpu: 500m + memory: 256Mi +{{- end }} + {{- /* + Render securityContext configs if it is set. + If securityContext is not set, render the default securityContext. + If securityContext is set to 'false', render an empty map. + */}} + securityContext: +{{- if or ($painter.securityContext) (eq "map[]" (printf "%v" $painter.securityContext)) }} +{{ toYaml $painter.securityContext | indent 10}} +{{/* Because securityContext is nil by default we can only perform following conversion if it is a boolean. Skip conditional otherwise. */}} +{{- else if eq (ternary $painter.securityContext true (eq "bool" (printf "%T" $painter.securityContext))) false }} + {} +{{- else}} + runAsNonRoot: true + {{- if not $painter.floatingUserId }} + runAsUser: {{ printf "%.0f" (float64 $painter.runAsUser) }} + {{- end }} + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +{{- end }} + {{- if $painterImage.pullSecret }} + imagePullSecrets: + - name: {{ $painterImage.pullSecret }} + {{- end}} +{{- end }} {{/* define "painter.deploymentSpec" */}} + +{{/* Render painter deployment template with overrides from values*/}} +{{ if $painter.enabled }} +{{- $painterDeploymentOverrides := dict }} +{{- if $painter.deploymentOverrides }} +{{- $painterDeploymentOverrides = $painter.deploymentOverrides }} +{{- end }} +--- +{{ include "skv2.utils.merge" (list . $painterDeploymentOverrides "painter.deploymentSpec") }} +{{- end }} +--- +{{ if $painter.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: painter + {{- if $painter.serviceAccount}} + {{- if $painter.serviceAccount.extraAnnotations }} + annotations: + {{- range $key, $value := $painter.serviceAccount.extraAnnotations }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + {{- end}} + name: painter + namespace: {{ default .Release.Namespace $.Values.painter.namespace }} +{{- end }} + + +{{- define "painter.serviceSpec"}} + +{{- end }} {{/* define "painter.serviceSpec" */}} +{{ if $painter.enabled }} +{{/* Render painter service template with overrides from values*/}} +{{- $painterServiceOverrides := dict }} +{{- if $painter.serviceOverrides }} +{{- $painterServiceOverrides = $painter.serviceOverrides }} +{{- end }} + +--- + +{{ include "skv2.utils.merge" (list . $painterServiceOverrides "painter.serviceSpec") }} +{{- end }} + diff --git a/codegen/test/chart-conditional-deployment-strategy/templates/rbac.yaml b/codegen/test/chart-conditional-deployment-strategy/templates/rbac.yaml new file mode 100644 index 000000000..feb93b669 --- /dev/null +++ b/codegen/test/chart-conditional-deployment-strategy/templates/rbac.yaml @@ -0,0 +1,2 @@ +# Code generated by skv2. DO NOT EDIT. + diff --git a/codegen/test/chart-conditional-deployment-strategy/values.yaml b/codegen/test/chart-conditional-deployment-strategy/values.yaml new file mode 100644 index 000000000..3796fee5e --- /dev/null +++ b/codegen/test/chart-conditional-deployment-strategy/values.yaml @@ -0,0 +1,18 @@ +# Code generated by skv2. DO NOT EDIT. + +painter: + deploymentOverrides: null + env: null + extraEnvs: {} + floatingUserId: false + image: + pullPolicy: IfNotPresent + registry: quay.io/solo-io + repository: painter + tag: v0.0.0 + ports: {} + runAsUser: 10101 + serviceOverrides: null + serviceType: "" + sidecars: {} + diff --git a/codegen/test/chart-deployment-strategy/templates/deployment.yaml b/codegen/test/chart-deployment-strategy/templates/deployment.yaml index 2eae2d3f8..da16e12c3 100644 --- a/codegen/test/chart-deployment-strategy/templates/deployment.yaml +++ b/codegen/test/chart-deployment-strategy/templates/deployment.yaml @@ -22,9 +22,9 @@ spec: matchLabels: app: painter strategy: - type: RollingUpdate rollingUpdate: maxUnavailable: 1 + type: RollingUpdate template: metadata: labels: