diff --git a/argocd-operator/api/v1alpha1/argocd_conversion.go b/argocd-operator/api/v1alpha1/argocd_conversion.go index 9a050807d7b..e5be4fdb224 100644 --- a/argocd-operator/api/v1alpha1/argocd_conversion.go +++ b/argocd-operator/api/v1alpha1/argocd_conversion.go @@ -1220,6 +1220,7 @@ func ConvertAlphaToBetaPrincipalSelfRegistration(src *PrincipalSelfRegistrationS dst = &v1beta1.PrincipalSelfRegistrationSpec{ Enabled: src.Enabled, ClientCertSecretName: src.ClientCertSecretName, + ClusterSecretLabels: src.ClusterSecretLabels, } } return dst @@ -1231,6 +1232,7 @@ func ConvertBetaToAlphaPrincipalSelfRegistration(src *v1beta1.PrincipalSelfRegis dst = &PrincipalSelfRegistrationSpec{ Enabled: src.Enabled, ClientCertSecretName: src.ClientCertSecretName, + ClusterSecretLabels: src.ClusterSecretLabels, } } return dst diff --git a/argocd-operator/api/v1alpha1/argocd_conversion_test.go b/argocd-operator/api/v1alpha1/argocd_conversion_test.go index 8a3f143479c..bbb885f7339 100644 --- a/argocd-operator/api/v1alpha1/argocd_conversion_test.go +++ b/argocd-operator/api/v1alpha1/argocd_conversion_test.go @@ -848,6 +848,7 @@ func TestAlphaToBetaConversion(t *testing.T) { SelfRegistration: &PrincipalSelfRegistrationSpec{ Enabled: &selfRegEnabled, ClientCertSecretName: "argocd-agent-shared-client-cert", + ClusterSecretLabels: map[string]string{"foo": "bar"}, }, }, } @@ -861,6 +862,7 @@ func TestAlphaToBetaConversion(t *testing.T) { SelfRegistration: &v1beta1.PrincipalSelfRegistrationSpec{ Enabled: &selfRegEnabled, ClientCertSecretName: "argocd-agent-shared-client-cert", + ClusterSecretLabels: map[string]string{"foo": "bar"}, }, }, } @@ -1310,6 +1312,7 @@ func TestBetaToAlphaConversion(t *testing.T) { SelfRegistration: &v1beta1.PrincipalSelfRegistrationSpec{ Enabled: &selfRegEnabled, ClientCertSecretName: "argocd-agent-shared-client-cert", + ClusterSecretLabels: map[string]string{"foo": "bar"}, }, }, } @@ -1323,6 +1326,7 @@ func TestBetaToAlphaConversion(t *testing.T) { SelfRegistration: &PrincipalSelfRegistrationSpec{ Enabled: &selfRegEnabled, ClientCertSecretName: "argocd-agent-shared-client-cert", + ClusterSecretLabels: map[string]string{"foo": "bar"}, }, }, } diff --git a/argocd-operator/api/v1alpha1/argocd_types.go b/argocd-operator/api/v1alpha1/argocd_types.go index ff036f157d9..d243dfa9700 100644 --- a/argocd-operator/api/v1alpha1/argocd_types.go +++ b/argocd-operator/api/v1alpha1/argocd_types.go @@ -1302,6 +1302,9 @@ type PrincipalSelfRegistrationSpec struct { // ClientCertSecretName is the name of the TLS secret containing shared client cert // for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). ClientCertSecretName string `json:"clientCertSecretName,omitempty"` + + // ClusterSecretLabels is the set of labels to apply to cluster secrets created for agents. Ex: {"foo": "bar", "bar": "baz"} + ClusterSecretLabels map[string]string `json:"clusterSecretLabels,omitempty"` } type PrincipalServerSpec struct { diff --git a/argocd-operator/api/v1alpha1/zz_generated.deepcopy.go b/argocd-operator/api/v1alpha1/zz_generated.deepcopy.go index adb7b7d76b6..fc1420a4ceb 100644 --- a/argocd-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/argocd-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -1808,6 +1808,13 @@ func (in *PrincipalSelfRegistrationSpec) DeepCopyInto(out *PrincipalSelfRegistra *out = new(bool) **out = **in } + if in.ClusterSecretLabels != nil { + in, out := &in.ClusterSecretLabels, &out.ClusterSecretLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrincipalSelfRegistrationSpec. diff --git a/argocd-operator/api/v1beta1/argocd_types.go b/argocd-operator/api/v1beta1/argocd_types.go index ad8614a3978..269c0315573 100644 --- a/argocd-operator/api/v1beta1/argocd_types.go +++ b/argocd-operator/api/v1beta1/argocd_types.go @@ -1507,6 +1507,9 @@ type PrincipalSelfRegistrationSpec struct { // ClientCertSecretName is the name of the TLS secret containing shared client cert // for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). ClientCertSecretName string `json:"clientCertSecretName,omitempty"` + + // ClusterSecretLabels is the set of labels to apply to cluster secrets created for agents. Ex: {"foo": "bar", "bar": "baz"} + ClusterSecretLabels map[string]string `json:"clusterSecretLabels,omitempty"` } type PrincipalServerSpec struct { diff --git a/argocd-operator/api/v1beta1/zz_generated.deepcopy.go b/argocd-operator/api/v1beta1/zz_generated.deepcopy.go index 75edb36203c..da75d5f5edc 100644 --- a/argocd-operator/api/v1beta1/zz_generated.deepcopy.go +++ b/argocd-operator/api/v1beta1/zz_generated.deepcopy.go @@ -2022,6 +2022,13 @@ func (in *PrincipalSelfRegistrationSpec) DeepCopyInto(out *PrincipalSelfRegistra *out = new(bool) **out = **in } + if in.ClusterSecretLabels != nil { + in, out := &in.ClusterSecretLabels, &out.ClusterSecretLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrincipalSelfRegistrationSpec. diff --git a/argocd-operator/controllers/argocdagent/deployment.go b/argocd-operator/controllers/argocdagent/deployment.go index f1be968a2c5..1d8b55a6bf8 100644 --- a/argocd-operator/controllers/argocdagent/deployment.go +++ b/argocd-operator/controllers/argocdagent/deployment.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "reflect" + "sort" "strconv" "strings" @@ -441,6 +442,9 @@ func buildPrincipalContainerEnv(cr *argoproj.ArgoCD, centralTLSProfile tlsProfil }, { Name: EnvArgoCDPrincipalSelfRegistrationClientCertSecret, Value: getPrincipalSelfRegistrationClientCertSecret(cr), + }, { + Name: EnvArgoCDPrincipalSelfRegistrationSecretLabels, + Value: getPrincipalSelfRegistrationSecretLabels(cr), }, { Name: EnvArgoCDPrincipalResourceProxyAddress, Value: getPrincipalResourceProxyAddress(cr), @@ -487,6 +491,7 @@ const ( EnvArgoCDPrincipalCipherSuites = "ARGOCD_PRINCIPAL_TLS_CIPHERSUITES" EnvArgoCDPrincipalEnableSelfClusterRegistration = "ARGOCD_PRINCIPAL_ENABLE_SELF_CLUSTER_REGISTRATION" EnvArgoCDPrincipalSelfRegistrationClientCertSecret = "ARGOCD_PRINCIPAL_SELF_REGISTRATION_CLIENT_CERT_SECRET" + EnvArgoCDPrincipalSelfRegistrationSecretLabels = "ARGOCD_PRINCIPAL_SELF_REGISTRATION_SECRET_LABELS" EnvArgoCDPrincipalResourceProxyAddress = "ARGOCD_PRINCIPAL_RESOURCE_PROXY_ADDRESS" EnvArgoCDPrincipalTLSCurvePreferences = "ARGOCD_PRINCIPAL_TLS_CURVE_PREFERENCES" ) @@ -755,6 +760,23 @@ func getPrincipalSelfRegistrationClientCertSecret(cr *argoproj.ArgoCD) string { return "" } +func getPrincipalSelfRegistrationSecretLabels(cr *argoproj.ArgoCD) string { + if !hasSelfRegistration(cr) || len(cr.Spec.ArgoCDAgent.Principal.SelfRegistration.ClusterSecretLabels) == 0 { + return "" + } + labels := cr.Spec.ArgoCDAgent.Principal.SelfRegistration.ClusterSecretLabels + keys := make([]string, 0, len(labels)) + for k := range labels { + keys = append(keys, k) + } + sort.Strings(keys) + pairs := make([]string, 0, len(labels)) + for _, k := range keys { + pairs = append(pairs, k+"="+labels[k]) + } + return strings.Join(pairs, ",") +} + func getPrincipalResourceProxyAddress(cr *argoproj.ArgoCD) string { return fmt.Sprintf("%s:%d", generateAgentResourceName(cr.Name, string(argoproj.AgentComponentTypePrincipal)+"-resource-proxy"), diff --git a/argocd-operator/controllers/argocdagent/deployment_test.go b/argocd-operator/controllers/argocdagent/deployment_test.go index 9e2c414be9b..0b48a328aa2 100644 --- a/argocd-operator/controllers/argocdagent/deployment_test.go +++ b/argocd-operator/controllers/argocdagent/deployment_test.go @@ -966,6 +966,21 @@ func withSelfRegistration(enabled bool, clientCertSecretName string) argoCDOpt { } } +func withSelfRegistrationClusterSecretLabels(labels map[string]string) argoCDOpt { + return func(a *argoproj.ArgoCD) { + if a.Spec.ArgoCDAgent == nil { + a.Spec.ArgoCDAgent = &argoproj.ArgoCDAgentSpec{} + } + if a.Spec.ArgoCDAgent.Principal == nil { + a.Spec.ArgoCDAgent.Principal = &argoproj.PrincipalSpec{} + } + if a.Spec.ArgoCDAgent.Principal.SelfRegistration == nil { + a.Spec.ArgoCDAgent.Principal.SelfRegistration = &argoproj.PrincipalSelfRegistrationSpec{} + } + a.Spec.ArgoCDAgent.Principal.SelfRegistration.ClusterSecretLabels = labels + } +} + func TestGetPrincipalEnableSelfClusterRegistration(t *testing.T) { tests := []struct { name string @@ -1042,6 +1057,7 @@ func TestBuildPrincipalContainerEnv_SelfRegistration(t *testing.T) { cr *argoproj.ArgoCD expectedEnabled string expectedClientCertSecret string + expectedSecretLabels string expectedResourceProxyAddr string }{ { @@ -1049,6 +1065,7 @@ func TestBuildPrincipalContainerEnv_SelfRegistration(t *testing.T) { cr: makeTestArgoCD(withPrincipalEnabled(true)), expectedEnabled: "false", expectedClientCertSecret: "", + expectedSecretLabels: "", expectedResourceProxyAddr: generateAgentResourceName(testArgoCDName, testCompName+"-resource-proxy") + ":9090", }, { @@ -1056,6 +1073,17 @@ func TestBuildPrincipalContainerEnv_SelfRegistration(t *testing.T) { cr: makeTestArgoCD(withPrincipalEnabled(true), withSelfRegistration(true, "argocd-agent-shared-client-cert")), expectedEnabled: "true", expectedClientCertSecret: "argocd-agent-shared-client-cert", + expectedSecretLabels: "", + expectedResourceProxyAddr: generateAgentResourceName(testArgoCDName, testCompName+"-resource-proxy") + ":9090", + }, + { + name: "self-registration enabled with cluster secret labels", + cr: makeTestArgoCD(withPrincipalEnabled(true), + withSelfRegistration(true, "argocd-agent-shared-client-cert"), + withSelfRegistrationClusterSecretLabels(map[string]string{"foo": "bar"})), + expectedEnabled: "true", + expectedClientCertSecret: "argocd-agent-shared-client-cert", + expectedSecretLabels: "foo=bar", expectedResourceProxyAddr: generateAgentResourceName(testArgoCDName, testCompName+"-resource-proxy") + ":9090", }, } @@ -1072,8 +1100,55 @@ func TestBuildPrincipalContainerEnv_SelfRegistration(t *testing.T) { "ARGOCD_PRINCIPAL_ENABLE_SELF_CLUSTER_REGISTRATION mismatch") assert.Equal(t, tt.expectedClientCertSecret, envMap[EnvArgoCDPrincipalSelfRegistrationClientCertSecret], "ARGOCD_PRINCIPAL_SELF_REGISTRATION_CLIENT_CERT_SECRET mismatch") + assert.Equal(t, tt.expectedSecretLabels, envMap[EnvArgoCDPrincipalSelfRegistrationSecretLabels], + "ARGOCD_PRINCIPAL_SELF_REGISTRATION_SECRET_LABELS mismatch") assert.Equal(t, tt.expectedResourceProxyAddr, envMap[EnvArgoCDPrincipalResourceProxyAddress], "ARGOCD_PRINCIPAL_RESOURCE_PROXY_ADDRESS mismatch") }) } } + +func TestGetPrincipalSelfRegistrationSecretLabels(t *testing.T) { + tests := []struct { + name string + cr *argoproj.ArgoCD + expected string + }{ + { + name: "no self-registration configured", + cr: makeTestArgoCD(withPrincipalEnabled(true)), + expected: "", + }, + { + name: "self-registration set but no cluster secret labels", + cr: makeTestArgoCD(withPrincipalEnabled(true), withSelfRegistration(true, "")), + expected: "", + }, + { + name: "single label", + cr: makeTestArgoCD(withPrincipalEnabled(true), + withSelfRegistrationClusterSecretLabels(map[string]string{"foo": "bar"})), + expected: "foo=bar", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, getPrincipalSelfRegistrationSecretLabels(tt.cr)) + }) + } +} + +func TestGetPrincipalSelfRegistrationSecretLabels_MultipleLabels(t *testing.T) { + cr := makeTestArgoCD(withPrincipalEnabled(true), + withSelfRegistrationClusterSecretLabels(map[string]string{ + "env": "production", + "app": "argocd", + "team": "platform", + })) + result := getPrincipalSelfRegistrationSecretLabels(cr) + // Map iteration order is non-deterministic; verify each pair is present individually. + assert.Contains(t, result, "env=production") + assert.Contains(t, result, "app=argocd") + assert.Contains(t, result, "team=platform") +} diff --git a/bundle/manifests/argoproj.io_argocds.yaml b/bundle/manifests/argoproj.io_argocds.yaml index 73e6f038ae4..db8360df397 100644 --- a/bundle/manifests/argoproj.io_argocds.yaml +++ b/bundle/manifests/argoproj.io_argocds.yaml @@ -1031,6 +1031,13 @@ spec: ClientCertSecretName is the name of the TLS secret containing shared client cert for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). type: string + clusterSecretLabels: + additionalProperties: + type: string + description: 'ClusterSecretLabels is the set of labels + to apply to cluster secrets created for agents. Ex: + {"foo": "bar", "bar": "baz"}' + type: object enabled: description: |- Enabled is the flag to enable self-registration of agents. @@ -14218,6 +14225,13 @@ spec: ClientCertSecretName is the name of the TLS secret containing shared client cert for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). type: string + clusterSecretLabels: + additionalProperties: + type: string + description: 'ClusterSecretLabels is the set of labels + to apply to cluster secrets created for agents. Ex: + {"foo": "bar", "bar": "baz"}' + type: object enabled: description: |- Enabled is the flag to enable self-registration of agents. diff --git a/config/crd/bases/argoproj.io_argocds.yaml b/config/crd/bases/argoproj.io_argocds.yaml index 43b078a2d80..2f14f9f3f99 100644 --- a/config/crd/bases/argoproj.io_argocds.yaml +++ b/config/crd/bases/argoproj.io_argocds.yaml @@ -1020,6 +1020,13 @@ spec: ClientCertSecretName is the name of the TLS secret containing shared client cert for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). type: string + clusterSecretLabels: + additionalProperties: + type: string + description: 'ClusterSecretLabels is the set of labels + to apply to cluster secrets created for agents. Ex: + {"foo": "bar", "bar": "baz"}' + type: object enabled: description: |- Enabled is the flag to enable self-registration of agents. @@ -14207,6 +14214,13 @@ spec: ClientCertSecretName is the name of the TLS secret containing shared client cert for self-registered cluster secrets (must have tls.crt, tls.key, ca.crt). type: string + clusterSecretLabels: + additionalProperties: + type: string + description: 'ClusterSecretLabels is the set of labels + to apply to cluster secrets created for agents. Ex: + {"foo": "bar", "bar": "baz"}' + type: object enabled: description: |- Enabled is the flag to enable self-registration of agents. diff --git a/test/openshift/e2e/ginkgo/sequential/1-051_validate_argocd_agent_principal_test.go b/test/openshift/e2e/ginkgo/sequential/1-051_validate_argocd_agent_principal_test.go index d7eacd6794d..35e31e6ea82 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-051_validate_argocd_agent_principal_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-051_validate_argocd_agent_principal_test.go @@ -1051,11 +1051,12 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { }) It("should propagate self-registration configuration to principal deployment env vars", func() { - By("Create ArgoCD instance with self-registration enabled") + By("Create ArgoCD instance with self-registration enabled and cluster secret labels") argoCD.Spec.ArgoCDAgent.Principal.SelfRegistration = &argov1beta1api.PrincipalSelfRegistrationSpec{ Enabled: new(true), ClientCertSecretName: "argocd-agent-shared-client-cert", + ClusterSecretLabels: map[string]string{"env": "production", "team": "platform"}, } Expect(k8sClient.Create(ctx, argoCD)).To(Succeed()) @@ -1075,12 +1076,25 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { Expect(container.Env).To(ContainElement(corev1.EnvVar{Name: key, Value: value}), "Environment variable %s should be set to %s", key, value) } - By("Update self-registration configuration: disable and change client cert secret") + By("Verify cluster secret labels env var contains all expected key=value pairs") + + var secretLabelsEnvValue string + for _, env := range container.Env { + if env.Name == argocdagent.EnvArgoCDPrincipalSelfRegistrationSecretLabels { + secretLabelsEnvValue = env.Value + break + } + } + Expect(secretLabelsEnvValue).To(ContainSubstring("env=production"), "Cluster secret labels should contain env=production") + Expect(secretLabelsEnvValue).To(ContainSubstring("team=platform"), "Cluster secret labels should contain team=platform") + + By("Update self-registration configuration: disable, change client cert secret, and update labels") Expect(k8sClient.Get(ctx, client.ObjectKey{Name: argoCDName, Namespace: ns.Name}, argoCD)).To(Succeed()) argocdFixture.Update(argoCD, func(ac *argov1beta1api.ArgoCD) { ac.Spec.ArgoCDAgent.Principal.SelfRegistration.Enabled = new(false) ac.Spec.ArgoCDAgent.Principal.SelfRegistration.ClientCertSecretName = "updated-client-cert" + ac.Spec.ArgoCDAgent.Principal.SelfRegistration.ClusterSecretLabels = map[string]string{"foo": "bar"} }) By("Wait for the client cert secret update to propagate, then verify all env vars") @@ -1108,6 +1122,17 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { for key, value := range expectedEnvVariables { Expect(container.Env).To(ContainElement(corev1.EnvVar{Name: key, Value: value}), "Environment variable %s should be set to %s", key, value) } + + By("Verify updated cluster secret labels env var") + + secretLabelsEnvValue = "" + for _, env := range container.Env { + if env.Name == argocdagent.EnvArgoCDPrincipalSelfRegistrationSecretLabels { + secretLabelsEnvValue = env.Value + break + } + } + Expect(secretLabelsEnvValue).To(Equal("foo=bar"), "Cluster secret labels should be updated to foo=bar") }) }) })