From 0f242c4a945aa6fccc6cd7a443ecc996b49eba32 Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Fri, 24 Oct 2025 19:54:14 +0530 Subject: [PATCH 1/3] added imagePullPolicy config for gitops-service Signed-off-by: Alka Kumari --- api/v1alpha1/gitopsservice_types.go | 3 + common/common.go | 32 ++++++- common/common_test.go | 87 +++++++++++++++++++ ...pipelines.openshift.io_gitopsservices.yaml | 8 ++ controllers/consoleplugin.go | 10 +-- controllers/consoleplugin_test.go | 2 +- controllers/gitopsservice_controller.go | 13 ++- controllers/gitopsservice_controller_test.go | 4 +- 8 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 common/common_test.go diff --git a/api/v1alpha1/gitopsservice_types.go b/api/v1alpha1/gitopsservice_types.go index 7c76b65ec..46d0412ed 100644 --- a/api/v1alpha1/gitopsservice_types.go +++ b/api/v1alpha1/gitopsservice_types.go @@ -34,6 +34,9 @@ type GitopsServiceSpec struct { NodeSelector map[string]string `json:"nodeSelector,omitempty"` // ConsolePlugin defines the Resource configuration for the Console Plugin components ConsolePlugin *ConsolePluginStruct `json:"consolePlugin,omitempty"` + // ImagePullPolicy defines the image pull policy for GitOps workloads + // +kubebuilder:validation:Enum=Always;IfNotPresent;Never + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` } // ConsolePluginStruct defines the resource configuration for the Console Plugin components diff --git a/common/common.go b/common/common.go index 4599db6c0..7e630b5c0 100644 --- a/common/common.go +++ b/common/common.go @@ -16,7 +16,11 @@ limitations under the License. package common -import "os" +import ( + "os" + + corev1 "k8s.io/api/core/v1" +) const ( // ArgoCDInstanceName is the default Argo CD instance name @@ -33,6 +37,8 @@ const ( DefaultConsoleVersion = "v0.1.0" // Default console plugin installation OCP version DefaultDynamicPluginStartOCPVersion = "4.15.0" + // ImagePullPolicyEnvVar is the environment variable for configuring image pull policy + ImagePullPolicy = "IMAGE_PULL_POLICY" ) // InfraNodeSelector returns openshift label for infrastructure nodes @@ -48,3 +54,27 @@ func StringFromEnv(env string, defaultValue string) string { } return defaultValue } + +// GetImagePullPolicy returns the image pull policy based on precedence: +// 1. CR-level override (highest precedence) +// 2. Environment variable (middle precedence) +// 3. Default fallback (lowest precedence - IfNotPresent) +func GetImagePullPolicy(crImagePullPolicy corev1.PullPolicy) corev1.PullPolicy { + // If CR specifies a policy, use it (highest precedence) + if crImagePullPolicy != "" { + return crImagePullPolicy + } + + // Check environment variable (middle precedence) + envPolicy := os.Getenv(ImagePullPolicy) + switch envPolicy { + case "Always": + return corev1.PullAlways + case "IfNotPresent": + return corev1.PullIfNotPresent + case "Never": + return corev1.PullNever + default: + return corev1.PullPolicy("IfNotPresent") + } +} diff --git a/common/common_test.go b/common/common_test.go new file mode 100644 index 000000000..2ce897ebe --- /dev/null +++ b/common/common_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "os" + "testing" + + corev1 "k8s.io/api/core/v1" +) + +func TestGetImagePullPolicy(t *testing.T) { + tests := []struct { + name string + crPolicy corev1.PullPolicy + envValue string + expectedPolicy corev1.PullPolicy + }{ + { + name: "CR policy takes precedence", + crPolicy: corev1.PullIfNotPresent, + envValue: "Never", + expectedPolicy: corev1.PullIfNotPresent, + }, + { + name: "Environment variable used when CR policy empty", + crPolicy: "", + envValue: "IfNotPresent", + expectedPolicy: corev1.PullIfNotPresent, + }, + { + name: "Environment variable Never", + crPolicy: "", + envValue: "Never", + expectedPolicy: corev1.PullNever, + }, + { + name: "Environment variable Always", + crPolicy: "", + envValue: "Always", + expectedPolicy: corev1.PullAlways, + }, + { + name: "Default to IfNotPresent when no config", + crPolicy: "", + envValue: "", + expectedPolicy: corev1.PullIfNotPresent, + }, + { + name: "Invalid env value defaults to IfNotPresent", + crPolicy: "", + envValue: "InvalidValue", + expectedPolicy: corev1.PullIfNotPresent, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set environment variable + if tt.envValue != "" { + os.Setenv(ImagePullPolicy, tt.envValue) + } else { + os.Unsetenv(ImagePullPolicy) + } + defer os.Unsetenv(ImagePullPolicy) + + result := GetImagePullPolicy(tt.crPolicy) + if result != tt.expectedPolicy { + t.Errorf("GetImagePullPolicy() = %v, want %v", result, tt.expectedPolicy) + } + }) + } +} diff --git a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml index 1dd2f77a9..6589958ce 100644 --- a/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml +++ b/config/crd/bases/pipelines.openshift.io_gitopsservices.yaml @@ -174,6 +174,14 @@ spec: type: object type: object type: object + imagePullPolicy: + description: ImagePullPolicy defines the image pull policy for GitOps + workloads + enum: + - Always + - IfNotPresent + - Never + type: string nodeSelector: additionalProperties: type: string diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go index 4ad49ad88..e01b405e0 100644 --- a/controllers/consoleplugin.go +++ b/controllers/consoleplugin.go @@ -44,7 +44,7 @@ const ( kubeAppLabelName = "app.kubernetes.io/name" ) -func getPluginPodSpec() corev1.PodSpec { +func getPluginPodSpec(crImagePullPolicy corev1.PullPolicy) corev1.PodSpec { consolePluginImage := os.Getenv(pluginImageEnv) if consolePluginImage == "" { image := common.DefaultConsoleImage @@ -58,7 +58,7 @@ func getPluginPodSpec() corev1.PodSpec { Env: util.ProxyEnvVars(), Name: gitopsPluginName, Image: consolePluginImage, - ImagePullPolicy: corev1.PullAlways, + ImagePullPolicy: common.GetImagePullPolicy(crImagePullPolicy), Ports: []corev1.ContainerPort{ { Name: "http", @@ -133,8 +133,8 @@ func getPluginPodSpec() corev1.PodSpec { return podSpec } -func pluginDeployment() *appsv1.Deployment { - podSpec := getPluginPodSpec() +func pluginDeployment(crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { + podSpec := getPluginPodSpec(crImagePullPolicy) template := corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ @@ -269,7 +269,7 @@ func pluginConfigMap() *corev1.ConfigMap { func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) { reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - newPluginDeployment := pluginDeployment() + newPluginDeployment := pluginDeployment(cr.Spec.ImagePullPolicy) if err := controllerutil.SetControllerReference(cr, newPluginDeployment, r.Scheme); err != nil { return reconcile.Result{}, err diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go index 872e3d0c8..267a907f4 100644 --- a/controllers/consoleplugin_test.go +++ b/controllers/consoleplugin_test.go @@ -625,7 +625,7 @@ func TestPlugin_reconcileDeployment_changedContainers(t *testing.T) { consoleImage := common.DefaultConsoleImage + ":" + common.DefaultConsoleVersion assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Name, "gitops-plugin") assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, consoleImage) - assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy, corev1.PullAlways) + assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy, corev1.PullIfNotPresent) assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Name, "http") assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Protocol, corev1.ProtocolTCP) assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort, int32(9001)) diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 119e8364f..639b08e31 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -607,7 +607,7 @@ func (r *ReconcileGitopsService) reconcileBackend(gitopsserviceNamespacedName ty // Define a new backend Deployment { - deploymentObj := newBackendDeployment(gitopsserviceNamespacedName) + deploymentObj := newBackendDeployment(gitopsserviceNamespacedName, instance.Spec.ImagePullPolicy) // Add SeccompProfile based on cluster version util.AddSeccompProfileForOpenShift(r.Client, &deploymentObj.Spec.Template.Spec) @@ -654,6 +654,10 @@ func (r *ReconcileGitopsService) reconcileBackend(gitopsserviceNamespacedName ty found.Spec.Template.Spec.Containers[0].Env = deploymentObj.Spec.Template.Spec.Containers[0].Env changed = true } + if !reflect.DeepEqual(found.Spec.Template.Spec.Containers[0].ImagePullPolicy, deploymentObj.Spec.Template.Spec.Containers[0].ImagePullPolicy) { + found.Spec.Template.Spec.Containers[0].ImagePullPolicy = deploymentObj.Spec.Template.Spec.Containers[0].ImagePullPolicy + changed = true + } if !reflect.DeepEqual(found.Spec.Template.Spec.Containers[0].Resources, deploymentObj.Spec.Template.Spec.Containers[0].Resources) { found.Spec.Template.Spec.Containers[0].Resources = deploymentObj.Spec.Template.Spec.Containers[0].Resources changed = true @@ -744,7 +748,7 @@ func objectMeta(resourceName string, namespace string, opts ...func(*metav1.Obje return objectMeta } -func newBackendDeployment(ns types.NamespacedName) *appsv1.Deployment { +func newBackendDeployment(ns types.NamespacedName, crImagePullPolicy corev1.PullPolicy) *appsv1.Deployment { image := os.Getenv(backendImageEnvName) if image == "" { image = backendImage @@ -752,8 +756,9 @@ func newBackendDeployment(ns types.NamespacedName) *appsv1.Deployment { podSpec := corev1.PodSpec{ Containers: []corev1.Container{ { - Name: ns.Name, - Image: image, + Name: ns.Name, + Image: image, + ImagePullPolicy: common.GetImagePullPolicy(crImagePullPolicy), Ports: []corev1.ContainerPort{ { Name: "http", diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index f1cb37426..ddfad2bf3 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -54,7 +54,7 @@ func TestImageFromEnvVariable(t *testing.T) { image := "quay.io/org/test" t.Setenv(backendImageEnvName, image) - deployment := newBackendDeployment(ns) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent)) got := deployment.Spec.Template.Spec.Containers[0].Image if got != image { @@ -62,7 +62,7 @@ func TestImageFromEnvVariable(t *testing.T) { } }) t.Run("env variable for image not found", func(t *testing.T) { - deployment := newBackendDeployment(ns) + deployment := newBackendDeployment(ns, corev1.PullPolicy(corev1.PullIfNotPresent)) got := deployment.Spec.Template.Spec.Containers[0].Image if got != backendImage { From e55da504a9c4a89a22cf8d4473291f6370054ab8 Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Fri, 24 Oct 2025 23:17:21 +0530 Subject: [PATCH 2/3] added ginkgo test cases for imagePullPolicy feature in Gitops Servicee Signed-off-by: Alka Kumari --- ...ate_imagepullpolicy_console_plugin_test.go | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 test/openshift/e2e/ginkgo/parallel/1-115_validate_imagepullpolicy_console_plugin_test.go diff --git a/test/openshift/e2e/ginkgo/parallel/1-115_validate_imagepullpolicy_console_plugin_test.go b/test/openshift/e2e/ginkgo/parallel/1-115_validate_imagepullpolicy_console_plugin_test.go new file mode 100644 index 000000000..d4916ffcf --- /dev/null +++ b/test/openshift/e2e/ginkgo/parallel/1-115_validate_imagepullpolicy_console_plugin_test.go @@ -0,0 +1,210 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parallel + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gitopsoperatorv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + gitopsserviceFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/gitopsservice" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("GitOps Operator Parallel E2E Tests", func() { + + Context("1-115-validate_imagepullpolicy_gitopsservice", func() { + + var ( + ctx context.Context + k8sClient client.Client + ) + + BeforeEach(func() { + fixture.EnsureParallelCleanSlate() + k8sClient, _ = utils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + It("validates ImagePullPolicy propagation from GitOpsService CR to console plugin and backend deployments", func() { + + By("getting the cluster-scoped GitOpsService CR") + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + } + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gitopsService), gitopsService)).To(Succeed()) + + By("setting ImagePullPolicy to Always in GitOpsService CR") + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ImagePullPolicy = corev1.PullAlways + }) + + By("verifying console plugin deployment has ImagePullPolicy set to Always") + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(pluginDepl).Should(k8sFixture.ExistByName()) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) + if err != nil { + return false + } + for _, container := range pluginDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(pluginDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullAlways)) + + By("verifying backend deployment has ImagePullPolicy set to Always") + clusterDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}} + Eventually(clusterDepl).Should(k8sFixture.ExistByName()) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterDepl), clusterDepl) + if err != nil { + return false + } + for _, container := range clusterDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(clusterDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullAlways)) + + By("setting ImagePullPolicy to Never in GitOpsService CR") + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ImagePullPolicy = corev1.PullNever + }) + + By("verifying console plugin deployment has ImagePullPolicy set to Never") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) + if err != nil { + return false + } + for _, container := range pluginDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullNever { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(pluginDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullNever)) + + By("verifying backend deployment has ImagePullPolicy set to Never") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterDepl), clusterDepl) + if err != nil { + return false + } + for _, container := range clusterDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullNever { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(clusterDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullNever)) + + By("setting ImagePullPolicy to IfNotPresent in GitOpsService CR") + gitopsserviceFixture.Update(gitopsService, func(gs *gitopsoperatorv1alpha1.GitopsService) { + gs.Spec.ImagePullPolicy = corev1.PullIfNotPresent + }) + + By("verifying console plugin deployment has ImagePullPolicy set to IfNotPresent") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) + if err != nil { + return false + } + for _, container := range pluginDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(pluginDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullIfNotPresent)) + + By("verifying backend deployment has ImagePullPolicy set to IfNotPresent") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterDepl), clusterDepl) + if err != nil { + return false + } + for _, container := range clusterDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(clusterDepl, "3m", "5s").Should(deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullIfNotPresent)) + }) + + It("validates default ImagePullPolicy when not set in CR", func() { + By("getting the GitOpsService CR") + gitopsService := &gitopsoperatorv1alpha1.GitopsService{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + } + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gitopsService), gitopsService)).To(Succeed()) + + By("verifying backend deployment defaults to IfNotPresent") + clusterDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}} + Eventually(clusterDepl).Should(k8sFixture.ExistByName()) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterDepl), clusterDepl) + if err != nil { + return false + } + for _, container := range clusterDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + //Eventually(clusterDepl.Spec.Template.Spec.Containers[0].ImagePullPolicy == corev1.PullIfNotPresent, "60s", "3s").Should(BeTrue()) + //deploymentFixture.HaveContainerImagePullPolicy(0, corev1.PullIfNotPresent)) + + By("verifying plugin deployment defaults to IfNotPresent") + pluginDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "gitops-plugin", Namespace: "openshift-gitops"}} + Eventually(pluginDepl).Should(k8sFixture.ExistByName()) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pluginDepl), pluginDepl) + if err != nil { + return false + } + for _, container := range pluginDepl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue()) + }) + }) +}) From 32daa3eaeb8aec5f537a168333aa9e4ccf084993 Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Fri, 24 Oct 2025 23:25:49 +0530 Subject: [PATCH 3/3] updated the manifest files Signed-off-by: Alka Kumari --- .../manifests/gitops-operator.clusterserviceversion.yaml | 2 +- .../manifests/pipelines.openshift.io_gitopsservices.yaml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bundle/manifests/gitops-operator.clusterserviceversion.yaml b/bundle/manifests/gitops-operator.clusterserviceversion.yaml index e758cdf85..febc10c04 100644 --- a/bundle/manifests/gitops-operator.clusterserviceversion.yaml +++ b/bundle/manifests/gitops-operator.clusterserviceversion.yaml @@ -180,7 +180,7 @@ metadata: capabilities: Deep Insights console.openshift.io/plugins: '["gitops-plugin"]' containerImage: quay.io/redhat-developer/gitops-operator - createdAt: "2025-10-01T09:01:02Z" + createdAt: "2025-10-24T17:54:33Z" description: Enables teams to adopt GitOps principles for managing cluster configurations and application delivery across hybrid multi-cluster Kubernetes environments. features.operators.openshift.io/disconnected: "true" diff --git a/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml b/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml index 792cc853b..f790e517f 100644 --- a/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml +++ b/bundle/manifests/pipelines.openshift.io_gitopsservices.yaml @@ -174,6 +174,14 @@ spec: type: object type: object type: object + imagePullPolicy: + description: ImagePullPolicy defines the image pull policy for GitOps + workloads + enum: + - Always + - IfNotPresent + - Never + type: string nodeSelector: additionalProperties: type: string