github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/generators/cluster_test.go (about)

     1  package generators
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	corev1 "k8s.io/api/core/v1"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"k8s.io/apimachinery/pkg/runtime"
    11  	"sigs.k8s.io/controller-runtime/pkg/client"
    12  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    13  
    14  	kubefake "k8s.io/client-go/kubernetes/fake"
    15  
    16  	"github.com/argoproj/argo-cd/v3/applicationset/utils"
    17  	argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  type possiblyErroringFakeCtrlRuntimeClient struct {
    24  	client.Client
    25  	shouldError bool
    26  }
    27  
    28  func (p *possiblyErroringFakeCtrlRuntimeClient) List(ctx context.Context, secretList client.ObjectList, opts ...client.ListOption) error {
    29  	if p.shouldError {
    30  		return errors.New("could not list Secrets")
    31  	}
    32  	return p.Client.List(ctx, secretList, opts...)
    33  }
    34  
    35  func TestGenerateParams(t *testing.T) {
    36  	clusters := []client.Object{
    37  		&corev1.Secret{
    38  			TypeMeta: metav1.TypeMeta{
    39  				Kind:       "Secret",
    40  				APIVersion: "v1",
    41  			},
    42  			ObjectMeta: metav1.ObjectMeta{
    43  				Name:      "staging-01",
    44  				Namespace: "namespace",
    45  				Labels: map[string]string{
    46  					"argocd.argoproj.io/secret-type": "cluster",
    47  					"environment":                    "staging",
    48  					"org":                            "foo",
    49  				},
    50  				Annotations: map[string]string{
    51  					"foo.argoproj.io": "staging",
    52  				},
    53  			},
    54  			Data: map[string][]byte{
    55  				"config": []byte("{}"),
    56  				"name":   []byte("staging-01"),
    57  				"server": []byte("https://staging-01.example.com"),
    58  			},
    59  			Type: corev1.SecretType("Opaque"),
    60  		},
    61  		&corev1.Secret{
    62  			TypeMeta: metav1.TypeMeta{
    63  				Kind:       "Secret",
    64  				APIVersion: "v1",
    65  			},
    66  			ObjectMeta: metav1.ObjectMeta{
    67  				Name:      "production-01",
    68  				Namespace: "namespace",
    69  				Labels: map[string]string{
    70  					"argocd.argoproj.io/secret-type": "cluster",
    71  					"environment":                    "production",
    72  					"org":                            "bar",
    73  				},
    74  				Annotations: map[string]string{
    75  					"foo.argoproj.io": "production",
    76  				},
    77  			},
    78  			Data: map[string][]byte{
    79  				"config":  []byte("{}"),
    80  				"name":    []byte("production_01/west"),
    81  				"server":  []byte("https://production-01.example.com"),
    82  				"project": []byte("prod-project"),
    83  			},
    84  			Type: corev1.SecretType("Opaque"),
    85  		},
    86  	}
    87  	testCases := []struct {
    88  		name       string
    89  		selector   metav1.LabelSelector
    90  		isFlatMode bool
    91  		values     map[string]string
    92  		expected   []map[string]any
    93  		// clientError is true if a k8s client error should be simulated
    94  		clientError   bool
    95  		expectedError error
    96  	}{
    97  		{
    98  			name:     "no label selector",
    99  			selector: metav1.LabelSelector{},
   100  			values: map[string]string{
   101  				"lol1":  "lol",
   102  				"lol2":  "{{values.lol1}}{{values.lol1}}",
   103  				"lol3":  "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
   104  				"foo":   "bar",
   105  				"bar":   "{{ metadata.annotations.foo.argoproj.io }}",
   106  				"bat":   "{{ metadata.labels.environment }}",
   107  				"aaa":   "{{ server }}",
   108  				"no-op": "{{ this-does-not-exist }}",
   109  			}, expected: []map[string]any{
   110  				{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
   111  				{
   112  					"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   113  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   114  				},
   115  
   116  				{
   117  					"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   118  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   119  				},
   120  			},
   121  			clientError:   false,
   122  			expectedError: nil,
   123  		},
   124  		{
   125  			name: "secret type label selector",
   126  			selector: metav1.LabelSelector{
   127  				MatchLabels: map[string]string{
   128  					"argocd.argoproj.io/secret-type": "cluster",
   129  				},
   130  			},
   131  			values: nil,
   132  			expected: []map[string]any{
   133  				{
   134  					"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   135  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   136  				},
   137  
   138  				{
   139  					"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   140  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   141  				},
   142  			},
   143  			clientError:   false,
   144  			expectedError: nil,
   145  		},
   146  		{
   147  			name: "production-only",
   148  			selector: metav1.LabelSelector{
   149  				MatchLabels: map[string]string{
   150  					"environment": "production",
   151  				},
   152  			},
   153  			values: map[string]string{
   154  				"foo": "bar",
   155  			},
   156  			expected: []map[string]any{
   157  				{
   158  					"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   159  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   160  				},
   161  			},
   162  			clientError:   false,
   163  			expectedError: nil,
   164  		},
   165  		{
   166  			name: "production or staging",
   167  			selector: metav1.LabelSelector{
   168  				MatchExpressions: []metav1.LabelSelectorRequirement{
   169  					{
   170  						Key:      "environment",
   171  						Operator: "In",
   172  						Values: []string{
   173  							"production",
   174  							"staging",
   175  						},
   176  					},
   177  				},
   178  			},
   179  			values: map[string]string{
   180  				"foo": "bar",
   181  			},
   182  			expected: []map[string]any{
   183  				{
   184  					"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   185  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   186  				},
   187  				{
   188  					"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   189  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   190  				},
   191  			},
   192  			clientError:   false,
   193  			expectedError: nil,
   194  		},
   195  		{
   196  			name: "production or staging with match labels",
   197  			selector: metav1.LabelSelector{
   198  				MatchExpressions: []metav1.LabelSelectorRequirement{
   199  					{
   200  						Key:      "environment",
   201  						Operator: "In",
   202  						Values: []string{
   203  							"production",
   204  							"staging",
   205  						},
   206  					},
   207  				},
   208  				MatchLabels: map[string]string{
   209  					"org": "foo",
   210  				},
   211  			},
   212  			values: map[string]string{
   213  				"name": "baz",
   214  			},
   215  			expected: []map[string]any{
   216  				{
   217  					"values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   218  					"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   219  				},
   220  			},
   221  			clientError:   false,
   222  			expectedError: nil,
   223  		},
   224  		{
   225  			name:          "simulate client error",
   226  			selector:      metav1.LabelSelector{},
   227  			values:        nil,
   228  			expected:      nil,
   229  			clientError:   true,
   230  			expectedError: errors.New("error getting cluster secrets: could not list Secrets"),
   231  		},
   232  		{
   233  			name:     "flat mode without selectors",
   234  			selector: metav1.LabelSelector{},
   235  			values: map[string]string{
   236  				"lol1":  "lol",
   237  				"lol2":  "{{values.lol1}}{{values.lol1}}",
   238  				"lol3":  "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
   239  				"foo":   "bar",
   240  				"bar":   "{{ metadata.annotations.foo.argoproj.io }}",
   241  				"bat":   "{{ metadata.labels.environment }}",
   242  				"aaa":   "{{ server }}",
   243  				"no-op": "{{ this-does-not-exist }}",
   244  			},
   245  			expected: []map[string]any{
   246  				{
   247  					"clusters": []map[string]any{
   248  						{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
   249  						{
   250  							"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   251  							"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   252  						},
   253  
   254  						{
   255  							"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   256  							"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   257  						},
   258  					},
   259  				},
   260  			},
   261  			isFlatMode:    true,
   262  			clientError:   false,
   263  			expectedError: nil,
   264  		},
   265  		{
   266  			name: "production or staging with flat mode",
   267  			selector: metav1.LabelSelector{
   268  				MatchExpressions: []metav1.LabelSelectorRequirement{
   269  					{
   270  						Key:      "environment",
   271  						Operator: "In",
   272  						Values: []string{
   273  							"production",
   274  							"staging",
   275  						},
   276  					},
   277  				},
   278  			},
   279  			isFlatMode: true,
   280  			values: map[string]string{
   281  				"foo": "bar",
   282  			},
   283  			expected: []map[string]any{
   284  				{
   285  					"clusters": []map[string]any{
   286  						{
   287  							"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
   288  							"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
   289  						},
   290  						{
   291  							"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
   292  							"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
   293  						},
   294  					},
   295  				},
   296  			},
   297  			clientError:   false,
   298  			expectedError: nil,
   299  		},
   300  	}
   301  
   302  	// convert []client.Object to []runtime.Object, for use by kubefake package
   303  	runtimeClusters := []runtime.Object{}
   304  	for _, clientCluster := range clusters {
   305  		runtimeClusters = append(runtimeClusters, clientCluster)
   306  	}
   307  
   308  	for _, testCase := range testCases {
   309  		t.Run(testCase.name, func(t *testing.T) {
   310  			appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
   311  
   312  			fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
   313  			cl := &possiblyErroringFakeCtrlRuntimeClient{
   314  				fakeClient,
   315  				testCase.clientError,
   316  			}
   317  
   318  			clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
   319  
   320  			applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
   321  				ObjectMeta: metav1.ObjectMeta{
   322  					Name: "set",
   323  				},
   324  				Spec: argoprojiov1alpha1.ApplicationSetSpec{},
   325  			}
   326  
   327  			got, err := clusterGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
   328  				Clusters: &argoprojiov1alpha1.ClusterGenerator{
   329  					Selector: testCase.selector,
   330  					Values:   testCase.values,
   331  					FlatList: testCase.isFlatMode,
   332  				},
   333  			}, &applicationSetInfo, nil)
   334  
   335  			if testCase.expectedError != nil {
   336  				require.EqualError(t, err, testCase.expectedError.Error())
   337  			} else {
   338  				require.NoError(t, err)
   339  				assert.ElementsMatch(t, testCase.expected, got)
   340  			}
   341  		})
   342  	}
   343  }
   344  
   345  func TestGenerateParamsGoTemplate(t *testing.T) {
   346  	clusters := []client.Object{
   347  		&corev1.Secret{
   348  			TypeMeta: metav1.TypeMeta{
   349  				Kind:       "Secret",
   350  				APIVersion: "v1",
   351  			},
   352  			ObjectMeta: metav1.ObjectMeta{
   353  				Name:      "staging-01",
   354  				Namespace: "namespace",
   355  				Labels: map[string]string{
   356  					"argocd.argoproj.io/secret-type": "cluster",
   357  					"environment":                    "staging",
   358  					"org":                            "foo",
   359  				},
   360  				Annotations: map[string]string{
   361  					"foo.argoproj.io": "staging",
   362  				},
   363  			},
   364  			Data: map[string][]byte{
   365  				"config": []byte("{}"),
   366  				"name":   []byte("staging-01"),
   367  				"server": []byte("https://staging-01.example.com"),
   368  			},
   369  			Type: corev1.SecretType("Opaque"),
   370  		},
   371  		&corev1.Secret{
   372  			TypeMeta: metav1.TypeMeta{
   373  				Kind:       "Secret",
   374  				APIVersion: "v1",
   375  			},
   376  			ObjectMeta: metav1.ObjectMeta{
   377  				Name:      "production-01",
   378  				Namespace: "namespace",
   379  				Labels: map[string]string{
   380  					"argocd.argoproj.io/secret-type": "cluster",
   381  					"environment":                    "production",
   382  					"org":                            "bar",
   383  				},
   384  				Annotations: map[string]string{
   385  					"foo.argoproj.io": "production",
   386  				},
   387  			},
   388  			Data: map[string][]byte{
   389  				"config": []byte("{}"),
   390  				"name":   []byte("production_01/west"),
   391  				"server": []byte("https://production-01.example.com"),
   392  			},
   393  			Type: corev1.SecretType("Opaque"),
   394  		},
   395  	}
   396  	testCases := []struct {
   397  		name       string
   398  		selector   metav1.LabelSelector
   399  		values     map[string]string
   400  		isFlatMode bool
   401  		expected   []map[string]any
   402  		// clientError is true if a k8s client error should be simulated
   403  		clientError   bool
   404  		expectedError error
   405  	}{
   406  		{
   407  			name:     "no label selector",
   408  			selector: metav1.LabelSelector{},
   409  			values: map[string]string{
   410  				"lol1":  "lol",
   411  				"lol2":  "{{ .values.lol1 }}{{ .values.lol1 }}",
   412  				"lol3":  "{{ .values.lol2 }}{{ .values.lol2 }}{{ .values.lol2 }}",
   413  				"foo":   "bar",
   414  				"bar":   "{{ if not (empty .metadata) }}{{index .metadata.annotations \"foo.argoproj.io\" }}{{ end }}",
   415  				"bat":   "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
   416  				"aaa":   "{{ .server }}",
   417  				"no-op": "{{ .thisDoesNotExist }}",
   418  			}, expected: []map[string]any{
   419  				{
   420  					"name":           "production_01/west",
   421  					"nameNormalized": "production-01-west",
   422  					"server":         "https://production-01.example.com",
   423  					"project":        "",
   424  					"metadata": map[string]any{
   425  						"labels": map[string]string{
   426  							"argocd.argoproj.io/secret-type": "cluster",
   427  							"environment":                    "production",
   428  							"org":                            "bar",
   429  						},
   430  						"annotations": map[string]string{
   431  							"foo.argoproj.io": "production",
   432  						},
   433  					},
   434  					"values": map[string]string{
   435  						"lol1":  "lol",
   436  						"lol2":  "<no value><no value>",
   437  						"lol3":  "<no value><no value><no value>",
   438  						"foo":   "bar",
   439  						"bar":   "production",
   440  						"bat":   "production",
   441  						"aaa":   "https://production-01.example.com",
   442  						"no-op": "<no value>",
   443  					},
   444  				},
   445  				{
   446  					"name":           "staging-01",
   447  					"nameNormalized": "staging-01",
   448  					"server":         "https://staging-01.example.com",
   449  					"project":        "",
   450  					"metadata": map[string]any{
   451  						"labels": map[string]string{
   452  							"argocd.argoproj.io/secret-type": "cluster",
   453  							"environment":                    "staging",
   454  							"org":                            "foo",
   455  						},
   456  						"annotations": map[string]string{
   457  							"foo.argoproj.io": "staging",
   458  						},
   459  					},
   460  					"values": map[string]string{
   461  						"lol1":  "lol",
   462  						"lol2":  "<no value><no value>",
   463  						"lol3":  "<no value><no value><no value>",
   464  						"foo":   "bar",
   465  						"bar":   "staging",
   466  						"bat":   "staging",
   467  						"aaa":   "https://staging-01.example.com",
   468  						"no-op": "<no value>",
   469  					},
   470  				},
   471  				{
   472  					"nameNormalized": "in-cluster",
   473  					"name":           "in-cluster",
   474  					"server":         "https://kubernetes.default.svc",
   475  					"project":        "",
   476  					"values": map[string]string{
   477  						"lol1":  "lol",
   478  						"lol2":  "<no value><no value>",
   479  						"lol3":  "<no value><no value><no value>",
   480  						"foo":   "bar",
   481  						"bar":   "",
   482  						"bat":   "",
   483  						"aaa":   "https://kubernetes.default.svc",
   484  						"no-op": "<no value>",
   485  					},
   486  				},
   487  			},
   488  			clientError:   false,
   489  			expectedError: nil,
   490  		},
   491  		{
   492  			name: "secret type label selector",
   493  			selector: metav1.LabelSelector{
   494  				MatchLabels: map[string]string{
   495  					"argocd.argoproj.io/secret-type": "cluster",
   496  				},
   497  			},
   498  			values: nil,
   499  			expected: []map[string]any{
   500  				{
   501  					"name":           "production_01/west",
   502  					"nameNormalized": "production-01-west",
   503  					"server":         "https://production-01.example.com",
   504  					"project":        "",
   505  					"metadata": map[string]any{
   506  						"labels": map[string]string{
   507  							"argocd.argoproj.io/secret-type": "cluster",
   508  							"environment":                    "production",
   509  							"org":                            "bar",
   510  						},
   511  						"annotations": map[string]string{
   512  							"foo.argoproj.io": "production",
   513  						},
   514  					},
   515  				},
   516  				{
   517  					"name":           "staging-01",
   518  					"nameNormalized": "staging-01",
   519  					"server":         "https://staging-01.example.com",
   520  					"project":        "",
   521  					"metadata": map[string]any{
   522  						"labels": map[string]string{
   523  							"argocd.argoproj.io/secret-type": "cluster",
   524  							"environment":                    "staging",
   525  							"org":                            "foo",
   526  						},
   527  						"annotations": map[string]string{
   528  							"foo.argoproj.io": "staging",
   529  						},
   530  					},
   531  				},
   532  			},
   533  			clientError:   false,
   534  			expectedError: nil,
   535  		},
   536  		{
   537  			name: "production-only",
   538  			selector: metav1.LabelSelector{
   539  				MatchLabels: map[string]string{
   540  					"environment": "production",
   541  				},
   542  			},
   543  			values: map[string]string{
   544  				"foo": "bar",
   545  			},
   546  			expected: []map[string]any{
   547  				{
   548  					"name":           "production_01/west",
   549  					"nameNormalized": "production-01-west",
   550  					"server":         "https://production-01.example.com",
   551  					"project":        "",
   552  					"metadata": map[string]any{
   553  						"labels": map[string]string{
   554  							"argocd.argoproj.io/secret-type": "cluster",
   555  							"environment":                    "production",
   556  							"org":                            "bar",
   557  						},
   558  						"annotations": map[string]string{
   559  							"foo.argoproj.io": "production",
   560  						},
   561  					},
   562  					"values": map[string]string{
   563  						"foo": "bar",
   564  					},
   565  				},
   566  			},
   567  			clientError:   false,
   568  			expectedError: nil,
   569  		},
   570  		{
   571  			name: "production or staging",
   572  			selector: metav1.LabelSelector{
   573  				MatchExpressions: []metav1.LabelSelectorRequirement{
   574  					{
   575  						Key:      "environment",
   576  						Operator: "In",
   577  						Values: []string{
   578  							"production",
   579  							"staging",
   580  						},
   581  					},
   582  				},
   583  			},
   584  			values: map[string]string{
   585  				"foo": "bar",
   586  			},
   587  			expected: []map[string]any{
   588  				{
   589  					"name":           "production_01/west",
   590  					"nameNormalized": "production-01-west",
   591  					"server":         "https://production-01.example.com",
   592  					"project":        "",
   593  					"metadata": map[string]any{
   594  						"labels": map[string]string{
   595  							"argocd.argoproj.io/secret-type": "cluster",
   596  							"environment":                    "production",
   597  							"org":                            "bar",
   598  						},
   599  						"annotations": map[string]string{
   600  							"foo.argoproj.io": "production",
   601  						},
   602  					},
   603  					"values": map[string]string{
   604  						"foo": "bar",
   605  					},
   606  				},
   607  				{
   608  					"name":           "staging-01",
   609  					"nameNormalized": "staging-01",
   610  					"server":         "https://staging-01.example.com",
   611  					"project":        "",
   612  					"metadata": map[string]any{
   613  						"labels": map[string]string{
   614  							"argocd.argoproj.io/secret-type": "cluster",
   615  							"environment":                    "staging",
   616  							"org":                            "foo",
   617  						},
   618  						"annotations": map[string]string{
   619  							"foo.argoproj.io": "staging",
   620  						},
   621  					},
   622  					"values": map[string]string{
   623  						"foo": "bar",
   624  					},
   625  				},
   626  			},
   627  			clientError:   false,
   628  			expectedError: nil,
   629  		},
   630  		{
   631  			name: "production or staging with match labels",
   632  			selector: metav1.LabelSelector{
   633  				MatchExpressions: []metav1.LabelSelectorRequirement{
   634  					{
   635  						Key:      "environment",
   636  						Operator: "In",
   637  						Values: []string{
   638  							"production",
   639  							"staging",
   640  						},
   641  					},
   642  				},
   643  				MatchLabels: map[string]string{
   644  					"org": "foo",
   645  				},
   646  			},
   647  			values: map[string]string{
   648  				"name": "baz",
   649  			},
   650  			expected: []map[string]any{
   651  				{
   652  					"name":           "staging-01",
   653  					"nameNormalized": "staging-01",
   654  					"server":         "https://staging-01.example.com",
   655  					"project":        "",
   656  					"metadata": map[string]any{
   657  						"labels": map[string]string{
   658  							"argocd.argoproj.io/secret-type": "cluster",
   659  							"environment":                    "staging",
   660  							"org":                            "foo",
   661  						},
   662  						"annotations": map[string]string{
   663  							"foo.argoproj.io": "staging",
   664  						},
   665  					},
   666  					"values": map[string]string{
   667  						"name": "baz",
   668  					},
   669  				},
   670  			},
   671  			clientError:   false,
   672  			expectedError: nil,
   673  		},
   674  		{
   675  			name:          "simulate client error",
   676  			selector:      metav1.LabelSelector{},
   677  			values:        nil,
   678  			expected:      nil,
   679  			clientError:   true,
   680  			expectedError: errors.New("error getting cluster secrets: could not list Secrets"),
   681  		},
   682  		{
   683  			name:       "Clusters with flat list mode and no selector",
   684  			selector:   metav1.LabelSelector{},
   685  			isFlatMode: true,
   686  			values: map[string]string{
   687  				"lol1":  "lol",
   688  				"lol2":  "{{ .values.lol1 }}{{ .values.lol1 }}",
   689  				"lol3":  "{{ .values.lol2 }}{{ .values.lol2 }}{{ .values.lol2 }}",
   690  				"foo":   "bar",
   691  				"bar":   "{{ if not (empty .metadata) }}{{index .metadata.annotations \"foo.argoproj.io\" }}{{ end }}",
   692  				"bat":   "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
   693  				"aaa":   "{{ .server }}",
   694  				"no-op": "{{ .thisDoesNotExist }}",
   695  			},
   696  			expected: []map[string]any{
   697  				{
   698  					"clusters": []map[string]any{
   699  						{
   700  							"nameNormalized": "in-cluster",
   701  							"name":           "in-cluster",
   702  							"server":         "https://kubernetes.default.svc",
   703  							"project":        "",
   704  							"values": map[string]string{
   705  								"lol1":  "lol",
   706  								"lol2":  "<no value><no value>",
   707  								"lol3":  "<no value><no value><no value>",
   708  								"foo":   "bar",
   709  								"bar":   "",
   710  								"bat":   "",
   711  								"aaa":   "https://kubernetes.default.svc",
   712  								"no-op": "<no value>",
   713  							},
   714  						},
   715  						{
   716  							"name":           "production_01/west",
   717  							"nameNormalized": "production-01-west",
   718  							"server":         "https://production-01.example.com",
   719  							"project":        "",
   720  							"metadata": map[string]any{
   721  								"labels": map[string]string{
   722  									"argocd.argoproj.io/secret-type": "cluster",
   723  									"environment":                    "production",
   724  									"org":                            "bar",
   725  								},
   726  								"annotations": map[string]string{
   727  									"foo.argoproj.io": "production",
   728  								},
   729  							},
   730  							"values": map[string]string{
   731  								"lol1":  "lol",
   732  								"lol2":  "<no value><no value>",
   733  								"lol3":  "<no value><no value><no value>",
   734  								"foo":   "bar",
   735  								"bar":   "production",
   736  								"bat":   "production",
   737  								"aaa":   "https://production-01.example.com",
   738  								"no-op": "<no value>",
   739  							},
   740  						},
   741  						{
   742  							"name":           "staging-01",
   743  							"nameNormalized": "staging-01",
   744  							"server":         "https://staging-01.example.com",
   745  							"project":        "",
   746  							"metadata": map[string]any{
   747  								"labels": map[string]string{
   748  									"argocd.argoproj.io/secret-type": "cluster",
   749  									"environment":                    "staging",
   750  									"org":                            "foo",
   751  								},
   752  								"annotations": map[string]string{
   753  									"foo.argoproj.io": "staging",
   754  								},
   755  							},
   756  							"values": map[string]string{
   757  								"lol1":  "lol",
   758  								"lol2":  "<no value><no value>",
   759  								"lol3":  "<no value><no value><no value>",
   760  								"foo":   "bar",
   761  								"bar":   "staging",
   762  								"bat":   "staging",
   763  								"aaa":   "https://staging-01.example.com",
   764  								"no-op": "<no value>",
   765  							},
   766  						},
   767  					},
   768  				},
   769  			},
   770  			clientError:   false,
   771  			expectedError: nil,
   772  		},
   773  		{
   774  			name: "production or staging with flat mode",
   775  			selector: metav1.LabelSelector{
   776  				MatchExpressions: []metav1.LabelSelectorRequirement{
   777  					{
   778  						Key:      "environment",
   779  						Operator: "In",
   780  						Values: []string{
   781  							"production",
   782  							"staging",
   783  						},
   784  					},
   785  				},
   786  			},
   787  			isFlatMode: true,
   788  			values: map[string]string{
   789  				"foo": "bar",
   790  			},
   791  			expected: []map[string]any{
   792  				{
   793  					"clusters": []map[string]any{
   794  						{
   795  							"name":           "production_01/west",
   796  							"nameNormalized": "production-01-west",
   797  							"server":         "https://production-01.example.com",
   798  							"project":        "",
   799  							"metadata": map[string]any{
   800  								"labels": map[string]string{
   801  									"argocd.argoproj.io/secret-type": "cluster",
   802  									"environment":                    "production",
   803  									"org":                            "bar",
   804  								},
   805  								"annotations": map[string]string{
   806  									"foo.argoproj.io": "production",
   807  								},
   808  							},
   809  							"values": map[string]string{
   810  								"foo": "bar",
   811  							},
   812  						},
   813  						{
   814  							"name":           "staging-01",
   815  							"nameNormalized": "staging-01",
   816  							"server":         "https://staging-01.example.com",
   817  							"project":        "",
   818  							"metadata": map[string]any{
   819  								"labels": map[string]string{
   820  									"argocd.argoproj.io/secret-type": "cluster",
   821  									"environment":                    "staging",
   822  									"org":                            "foo",
   823  								},
   824  								"annotations": map[string]string{
   825  									"foo.argoproj.io": "staging",
   826  								},
   827  							},
   828  							"values": map[string]string{
   829  								"foo": "bar",
   830  							},
   831  						},
   832  					},
   833  				},
   834  			},
   835  			clientError:   false,
   836  			expectedError: nil,
   837  		},
   838  	}
   839  
   840  	// convert []client.Object to []runtime.Object, for use by kubefake package
   841  	runtimeClusters := []runtime.Object{}
   842  	for _, clientCluster := range clusters {
   843  		runtimeClusters = append(runtimeClusters, clientCluster)
   844  	}
   845  
   846  	for _, testCase := range testCases {
   847  		t.Run(testCase.name, func(t *testing.T) {
   848  			appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
   849  
   850  			fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
   851  			cl := &possiblyErroringFakeCtrlRuntimeClient{
   852  				fakeClient,
   853  				testCase.clientError,
   854  			}
   855  
   856  			clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
   857  
   858  			applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
   859  				ObjectMeta: metav1.ObjectMeta{
   860  					Name: "set",
   861  				},
   862  				Spec: argoprojiov1alpha1.ApplicationSetSpec{
   863  					GoTemplate: true,
   864  				},
   865  			}
   866  
   867  			got, err := clusterGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
   868  				Clusters: &argoprojiov1alpha1.ClusterGenerator{
   869  					Selector: testCase.selector,
   870  					Values:   testCase.values,
   871  					FlatList: testCase.isFlatMode,
   872  				},
   873  			}, &applicationSetInfo, nil)
   874  
   875  			if testCase.expectedError != nil {
   876  				require.EqualError(t, err, testCase.expectedError.Error())
   877  			} else {
   878  				require.NoError(t, err)
   879  				assert.ElementsMatch(t, testCase.expected, got)
   880  			}
   881  		})
   882  	}
   883  }
   884  
   885  func TestSanitizeClusterName(t *testing.T) {
   886  	t.Run("valid DNS-1123 subdomain name", func(t *testing.T) {
   887  		assert.Equal(t, "cluster-name", utils.SanitizeName("cluster-name"))
   888  	})
   889  	t.Run("invalid DNS-1123 subdomain name", func(t *testing.T) {
   890  		invalidName := "-.--CLUSTER/name  -./.-"
   891  		assert.Equal(t, "cluster-name", utils.SanitizeName(invalidName))
   892  	})
   893  }