From 04b35964b17084444886431f118e6d4a6f3a6823 Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Wed, 29 Oct 2025 23:08:56 +0530 Subject: [PATCH 1/3] downstream ImagePullPolicy feature Signed-off-by: Alka Kumari --- bundle/manifests/argoproj.io_argocds.yaml | 18 + ...gitops-operator.clusterserviceversion.yaml | 2 +- config/crd/bases/argoproj.io_argocds.yaml | 18 + go.mod | 2 +- go.sum | 4 +- .../1-114_validate_imagepullpolicy_test.go | 569 ++++++++++++++++++ 6 files changed, 609 insertions(+), 4 deletions(-) create mode 100644 test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go diff --git a/bundle/manifests/argoproj.io_argocds.yaml b/bundle/manifests/argoproj.io_argocds.yaml index cf1a6340d..99f457ed9 100644 --- a/bundle/manifests/argoproj.io_argocds.yaml +++ b/bundle/manifests/argoproj.io_argocds.yaml @@ -1425,6 +1425,15 @@ spec: image: description: Image is the ArgoCD container image for all ArgoCD components. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the image pull policy for all ArgoCD components. + Valid values are Always, IfNotPresent, Never. If not specified, defaults to the operator's global setting. + enum: + - Always + - IfNotPresent + - Never + type: string import: description: Import is the import/restore options for ArgoCD. properties: @@ -16064,6 +16073,15 @@ spec: image: description: Image is the ArgoCD container image for all ArgoCD components. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the image pull policy for all ArgoCD components. + Valid values are Always, IfNotPresent, Never. If not specified, defaults to the operator's global setting. + enum: + - Always + - IfNotPresent + - Never + type: string imageUpdater: description: ImageUpdater defines whether the Argo CD ImageUpdater controller should be installed. diff --git a/bundle/manifests/gitops-operator.clusterserviceversion.yaml b/bundle/manifests/gitops-operator.clusterserviceversion.yaml index 0ed1a90b0..db9e859fe 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-23T13:34:07Z" + createdAt: "2025-10-29T14:30:25Z" 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/config/crd/bases/argoproj.io_argocds.yaml b/config/crd/bases/argoproj.io_argocds.yaml index ce5290bf9..c89f73a5d 100644 --- a/config/crd/bases/argoproj.io_argocds.yaml +++ b/config/crd/bases/argoproj.io_argocds.yaml @@ -1414,6 +1414,15 @@ spec: image: description: Image is the ArgoCD container image for all ArgoCD components. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the image pull policy for all ArgoCD components. + Valid values are Always, IfNotPresent, Never. If not specified, defaults to the operator's global setting. + enum: + - Always + - IfNotPresent + - Never + type: string import: description: Import is the import/restore options for ArgoCD. properties: @@ -16053,6 +16062,15 @@ spec: image: description: Image is the ArgoCD container image for all ArgoCD components. type: string + imagePullPolicy: + description: |- + ImagePullPolicy is the image pull policy for all ArgoCD components. + Valid values are Always, IfNotPresent, Never. If not specified, defaults to the operator's global setting. + enum: + - Always + - IfNotPresent + - Never + type: string imageUpdater: description: ImageUpdater defines whether the Argo CD ImageUpdater controller should be installed. diff --git a/go.mod b/go.mod index b72e66f33..fe25d4240 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.6 require ( github.com/argoproj-labs/argo-rollouts-manager v0.0.7-0.20251020065637-7f928e52c0d9 - github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251017041829-18184f26c64e + github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251024105544-f7c3f5b0cc95 github.com/argoproj/argo-cd/v3 v3.1.8 github.com/argoproj/gitops-engine v0.7.1-0.20250905160054-e48120133eec github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index f1ba904ae..34854cd8c 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/argoproj-labs/argo-rollouts-manager v0.0.7-0.20251020065637-7f928e52c0d9 h1:WcUWvh0qIqUaY+JfVIUfBV0ACv9ep2by3YEi04dRNr4= github.com/argoproj-labs/argo-rollouts-manager v0.0.7-0.20251020065637-7f928e52c0d9/go.mod h1:iVIrf0/GJPZR3NMtJvpo1Ui6qqPjpY34Lp+5RmZo9vY= -github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251017041829-18184f26c64e h1:0JYIQ9qRNce5t7ak5mEL1hLk9S3CKukdjvlVJySJNzE= -github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251017041829-18184f26c64e/go.mod h1:LTBNqNbKk9Us5xiCrK612HLOr8SJFfyxlMJQErzMghg= +github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251024105544-f7c3f5b0cc95 h1:v2J4IPd8Fab5udUD7nMZsYflqGDhkVGx30q5uenMBbE= +github.com/argoproj-labs/argocd-operator v0.14.0-rc1.0.20251024105544-f7c3f5b0cc95/go.mod h1:LTBNqNbKk9Us5xiCrK612HLOr8SJFfyxlMJQErzMghg= github.com/argoproj/argo-cd/v3 v3.1.8 h1:NkLPiRI5qGkV+q1EN3O7/0Wb9O/MVl62vadKteZqMUw= github.com/argoproj/argo-cd/v3 v3.1.8/go.mod h1:ZHb/LOz/hr88VWMJiVTd8DGYL7MheHCAT8S6DgYOBFo= github.com/argoproj/gitops-engine v0.7.1-0.20250905160054-e48120133eec h1:rNAwbRQFvRIuW/e2bU+B10mlzghYXsnwZedYeA7Drz4= diff --git a/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go new file mode 100644 index 000000000..bb65d897a --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go @@ -0,0 +1,569 @@ +/* +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 sequential + +import ( + "context" + + argov1beta1api "github.com/argoproj-labs/argocd-operator/api/v1beta1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + argocdFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/argocd" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/deployment" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + statefulsetFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/statefulset" + "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" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("GitOps Operator Sequential E2E Tests", func() { + + Context("1-114_validate_imagepullpolicy", func() { + + var ( + k8sClient client.Client + ctx context.Context + ) + + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + k8sClient, _ = utils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + It("verifies that imagePullPolicy from ArgoCD CR spec is propagated to all ArgoCD workload resources", func() { + + By("creating a test namespace for ArgoCD instance") + ns := fixture.CreateNamespace("test-1-114-imagepullpolicy") + defer func() { + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + }() + + By("creating an ArgoCD instance with imagePullPolicy set to Always") + policy := corev1.PullAlways + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + ImagePullPolicy: &policy, + }, + } + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + defer func() { + Expect(k8sClient.Delete(ctx, argoCD)).To(Succeed()) + }() + + By("waiting for ArgoCD to become available") + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying all ArgoCD deployments have imagePullPolicy set to Always on all containers") + deploymentNames := []string{ + "argocd-server", + "argocd-repo-server", + "argocd-redis", + } + + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: ns.Name}, + } + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(depl, "2m", "5s").Should(deployment.HaveReadyReplicas(1)) + + // Verify all containers in the deployment have the correct imagePullPolicy + Expect(depl.Spec.Template.Spec.Containers).ToNot(BeEmpty()) + for _, container := range depl.Spec.Template.Spec.Containers { + Expect(container.ImagePullPolicy).To(Equal(corev1.PullAlways), + "Deployment %s, container %s should have ImagePullPolicy set to Always", + deplName, container.Name) + } + } + + By("verifying ArgoCD application-controller statefulset has imagePullPolicy set to Always") + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns.Name, + }, + } + Eventually(ss, "3m", "5s").Should(k8sFixture.ExistByName()) + Eventually(ss, "2m", "5s").Should(statefulsetFixture.HaveReadyReplicas(1)) + + Expect(ss.Spec.Template.Spec.Containers).ToNot(BeEmpty()) + for _, container := range ss.Spec.Template.Spec.Containers { + Expect(container.ImagePullPolicy).To(Equal(corev1.PullAlways), + "StatefulSet argocd-application-controller, container %s should have ImagePullPolicy set to Always", + container.Name) + } + + By("updating ArgoCD instance to use imagePullPolicy IfNotPresent") + policy = corev1.PullIfNotPresent + argocdFixture.Update(argoCD, func(ac *argov1beta1api.ArgoCD) { + ac.Spec.ImagePullPolicy = &policy + }) + + By("waiting for ArgoCD to reconcile the change") + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying all deployments have been updated to use IfNotPresent imagePullPolicy") + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: ns.Name}, + } + Eventually(depl).Should(k8sFixture.ExistByName()) + + // Eventually the imagePullPolicy should be updated + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns.Name}, depl) + if err != nil { + return false + } + if len(depl.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "Deployment %s should have all containers with ImagePullPolicy set to IfNotPresent", deplName) + } + + By("verifying statefulset has been updated to use IfNotPresent imagePullPolicy") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns.Name}, ss) + if err != nil { + return false + } + for _, container := range ss.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "StatefulSet argocd-application-controller should have all containers with ImagePullPolicy set to IfNotPresent") + + }) + + It("verifies that imagePullPolicy works correctly on default openshift-gitops ArgoCD instance", func() { + + openshiftGitopsArgoCD, err := argocdFixture.GetOpenShiftGitOpsNSArgoCD() + Expect(err).ToNot(HaveOccurred()) + + By("verifying that the openshift-gitops ArgoCD instance exists and is available") + Eventually(openshiftGitopsArgoCD).Should(k8sFixture.ExistByName()) + Eventually(openshiftGitopsArgoCD).Should(argocdFixture.BeAvailable()) + + By("updating openshift-gitops ArgoCD to set imagePullPolicy to Always") + argocdFixture.Update(openshiftGitopsArgoCD, func(ac *argov1beta1api.ArgoCD) { + ac.Spec.ImagePullPolicy = ptr.To(corev1.PullAlways) + }) + + defer func() { + By("restoring openshift-gitops ArgoCD imagePullPolicy to default after test") + argocdFixture.Update(openshiftGitopsArgoCD, func(ac *argov1beta1api.ArgoCD) { + ac.Spec.ImagePullPolicy = nil + }) + }() + + By("waiting for ArgoCD to reconcile") + Eventually(openshiftGitopsArgoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying openshift-gitops deployments have imagePullPolicy set to Always") + deploymentNames := []string{ + "openshift-gitops-server", + "openshift-gitops-repo-server", + "openshift-gitops-redis", + "openshift-gitops-applicationset-controller", + } + + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: "openshift-gitops"}, + } + Eventually(depl).Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: "openshift-gitops"}, depl) + if err != nil { + return false + } + if len(depl.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "openshift-gitops Deployment %s should have all containers with ImagePullPolicy set to Always", deplName) + } + + By("verifying openshift-gitops statefulset has imagePullPolicy set to Always") + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-gitops-application-controller", + Namespace: "openshift-gitops", + }, + } + Eventually(ss).Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "openshift-gitops-application-controller", Namespace: "openshift-gitops"}, ss) + if err != nil { + return false + } + if len(ss.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "openshift-gitops StatefulSet should have all containers with ImagePullPolicy set to Always") + + }) + + It("verifies default imagePullPolicy is applied to all ArgoCD workload resources when not specified in either CR spec or subscription", func() { + + openshiftGitopsArgoCD, err := argocdFixture.GetOpenShiftGitOpsNSArgoCD() + Expect(err).ToNot(HaveOccurred()) + + By("verifying that the openshift-gitops ArgoCD instance exists and is available") + Eventually(openshiftGitopsArgoCD).Should(k8sFixture.ExistByName()) + Eventually(openshiftGitopsArgoCD).Should(argocdFixture.BeAvailable()) + + By("waiting for ArgoCD to reconcile") + Eventually(openshiftGitopsArgoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying openshift-gitops deployments have imagePullPolicy set to default(IfNotPresent)") + deploymentNames := []string{ + "openshift-gitops-server", + "openshift-gitops-repo-server", + "openshift-gitops-redis", + "openshift-gitops-applicationset-controller", + } + + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: "openshift-gitops"}, + } + Eventually(depl).Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: "openshift-gitops"}, depl) + if err != nil { + return false + } + if len(depl.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "openshift-gitops Deployment %s should have all containers with ImagePullPolicy set to default(IfNotPresent)", deplName) + } + + By("verifying openshift-gitops statefulset has imagePullPolicy set to default(IfNotPresent)") + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-gitops-application-controller", + Namespace: "openshift-gitops", + }, + } + Eventually(ss).Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "openshift-gitops-application-controller", Namespace: "openshift-gitops"}, ss) + if err != nil { + return false + } + if len(ss.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "openshift-gitops StatefulSet should have all containers with ImagePullPolicy set to default(PullIfNotPresent)") + + }) + + It("verifies that IMAGE_PULL_POLICY environment variable at operator level sets default imagePullPolicy for all ArgoCD instances", func() { + + if fixture.EnvLocalRun() { + Skip("Skipping test as LOCAL_RUN env var is set. In this case, it is not possible to set env var on gitops operator controller process.") + return + } + + By("creating a test namespace for first ArgoCD instance without imagePullPolicy set") + ns1 := fixture.CreateNamespace("test-1-114-env-default") + defer func() { + Expect(k8sClient.Delete(ctx, ns1)).To(Succeed()) + }() + + By("creating an ArgoCD instance without explicitly setting imagePullPolicy") + argoCD1 := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns1.Name}, + Spec: argov1beta1api.ArgoCDSpec{}, + } + Expect(k8sClient.Create(ctx, argoCD1)).To(Succeed()) + defer func() { + Expect(k8sClient.Delete(ctx, argoCD1)).To(Succeed()) + }() + + By("waiting for ArgoCD to become available") + Eventually(argoCD1, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("setting IMAGE_PULL_POLICY environment variable at operator level to Always") + fixture.SetEnvInOperatorSubscriptionOrDeployment("IMAGE_PULL_POLICY", "Always") + + defer func() { + By("removing IMAGE_PULL_POLICY environment variable to restore default behavior") + fixture.RestoreSubcriptionToDefault() + }() + + By("verifying second ArgoCD deployments inherit operator-level imagePullPolicy (Always)") + deploymentNames := []string{ + "argocd-server", + "argocd-repo-server", + "argocd-redis", + "argocd-applicationset-controller", + } + + By("verifying operator has restarted with IMAGE_PULL_POLICY environment variable set") + operatorControllerDepl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-gitops-operator-controller-manager", + Namespace: "openshift-gitops-operator", + }, + } + Eventually(operatorControllerDepl).Should(k8sFixture.ExistByName()) + Eventually(operatorControllerDepl).Should(deployment.HaveAvailableReplicas(1)) + Eventually(operatorControllerDepl).Should(deployment.HaveReadyReplicas(1)) + + By("verifying first ArgoCD deployment has ImagePullPolicy set to Always") + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: ns1.Name}, + } + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns1.Name}, depl) + if err != nil { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "Deployment %s in namespace %s should inherit operator-level imagePullPolicy (Always)", deplName, ns1.Name) + } + + By("verifying first ArgoCD statefulset inherits operator-level imagePullPolicy (Always)") + ss1 := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns1.Name, + }, + } + Eventually(ss1, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns1.Name}, ss1) + if err != nil { + return false + } + if len(ss1.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss1.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "StatefulSet in namespace %s should inherit operator-level imagePullPolicy (Always)", ns1.Name) + + By("creating a second test namespace for ArgoCD instance that should inherit operator-level default") + ns2 := fixture.CreateNamespace("test-1-114-env-inherit") + defer func() { + Expect(k8sClient.Delete(ctx, ns2)).To(Succeed()) + }() + + By("creating a second ArgoCD instance without explicitly setting imagePullPolicy") + argoCD2 := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns2.Name}, + Spec: argov1beta1api.ArgoCDSpec{}, + } + Expect(k8sClient.Create(ctx, argoCD2)).To(Succeed()) + defer func() { + Expect(k8sClient.Delete(ctx, argoCD2)).To(Succeed()) + }() + + By("waiting for second ArgoCD to become available") + Eventually(argoCD2, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying second ArgoCD deployments inherits operator-level imagePullPolicy (Always)") + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: ns2.Name}, + } + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns2.Name}, depl) + if err != nil { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "Deployment %s in namespace %s should inherit operator-level imagePullPolicy (Always)", deplName, ns2.Name) + } + + By("verifying second ArgoCD statefulset inherits operator-level imagePullPolicy (Always)") + ss2 := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns2.Name, + }, + } + Eventually(ss2, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns2.Name}, ss2) + if err != nil { + return false + } + if len(ss2.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss2.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullAlways { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "StatefulSet in namespace %s should inherit operator-level imagePullPolicy (Always)", ns2.Name) + + By("creating a third test namespace for ArgoCD instance with explicit imagePullPolicy override") + ns3 := fixture.CreateNamespace("test-1-114-env-override") + defer func() { + Expect(k8sClient.Delete(ctx, ns3)).To(Succeed()) + }() + + By("creating a third ArgoCD instance with explicit imagePullPolicy set to IfNotPresent (overriding operator default)") + policy := corev1.PullIfNotPresent + argoCD3 := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns3.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + ImagePullPolicy: &policy, + }, + } + Expect(k8sClient.Create(ctx, argoCD3)).To(Succeed()) + defer func() { + Expect(k8sClient.Delete(ctx, argoCD3)).To(Succeed()) + }() + + By("waiting for third ArgoCD to become available") + Eventually(argoCD3, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("verifying third ArgoCD deployments use explicit imagePullPolicy (IfNotPresent) instead of operator default") + for _, deplName := range deploymentNames { + depl := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: deplName, Namespace: ns3.Name}, + } + Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns3.Name}, depl) + if err != nil { + return false + } + if len(depl.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "Deployment %s in namespace %s should use explicit imagePullPolicy (IfNotPresent) overriding operator default", deplName, ns3.Name) + } + + By("verifying third ArgoCD statefulset uses explicit imagePullPolicy (IfNotPresent)") + ss3 := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns3.Name, + }, + } + Eventually(ss3, "3m", "5s").Should(k8sFixture.ExistByName()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns3.Name}, ss3) + if err != nil { + return false + } + if len(ss3.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss3.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != corev1.PullIfNotPresent { + return false + } + } + return true + }, "3m", "5s").Should(BeTrue(), + "StatefulSet in namespace %s should use explicit imagePullPolicy (IfNotPresent) overriding operator default", ns3.Name) + + }) + + }) +}) From ba1848ae7f7a7d584e1fcd2aa6209dbf6e077fca Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Thu, 30 Oct 2025 10:46:53 +0530 Subject: [PATCH 2/3] improved ginkgo test for imagePullPolicy Signed-off-by: Alka Kumari --- .../e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go index bb65d897a..98dd80efd 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go @@ -361,7 +361,6 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { "argocd-server", "argocd-repo-server", "argocd-redis", - "argocd-applicationset-controller", } By("verifying operator has restarted with IMAGE_PULL_POLICY environment variable set") From 06fb45469736bd7054d5027ae5bd909e33531e17 Mon Sep 17 00:00:00 2001 From: Alka Kumari Date: Fri, 31 Oct 2025 19:16:47 +0530 Subject: [PATCH 3/3] refactored ginkgo test cases for imagePullPolicy Signed-off-by: Alka Kumari --- .../e2e/ginkgo/fixture/deployment/fixture.go | 21 ++ .../e2e/ginkgo/fixture/statefulset/fixture.go | 20 ++ .../1-114_validate_imagepullpolicy_test.go | 207 ++---------------- 3 files changed, 61 insertions(+), 187 deletions(-) diff --git a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go index 5053074ff..d04afe677 100644 --- a/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go +++ b/test/openshift/e2e/ginkgo/fixture/deployment/fixture.go @@ -427,3 +427,24 @@ func fetchDeployment(f func(*appsv1.Deployment) bool) matcher.GomegaMatcher { }, BeTrue()) } + +// verifyDeploymentImagePullPolicy checks if all containers in a deployment have the expected imagePullPolicy +func VerifyDeploymentImagePullPolicy(name, namespace string, expectedPolicy corev1.PullPolicy, depl *appsv1.Deployment) func() bool { + return func() bool { + depl := &appsv1.Deployment{} + k8sClient, _ := utils.GetE2ETestKubeClient() + err := k8sClient.Get(context.Background(), client.ObjectKey{Name: name, Namespace: namespace}, depl) + if err != nil { + return false + } + if len(depl.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range depl.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != expectedPolicy { + return false + } + } + return true + } +} diff --git a/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go b/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go index 21ca42152..7c1f353e2 100644 --- a/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go +++ b/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go @@ -280,3 +280,23 @@ func fetchStatefulSet(f func(*appsv1.StatefulSet) bool) matcher.GomegaMatcher { }, BeTrue()) } + +// verifyStatefulSetImagePullPolicy checks if all containers in a statefulset have the expected imagePullPolicy +func VerifyStatefulSetImagePullPolicy(name, namespace string, expectedPolicy corev1.PullPolicy, ss *appsv1.StatefulSet) func() bool { + return func() bool { + k8sClient, _ := utils.GetE2ETestKubeClient() + err := k8sClient.Get(context.Background(), client.ObjectKey{Name: name, Namespace: namespace}, ss) + if err != nil { + return false + } + if len(ss.Spec.Template.Spec.Containers) == 0 { + return false + } + for _, container := range ss.Spec.Template.Spec.Containers { + if container.ImagePullPolicy != expectedPolicy { + return false + } + } + return true + } +} diff --git a/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go index 98dd80efd..fd85db816 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-114_validate_imagepullpolicy_test.go @@ -53,10 +53,8 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { It("verifies that imagePullPolicy from ArgoCD CR spec is propagated to all ArgoCD workload resources", func() { By("creating a test namespace for ArgoCD instance") - ns := fixture.CreateNamespace("test-1-114-imagepullpolicy") - defer func() { - Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) - }() + ns, cleanupFunc := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + defer cleanupFunc() By("creating an ArgoCD instance with imagePullPolicy set to Always") policy := corev1.PullAlways @@ -131,37 +129,12 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { Eventually(depl).Should(k8sFixture.ExistByName()) // Eventually the imagePullPolicy should be updated - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns.Name}, depl) - if err != nil { - return false - } - if len(depl.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, ns.Name, corev1.PullIfNotPresent, depl), "3m", "5s").Should(BeTrue(), "Deployment %s should have all containers with ImagePullPolicy set to IfNotPresent", deplName) } By("verifying statefulset has been updated to use IfNotPresent imagePullPolicy") - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns.Name}, ss) - if err != nil { - return false - } - for _, container := range ss.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("argocd-application-controller", ns.Name, corev1.PullIfNotPresent, ss), "3m", "5s").Should(BeTrue(), "StatefulSet argocd-application-controller should have all containers with ImagePullPolicy set to IfNotPresent") }) @@ -204,21 +177,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(depl).Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: "openshift-gitops"}, depl) - if err != nil { - return false - } - if len(depl.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, "openshift-gitops", corev1.PullAlways, depl), "3m", "5s").Should(BeTrue(), "openshift-gitops Deployment %s should have all containers with ImagePullPolicy set to Always", deplName) } @@ -231,21 +190,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(ss).Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "openshift-gitops-application-controller", Namespace: "openshift-gitops"}, ss) - if err != nil { - return false - } - if len(ss.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range ss.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("openshift-gitops-application-controller", "openshift-gitops", corev1.PullAlways, ss), "3m", "5s").Should(BeTrue(), "openshift-gitops StatefulSet should have all containers with ImagePullPolicy set to Always") }) @@ -276,21 +221,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(depl).Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: "openshift-gitops"}, depl) - if err != nil { - return false - } - if len(depl.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, "openshift-gitops", corev1.PullIfNotPresent, depl), "3m", "5s").Should(BeTrue(), "openshift-gitops Deployment %s should have all containers with ImagePullPolicy set to default(IfNotPresent)", deplName) } @@ -303,21 +234,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(ss).Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "openshift-gitops-application-controller", Namespace: "openshift-gitops"}, ss) - if err != nil { - return false - } - if len(ss.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range ss.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("openshift-gitops-application-controller", "openshift-gitops", corev1.PullIfNotPresent, ss), "3m", "5s").Should(BeTrue(), "openshift-gitops StatefulSet should have all containers with ImagePullPolicy set to default(PullIfNotPresent)") }) @@ -330,10 +247,8 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } By("creating a test namespace for first ArgoCD instance without imagePullPolicy set") - ns1 := fixture.CreateNamespace("test-1-114-env-default") - defer func() { - Expect(k8sClient.Delete(ctx, ns1)).To(Succeed()) - }() + ns1, cleanupFunc := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + defer cleanupFunc() By("creating an ArgoCD instance without explicitly setting imagePullPolicy") argoCD1 := &argov1beta1api.ArgoCD{ @@ -381,18 +296,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns1.Name}, depl) - if err != nil { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, ns1.Name, corev1.PullAlways, depl), "3m", "5s").Should(BeTrue(), "Deployment %s in namespace %s should inherit operator-level imagePullPolicy (Always)", deplName, ns1.Name) } @@ -405,28 +309,12 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(ss1, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns1.Name}, ss1) - if err != nil { - return false - } - if len(ss1.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range ss1.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("argocd-application-controller", ns1.Name, corev1.PullAlways, ss1), "3m", "5s").Should(BeTrue(), "StatefulSet in namespace %s should inherit operator-level imagePullPolicy (Always)", ns1.Name) By("creating a second test namespace for ArgoCD instance that should inherit operator-level default") - ns2 := fixture.CreateNamespace("test-1-114-env-inherit") - defer func() { - Expect(k8sClient.Delete(ctx, ns2)).To(Succeed()) - }() + ns2, cleanupFunc := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + defer cleanupFunc() By("creating a second ArgoCD instance without explicitly setting imagePullPolicy") argoCD2 := &argov1beta1api.ArgoCD{ @@ -448,18 +336,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns2.Name}, depl) - if err != nil { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, ns2.Name, corev1.PullAlways, depl), "3m", "5s").Should(BeTrue(), "Deployment %s in namespace %s should inherit operator-level imagePullPolicy (Always)", deplName, ns2.Name) } @@ -472,28 +349,12 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(ss2, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns2.Name}, ss2) - if err != nil { - return false - } - if len(ss2.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range ss2.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullAlways { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("argocd-application-controller", ns2.Name, corev1.PullAlways, ss2), "3m", "5s").Should(BeTrue(), "StatefulSet in namespace %s should inherit operator-level imagePullPolicy (Always)", ns2.Name) By("creating a third test namespace for ArgoCD instance with explicit imagePullPolicy override") - ns3 := fixture.CreateNamespace("test-1-114-env-override") - defer func() { - Expect(k8sClient.Delete(ctx, ns3)).To(Succeed()) - }() + ns3, cleanupFunc := fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + defer cleanupFunc() By("creating a third ArgoCD instance with explicit imagePullPolicy set to IfNotPresent (overriding operator default)") policy := corev1.PullIfNotPresent @@ -518,21 +379,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(depl, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: deplName, Namespace: ns3.Name}, depl) - if err != nil { - return false - } - if len(depl.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range depl.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(deployment.VerifyDeploymentImagePullPolicy(deplName, ns3.Name, corev1.PullIfNotPresent, depl), "3m", "5s").Should(BeTrue(), "Deployment %s in namespace %s should use explicit imagePullPolicy (IfNotPresent) overriding operator default", deplName, ns3.Name) } @@ -545,21 +392,7 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { } Eventually(ss3, "3m", "5s").Should(k8sFixture.ExistByName()) - Eventually(func() bool { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "argocd-application-controller", Namespace: ns3.Name}, ss3) - if err != nil { - return false - } - if len(ss3.Spec.Template.Spec.Containers) == 0 { - return false - } - for _, container := range ss3.Spec.Template.Spec.Containers { - if container.ImagePullPolicy != corev1.PullIfNotPresent { - return false - } - } - return true - }, "3m", "5s").Should(BeTrue(), + Eventually(statefulsetFixture.VerifyStatefulSetImagePullPolicy("argocd-application-controller", ns3.Name, corev1.PullIfNotPresent, ss3), "3m", "5s").Should(BeTrue(), "StatefulSet in namespace %s should use explicit imagePullPolicy (IfNotPresent) overriding operator default", ns3.Name) })