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 }