From 76064a6eff31bad191352fa988367ecb429d0196 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Mon, 11 May 2026 14:16:50 -0700 Subject: [PATCH 1/3] Add Installation.Spec.ImagePullPolicy to control image pull policy When set, the configured pull policy is applied to every container in pods rendered by the operator, overriding any default the renderer would otherwise set. Useful in air-gapped clusters where images are pre-loaded onto nodes. --- api/v1/installation_types.go | 8 ++ api/v1/zz_generated.deepcopy.go | 5 ++ pkg/controller/utils/component.go | 73 ++++++++++++++----- pkg/controller/utils/component_test.go | 29 +++++++- pkg/controller/utils/merge.go | 6 ++ pkg/controller/utils/merge_test.go | 19 +++++ .../operator.tigera.io_installations.yaml | 22 ++++++ pkg/render/apiserver.go | 3 - .../applicationlayer/applicationlayer.go | 3 - pkg/render/aws-securitygroup-setup.go | 3 +- pkg/render/compliance.go | 5 -- pkg/render/csi.go | 14 ++-- pkg/render/dex.go | 1 - pkg/render/egressgateway/egressgateway.go | 2 - pkg/render/fluentd.go | 9 +-- pkg/render/goldmane/component.go | 1 - pkg/render/goldmane/component_test.go | 8 +- pkg/render/guardian.go | 11 ++- pkg/render/intrusion_detection.go | 2 - pkg/render/intrusiondetection/dpi/dpi.go | 16 ++-- .../kubecontrollers/kube-controllers.go | 1 - pkg/render/logstorage.go | 15 ++-- .../logstorage/dashboards/dashboards.go | 1 - .../logstorage/dashboards/dashboards_test.go | 3 +- pkg/render/logstorage/eck/eck.go | 5 +- pkg/render/logstorage/esgateway/esgateway.go | 1 - .../esmetrics/elasticsearch_metrics.go | 1 - pkg/render/logstorage/linseed/linseed.go | 1 - pkg/render/logstorage/linseed/linseed_test.go | 1 - pkg/render/manager.go | 4 - pkg/render/monitor/monitor.go | 3 - pkg/render/node.go | 4 - pkg/render/packet_capture_api.go | 1 - pkg/render/packet_capture_api_test.go | 1 - pkg/render/policyrecommendation.go | 1 - pkg/render/render.go | 7 +- pkg/render/render_test.go | 6 -- pkg/render/typha.go | 1 - pkg/render/whisker/component.go | 12 ++- pkg/render/whisker/component_test.go | 13 ++-- 40 files changed, 187 insertions(+), 135 deletions(-) diff --git a/api/v1/installation_types.go b/api/v1/installation_types.go index b080d6a628..5c8ef6b222 100644 --- a/api/v1/installation_types.go +++ b/api/v1/installation_types.go @@ -97,6 +97,14 @@ type InstallationSpec struct { // +optional ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + // ImagePullPolicy is the pull policy applied to containers in pods rendered by the operator + // that do not explicitly set their own pull policy. If unset, defaults to IfNotPresent. + // This is useful in air-gapped environments where images are pre-loaded onto nodes and + // must not be re-pulled from a remote registry. + // +optional + // +kubebuilder:validation:Enum=Always;IfNotPresent;Never + ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"` + // KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. // If the specified value is empty, the Operator will attempt to automatically determine the current provider. // If the specified value is not empty, the Operator will still attempt auto-detection, but diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 271a0f0956..7a26d77a2f 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -5937,6 +5937,11 @@ func (in *InstallationSpec) DeepCopyInto(out *InstallationSpec) { *out = make([]corev1.LocalObjectReference, len(*in)) copy(*out, *in) } + if in.ImagePullPolicy != nil { + in, out := &in.ImagePullPolicy, &out.ImagePullPolicy + *out = new(corev1.PullPolicy) + **out = **in + } if in.CNI != nil { in, out := &in.CNI, &out.CNI *out = new(CNISpec) diff --git a/pkg/controller/utils/component.go b/pkg/controller/utils/component.go index 10ffc062c8..2ce3670667 100644 --- a/pkg/controller/utils/component.go +++ b/pkg/controller/utils/component.go @@ -42,6 +42,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" + operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apigroup" "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/controller/status" @@ -230,8 +231,27 @@ func (c *componentHandler) createOrUpdateObject(ctx context.Context, obj client. // system as specified by the osType. ensureOSSchedulingRestrictions(obj, osType) - // Make sure any objects with images also have an image pull policy. - modifyPodSpec(obj, setImagePullPolicy) + // Look up the InstallationSpec once and reuse it for the passes that need it + // (image pull policy and TLS ciphers), so we don't pay for the same Get twice. + // Objects without a pod spec (e.g. NetworkPolicy, Tier) don't need it. + var installationSpec *operatorv1.InstallationSpec + if hasPodSpec(obj) { + if _, spec, err := GetInstallationSpec(ctx, c.client); err == nil { + installationSpec = spec + } + } + + // Make sure any objects with images also have an image pull policy. If the user has + // configured a pull policy on the Installation, override every container's policy with + // it — the user's choice (e.g. for air-gapped clusters) must win over operator defaults. + // Otherwise, fill in IfNotPresent for any container that does not already set a policy. + var configuredPolicy *v1.PullPolicy + if installationSpec != nil { + configuredPolicy = installationSpec.ImagePullPolicy + } + modifyPodSpec(obj, func(podSpec *v1.PodSpec) { + setImagePullPolicy(podSpec, configuredPolicy) + }) // Order volumes and volume mounts modifyPodSpec(obj, orderVolumes) modifyPodSpec(obj, orderVolumeMounts) @@ -242,7 +262,7 @@ func (c *componentHandler) createOrUpdateObject(ctx context.Context, obj client. // Make sure we have our standard selector and pod labels setStandardSelectorAndLabels(obj, c.cr) - if err := ensureTLSCiphers(ctx, obj, c.client); err != nil { + if err := ensureTLSCiphers(obj, installationSpec); err != nil { return fmt.Errorf("failed to set TLS Ciphers: %w", err) } @@ -770,6 +790,16 @@ func mergeState(desired client.Object, current runtime.Object) client.Object { } } +// hasPodSpec reports whether the given object carries a pod spec that modifyPodSpec can reach. +func hasPodSpec(obj client.Object) bool { + switch obj.(type) { + case *v1.PodTemplate, *apps.Deployment, *apps.DaemonSet, *apps.StatefulSet, + *batchv1.CronJob, *batchv1.Job, *kbv1.Kibana, *esv1.Elasticsearch: + return true + } + return false +} + // modifyPodSpec is a helper for pulling out pod specifications from an arbitrary object. func modifyPodSpec(obj client.Object, f func(*v1.PodSpec)) { switch x := obj.(type) { @@ -796,17 +826,33 @@ func modifyPodSpec(obj client.Object, f func(*v1.PodSpec)) { } } -// setImagePullPolicy ensures that an image pull policy is set if not set already. -func setImagePullPolicy(podSpec *v1.PodSpec) { - for i := range podSpec.Containers { - if len(podSpec.Containers[i].ImagePullPolicy) == 0 { - podSpec.Containers[i].ImagePullPolicy = v1.PullIfNotPresent +// setImagePullPolicy applies an image pull policy to all containers and init containers in +// the given pod spec. If configuredPolicy is non-nil it is applied to every container, +// overriding any policy the renderer set — this is what lets a user force IfNotPresent or +// Never for air-gapped clusters. If configuredPolicy is nil, containers that do not already +// specify a policy fall back to IfNotPresent. +func setImagePullPolicy(podSpec *v1.PodSpec, configuredPolicy *v1.PullPolicy) { + apply := func(c *v1.Container) { + switch { + case configuredPolicy != nil: + c.ImagePullPolicy = *configuredPolicy + case c.ImagePullPolicy == "": + c.ImagePullPolicy = v1.PullIfNotPresent } } + for i := range podSpec.Containers { + apply(&podSpec.Containers[i]) + } + for i := range podSpec.InitContainers { + apply(&podSpec.InitContainers[i]) + } } // ensureTLSCiphers sets the TLSCipherSuites configuration as a Env Var to the Deployments and DaemonSets. -func ensureTLSCiphers(ctx context.Context, obj client.Object, c client.Client) error { +func ensureTLSCiphers(obj client.Object, installationSpec *operatorv1.InstallationSpec) error { + if installationSpec == nil { + return nil + } var containers []v1.Container switch obj := obj.(type) { case *apps.Deployment: @@ -817,15 +863,6 @@ func ensureTLSCiphers(ctx context.Context, obj client.Object, c client.Client) e return nil } - _, installationSpec, err := GetInstallationSpec(ctx, c) - if err != nil { - if errors.IsNotFound(err) { - return nil - } else { - return err - } - } - for i := range containers { exists := false for _, envVar := range containers[i].Env { diff --git a/pkg/controller/utils/component_test.go b/pkg/controller/utils/component_test.go index 6716fa9221..782f3fe2c6 100644 --- a/pkg/controller/utils/component_test.go +++ b/pkg/controller/utils/component_test.go @@ -35,6 +35,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -603,7 +604,7 @@ var _ = Describe("Component handler tests", func() { }, } Expect(c.Create(ctx, installation)).To(BeNil()) - Expect(ensureTLSCiphers(ctx, obj, c)).To(BeNil()) + Expect(ensureTLSCiphers(obj, &installation.Spec)).To(BeNil()) var containers []corev1.Container switch o := obj.(type) { @@ -693,7 +694,7 @@ var _ = Describe("Component handler tests", func() { ) }) DescribeTable("ensuring ImagePullPolicy is set", func(obj client.Object) { - modifyPodSpec(obj, setImagePullPolicy) + modifyPodSpec(obj, func(p *corev1.PodSpec) { setImagePullPolicy(p, nil) }) switch o := obj.(type) { case *apps.Deployment: @@ -742,6 +743,30 @@ var _ = Describe("Component handler tests", func() { ), ) + Describe("setImagePullPolicy", func() { + It("fills missing policies with IfNotPresent and leaves explicit policies alone when no policy is configured", func() { + ps := &corev1.PodSpec{ + Containers: []corev1.Container{{Image: "a"}, {Image: "b", ImagePullPolicy: corev1.PullAlways}}, + InitContainers: []corev1.Container{{Image: "init"}}, + } + setImagePullPolicy(ps, nil) + Expect(ps.Containers[0].ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) + Expect(ps.Containers[1].ImagePullPolicy).To(Equal(corev1.PullAlways), "should not override an explicitly set policy") + Expect(ps.InitContainers[0].ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) + }) + + It("overrides every container's policy when a policy is configured on the Installation", func() { + ps := &corev1.PodSpec{ + Containers: []corev1.Container{{Image: "a"}, {Image: "b", ImagePullPolicy: corev1.PullAlways}}, + InitContainers: []corev1.Container{{Image: "init", ImagePullPolicy: corev1.PullAlways}}, + } + setImagePullPolicy(ps, ptr.To(corev1.PullNever)) + Expect(ps.Containers[0].ImagePullPolicy).To(Equal(corev1.PullNever)) + Expect(ps.Containers[1].ImagePullPolicy).To(Equal(corev1.PullNever), "configured policy must win over renderer defaults") + Expect(ps.InitContainers[0].ImagePullPolicy).To(Equal(corev1.PullNever)) + }) + }) + DescribeTable("ensuring os node selectors", func(component render.Component, key client.ObjectKey, obj client.Object, expectedNodeSelectors map[string]string) { Expect(handler.CreateOrUpdateOrDelete(ctx, component, sm)).ShouldNot(HaveOccurred()) Expect(c.Get(ctx, key, obj)).ShouldNot(HaveOccurred()) diff --git a/pkg/controller/utils/merge.go b/pkg/controller/utils/merge.go index f729bbcbd9..606b5e34d5 100644 --- a/pkg/controller/utils/merge.go +++ b/pkg/controller/utils/merge.go @@ -18,6 +18,7 @@ import ( "reflect" v1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" operatorv1 "github.com/tigera/operator/api/v1" ) @@ -60,6 +61,11 @@ func OverrideInstallationSpec(cfg, override operatorv1.InstallationSpec) operato copy(inst.ImagePullSecrets, override.ImagePullSecrets) } + switch compareFields(inst.ImagePullPolicy, override.ImagePullPolicy) { + case BOnlySet, Different: + inst.ImagePullPolicy = ptr.To(*override.ImagePullPolicy) + } + switch compareFields(inst.KubernetesProvider, override.KubernetesProvider) { case BOnlySet, Different: inst.KubernetesProvider = override.KubernetesProvider diff --git a/pkg/controller/utils/merge_test.go b/pkg/controller/utils/merge_test.go index 8aefe13fb6..d02f3e26e6 100644 --- a/pkg/controller/utils/merge_test.go +++ b/pkg/controller/utils/merge_test.go @@ -26,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" opv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/test" @@ -115,6 +116,24 @@ var _ = Describe("Installation merge tests", func() { Entry("Both set not matching", []v1.LocalObjectReference{{Name: "pull-secret"}}, []v1.LocalObjectReference{{Name: "other-pull-secret"}}, []v1.LocalObjectReference{{Name: "other-pull-secret"}}), ) + DescribeTable("merge ImagePullPolicy", func(main, second, expect *v1.PullPolicy) { + m := opv1.InstallationSpec{ImagePullPolicy: main} + s := opv1.InstallationSpec{ImagePullPolicy: second} + inst := OverrideInstallationSpec(m, s) + if expect == nil { + Expect(inst.ImagePullPolicy).To(BeNil()) + } else { + Expect(inst.ImagePullPolicy).NotTo(BeNil()) + Expect(*inst.ImagePullPolicy).To(Equal(*expect)) + } + }, + Entry("Both unset", nil, nil, nil), + Entry("Main only set", ptr.To(v1.PullIfNotPresent), nil, ptr.To(v1.PullIfNotPresent)), + Entry("Second only set", nil, ptr.To(v1.PullAlways), ptr.To(v1.PullAlways)), + Entry("Both set equal", ptr.To(v1.PullIfNotPresent), ptr.To(v1.PullIfNotPresent), ptr.To(v1.PullIfNotPresent)), + Entry("Both set not matching", ptr.To(v1.PullAlways), ptr.To(v1.PullNever), ptr.To(v1.PullNever)), + ) + DescribeTable("merge KubernetesProvider", func(main, second, expect *opv1.Provider) { m := opv1.InstallationSpec{} s := opv1.InstallationSpec{} diff --git a/pkg/imports/crds/operator/operator.tigera.io_installations.yaml b/pkg/imports/crds/operator/operator.tigera.io_installations.yaml index 75f6bfedee..1e04c6eb26 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_installations.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_installations.yaml @@ -7184,6 +7184,17 @@ spec: `/:` This option allows configuring the `` portion of the above format. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the pull policy applied to containers in pods rendered by the operator + that do not explicitly set their own pull policy. If unset, defaults to IfNotPresent. + This is useful in air-gapped environments where images are pre-loaded onto nodes and + must not be re-pulled from a remote registry. + enum: + - Always + - IfNotPresent + - Never + type: string imagePullSecrets: description: |- ImagePullSecrets is an array of references to container registry pull secrets to use. These are @@ -16524,6 +16535,17 @@ spec: `/:` This option allows configuring the `` portion of the above format. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the pull policy applied to containers in pods rendered by the operator + that do not explicitly set their own pull policy. If unset, defaults to IfNotPresent. + This is useful in air-gapped environments where images are pre-loaded onto nodes and + must not be re-pulled from a remote registry. + enum: + - Always + - IfNotPresent + - Never + type: string imagePullSecrets: description: |- ImagePullSecrets is an array of references to container registry pull secrets to use. These are diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 5eb55c14a1..37e7e6fe55 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -1179,7 +1179,6 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { Name: string(APIServerContainerName), Image: c.apiServerImage, Command: []string{components.CalicoBinaryPath, "component", "apiserver"}, - ImagePullPolicy: ImagePullPolicy(), Args: c.startUpArgs(), Env: env, VolumeMounts: volumeMounts, @@ -1319,7 +1318,6 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { container := corev1.Container{ Name: string(TigeraAPIServerQueryServerContainerName), Image: c.queryServerImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "queryserver"}, Env: env, LivenessProbe: &corev1.Probe{ @@ -2307,7 +2305,6 @@ func (c *apiServerComponent) l7AdmissionControllerContainer() corev1.Container { l7AdmssCtrl := corev1.Container{ Name: string(L7AdmissionControllerContainerName), Image: c.l7AdmissionControllerImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "l7-admission-controller"}, Env: []corev1.EnvVar{ { diff --git a/pkg/render/applicationlayer/applicationlayer.go b/pkg/render/applicationlayer/applicationlayer.go index 99ffe7eeb5..f06dca8685 100644 --- a/pkg/render/applicationlayer/applicationlayer.go +++ b/pkg/render/applicationlayer/applicationlayer.go @@ -273,7 +273,6 @@ func (c *component) containers() []corev1.Container { proxy := corev1.Container{ Name: ProxyContainerName, Image: c.config.proxyImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{ "envoy", "-c", "/etc/envoy/envoy-config.yaml", }, @@ -290,7 +289,6 @@ func (c *component) containers() []corev1.Container { collector := corev1.Container{ Name: L7CollectorContainerName, Image: c.config.collectorImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "l7-collector"}, Env: c.collectorEnv(), SecurityContext: securitycontext.NewRootContext(false), @@ -351,7 +349,6 @@ func (c *component) containers() []corev1.Container { dikastes := corev1.Container{ Name: DikastesContainerName, Image: c.config.dikastesImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: commandArgs, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "Info"}, diff --git a/pkg/render/aws-securitygroup-setup.go b/pkg/render/aws-securitygroup-setup.go index 451dc61d97..c177728e9b 100644 --- a/pkg/render/aws-securitygroup-setup.go +++ b/pkg/render/aws-securitygroup-setup.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -117,7 +117,6 @@ func (c *awsSGSetupComponent) setupJob() *batchv1.Job { Containers: []corev1.Container{{ Name: "aws-security-group-setup", Image: c.image, - ImagePullPolicy: ImagePullPolicy(), Args: []string{"--aws-sg-setup"}, Env: envVars, SecurityContext: securitycontext.NewNonRootContext(), diff --git a/pkg/render/compliance.go b/pkg/render/compliance.go index b1e7a8ba8c..e4c8cd1677 100644 --- a/pkg/render/compliance.go +++ b/pkg/render/compliance.go @@ -485,7 +485,6 @@ func (c *complianceComponent) complianceControllerDeployment() *appsv1.Deploymen { Name: ComplianceControllerName, Image: c.controllerImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "compliance-controller"}, Env: envVars, LivenessProbe: &corev1.Probe{ @@ -686,7 +685,6 @@ func (c *complianceComponent) complianceReporterPodTemplate() *corev1.PodTemplat { Name: "reporter", Image: c.reporterImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "compliance-reporter"}, Env: envVars, LivenessProbe: &corev1.Probe{ @@ -896,7 +894,6 @@ func (c *complianceComponent) complianceServerDeployment() *appsv1.Deployment { Name: ComplianceServerName, Image: c.serverImage, Command: []string{components.CalicoBinaryPath, "component", "compliance-server"}, - ImagePullPolicy: ImagePullPolicy(), Env: envVars, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -1109,7 +1106,6 @@ func (c *complianceComponent) complianceSnapshotterDeployment() *appsv1.Deployme { Name: ComplianceSnapshotterName, Image: c.snapshotterImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "compliance-snapshotter"}, Env: envVars, LivenessProbe: &corev1.Probe{ @@ -1316,7 +1312,6 @@ func (c *complianceComponent) complianceBenchmarkerDaemonSet() *appsv1.DaemonSet { Name: ComplianceBenchmarkerName, Image: c.benchmarkerImage, - ImagePullPolicy: ImagePullPolicy(), Env: envVars, SecurityContext: sc, VolumeMounts: volMounts, diff --git a/pkg/render/csi.go b/pkg/render/csi.go index d34faf2c68..0fc7a81f04 100644 --- a/pkg/render/csi.go +++ b/pkg/render/csi.go @@ -139,10 +139,9 @@ func (c *csiComponent) csiAffinities() *corev1.Affinity { func (c *csiComponent) csiContainers() []corev1.Container { mountPropagation := corev1.MountPropagationBidirectional csiContainer := corev1.Container{ - Name: CSIContainerName, - Image: c.csiImage, - Command: []string{components.CalicoBinaryPath, "component", "csi"}, - ImagePullPolicy: ImagePullPolicy(), + Name: CSIContainerName, + Image: c.csiImage, + Command: []string{components.CalicoBinaryPath, "component", "csi"}, Args: []string{ "--nodeid=$(KUBE_NODE_NAME)", "--loglevel=$(LOG_LEVEL)", @@ -181,10 +180,9 @@ func (c *csiComponent) csiContainers() []corev1.Container { // Construct "csi-node-driver-registrar" container registrarContainer := corev1.Container{ - Name: CSIRegistrarContainerName, - Image: c.csiRegistrarImage, - Command: []string{"/usr/bin/csi-node-driver-registrar"}, - ImagePullPolicy: ImagePullPolicy(), + Name: CSIRegistrarContainerName, + Image: c.csiRegistrarImage, + Command: []string{"/usr/bin/csi-node-driver-registrar"}, Args: []string{ "--v=5", "--csi-address=$(ADDRESS)", diff --git a/pkg/render/dex.go b/pkg/render/dex.go index 1dcff4dd69..b9a0675b40 100644 --- a/pkg/render/dex.go +++ b/pkg/render/dex.go @@ -321,7 +321,6 @@ func (c *dexComponent) deployment() client.Object { { Name: DexObjectName, Image: c.image, - ImagePullPolicy: ImagePullPolicy(), Env: envVars, LivenessProbe: c.probe(), SecurityContext: sc, diff --git a/pkg/render/egressgateway/egressgateway.go b/pkg/render/egressgateway/egressgateway.go index 8af4b95d35..84c675e75c 100644 --- a/pkg/render/egressgateway/egressgateway.go +++ b/pkg/render/egressgateway/egressgateway.go @@ -201,7 +201,6 @@ func (c *component) egwInitContainer() *corev1.Container { return &corev1.Container{ Name: "egress-gateway-init", Image: c.config.egwImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{"/init-gateway.sh"}, SecurityContext: securitycontext.NewRootContext(true), Env: c.egwInitEnvVars(), @@ -215,7 +214,6 @@ func (c *component) egwContainer() *corev1.Container { return &corev1.Container{ Name: "egress-gateway", Image: c.config.egwImage, - ImagePullPolicy: render.ImagePullPolicy(), Env: c.egwEnvVars(), VolumeMounts: c.egwVolumeMounts(), Ports: c.egwPorts(), diff --git a/pkg/render/fluentd.go b/pkg/render/fluentd.go index 790d832a01..9673bcc1e6 100644 --- a/pkg/render/fluentd.go +++ b/pkg/render/fluentd.go @@ -639,10 +639,9 @@ func (c *fluentdComponent) container() corev1.Container { } return corev1.Container{ - Name: "fluentd", - Image: c.image, - ImagePullPolicy: ImagePullPolicy(), - Env: envs, + Name: "fluentd", + Image: c.image, + Env: envs, // On OpenShift Fluentd needs privileged access to access logs on host path volume SecurityContext: c.securityContext(c.cfg.Installation.KubernetesProvider.IsOpenShift()), VolumeMounts: volumeMounts, @@ -1123,7 +1122,6 @@ func (c *fluentdComponent) eksLogForwarderDeployment() *appsv1.Deployment { InitContainers: []corev1.Container{{ Name: EKSLogForwarderName + "-startup", Image: c.image, - ImagePullPolicy: ImagePullPolicy(), Command: []string{c.path("/bin/eks-log-forwarder-startup")}, Env: envVars, SecurityContext: c.securityContext(false), @@ -1132,7 +1130,6 @@ func (c *fluentdComponent) eksLogForwarderDeployment() *appsv1.Deployment { Containers: []corev1.Container{{ Name: EKSLogForwarderName, Image: c.image, - ImagePullPolicy: ImagePullPolicy(), Env: envVars, SecurityContext: c.securityContext(false), VolumeMounts: c.eksLogForwarderVolumeMounts(), diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 3290f7b1f2..d6ee4c98bf 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -235,7 +235,6 @@ func (c *Component) goldmaneContainer() corev1.Container { return corev1.Container{ Name: GoldmaneContainerName, Image: c.goldmaneImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "goldmane"}, Env: env, SecurityContext: securitycontext.NewNonRootContext(), diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index bbc55f20c3..6d3f33e845 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -30,7 +30,6 @@ import ( v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" - "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/securitycontext" rtest "github.com/tigera/operator/pkg/render/common/test" @@ -140,10 +139,9 @@ var _ = Describe("ComponentRendering", func() { Tolerations: append(rmeta.TolerateCriticalAddonsAndControlPlane, rmeta.TolerateGKEARM64NoSchedule), Containers: []corev1.Container{ { - Name: goldmane.GoldmaneContainerName, - Image: "quay.io/calico/calico:master", - Command: []string{"/usr/bin/calico", "component", "goldmane"}, - ImagePullPolicy: render.ImagePullPolicy(), + Name: goldmane.GoldmaneContainerName, + Image: "quay.io/calico/calico:master", + Command: []string{"/usr/bin/calico", "component", "goldmane"}, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, {Name: "PORT", Value: "7443"}, diff --git a/pkg/render/guardian.go b/pkg/render/guardian.go index b4f247b7e9..52ee44ed01 100644 --- a/pkg/render/guardian.go +++ b/pkg/render/guardian.go @@ -495,12 +495,11 @@ func (c *GuardianComponent) container() []corev1.Container { return []corev1.Container{ { - Name: GuardianContainerName, - Image: c.image, - Command: []string{components.CalicoBinaryPath, "component", "guardian"}, - ImagePullPolicy: ImagePullPolicy(), - Env: envVars, - VolumeMounts: c.volumeMounts(), + Name: GuardianContainerName, + Image: c.image, + Command: []string{components.CalicoBinaryPath, "component", "guardian"}, + Env: envVars, + VolumeMounts: c.volumeMounts(), LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/render/intrusion_detection.go b/pkg/render/intrusion_detection.go index 8577ea205a..dac03e19ec 100644 --- a/pkg/render/intrusion_detection.go +++ b/pkg/render/intrusion_detection.go @@ -687,7 +687,6 @@ func (c *intrusionDetectionComponent) webhooksControllerContainer() corev1.Conta return corev1.Container{ Name: "webhooks-processor", Image: c.webhooksProcessorImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "webhooks-processor"}, Env: envVars, SecurityContext: securitycontext.NewNonRootContext(), @@ -769,7 +768,6 @@ func (c *intrusionDetectionComponent) intrusionDetectionControllerContainer() co return corev1.Container{ Name: "controller", Image: c.controllerImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "intrusion-detection-controller"}, Env: envs, LivenessProbe: &corev1.Probe{ diff --git a/pkg/render/intrusiondetection/dpi/dpi.go b/pkg/render/intrusiondetection/dpi/dpi.go index 33952c01e7..ef2611e69a 100644 --- a/pkg/render/intrusiondetection/dpi/dpi.go +++ b/pkg/render/intrusiondetection/dpi/dpi.go @@ -190,9 +190,8 @@ func (d *dpiComponent) dpiDaemonset() *appsv1.DaemonSet { if d.dpiInitContainers() { for _, initContainer := range d.cfg.IntrusionDetection.Spec.DeepPacketInspectionDaemonset.Spec.Template.Spec.InitContainers { container := corev1.Container{ - Name: initContainer.Name, - Image: initContainer.Image, - ImagePullPolicy: render.ImagePullPolicy(), + Name: initContainer.Name, + Image: initContainer.Image, VolumeMounts: []corev1.VolumeMount{ { Name: DeepPacketInspectionSnortRulesVolumeName, @@ -243,12 +242,11 @@ func (d *dpiComponent) dpiContainer() corev1.Container { "NET_RAW", } dpiContainer := corev1.Container{ - Name: DeepPacketInspectionName, - Image: d.dpiImage, - ImagePullPolicy: render.ImagePullPolicy(), - Resources: *d.cfg.IntrusionDetection.Spec.ComponentResources[0].ResourceRequirements, - Env: d.dpiEnvVars(), - VolumeMounts: d.dpiVolumeMounts(), + Name: DeepPacketInspectionName, + Image: d.dpiImage, + Resources: *d.cfg.IntrusionDetection.Spec.ComponentResources[0].ResourceRequirements, + Env: d.dpiEnvVars(), + VolumeMounts: d.dpiVolumeMounts(), // On OpenShift Snort needs privileged access to access host network SecurityContext: sc, ReadinessProbe: d.dpiReadinessProbes(), diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index af72b72c1e..e6596b70b9 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -615,7 +615,6 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { Name: c.kubeControllerName, Image: c.image, Command: containerCommand, - ImagePullPolicy: render.ImagePullPolicy(), Env: env, Resources: c.kubeControllersResources(), ReadinessProbe: readinessProbe, diff --git a/pkg/render/logstorage.go b/pkg/render/logstorage.go index 1c33d07297..e845a25b2c 100644 --- a/pkg/render/logstorage.go +++ b/pkg/render/logstorage.go @@ -402,9 +402,8 @@ func (es *elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { // https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html initOSSettingsContainer := corev1.Container{ - Name: "elastic-internal-init-os-settings", - Image: es.esImage, - ImagePullPolicy: ImagePullPolicy(), + Name: "elastic-internal-init-os-settings", + Image: es.esImage, Command: []string{ "/bin/sh", }, @@ -417,9 +416,8 @@ func (es *elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { initContainers := []corev1.Container{initOSSettingsContainer} initFSContainer := corev1.Container{ - Name: "elastic-internal-init-filesystem", - Image: es.esImage, - ImagePullPolicy: ImagePullPolicy(), + Name: "elastic-internal-init-filesystem", + Image: es.esImage, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ "cpu": resource.MustParse("100m"), @@ -435,9 +433,8 @@ func (es *elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { } suspendContainer := corev1.Container{ - Name: "elastic-internal-suspend", - Image: es.esImage, - ImagePullPolicy: ImagePullPolicy(), + Name: "elastic-internal-suspend", + Image: es.esImage, // Without a root context, it is not able to start. SecurityContext: securitycontext.NewRootContext(true), } diff --git a/pkg/render/logstorage/dashboards/dashboards.go b/pkg/render/logstorage/dashboards/dashboards.go index 3c7075bda9..40304d9315 100644 --- a/pkg/render/logstorage/dashboards/dashboards.go +++ b/pkg/render/logstorage/dashboards/dashboards.go @@ -283,7 +283,6 @@ func (d *dashboards) Job() *batchv1.Job { { Name: Name, Image: d.image, - ImagePullPolicy: render.ImagePullPolicy(), Env: envVars, SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: volumeMounts, diff --git a/pkg/render/logstorage/dashboards/dashboards_test.go b/pkg/render/logstorage/dashboards/dashboards_test.go index 0d4a365979..8a0b9a5037 100644 --- a/pkg/render/logstorage/dashboards/dashboards_test.go +++ b/pkg/render/logstorage/dashboards/dashboards_test.go @@ -609,8 +609,7 @@ func expectedVolumes() []corev1.Volume { func expectedContainers() []corev1.Container { return []corev1.Container{ { - Name: Name, - ImagePullPolicy: render.ImagePullPolicy(), + Name: Name, SecurityContext: &corev1.SecurityContext{ Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, AllowPrivilegeEscalation: ptr.To(false), diff --git a/pkg/render/logstorage/eck/eck.go b/pkg/render/logstorage/eck/eck.go index 25e733959a..475fe971b9 100644 --- a/pkg/render/logstorage/eck/eck.go +++ b/pkg/render/logstorage/eck/eck.go @@ -363,9 +363,8 @@ func (e *eck) operatorStatefulSet() *appsv1.StatefulSet { NodeSelector: e.cfg.Installation.ControlPlaneNodeSelector, Tolerations: tolerations, Containers: []corev1.Container{{ - Image: e.esOperatorImage, - ImagePullPolicy: render.ImagePullPolicy(), - Name: "manager", + Image: e.esOperatorImage, + Name: "manager", // Verbosity level of logs. -2=Error, -1=Warn, 0=Info, 0 and above=Debug Args: []string{ "manager", diff --git a/pkg/render/logstorage/esgateway/esgateway.go b/pkg/render/logstorage/esgateway/esgateway.go index ec217ae11e..fbdcd53934 100644 --- a/pkg/render/logstorage/esgateway/esgateway.go +++ b/pkg/render/logstorage/esgateway/esgateway.go @@ -253,7 +253,6 @@ func (e *esGateway) esGatewayDeployment() *appsv1.Deployment { { Name: DeploymentName, Image: e.esGatewayImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "es-gateway"}, Env: envVars, VolumeMounts: volumeMounts, diff --git a/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go b/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go index 97e09f71ba..2db85cc9db 100644 --- a/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go +++ b/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go @@ -235,7 +235,6 @@ func (e *elasticsearchMetrics) metricsDeployment() *appsv1.Deployment { { Name: ElasticsearchMetricsName, Image: e.esMetricsImage, - ImagePullPolicy: render.ImagePullPolicy(), SecurityContext: sc, Command: []string{components.CalicoBinaryPath, "component", "elasticsearch-metrics"}, Args: []string{ diff --git a/pkg/render/logstorage/linseed/linseed.go b/pkg/render/logstorage/linseed/linseed.go index 9ab6cae0ef..e1e82c3b84 100644 --- a/pkg/render/logstorage/linseed/linseed.go +++ b/pkg/render/logstorage/linseed/linseed.go @@ -460,7 +460,6 @@ func (l *linseed) linseedDeployment() *appsv1.Deployment { { Name: DeploymentName, Image: l.linseedImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "linseed"}, Env: envVars, VolumeMounts: volumeMounts, diff --git a/pkg/render/logstorage/linseed/linseed_test.go b/pkg/render/logstorage/linseed/linseed_test.go index bae479e483..4050449306 100644 --- a/pkg/render/logstorage/linseed/linseed_test.go +++ b/pkg/render/logstorage/linseed/linseed_test.go @@ -1030,7 +1030,6 @@ func expectedContainers() []corev1.Container { return []corev1.Container{ { Name: DeploymentName, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "linseed"}, SecurityContext: &corev1.SecurityContext{ Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, diff --git a/pkg/render/manager.go b/pkg/render/manager.go index dcaf37e87e..189d14ca73 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -508,7 +508,6 @@ func (c *managerComponent) managerContainer() corev1.Container { return corev1.Container{ Name: ManagerName, Image: c.managerImage, - ImagePullPolicy: ImagePullPolicy(), Env: c.managerEnvVars(), LivenessProbe: c.managerProbe(), SecurityContext: securitycontext.NewNonRootContext(), @@ -669,7 +668,6 @@ func (c *managerComponent) voltronContainer() corev1.Container { return corev1.Container{ Name: VoltronName, Image: c.voltronImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "voltron"}, Env: env, VolumeMounts: mounts, @@ -704,7 +702,6 @@ func (c *managerComponent) dashboardContainer() corev1.Container { return corev1.Container{ Name: DashboardAPIName, Image: c.uiAPIsImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "dashboards"}, Env: env, VolumeMounts: mounts, @@ -795,7 +792,6 @@ func (c *managerComponent) managerUIAPIsContainer() corev1.Container { return corev1.Container{ Name: UIAPIsName, Image: c.uiAPIsImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "ui-apis"}, LivenessProbe: c.managerUIAPIsProbe(), SecurityContext: securitycontext.NewNonRootContext(), diff --git a/pkg/render/monitor/monitor.go b/pkg/render/monitor/monitor.go index f966046b0f..9b502b461e 100644 --- a/pkg/render/monitor/monitor.go +++ b/pkg/render/monitor/monitor.go @@ -520,7 +520,6 @@ func (mc *monitorComponent) alertmanager() *monitoringv1.Alertmanager { }, Spec: monitoringv1.AlertmanagerSpec{ Image: &mc.alertmanagerImage, - ImagePullPolicy: render.ImagePullPolicy(), ImagePullSecrets: secret.GetReferenceList(mc.cfg.PullSecrets), NodeSelector: mc.cfg.Installation.ControlPlaneNodeSelector, Replicas: mc.cfg.Monitor.Alertmanager.AlertmanagerSpec.Replicas, @@ -641,7 +640,6 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { { Name: "authn-proxy", Image: mc.prometheusServiceImage, - ImagePullPolicy: render.ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "prometheus-service"}, Ports: []corev1.ContainerPort{ { @@ -672,7 +670,6 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { }, }, Image: &mc.prometheusImage, - ImagePullPolicy: render.ImagePullPolicy(), ImagePullSecrets: secret.GetReferenceList(mc.cfg.PullSecrets), InitContainers: initContainers, // ListenLocal makes the Prometheus server listen on loopback, so that it diff --git a/pkg/render/node.go b/pkg/render/node.go index e6c088cb6c..91aa1ef3ba 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -1212,7 +1212,6 @@ func (c *nodeComponent) cniContainer() corev1.Container { return corev1.Container{ Name: "install-cni", Image: c.cniImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "cni", "install"}, Env: cniEnv, SecurityContext: securitycontext.NewRootContext(true), @@ -1231,7 +1230,6 @@ func (c *nodeComponent) flexVolumeContainer() corev1.Container { Name: "flexvol-driver", Image: c.flexvolImage, Command: []string{components.CalicoBinaryPath, "component", "flexvol", "install", "--target", "/host/driver/uds"}, - ImagePullPolicy: ImagePullPolicy(), SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: flexVolumeMounts, } @@ -1272,7 +1270,6 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { return corev1.Container{ Name: "ebpf-bootstrap", Image: c.nodeImage, - ImagePullPolicy: ImagePullPolicy(), Env: c.bpffsEnvvars(), Command: command, SecurityContext: securitycontext.NewRootContext(true), @@ -1355,7 +1352,6 @@ func (c *nodeComponent) nodeContainer() corev1.Container { return corev1.Container{ Name: CalicoNodeObjectName, Image: c.nodeImage, - ImagePullPolicy: ImagePullPolicy(), Resources: c.nodeResources(), SecurityContext: sc, Env: c.nodeEnvVars(), diff --git a/pkg/render/packet_capture_api.go b/pkg/render/packet_capture_api.go index 8015fc443c..0e43bd1dbc 100644 --- a/pkg/render/packet_capture_api.go +++ b/pkg/render/packet_capture_api.go @@ -304,7 +304,6 @@ func (pc *packetCaptureApiComponent) container() corev1.Container { return corev1.Container{ Name: PacketCaptureContainerName, Image: pc.image, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "packetcapture"}, LivenessProbe: pc.healthProbe(), ReadinessProbe: pc.healthProbe(), diff --git a/pkg/render/packet_capture_api_test.go b/pkg/render/packet_capture_api_test.go index f0cea6e85a..47458b0864 100644 --- a/pkg/render/packet_capture_api_test.go +++ b/pkg/render/packet_capture_api_test.go @@ -194,7 +194,6 @@ var _ = Describe("Rendering tests for PacketCapture API component", func() { Name: render.PacketCaptureContainerName, Image: fmt.Sprintf("%s%s%s:%s", components.TigeraRegistry, components.TigeraImagePath, components.ComponentTigeraCalico.Image, components.ComponentTigeraCalico.Version), Command: []string{components.CalicoBinaryPath, "component", "packetcapture"}, - ImagePullPolicy: render.ImagePullPolicy(), SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: ptr.To(false), Capabilities: &corev1.Capabilities{ diff --git a/pkg/render/policyrecommendation.go b/pkg/render/policyrecommendation.go index 9526d9d2a9..7d2a9afdb9 100644 --- a/pkg/render/policyrecommendation.go +++ b/pkg/render/policyrecommendation.go @@ -360,7 +360,6 @@ func (pr *policyRecommendationComponent) deployment() *appsv1.Deployment { controllerContainer := corev1.Container{ Name: "policy-recommendation-controller", Image: pr.image, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "policy-recommendation"}, Env: envs, SecurityContext: securitycontext.NewNonRootContext(), diff --git a/pkg/render/render.go b/pkg/render/render.go index 60993124c2..28fe4537c2 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -44,8 +44,3 @@ func setNodeCriticalPod(t *corev1.PodTemplateSpec) { func SetClusterCriticalPod(t *corev1.PodTemplateSpec) { t.Spec.PriorityClassName = ClusterPriorityClassName } - -// ImagePullPolicy returns the image pull policy to use for all components. -func ImagePullPolicy() corev1.PullPolicy { - return corev1.PullIfNotPresent -} diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go index 28f320109a..2b97cfaeb9 100644 --- a/pkg/render/render_test.go +++ b/pkg/render/render_test.go @@ -208,12 +208,6 @@ var _ = Describe("Rendering tests", func() { } }) - It("should render IfNotPresent image pull policy", func() { - // This test ensures we don't accidentally commit a change that switches the - // default image pull policy to Always as part of development. - Expect(render.ImagePullPolicy()).To(Equal(corev1.PullIfNotPresent)) - }) - It("should render all resources for a default configuration", func() { // For this scenario, we expect the basic resources // created by the controller without any optional ones. These include: diff --git a/pkg/render/typha.go b/pkg/render/typha.go index 19350ab571..c83f2d3544 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -561,7 +561,6 @@ func (c *typhaComponent) typhaContainer() corev1.Container { return corev1.Container{ Name: TyphaContainerName, Image: c.typhaImage, - ImagePullPolicy: ImagePullPolicy(), Command: []string{components.CalicoBinaryPath, "component", "typha"}, Resources: c.typhaResources(), Env: c.typhaEnvVars(c.cfg.TLS.TyphaSecret), diff --git a/pkg/render/whisker/component.go b/pkg/render/whisker/component.go index d87a8f052d..2642021f6b 100644 --- a/pkg/render/whisker/component.go +++ b/pkg/render/whisker/component.go @@ -157,9 +157,8 @@ func (c *Component) serviceAccount() *corev1.ServiceAccount { func (c *Component) whiskerContainer() corev1.Container { return corev1.Container{ - Name: WhiskerContainerName, - Image: c.whiskerImage, - ImagePullPolicy: render.ImagePullPolicy(), + Name: WhiskerContainerName, + Image: c.whiskerImage, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, {Name: "CALICO_VERSION", Value: c.cfg.CalicoVersion}, @@ -195,10 +194,9 @@ func (c *Component) whiskerService() *corev1.Service { func (c *Component) whiskerBackendContainer() corev1.Container { return corev1.Container{ - Name: WhiskerBackendContainerName, - Image: c.whiskerBackendImage, - ImagePullPolicy: render.ImagePullPolicy(), - Command: []string{components.CalicoBinaryPath, "component", "whisker-backend"}, + Name: WhiskerBackendContainerName, + Image: c.whiskerBackendImage, + Command: []string{components.CalicoBinaryPath, "component", "whisker-backend"}, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, {Name: "PORT", Value: "3002"}, diff --git a/pkg/render/whisker/component_test.go b/pkg/render/whisker/component_test.go index 9ceda16a98..3fae01728c 100644 --- a/pkg/render/whisker/component_test.go +++ b/pkg/render/whisker/component_test.go @@ -27,7 +27,6 @@ import ( "k8s.io/utils/ptr" operatorv1 "github.com/tigera/operator/api/v1" - "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/securitycontext" rtest "github.com/tigera/operator/pkg/render/common/test" @@ -118,9 +117,8 @@ var _ = Describe("ComponentRendering", func() { Tolerations: append(rmeta.TolerateCriticalAddonsAndControlPlane, rmeta.TolerateGKEARM64NoSchedule), Containers: []corev1.Container{ { - Name: whisker.WhiskerContainerName, - Image: "quay.io/calico/whisker:master", - ImagePullPolicy: render.ImagePullPolicy(), + Name: whisker.WhiskerContainerName, + Image: "quay.io/calico/whisker:master", Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, {Name: "CALICO_VERSION", Value: "test-calico-version"}, @@ -138,10 +136,9 @@ var _ = Describe("ComponentRendering", func() { }, }, { - Name: whisker.WhiskerBackendContainerName, - Image: "quay.io/calico/calico:master", - Command: []string{"/usr/bin/calico", "component", "whisker-backend"}, - ImagePullPolicy: render.ImagePullPolicy(), + Name: whisker.WhiskerBackendContainerName, + Image: "quay.io/calico/calico:master", + Command: []string{"/usr/bin/calico", "component", "whisker-backend"}, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, {Name: "PORT", Value: "3002"}, From 82ccb8adbf8d7a0c33994e6f5459b2eadf247ee8 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Wed, 13 May 2026 11:01:55 -0700 Subject: [PATCH 2/3] Address review feedback: drop hasPodSpec and simplify comment --- pkg/controller/utils/component.go | 22 +++------------------- pkg/controller/utils/component_test.go | 26 ++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/pkg/controller/utils/component.go b/pkg/controller/utils/component.go index 2ce3670667..b6679a2647 100644 --- a/pkg/controller/utils/component.go +++ b/pkg/controller/utils/component.go @@ -233,18 +233,12 @@ func (c *componentHandler) createOrUpdateObject(ctx context.Context, obj client. // Look up the InstallationSpec once and reuse it for the passes that need it // (image pull policy and TLS ciphers), so we don't pay for the same Get twice. - // Objects without a pod spec (e.g. NetworkPolicy, Tier) don't need it. var installationSpec *operatorv1.InstallationSpec - if hasPodSpec(obj) { - if _, spec, err := GetInstallationSpec(ctx, c.client); err == nil { - installationSpec = spec - } + if _, spec, err := GetInstallationSpec(ctx, c.client); err == nil { + installationSpec = spec } - // Make sure any objects with images also have an image pull policy. If the user has - // configured a pull policy on the Installation, override every container's policy with - // it — the user's choice (e.g. for air-gapped clusters) must win over operator defaults. - // Otherwise, fill in IfNotPresent for any container that does not already set a policy. + // Set image pull policy based on user input, if specified. var configuredPolicy *v1.PullPolicy if installationSpec != nil { configuredPolicy = installationSpec.ImagePullPolicy @@ -790,16 +784,6 @@ func mergeState(desired client.Object, current runtime.Object) client.Object { } } -// hasPodSpec reports whether the given object carries a pod spec that modifyPodSpec can reach. -func hasPodSpec(obj client.Object) bool { - switch obj.(type) { - case *v1.PodTemplate, *apps.Deployment, *apps.DaemonSet, *apps.StatefulSet, - *batchv1.CronJob, *batchv1.Job, *kbv1.Kibana, *esv1.Elasticsearch: - return true - } - return false -} - // modifyPodSpec is a helper for pulling out pod specifications from an arbitrary object. func modifyPodSpec(obj client.Object, f func(*v1.PodSpec)) { switch x := obj.(type) { diff --git a/pkg/controller/utils/component_test.go b/pkg/controller/utils/component_test.go index 782f3fe2c6..d2e18c8482 100644 --- a/pkg/controller/utils/component_test.go +++ b/pkg/controller/utils/component_test.go @@ -2215,7 +2215,15 @@ var _ = Describe("Mocked client Component handler tests", func() { objs: []client.Object{baseNP}, } + // Two Get calls are issued up-front to load the InstallationSpec + // (one for the Installation, one for the overlay). + installationGets := func() { + mc.Info = append(mc.Info, mockReturn{Method: "Get", Return: nil}) + mc.Info = append(mc.Info, mockReturn{Method: "Get", Return: nil}) + } + It("NetworkPolicy updates are omitted if there is no change", func() { + installationGets() mc.Info = append(mc.Info, mockReturn{ Method: "Get", Return: nil, @@ -2224,7 +2232,7 @@ var _ = Describe("Mocked client Component handler tests", func() { err := handler.CreateOrUpdateOrDelete(ctx, fc, nil) Expect(err).To(BeNil()) - Expect(mc.Index).To(Equal(1)) + Expect(mc.Index).To(Equal(3)) }) It("NetworkPolicy updates are applied if there is a change", func() { @@ -2236,6 +2244,7 @@ var _ = Describe("Mocked client Component handler tests", func() { } } + installationGets() mc.Info = append(mc.Info, mockReturn{ Method: "Get", Return: nil, @@ -2250,7 +2259,7 @@ var _ = Describe("Mocked client Component handler tests", func() { err := handler.CreateOrUpdateOrDelete(ctx, fc, nil) Expect(err).To(BeNil()) - Expect(mc.Index).To(Equal(2)) + Expect(mc.Index).To(Equal(4)) }) }) @@ -2271,7 +2280,15 @@ var _ = Describe("Mocked client Component handler tests", func() { objs: []client.Object{baseTier}, } + // Two Get calls are issued up-front to load the InstallationSpec + // (one for the Installation, one for the overlay). + installationGets := func() { + mc.Info = append(mc.Info, mockReturn{Method: "Get", Return: nil}) + mc.Info = append(mc.Info, mockReturn{Method: "Get", Return: nil}) + } + It("Tier updates are omitted if there is no change", func() { + installationGets() mc.Info = append(mc.Info, mockReturn{ Method: "Get", Return: nil, @@ -2280,7 +2297,7 @@ var _ = Describe("Mocked client Component handler tests", func() { err := handler.CreateOrUpdateOrDelete(ctx, fc, nil) Expect(err).To(BeNil()) - Expect(mc.Index).To(Equal(1)) + Expect(mc.Index).To(Equal(3)) }) It("Tier updates are applied if there is a change", func() { @@ -2293,6 +2310,7 @@ var _ = Describe("Mocked client Component handler tests", func() { } } + installationGets() mc.Info = append(mc.Info, mockReturn{ Method: "Get", Return: nil, @@ -2307,7 +2325,7 @@ var _ = Describe("Mocked client Component handler tests", func() { err := handler.CreateOrUpdateOrDelete(ctx, fc, nil) Expect(err).To(BeNil()) - Expect(mc.Index).To(Equal(2)) + Expect(mc.Index).To(Equal(4)) }) }) }) From 05361aa10b34ba9e86b0d45562c496185e982cb2 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Wed, 13 May 2026 11:06:33 -0700 Subject: [PATCH 3/3] gofmt --- pkg/render/apiserver.go | 26 +++++++-------- .../applicationlayer/applicationlayer.go | 10 +++--- pkg/render/compliance.go | 32 +++++++++---------- pkg/render/intrusion_detection.go | 8 ++--- pkg/render/logstorage/esgateway/esgateway.go | 10 +++--- pkg/render/logstorage/linseed/linseed_test.go | 4 +-- pkg/render/monitor/monitor.go | 6 ++-- pkg/render/packet_capture_api_test.go | 6 ++-- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 37e7e6fe55..143497f034 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -1176,12 +1176,12 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { apiServerTargetPort := getContainerPort(c.cfg, APIServerContainerName).ContainerPort apiServer := corev1.Container{ - Name: string(APIServerContainerName), - Image: c.apiServerImage, - Command: []string{components.CalicoBinaryPath, "component", "apiserver"}, - Args: c.startUpArgs(), - Env: env, - VolumeMounts: volumeMounts, + Name: string(APIServerContainerName), + Image: c.apiServerImage, + Command: []string{components.CalicoBinaryPath, "component", "apiserver"}, + Args: c.startUpArgs(), + Env: env, + VolumeMounts: volumeMounts, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -1316,10 +1316,10 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { } container := corev1.Container{ - Name: string(TigeraAPIServerQueryServerContainerName), - Image: c.queryServerImage, - Command: []string{components.CalicoBinaryPath, "component", "queryserver"}, - Env: env, + Name: string(TigeraAPIServerQueryServerContainerName), + Image: c.queryServerImage, + Command: []string{components.CalicoBinaryPath, "component", "queryserver"}, + Env: env, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -2303,9 +2303,9 @@ func (c *apiServerComponent) l7AdmissionControllerContainer() corev1.Container { } l7AdmssCtrl := corev1.Container{ - Name: string(L7AdmissionControllerContainerName), - Image: c.l7AdmissionControllerImage, - Command: []string{components.CalicoBinaryPath, "component", "l7-admission-controller"}, + Name: string(L7AdmissionControllerContainerName), + Image: c.l7AdmissionControllerImage, + Command: []string{components.CalicoBinaryPath, "component", "l7-admission-controller"}, Env: []corev1.EnvVar{ { Name: "L7ADMCTRL_TLSCERTPATH", diff --git a/pkg/render/applicationlayer/applicationlayer.go b/pkg/render/applicationlayer/applicationlayer.go index f06dca8685..844fd6ce58 100644 --- a/pkg/render/applicationlayer/applicationlayer.go +++ b/pkg/render/applicationlayer/applicationlayer.go @@ -271,8 +271,8 @@ func (c *component) containers() []corev1.Container { "NET_RAW", } proxy := corev1.Container{ - Name: ProxyContainerName, - Image: c.config.proxyImage, + Name: ProxyContainerName, + Image: c.config.proxyImage, Command: []string{ "envoy", "-c", "/etc/envoy/envoy-config.yaml", }, @@ -347,9 +347,9 @@ func (c *component) containers() []corev1.Container { } dikastes := corev1.Container{ - Name: DikastesContainerName, - Image: c.config.dikastesImage, - Command: commandArgs, + Name: DikastesContainerName, + Image: c.config.dikastesImage, + Command: commandArgs, Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "Info"}, {Name: "DIKASTES_SUBSCRIPTION_TYPE", Value: "per-host-policies"}, diff --git a/pkg/render/compliance.go b/pkg/render/compliance.go index e4c8cd1677..b2039d2d0f 100644 --- a/pkg/render/compliance.go +++ b/pkg/render/compliance.go @@ -483,10 +483,10 @@ func (c *complianceComponent) complianceControllerDeployment() *appsv1.Deploymen InitContainers: initContainers, Containers: []corev1.Container{ { - Name: ComplianceControllerName, - Image: c.controllerImage, - Command: []string{components.CalicoBinaryPath, "component", "compliance-controller"}, - Env: envVars, + Name: ComplianceControllerName, + Image: c.controllerImage, + Command: []string{components.CalicoBinaryPath, "component", "compliance-controller"}, + Env: envVars, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -683,10 +683,10 @@ func (c *complianceComponent) complianceReporterPodTemplate() *corev1.PodTemplat InitContainers: initContainers, Containers: []corev1.Container{ { - Name: "reporter", - Image: c.reporterImage, - Command: []string{components.CalicoBinaryPath, "component", "compliance-reporter"}, - Env: envVars, + Name: "reporter", + Image: c.reporterImage, + Command: []string{components.CalicoBinaryPath, "component", "compliance-reporter"}, + Env: envVars, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -891,10 +891,10 @@ func (c *complianceComponent) complianceServerDeployment() *appsv1.Deployment { InitContainers: initContainers, Containers: []corev1.Container{ { - Name: ComplianceServerName, - Image: c.serverImage, - Command: []string{components.CalicoBinaryPath, "component", "compliance-server"}, - Env: envVars, + Name: ComplianceServerName, + Image: c.serverImage, + Command: []string{components.CalicoBinaryPath, "component", "compliance-server"}, + Env: envVars, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -1104,10 +1104,10 @@ func (c *complianceComponent) complianceSnapshotterDeployment() *appsv1.Deployme InitContainers: initContainers, Containers: []corev1.Container{ { - Name: ComplianceSnapshotterName, - Image: c.snapshotterImage, - Command: []string{components.CalicoBinaryPath, "component", "compliance-snapshotter"}, - Env: envVars, + Name: ComplianceSnapshotterName, + Image: c.snapshotterImage, + Command: []string{components.CalicoBinaryPath, "component", "compliance-snapshotter"}, + Env: envVars, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/render/intrusion_detection.go b/pkg/render/intrusion_detection.go index dac03e19ec..027d64cadf 100644 --- a/pkg/render/intrusion_detection.go +++ b/pkg/render/intrusion_detection.go @@ -766,10 +766,10 @@ func (c *intrusionDetectionComponent) intrusionDetectionControllerContainer() co } return corev1.Container{ - Name: "controller", - Image: c.controllerImage, - Command: []string{components.CalicoBinaryPath, "component", "intrusion-detection-controller"}, - Env: envs, + Name: "controller", + Image: c.controllerImage, + Command: []string{components.CalicoBinaryPath, "component", "intrusion-detection-controller"}, + Env: envs, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ diff --git a/pkg/render/logstorage/esgateway/esgateway.go b/pkg/render/logstorage/esgateway/esgateway.go index fbdcd53934..7c46e9d0d8 100644 --- a/pkg/render/logstorage/esgateway/esgateway.go +++ b/pkg/render/logstorage/esgateway/esgateway.go @@ -251,11 +251,11 @@ func (e *esGateway) esGatewayDeployment() *appsv1.Deployment { InitContainers: initContainers, Containers: []corev1.Container{ { - Name: DeploymentName, - Image: e.esGatewayImage, - Command: []string{components.CalicoBinaryPath, "component", "es-gateway"}, - Env: envVars, - VolumeMounts: volumeMounts, + Name: DeploymentName, + Image: e.esGatewayImage, + Command: []string{components.CalicoBinaryPath, "component", "es-gateway"}, + Env: envVars, + VolumeMounts: volumeMounts, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/render/logstorage/linseed/linseed_test.go b/pkg/render/logstorage/linseed/linseed_test.go index 4050449306..936db2f3bc 100644 --- a/pkg/render/logstorage/linseed/linseed_test.go +++ b/pkg/render/logstorage/linseed/linseed_test.go @@ -1029,8 +1029,8 @@ func expectedVolumes(useCSR bool) []corev1.Volume { func expectedContainers() []corev1.Container { return []corev1.Container{ { - Name: DeploymentName, - Command: []string{components.CalicoBinaryPath, "component", "linseed"}, + Name: DeploymentName, + Command: []string{components.CalicoBinaryPath, "component", "linseed"}, SecurityContext: &corev1.SecurityContext{ Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, AllowPrivilegeEscalation: ptr.To(false), diff --git a/pkg/render/monitor/monitor.go b/pkg/render/monitor/monitor.go index 9b502b461e..fa3473df5c 100644 --- a/pkg/render/monitor/monitor.go +++ b/pkg/render/monitor/monitor.go @@ -638,9 +638,9 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { }, Containers: []corev1.Container{ { - Name: "authn-proxy", - Image: mc.prometheusServiceImage, - Command: []string{components.CalicoBinaryPath, "component", "prometheus-service"}, + Name: "authn-proxy", + Image: mc.prometheusServiceImage, + Command: []string{components.CalicoBinaryPath, "component", "prometheus-service"}, Ports: []corev1.ContainerPort{ { ContainerPort: PrometheusProxyPort, diff --git a/pkg/render/packet_capture_api_test.go b/pkg/render/packet_capture_api_test.go index 47458b0864..d4b1839b7f 100644 --- a/pkg/render/packet_capture_api_test.go +++ b/pkg/render/packet_capture_api_test.go @@ -191,9 +191,9 @@ var _ = Describe("Rendering tests for PacketCapture API component", func() { return []corev1.Container{ { - Name: render.PacketCaptureContainerName, - Image: fmt.Sprintf("%s%s%s:%s", components.TigeraRegistry, components.TigeraImagePath, components.ComponentTigeraCalico.Image, components.ComponentTigeraCalico.Version), - Command: []string{components.CalicoBinaryPath, "component", "packetcapture"}, + Name: render.PacketCaptureContainerName, + Image: fmt.Sprintf("%s%s%s:%s", components.TigeraRegistry, components.TigeraImagePath, components.ComponentTigeraCalico.Image, components.ComponentTigeraCalico.Version), + Command: []string{components.CalicoBinaryPath, "component", "packetcapture"}, SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: ptr.To(false), Capabilities: &corev1.Capabilities{