diff --git a/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership.go b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership.go new file mode 100644 index 00000000000..68450d0eaad --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership.go @@ -0,0 +1,187 @@ +/* +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" + argocdFixture "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/argocd" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + fixtureUtils "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" + rbacv1 "k8s.io/api/rbac/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-125_validate_role_ownership", func() { + + var ( + ctx context.Context + k8sClient client.Client + ) + const ( + applicationControllerClusterRoleName = "openshift-gitops-openshift-gitops-argocd-application-controller" + applicationSetControllerClusterRoleName = "openshift-gitops-openshift-gitops-argocd-applicationset-controller" + serverClusterRoleName = "openshift-gitops-openshift-gitops-argocd-server" + applicationControllerClusterRoleBindingName = "openshift-gitops-openshift-gitops-argocd-application-controller" + applicationSetControllerClusterRoleBindingName = "openshift-gitops-openshift-gitops-argocd-applicationset-controller" + serverClusterRoleBindingName = "openshift-gitops-openshift-gitops-argocd-server" + ) + + BeforeEach(func() { + fixture.EnsureParallelCleanSlate() + k8sClient, _ = fixtureUtils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + It("validates that namespace-scoped resources do not delete a ClusterRole or ClusterRoleBinding with a matching generated name", func() { + + By("checking that the default ClusterRole and clusterroleBinding for the ArgoCD Application Controller and Server exists") + defaultControllerClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationControllerClusterRoleName, + }, + } + defaultApplicationSetControllerClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationSetControllerClusterRoleName, + }, + } + defaultServerClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: serverClusterRoleName, + }, + } + defaultControllerClusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationControllerClusterRoleBindingName, + }, + } + defaultApplicationSetControllerClusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationSetControllerClusterRoleBindingName, + }, + } + defaultServerClusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: serverClusterRoleBindingName, + }, + } + Eventually(defaultControllerClusterRole).Should(k8sFixture.ExistByName()) + Eventually(defaultApplicationSetControllerClusterRole).Should(k8sFixture.ExistByName()) + Eventually(defaultServerClusterRole).Should(k8sFixture.ExistByName()) + Eventually(defaultControllerClusterRoleBinding).Should(k8sFixture.ExistByName()) + Eventually(defaultApplicationSetControllerClusterRoleBinding).Should(k8sFixture.ExistByName()) + Eventually(defaultServerClusterRoleBinding).Should(k8sFixture.ExistByName()) + + By("fetching initial UID of the clusterrole") + initialControllerUid := defaultControllerClusterRole.GetUID() + initialApplicationSetControllerUid := defaultApplicationSetControllerClusterRole.GetUID() + initialServerUid := defaultServerClusterRole.GetUID() + initialControllerRoleBindingUid := defaultControllerClusterRoleBinding.GetUID() + initialApplicationSetControllerRoleBindingUid := defaultApplicationSetControllerClusterRoleBinding.GetUID() + initialServerRoleBindingUid := defaultServerClusterRoleBinding.GetUID() + + defaultArgocd := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-gitops", + Namespace: "openshift-gitops", + }, + } + + Eventually(defaultArgocd, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("creating new namespace scoped ArgoCD instance to create the condition where clusterrole and clusterrolebinding are deleted by namespaced scoped resources") + ns, nsCleanup := fixture.CreateNamespaceWithCleanupFunc("gitops") + defer nsCleanup() + + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-gitops-openshift", + Namespace: ns.Name, + }, + } + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("checking that the default ClusterRole for the ArgoCD Application Controller still exists") + afterReconcileControllerClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationControllerClusterRoleName, + }, + } + afterReconcileApplicationSetControllerCR := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationSetControllerClusterRoleName, + }, + } + afterReconcileServerCR := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: serverClusterRoleName, + }, + } + afterReconcileControllerCRB := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationControllerClusterRoleBindingName, + }, + } + afterReconcileApplicationSetControllerCRB := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: applicationSetControllerClusterRoleBindingName, + }, + } + afterReconcileServerCRB := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: serverClusterRoleBindingName, + }, + } + + Eventually(afterReconcileControllerClusterRole).Should(k8sFixture.ExistByName()) + Eventually(afterReconcileApplicationSetControllerCR).Should(k8sFixture.ExistByName()) + Eventually(afterReconcileServerCR).Should(k8sFixture.ExistByName()) + + Eventually(afterReconcileControllerCRB).Should(k8sFixture.ExistByName()) + Eventually(afterReconcileApplicationSetControllerCRB).Should(k8sFixture.ExistByName()) + Eventually(afterReconcileServerCRB).Should(k8sFixture.ExistByName()) + + By("fetching UID of the clusterrole after reconciliation") + afterReconcileControllerUid := afterReconcileControllerClusterRole.GetUID() + afterReconcileApplicationSetControllerUid := afterReconcileApplicationSetControllerCR.GetUID() + afterReconcileServerUid := afterReconcileServerCR.GetUID() + + afterReconcileControllerRBUid := afterReconcileControllerCRB.GetUID() + afterReconcileApplicationSetControllerRBUid := afterReconcileApplicationSetControllerCRB.GetUID() + afterReconcileServerRBUid := afterReconcileServerCRB.GetUID() + + By("comparing the UID to check if the ClusterRole was recreated") + Expect(initialControllerUid).To(Equal(afterReconcileControllerUid)) + Expect(initialApplicationSetControllerUid).To(Equal(afterReconcileApplicationSetControllerUid)) + Expect(initialServerUid).To(Equal(afterReconcileServerUid)) + + Expect(initialControllerRoleBindingUid).To(Equal(afterReconcileControllerRBUid)) + Expect(initialApplicationSetControllerRoleBindingUid).To(Equal(afterReconcileApplicationSetControllerRBUid)) + Expect(initialServerRoleBindingUid).To(Equal(afterReconcileServerRBUid)) + + }) + + }) +}) diff --git a/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_agent_test.go b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_agent_test.go new file mode 100644 index 00000000000..b50e9882f17 --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_agent_test.go @@ -0,0 +1,246 @@ +/* +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" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + argov1beta1api "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + argocdFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/argocd" + k8sFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/k8s" + fixtureUtils "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" +) + +var _ = Describe("GitOps Operator Sequential E2E Tests", func() { + + const ( + clusterRoleName = "argocd-agent-argocd-agent-agent-agent" + clusterRoleBindingName = "argocd-agent-argocd-agent-agent-agent" + ) + + Context("1-125_validate_role_ownership_agent_agent", func() { + var ( + k8sClient client.Client + ctx context.Context + argoCD *argov1beta1api.ArgoCD + ns *corev1.Namespace + cleanupFunc func() + serviceNames []string + agentDeployment *appsv1.Deployment + ) + + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + fixture.SetEnvInOperatorSubscriptionOrDeployment("ARGOCD_CLUSTER_CONFIG_NAMESPACES", "openshift-gitops, argocd-agent") + + k8sClient, _ = fixtureUtils.GetE2ETestKubeClient() + ctx = context.Background() + ns, cleanupFunc = fixture.CreateNamespaceWithCleanupFunc("argocd-agent") + + // Define ArgoCD CR with agent enabled + argoCD = &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-agent", + Namespace: ns.Name, + }, + Spec: argov1beta1api.ArgoCDSpec{ + Controller: argov1beta1api.ArgoCDApplicationControllerSpec{ + Enabled: ptr.To(false), + }, + Server: argov1beta1api.ArgoCDServerSpec{ + Enabled: ptr.To(false), + }, + ArgoCDAgent: &argov1beta1api.ArgoCDAgentSpec{ + Agent: &argov1beta1api.AgentSpec{ + Enabled: ptr.To(true), + Creds: "mtls:any", + LogLevel: "info", + LogFormat: "text", + Client: &argov1beta1api.AgentClientSpec{ + PrincipalServerAddress: "argocd-agent-principal.example.com", + PrincipalServerPort: "443", + Mode: string(argov1beta1api.AgentModeManaged), + EnableWebSocket: ptr.To(false), + EnableCompression: ptr.To(false), + KeepAliveInterval: "30s", + }, + TLS: &argov1beta1api.AgentTLSSpec{ + SecretName: agentClientTLSSecretName, + RootCASecretName: agentRootCASecretName, + Insecure: ptr.To(false), + }, + Redis: &argov1beta1api.AgentRedisSpec{ + ServerAddress: fmt.Sprintf("%s-%s:%d", "argocd-agent", "redis", common.ArgoCDDefaultRedisPort), + }, + }, + }, + }, + } + + serviceNames = []string{ + "argocd-agent-agent-agent-metrics", + "argocd-agent-agent-agent-healthz", + "argocd-agent-redis", + } + + agentDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-agent-agent-agent", + Namespace: ns.Name, + }, + } + + }) + + AfterEach(func() { + By("Cleanup namespace") + if cleanupFunc != nil { + cleanupFunc() + } + }) + + verifyExpectedResourcesExist := func(ns *corev1.Namespace) { + + By("verifying expected resources exist") + for _, serviceName := range serviceNames { + + By("verifying Service '" + serviceName + "' exists and is a LoadBalancer or ClusterIP depending on which service") + + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: ns.Name, + }, + } + Eventually(service).Should(k8sFixture.ExistByName()) + } + By("verifying primary agent Deployment has expected labels") + + Eventually(agentDeployment).Should(k8sFixture.ExistByName()) + Eventually(agentDeployment).Should(k8sFixture.HaveLabelWithValue("app.kubernetes.io/component", string(argov1beta1api.AgentComponentTypeAgent))) + Eventually(agentDeployment).Should(k8sFixture.HaveLabelWithValue("app.kubernetes.io/managed-by", "argocd-agent")) + Eventually(agentDeployment).Should(k8sFixture.HaveLabelWithValue("app.kubernetes.io/name", "argocd-agent-agent-agent")) + Eventually(agentDeployment).Should(k8sFixture.HaveLabelWithValue("app.kubernetes.io/part-of", "argocd-agent")) + + } + + It("validates that namespace-scoped resources do not delete a ClusterRole or ClusterRoleBinding with a matching generated name for Agent", func() { + + By("creating ArgoCD instance with agent enabled") + 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()) + + By("verifying expected resources are created with correct values") + verifyExpectedResourcesExist(ns) + + By("verifying ClusterRole and ClusterRoleBinding for agent exist with correct names") + + clusterRole := &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleName}, clusterRole) + }).Should(Succeed()) + initialClusterRoleUid := clusterRole.GetUID() + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleBindingName}, clusterRoleBinding) + }).Should(Succeed()) + initialClusterRoleBindingUid := clusterRoleBinding.GetUID() + + By("Create namespace-scoped ArgoCD instance namespace") + nsScoped, cleanupFuncScoped := fixture.CreateNamespaceWithCleanupFunc("agent") + defer cleanupFuncScoped() + + argoCD1 := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-agent-argocd", + Namespace: nsScoped.Name, + }, + Spec: argov1beta1api.ArgoCDSpec{ + Controller: argov1beta1api.ArgoCDApplicationControllerSpec{ + Enabled: ptr.To(false), + }, + Server: argov1beta1api.ArgoCDServerSpec{ + Enabled: ptr.To(false), + }, + ArgoCDAgent: &argov1beta1api.ArgoCDAgentSpec{ + Agent: &argov1beta1api.AgentSpec{ + Enabled: ptr.To(true), + Creds: "mtls:any", + LogLevel: "info", + LogFormat: "text", + Client: &argov1beta1api.AgentClientSpec{ + PrincipalServerAddress: "argocd-agent-principal.example.com", + PrincipalServerPort: "443", + Mode: string(argov1beta1api.AgentModeManaged), + EnableWebSocket: ptr.To(false), + EnableCompression: ptr.To(false), + KeepAliveInterval: "30s", + }, + TLS: &argov1beta1api.AgentTLSSpec{ + SecretName: agentClientTLSSecretName, + RootCASecretName: agentRootCASecretName, + Insecure: ptr.To(false), + }, + Redis: &argov1beta1api.AgentRedisSpec{ + ServerAddress: fmt.Sprintf("%s-%s:%d", "argocd-agent-argocd", "redis", common.ArgoCDDefaultRedisPort), + }, + }, + }, + }, + } + + By("Create namespace-scoped ArgoCD instance with agent") + + Expect(k8sClient.Create(ctx, argoCD1)).To(Succeed()) + Eventually(argoCD1, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("Verifying UID of ClusterRole and ClusterRoleBinding to ensure they are not deleted by namespaced scoped resources") + afterReconcileClusterRole := &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleName}, afterReconcileClusterRole) + }).Should(Succeed(), "ClusterRole should exist and be fetchable") + afterReconcileClusterRoleUid := afterReconcileClusterRole.GetUID() + + afterReconcileClusterRB := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleBindingName}, afterReconcileClusterRB) + }).Should(Succeed(), "ClusterRoleBinding should exist and be fetchable") + afterReconcileClusterRBUid := afterReconcileClusterRB.GetUID() + + Expect(afterReconcileClusterRoleUid).To(Equal(initialClusterRoleUid)) + Expect(afterReconcileClusterRBUid).To(Equal(initialClusterRoleBindingUid)) + + }) + + }) + +}) diff --git a/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_principal_test.go b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_principal_test.go new file mode 100644 index 00000000000..5d3161df8b7 --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_agent_principal_test.go @@ -0,0 +1,344 @@ +/* +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" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + routev1 "github.com/openshift/api/route/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + argov1beta1api "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + agentFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/agent" + argocdFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/argocd" + deploymentFixture "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/deployment" + fixtureUtils "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture/utils" +) + +var _ = Describe("GitOps Operator Sequential E2E Tests", func() { + + Context("1-125_validate_role_ownership_agent_principal", func() { + + var ( + k8sClient client.Client + ctx context.Context + argoCD *argov1beta1api.ArgoCD + ns *corev1.Namespace + cleanupFunc func() + serviceAccount *corev1.ServiceAccount + role *rbacv1.Role + roleBinding *rbacv1.RoleBinding + clusterRole *rbacv1.ClusterRole + clusterRoleBinding *rbacv1.ClusterRoleBinding + serviceNames []string + deploymentNames []string + principalDeployment *appsv1.Deployment + secretNames agentFixture.AgentSecretNames + principalNetworkPolicy *networkingv1.NetworkPolicy + principalRoute *routev1.Route + resourceProxyServiceName string + ) + const ( + argoCDName = "argocd-principal" + argoCDAgentPrincipalName = "argocd-principal-agent-principal" // argoCDName + "-agent-principal" + principalMetricsServiceFmt = "%s-agent-principal-metrics" + principalRedisProxyServiceFmt = "%s-agent-principal-redisproxy" + principalHealthzServiceFmt = "%s-agent-principal-healthz" + clusterRoleName = "argocd-principal-argocd-principal-agent-principal" + clusterRoleBindingName = "argocd-principal-argocd-principal-agent-principal" + nsScopedArgoCDName = "argocd-principal-argocd" + ) + + BeforeEach(func() { + fixture.EnsureSequentialCleanSlate() + fixture.SetEnvInOperatorSubscriptionOrDeployment("ARGOCD_CLUSTER_CONFIG_NAMESPACES", "openshift-gitops, argocd-principal") + + k8sClient, _ = fixtureUtils.GetE2ETestKubeClient() + ctx = context.Background() + ns, cleanupFunc = fixture.CreateNamespaceWithCleanupFunc("argocd-principal") + + // Define ArgoCD CR with principal enabled + argoCD = &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: argoCDName, + Namespace: ns.Name, + }, + Spec: argov1beta1api.ArgoCDSpec{ + Controller: argov1beta1api.ArgoCDApplicationControllerSpec{ + Enabled: ptr.To(false), + }, + ArgoCDAgent: &argov1beta1api.ArgoCDAgentSpec{ + Principal: &argov1beta1api.PrincipalSpec{ + Enabled: ptr.To(true), + Auth: "mtls:CN=([^,]+)", + LogLevel: "info", + Namespace: &argov1beta1api.PrincipalNamespaceSpec{ + AllowedNamespaces: []string{ + "*", + }, + }, + TLS: &argov1beta1api.PrincipalTLSSpec{ + InsecureGenerate: ptr.To(true), + }, + JWT: &argov1beta1api.PrincipalJWTSpec{ + InsecureGenerate: ptr.To(true), + }, + Server: &argov1beta1api.PrincipalServerSpec{ + KeepAliveMinInterval: "30s", + }, + }, + }, + SourceNamespaces: []string{ + "agent-managed", + "agent-autonomous", + }, + }, + } + + // Define required resources for principal pod + serviceAccount = &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: argoCDAgentPrincipalName, + Namespace: ns.Name, + }, + } + + role = &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: argoCDAgentPrincipalName, + Namespace: ns.Name, + }, + } + + roleBinding = &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: argoCDAgentPrincipalName, + Namespace: ns.Name, + }, + } + + clusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterRoleName, + }, + } + + clusterRoleBinding = &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterRoleBindingName, + }, + } + + secretNames = agentFixture.AgentSecretNames{ + JWTSecretName: agentJWTSecretName, + PrincipalTLSSecretName: agentPrincipalTLSSecretName, + RootCASecretName: agentRootCASecretName, + ResourceProxyTLSSecretName: agentResourceProxyTLSSecretName, + RedisInitialPasswordSecretName: "argocd-principal-redis-initial-password", + } + + resourceProxyServiceName = fmt.Sprintf("%s-agent-principal-resource-proxy", argoCDName) + serviceNames = []string{ + argoCDAgentPrincipalName, + fmt.Sprintf(principalMetricsServiceFmt, argoCDName), + fmt.Sprintf("%s-redis", argoCDName), + fmt.Sprintf("%s-repo-server", argoCDName), + fmt.Sprintf("%s-server", argoCDName), + fmt.Sprintf(principalRedisProxyServiceFmt, argoCDName), + resourceProxyServiceName, + fmt.Sprintf(principalHealthzServiceFmt, argoCDName), + } + deploymentNames = []string{fmt.Sprintf("%s-redis", argoCDName), fmt.Sprintf("%s-repo-server", argoCDName), fmt.Sprintf("%s-server", argoCDName)} + + principalDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: argoCDAgentPrincipalName, + Namespace: ns.Name, + }, + } + + principalRoute = &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-agent-principal", argoCDName), + Namespace: ns.Name, + }, + } + principalNetworkPolicy = &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-agent-principal-network-policy", argoCDName), + Namespace: ns.Name, + }, + } + + principalNetworkPolicy = &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-agent-principal-network-policy", argoCDName), + Namespace: ns.Name, + }, + } + + }) + + AfterEach(func() { + By("Cleanup cluster-scoped resources") + if clusterRole != nil { + _ = k8sClient.Delete(ctx, clusterRole) + } + if clusterRoleBinding != nil { + _ = k8sClient.Delete(ctx, clusterRoleBinding) + } + + By("Cleanup namespace") + if cleanupFunc != nil { + cleanupFunc() + } + }) + + createRequiredSecrets := func(namespace *corev1.Namespace, additionalPrincipalSANs ...string) { + agentFixture.CreateRequiredSecrets(agentFixture.PrincipalSecretsConfig{ + PrincipalNamespaceName: namespace.Name, + PrincipalServiceName: argoCDAgentPrincipalName, + ResourceProxyServiceName: resourceProxyServiceName, + JWTSecretName: secretNames.JWTSecretName, + PrincipalTLSSecretName: secretNames.PrincipalTLSSecretName, + RootCASecretName: secretNames.RootCASecretName, + ResourceProxyTLSSecretName: secretNames.ResourceProxyTLSSecretName, + AdditionalPrincipalSANs: additionalPrincipalSANs, + }) + } + + verifyExpectedResourcesExist := func(namespace *corev1.Namespace, expectRoute ...bool) { + var expectRoutePtr *bool + if len(expectRoute) > 0 { + expectRoutePtr = ptr.To(expectRoute[0]) + } + + agentFixture.VerifyExpectedResourcesExist(agentFixture.VerifyExpectedResourcesExistParams{ + Namespace: namespace, + ArgoCDAgentPrincipalName: argoCDAgentPrincipalName, + ArgoCDName: argoCDName, + ServiceAccount: serviceAccount, + Role: role, + RoleBinding: roleBinding, + ClusterRole: clusterRole, + ClusterRoleBinding: clusterRoleBinding, + PrincipalDeployment: principalDeployment, + PrincipalRoute: principalRoute, + PrincipalNetworkPolicy: principalNetworkPolicy, + SecretNames: secretNames, + ServiceNames: serviceNames, + DeploymentNames: deploymentNames, + ExpectRoute: expectRoutePtr, + }) + } + + It("namespace-scoped resources do not delete a ClusterRole or ClusterRoleBinding with a matching generated name for Agent Principal", func() { + By("Create ArgoCD instance") + + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + + By("Verify expected resources are created for principal pod") + + verifyExpectedResourcesExist(ns) + + By("Create required secrets and certificates for principal pod to start properly") + + createRequiredSecrets(ns) + + By("Verify principal pod starts successfully by checking logs") + + agentFixture.VerifyLogs(argoCDAgentPrincipalName, ns.Name, []string{ + "Starting metrics server", + "Redis proxy started", + "Application informer synced and ready", + "AppProject informer synced and ready", + "Resource proxy started", + "Namespace informer synced and ready", + "Starting healthz server", + }) + + By("verify that deployment is in Ready state") + + Eventually(principalDeployment, "120s", "5s").Should(deploymentFixture.HaveReadyReplicas(1), "Principal deployment should become ready") + + By("Fetch Uid of clusterrole and clusterrolebinding") + clusterRole = &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleName}, clusterRole) + }).Should(Succeed(), "ClusterRole should exist") + initialClusterRoleUid := clusterRole.GetUID() + + clusterRoleBinding = &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleBindingName}, clusterRoleBinding) + }).Should(Succeed(), "ClusterRoleBinding should exist") + initialClusterRoleBindingUid := clusterRoleBinding.GetUID() + + By("Create namespace-scoped ArgoCD instance namespace") + + // Create namespace for hosting namespace-scoped ArgoCD instance with principal + nsScoped, cleanupFuncScoped := fixture.CreateNamespaceWithCleanupFunc("principal") + defer cleanupFuncScoped() + + // Update namespace in ArgoCD CR + argoCD.ResourceVersion = "" + argoCD.UID = "" + argoCD.Name = "argocd-principal-argocd" + argoCD.Namespace = nsScoped.Name + + // Update namespace in resource references + serviceAccount.Namespace = nsScoped.Name + role.Namespace = nsScoped.Name + roleBinding.Namespace = nsScoped.Name + principalDeployment.Namespace = nsScoped.Name + principalRoute.Namespace = nsScoped.Name + principalNetworkPolicy.Namespace = nsScoped.Name + + By("Create namespace-scoped ArgoCD instance with principal") + + Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) + Eventually(argoCD, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("Verify UID of ClusterRole and ClusterRoleBinding") + afterReconcileClusterRole := &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleName}, afterReconcileClusterRole) + }).Should(Succeed(), "ClusterRole should exist and be fetchable") + afterReconcileClusterRoleUid := afterReconcileClusterRole.GetUID() + + afterReconcileClusterRB := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleBindingName}, afterReconcileClusterRB) + }).Should(Succeed(), "ClusterRoleBinding should exist and be fetchable") + afterReconcileClusterRBUid := afterReconcileClusterRB.GetUID() + + Expect(afterReconcileClusterRoleUid).To(Equal(initialClusterRoleUid)) + Expect(afterReconcileClusterRBUid).To(Equal(initialClusterRoleBindingUid)) + + }) + + }) +}) diff --git a/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_image_updater.go b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_image_updater.go new file mode 100644 index 00000000000..895b4f87d03 --- /dev/null +++ b/test/openshift/e2e/ginkgo/sequential/1-125_validate_role_ownership_image_updater.go @@ -0,0 +1,154 @@ +/* +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" + argocdFixture "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/argocd" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/redhat-developer/gitops-operator/test/openshift/e2e/ginkgo/fixture" + deplFixture "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" + 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" + rbacv1 "k8s.io/api/rbac/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-125_validate_role_ownership_image_updater", func() { + + var ( + ctx context.Context + k8sClient client.Client + ns *corev1.Namespace + cleanupFunc func() + ) + const ( + imageUpdaterControllerClusterRoleName = "image-updater-image-updater-argocd-image-updater-controller" + imageUpdaterControllerClusterRoleBindingName = "image-updater-image-updater-argocd-image-updater-controller" + ) + BeforeEach(func() { + fixture.EnsureParallelCleanSlate() + k8sClient, _ = fixtureUtils.GetE2ETestKubeClient() + ctx = context.Background() + }) + + AfterEach(func() { + if cleanupFunc != nil { + cleanupFunc() + } + + fixture.OutputDebugOnFail(ns) + + }) + + It("validates that namespace-scoped resources do not delete a ClusterRole or ClusterRoleBinding with a matching generated name for Image Updater", func() { + By("create a simple namespace scoped ArgoCD instance with image updater enabled and watch namespace set to '*'") + ns, cleanupFunc = fixture.CreateNamespaceWithCleanupFunc("image-updater") + + fixture.SetEnvInOperatorSubscriptionOrDeployment("ARGOCD_CLUSTER_CONFIG_NAMESPACES", "openshift-gitops,image-updater") + + argoCD := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{Name: "image-updater", Namespace: ns.Name}, + Spec: argov1beta1api.ArgoCDSpec{ + ImageUpdater: argov1beta1api.ArgoCDImageUpdaterSpec{ + Env: []corev1.EnvVar{ + { + Name: "IMAGE_UPDATER_WATCH_NAMESPACES", + Value: "*", + }, + }, + Enabled: true}, + }, + } + + 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()) + + By("verifying image updater workload has started argocd-image-updater-controller") + depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "image-updater-argocd-image-updater-controller", Namespace: "image-updater"}} + Eventually(depl, "2m", "5s").Should(k8sFixture.ExistByName(), "Deployment image-updater-argocd-image-updater-controller did not exist within timeout") + Eventually(depl, "2m", "5s").Should(deplFixture.HaveReplicas(1), "Deployment image-updater-argocd-image-updater-controller did not have correct replicas within timeout") + Eventually(depl, "3m", "5s").Should(deplFixture.HaveReadyReplicas(1), "Deployment image-updater-argocd-image-updater-controller was not ready within timeout") + + By("Verify ClusterRole and ClusterRoleBinding for ArgoCD Image Updater Controller") + clusterRole := &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: imageUpdaterControllerClusterRoleName}, clusterRole) + }).Should(Succeed(), "ClusterRole should exist and be fetchable") + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: imageUpdaterControllerClusterRoleBindingName}, clusterRoleBinding) + }).Should(Succeed(), "ClusterRoleBinding should exist and be fetchable") + initialClusterRoleUid := clusterRole.GetUID() + initialClusterRoleBindingUid := clusterRoleBinding.GetUID() + + By("Create ArgoCD instance in a new namespace") + + ns1, nsCleanup := fixture.CreateNamespaceWithCleanupFunc("updater") + defer nsCleanup() + + argoCD1 := &argov1beta1api.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: "image-updater-image", + Namespace: ns1.Name, + }, + Spec: argov1beta1api.ArgoCDSpec{ + ImageUpdater: argov1beta1api.ArgoCDImageUpdaterSpec{ + Env: []corev1.EnvVar{ + { + Name: "IMAGE_UPDATER_LOGLEVEL", + Value: "trace", + }, + }, + Enabled: true, + }, + }, + } + Expect(k8sClient.Create(ctx, argoCD1)).To(Succeed()) + + Eventually(argoCD1, "5m", "5s").Should(argocdFixture.BeAvailable()) + + By("Verify UID of ClusterRole and ClusterRoleBinding remain the same after creating second ArgoCD instance to ensure they are not deleted by namespaced scoped resources") + afterReconcileClusterRole := &rbacv1.ClusterRole{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: imageUpdaterControllerClusterRoleName}, afterReconcileClusterRole) + }).Should(Succeed(), "ClusterRole should exist and be fetchable") + afterReconcileClusterRoleUid := afterReconcileClusterRole.GetUID() + + afterReconcileClusterRB := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: imageUpdaterControllerClusterRoleBindingName}, afterReconcileClusterRB) + }).Should(Succeed(), "ClusterRoleBinding should exist and be fetchable") + + afterReconcileClusterRBUid := afterReconcileClusterRB.GetUID() + + Expect(afterReconcileClusterRoleUid).To(Equal(initialClusterRoleUid)) + Expect(afterReconcileClusterRBUid).To(Equal(initialClusterRoleBindingUid)) + }) + }) +})