diff --git a/pkg/library/overrides/containers.go b/pkg/library/overrides/containers.go index 2e61fdb5c..acc95338c 100644 --- a/pkg/library/overrides/containers.go +++ b/pkg/library/overrides/containers.go @@ -24,6 +24,16 @@ import ( "github.com/devfile/devworkspace-operator/pkg/constants" ) +// Constants representing the default values used by Kubernetes for container fields. +// If a pod is created that does not set these fields in a Probe (but does create a probe) +// these are the values that will be automatically applied to the pod spec. +const ( + defaultProbeSuccessThreshold = 1 + defaultProbeFailureThreshold = 3 + defaultProbeTimeoutSeconds = 1 + defaultProbePeriodSeconds = 10 +) + func NeedsContainerOverride(component *dw.Component) bool { return component.Container != nil && component.Attributes.Exists(constants.ContainerOverridesAttribute) } @@ -57,6 +67,10 @@ func ApplyContainerOverrides(component *dw.Component, container *corev1.Containe // does not have the omitempty json tag. patched.Name = container.Name patched.Image = container.Image + + // Make sure any fields that will be defaulted by the cluster are set (e.g. probes) + handleDefaultedContainerFields(patched) + return patched, nil } @@ -91,3 +105,35 @@ func restrictContainerOverride(override *corev1.Container) error { } return nil } + +// handleDefaultedContainerFields fills partially-filled structs with defaulted fields +// in a container. This is required to avoid repeatedly reconciling a container where e.g. +// +// 1. We create a readinessProbe with no successThreshold since it's not defined in the override +// 2. Kubernetes sets the defaulted field successThreshold: 1 +// 3. We detect that the spec is different from the cluster and unset successThreshold (go to 1.) +func handleDefaultedContainerFields(patched *corev1.Container) { + setProbeFields := func(probe *corev1.Probe) { + if probe.SuccessThreshold == 0 { + probe.SuccessThreshold = defaultProbeSuccessThreshold + } + if probe.FailureThreshold == 0 { + probe.FailureThreshold = defaultProbeFailureThreshold + } + if probe.PeriodSeconds == 0 { + probe.PeriodSeconds = defaultProbePeriodSeconds + } + if probe.TimeoutSeconds == 0 { + probe.TimeoutSeconds = defaultProbeTimeoutSeconds + } + } + if patched.ReadinessProbe != nil { + setProbeFields(patched.ReadinessProbe) + } + if patched.LivenessProbe != nil { + setProbeFields(patched.LivenessProbe) + } + if patched.StartupProbe != nil { + setProbeFields(patched.StartupProbe) + } +} diff --git a/pkg/library/overrides/testdata/container-overrides/container-defines-overrides.yaml b/pkg/library/overrides/testdata/container-overrides/container-defines-overrides.yaml index 3cb8f353f..67053cd1b 100644 --- a/pkg/library/overrides/testdata/container-overrides/container-defines-overrides.yaml +++ b/pkg/library/overrides/testdata/container-overrides/container-defines-overrides.yaml @@ -10,9 +10,6 @@ input: nvidia.com/gpu: "1" requests: nvidia.com/gpu: "1" - readinessProbe: - exec: - command: ["echo", "hello"] securityContext: runAsUser: 1000 runAsGroup: 3000 @@ -32,9 +29,6 @@ output: nvidia.com/gpu: "1" requests: nvidia.com/gpu: "1" - readinessProbe: - exec: - command: ["echo", "hello"] securityContext: runAsUser: 1000 runAsGroup: 3000 diff --git a/pkg/library/overrides/testdata/container-overrides/overrides-handles-defaulted-probe-fields.yaml b/pkg/library/overrides/testdata/container-overrides/overrides-handles-defaulted-probe-fields.yaml new file mode 100644 index 000000000..b0754c6fd --- /dev/null +++ b/pkg/library/overrides/testdata/container-overrides/overrides-handles-defaulted-probe-fields.yaml @@ -0,0 +1,47 @@ +name: "Handles defaulted fields in Probes" + +input: + component: + name: test-component + attributes: + container-overrides: + readinessProbe: + exec: + command: ["echo", "hello"] + livenessProbe: + exec: + command: ["echo", "hello"] + startupProbe: + exec: + command: ["echo", "hello"] + container: + image: test-image + container: + name: test-component + image: test-image + +output: + container: + name: test-component + image: test-image + readinessProbe: + exec: + command: ["echo", "hello"] + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + timeoutSeconds: 1 + livenessProbe: + exec: + command: ["echo", "hello"] + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + timeoutSeconds: 1 + startupProbe: + exec: + command: ["echo", "hello"] + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + timeoutSeconds: 1