diff --git a/go.mod b/go.mod index 016c7dd93..fa64ebca0 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.20251105123110-0c547c7a7765 - github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20251212100514-ffddb3445337 + github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20260104093047-07f678ea4720 github.com/argoproj/argo-cd/v3 v3.1.9 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 ef3a3efe7..cd7f3fd82 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.20251105123110-0c547c7a7765 h1:zVN+W/nQrRB/kB63YcvcCseuiE//sEzNw6Oa8rqiFOs= github.com/argoproj-labs/argo-rollouts-manager v0.0.7-0.20251105123110-0c547c7a7765/go.mod h1:WPyZkNHZjir/OTt8mrRwcUZKe1euHrHPJsRv1Wp/F/0= -github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20251212100514-ffddb3445337 h1:lGkZs/iUHnc1JTPmg9DQH3ADZ4rBj21LuezYfr/5IKg= -github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20251212100514-ffddb3445337/go.mod h1:tugRb82zAQXSsQfogeBkNUfl5ffdPAAD5nk/9eRAUC8= +github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20260104093047-07f678ea4720 h1:4zz1AG1DjyoUdJiyQ0XpWu5FK3R19CL9jEUbHiiuYAU= +github.com/argoproj-labs/argocd-operator v0.17.0-rc1.0.20260104093047-07f678ea4720/go.mod h1:tugRb82zAQXSsQfogeBkNUfl5ffdPAAD5nk/9eRAUC8= github.com/argoproj/argo-cd/v3 v3.1.9 h1:9P9vJKo1RGWu6mtQnGu61r+0h3XKlA2j3kVhwogUQ/0= github.com/argoproj/argo-cd/v3 v3.1.9/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/fixture/statefulset/fixture.go b/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go index 7c1f353e2..d4e85333c 100644 --- a/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go +++ b/test/openshift/e2e/ginkgo/fixture/statefulset/fixture.go @@ -4,6 +4,7 @@ import ( "context" "reflect" "strings" + "time" //lint:ignore ST1001 "This is a common practice in Gomega tests for readability." . "github.com/onsi/ginkgo/v2" //nolint:all @@ -38,6 +39,15 @@ func Update(obj *appsv1.StatefulSet, modify func(*appsv1.StatefulSet)) { Expect(err).ToNot(HaveOccurred()) } +func Restart(obj *appsv1.StatefulSet) { + Update(obj, func(ss *appsv1.StatefulSet) { + if ss.Spec.Template.Annotations == nil { + ss.Spec.Template.Annotations = make(map[string]string) + } + ss.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) + }) +} + func HaveReplicas(replicas int) matcher.GomegaMatcher { return fetchStatefulSet(func(ss *appsv1.StatefulSet) bool { GinkgoWriter.Println("StatefulSet HaveReplicas:", "expected: ", replicas, "actual: ", ss.Status.Replicas) @@ -258,6 +268,35 @@ func HaveContainerWithEnvVar(envKey string, envValue string, containerIndex int) }) } +// HaveContainerWithEnvVarFromConfigMap checks if a container has an env var that references a ConfigMap key +func HaveContainerWithEnvVarFromConfigMap(envKey string, configMapName string, configMapKey string, containerIndex int) matcher.GomegaMatcher { + return fetchStatefulSet(func(ss *appsv1.StatefulSet) bool { + + containers := ss.Spec.Template.Spec.Containers + + if len(containers) <= containerIndex { + GinkgoWriter.Println("current container slice has length", len(containers), "index is", containerIndex) + return false + } + + container := containers[containerIndex] + + for _, env := range container.Env { + if env.Name == envKey { + if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil { + ref := env.ValueFrom.ConfigMapKeyRef + GinkgoWriter.Println("HaveContainerWithEnvVarFromConfigMap - Key:", envKey, + "Expected ConfigMap:", configMapName, "Key:", configMapKey, + "Actual ConfigMap:", ref.Name, "Key:", ref.Key) + return ref.Name == configMapName && ref.Key == configMapKey + } + } + } + + return false + }) +} + // This is intentionally NOT exported, for now. Create another function in this file/package that calls this function, and export that. func fetchStatefulSet(f func(*appsv1.StatefulSet) bool) matcher.GomegaMatcher { diff --git a/test/openshift/e2e/ginkgo/parallel/1-122_validate_argocd_reconciliation_timeout.go b/test/openshift/e2e/ginkgo/parallel/1-122_validate_argocd_reconciliation_timeout.go new file mode 100644 index 000000000..7d16112cf --- /dev/null +++ b/test/openshift/e2e/ginkgo/parallel/1-122_validate_argocd_reconciliation_timeout.go @@ -0,0 +1,222 @@ +/* +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" + "time" + + 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" + configmapFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/configmap" + 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" + fixtureUtils "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-122_validate_argocd_reconciliation_timeout environment variable", func() { + + var ( + k8sClient client.Client + ctx context.Context + ns *corev1.Namespace + cleanupFunc func() + ) + + BeforeEach(func() { + fixture.EnsureParallelCleanSlate() + k8sClient, _ = fixtureUtils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + AfterEach(func() { + fixture.OutputDebugOnFail(ns) + + if cleanupFunc != nil { + cleanupFunc() + } + }) + + It("should set ARGOCD_RECONCILIATION_TIMEOUT with appSync value", func() { + + By("creating simple namespace-scoped Argo CD instance") + ns, cleanupFunc = fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + Controller: argov1beta1api.ArgoCDApplicationControllerSpec{ + AppSync: &metav1.Duration{Duration: time.Minute * 10}, + }, + }, + } + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + + By("waiting for ArgoCD CR to be reconciled and the instance to be ready") + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns.Name, + }, + } + Eventually(ss).Should(k8sFixture.ExistByName()) + Eventually(ss).Should(statefulsetFixture.HaveReplicas(1)) + Eventually(ss).Should(statefulsetFixture.HaveReadyReplicas(1)) + + By("verifying env var is added to argocd-application-controller, and that other env vars are still present") + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVar("ARGOCD_RECONCILIATION_TIMEOUT", "600s", 0)) + + Expect(len(ss.Spec.Template.Spec.Containers[0].Env)).To(BeNumerically(">", 1)) + + By("Updating appSync to 5 minutes") + argoCD.Spec.Controller.AppSync = &metav1.Duration{Duration: time.Minute * 5} + Expect(k8sClient.Update(ctx, argoCD)).To(Succeed()) + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVar("ARGOCD_RECONCILIATION_TIMEOUT", "300s", 0)) + + }) + + It("should set environment variable ARGOCD_RECONCILIATION_TIMEOUT with timeout.reconciliation value in configmap", func() { + + By("creating simple namespace-scoped Argo CD instance") + ns, cleanupFunc = fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + ExtraConfig: map[string]string{"timeout.reconciliation": "10m"}, + }, + } + + By("waiting for ArgoCD CR to be reconciled and the instance to be ready") + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + + By("Fetching ConfigMap and verifying timeout.reconciliation value") + argocdCM := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd-cm", Namespace: ns.Name}, + } + Eventually(argocdCM).Should(configmapFixture.HaveStringDataKeyValue("timeout.reconciliation", "10m")) + + By("waiting for StatefulSet to be ready") + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns.Name, + }, + } + Eventually(ss).Should(k8sFixture.ExistByName()) + Eventually(ss).Should(statefulsetFixture.HaveReplicas(1)) + Eventually(ss).Should(statefulsetFixture.HaveReadyReplicas(1)) + + By("verifying env var is added to example-argocd-application-controller, and that other env vars are still present") + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVarFromConfigMap( + "ARGOCD_RECONCILIATION_TIMEOUT", + "argocd-cm", + "timeout.reconciliation", + 0, + )) + + By("Updating timeout.reconciliation to 5 minutes and waiting for ArgoCD CR to be reconciled and the instance to be ready") + argocdFixture.Update(argoCD, func(ac *argov1beta1api.ArgoCD) { + ac.Spec.ExtraConfig["timeout.reconciliation"] = "5m" + }) + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + Eventually(argocdCM).Should(configmapFixture.HaveStringDataKeyValue("timeout.reconciliation", "5m")) + + By("waiting for StatefulSet to be ready after restart") + statefulsetFixture.Restart(ss) + Eventually(ss, "3m", "5s").Should(statefulsetFixture.HaveReadyReplicas(1)) + ss = &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns.Name, + }, + } + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVarFromConfigMap( + "ARGOCD_RECONCILIATION_TIMEOUT", + "argocd-cm", + "timeout.reconciliation", + 0, + )) + }) + + It("Validate the precedence of the appSync value over the timeout.reconciliation value in configmap", func() { + ns, cleanupFunc = fixture.CreateRandomE2ETestNamespaceWithCleanupFunc() + + By("creating ArgoCD CR with appSync value and timeout.reconciliation value in configmap") + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd", Namespace: ns.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + Controller: argov1beta1api.ArgoCDApplicationControllerSpec{ + AppSync: &metav1.Duration{Duration: time.Minute * 10}, + }, + ExtraConfig: map[string]string{"timeout.reconciliation": "15m"}, + }, + } + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + + By("waiting for ArgoCD CR to be reconciled and the instance to be ready") + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + ss := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-application-controller", + Namespace: ns.Name, + }, + } + Eventually(ss).Should(k8sFixture.ExistByName()) + Eventually(ss).Should(statefulsetFixture.HaveReadyReplicas(1)) + + By("verifying env var is added to argocd-application-controller and the appSync value is used") + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVar("ARGOCD_RECONCILIATION_TIMEOUT", "600s", 0)) + + By("Removing appSync value and waiting for ArgoCD CR to be reconciled and the instance to be ready") + argocdFixture.Update(argoCD, func(ac *argov1beta1api.ArgoCD) { + ac.Spec.Controller.AppSync = nil + }) + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + statefulsetFixture.Restart(ss) + Eventually(ss, "3m", "5s").Should(statefulsetFixture.HaveReadyReplicas(1)) + By("Fetching ConfigMap and verifying timeout.reconciliation value") + argocdCM := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "argocd-cm", Namespace: ns.Name}, + } + Eventually(argocdCM).Should(configmapFixture.HaveStringDataKeyValue("timeout.reconciliation", "15m")) + By("verifying env var is added to example-argocd-application-controller and the timeout.reconciliation value is used") + Eventually(ss).Should(statefulsetFixture.HaveContainerWithEnvVarFromConfigMap( + "ARGOCD_RECONCILIATION_TIMEOUT", + "argocd-cm", + "timeout.reconciliation", + 0, + )) + + }) + }) +})