github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/generators/git_test.go (about) 1 package generators 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/argoproj/argo-cd/v2/applicationset/services/mocks" 8 argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/mock" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 ) 13 14 func Test_generateParamsFromGitFile(t *testing.T) { 15 defaultContent := []byte(` 16 foo: 17 bar: baz 18 `) 19 type args struct { 20 filePath string 21 fileContent []byte 22 values map[string]string 23 useGoTemplate bool 24 goTemplateOptions []string 25 pathParamPrefix string 26 } 27 tests := []struct { 28 name string 29 args args 30 want []map[string]interface{} 31 wantErr bool 32 }{ 33 { 34 name: "empty file returns path parameters", 35 args: args{ 36 filePath: "path/dir/file_name.yaml", 37 fileContent: []byte(""), 38 values: map[string]string{}, 39 useGoTemplate: false, 40 }, 41 want: []map[string]interface{}{ 42 { 43 "path": "path/dir", 44 "path.basename": "dir", 45 "path.filename": "file_name.yaml", 46 "path.basenameNormalized": "dir", 47 "path.filenameNormalized": "file-name.yaml", 48 "path[0]": "path", 49 "path[1]": "dir", 50 }, 51 }, 52 }, 53 { 54 name: "invalid json/yaml file returns error", 55 args: args{ 56 filePath: "path/dir/file_name.yaml", 57 fileContent: []byte("this is not json or yaml"), 58 values: map[string]string{}, 59 useGoTemplate: false, 60 }, 61 wantErr: true, 62 }, 63 { 64 name: "file parameters are added to params", 65 args: args{ 66 filePath: "path/dir/file_name.yaml", 67 fileContent: defaultContent, 68 values: map[string]string{}, 69 useGoTemplate: false, 70 }, 71 want: []map[string]interface{}{ 72 { 73 "foo.bar": "baz", 74 "path": "path/dir", 75 "path.basename": "dir", 76 "path.filename": "file_name.yaml", 77 "path.basenameNormalized": "dir", 78 "path.filenameNormalized": "file-name.yaml", 79 "path[0]": "path", 80 "path[1]": "dir", 81 }, 82 }, 83 }, 84 { 85 name: "path parameter are prefixed", 86 args: args{ 87 filePath: "path/dir/file_name.yaml", 88 fileContent: defaultContent, 89 values: map[string]string{}, 90 useGoTemplate: false, 91 pathParamPrefix: "myRepo", 92 }, 93 want: []map[string]interface{}{ 94 { 95 "foo.bar": "baz", 96 "myRepo.path": "path/dir", 97 "myRepo.path.basename": "dir", 98 "myRepo.path.filename": "file_name.yaml", 99 "myRepo.path.basenameNormalized": "dir", 100 "myRepo.path.filenameNormalized": "file-name.yaml", 101 "myRepo.path[0]": "path", 102 "myRepo.path[1]": "dir", 103 }, 104 }, 105 }, 106 { 107 name: "file parameters are added to params with go template", 108 args: args{ 109 filePath: "path/dir/file_name.yaml", 110 fileContent: defaultContent, 111 values: map[string]string{}, 112 useGoTemplate: true, 113 }, 114 want: []map[string]interface{}{ 115 { 116 "foo": map[string]interface{}{ 117 "bar": "baz", 118 }, 119 "path": map[string]interface{}{ 120 "path": "path/dir", 121 "basename": "dir", 122 "filename": "file_name.yaml", 123 "basenameNormalized": "dir", 124 "filenameNormalized": "file-name.yaml", 125 "segments": []string{ 126 "path", 127 "dir", 128 }, 129 }, 130 }, 131 }, 132 }, 133 { 134 name: "path parameter are prefixed with go template", 135 args: args{ 136 filePath: "path/dir/file_name.yaml", 137 fileContent: defaultContent, 138 values: map[string]string{}, 139 useGoTemplate: true, 140 pathParamPrefix: "myRepo", 141 }, 142 want: []map[string]interface{}{ 143 { 144 "foo": map[string]interface{}{ 145 "bar": "baz", 146 }, 147 "myRepo": map[string]interface{}{ 148 "path": map[string]interface{}{ 149 "path": "path/dir", 150 "basename": "dir", 151 "filename": "file_name.yaml", 152 "basenameNormalized": "dir", 153 "filenameNormalized": "file-name.yaml", 154 "segments": []string{ 155 "path", 156 "dir", 157 }, 158 }, 159 }, 160 }, 161 }, 162 }, 163 } 164 for _, tt := range tests { 165 t.Run(tt.name, func(t *testing.T) { 166 params, err := (*GitGenerator)(nil).generateParamsFromGitFile(tt.args.filePath, tt.args.fileContent, tt.args.values, tt.args.useGoTemplate, tt.args.goTemplateOptions, tt.args.pathParamPrefix) 167 if (err != nil) != tt.wantErr { 168 t.Errorf("GitGenerator.generateParamsFromGitFile() error = %v, wantErr %v", err, tt.wantErr) 169 return 170 } 171 assert.Equal(t, tt.want, params) 172 }) 173 } 174 } 175 176 func TestGitGenerateParamsFromDirectories(t *testing.T) { 177 178 cases := []struct { 179 name string 180 directories []argoprojiov1alpha1.GitDirectoryGeneratorItem 181 pathParamPrefix string 182 repoApps []string 183 repoError error 184 values map[string]string 185 expected []map[string]interface{} 186 expectedError error 187 }{ 188 { 189 name: "happy flow - created apps", 190 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 191 repoApps: []string{ 192 "app1", 193 "app2", 194 "app_3", 195 "p1/app4", 196 }, 197 repoError: nil, 198 expected: []map[string]interface{}{ 199 {"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1"}, 200 {"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "path[0]": "app2"}, 201 {"path": "app_3", "path.basename": "app_3", "path.basenameNormalized": "app-3", "path[0]": "app_3"}, 202 }, 203 expectedError: nil, 204 }, 205 { 206 name: "It prefixes path parameters with PathParamPrefix", 207 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 208 pathParamPrefix: "myRepo", 209 repoApps: []string{ 210 "app1", 211 "app2", 212 "app_3", 213 "p1/app4", 214 }, 215 repoError: nil, 216 expected: []map[string]interface{}{ 217 {"myRepo.path": "app1", "myRepo.path.basename": "app1", "myRepo.path.basenameNormalized": "app1", "myRepo.path[0]": "app1"}, 218 {"myRepo.path": "app2", "myRepo.path.basename": "app2", "myRepo.path.basenameNormalized": "app2", "myRepo.path[0]": "app2"}, 219 {"myRepo.path": "app_3", "myRepo.path.basename": "app_3", "myRepo.path.basenameNormalized": "app-3", "myRepo.path[0]": "app_3"}, 220 }, 221 expectedError: nil, 222 }, 223 { 224 name: "It filters application according to the paths", 225 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}}, 226 repoApps: []string{ 227 "app1", 228 "p1/app2", 229 "p1/p2/app3", 230 "p1/p2/p3/app4", 231 }, 232 repoError: nil, 233 expected: []map[string]interface{}{ 234 {"path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"}, 235 {"path": "p1/p2/app3", "path.basename": "app3", "path[0]": "p1", "path[1]": "p2", "path[2]": "app3", "path.basenameNormalized": "app3"}, 236 }, 237 expectedError: nil, 238 }, 239 { 240 name: "It filters application according to the paths with Exclude", 241 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*", Exclude: true}, {Path: "*"}, {Path: "*/*"}}, 242 repoApps: []string{ 243 "app1", 244 "app2", 245 "p1/app2", 246 "p1/app3", 247 "p2/app3", 248 }, 249 repoError: nil, 250 expected: []map[string]interface{}{ 251 {"path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"}, 252 {"path": "app2", "path.basename": "app2", "path[0]": "app2", "path.basenameNormalized": "app2"}, 253 {"path": "p2/app3", "path.basename": "app3", "path[0]": "p2", "path[1]": "app3", "path.basenameNormalized": "app3"}, 254 }, 255 expectedError: nil, 256 }, 257 { 258 name: "Expecting same exclude behavior with different order", 259 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}, {Path: "p1/*", Exclude: true}}, 260 repoApps: []string{ 261 "app1", 262 "app2", 263 "p1/app2", 264 "p1/app3", 265 "p2/app3", 266 }, 267 repoError: nil, 268 expected: []map[string]interface{}{ 269 {"path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"}, 270 {"path": "app2", "path.basename": "app2", "path[0]": "app2", "path.basenameNormalized": "app2"}, 271 {"path": "p2/app3", "path.basename": "app3", "path[0]": "p2", "path[1]": "app3", "path.basenameNormalized": "app3"}, 272 }, 273 expectedError: nil, 274 }, 275 { 276 name: "Value variable interpolation", 277 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}}, 278 repoApps: []string{ 279 "app1", 280 "p1/app2", 281 }, 282 repoError: nil, 283 values: map[string]string{ 284 "foo": "bar", 285 "aaa": "{{ path[0] }}", 286 "no-op": "{{ this-does-not-exist }}", 287 }, 288 expected: []map[string]interface{}{ 289 {"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "app1", "path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"}, 290 {"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "p1", "path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"}, 291 }, 292 expectedError: nil, 293 }, 294 { 295 name: "handles empty response from repo server", 296 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 297 repoApps: []string{}, 298 repoError: nil, 299 expected: []map[string]interface{}{}, 300 expectedError: nil, 301 }, 302 { 303 name: "handles error from repo server", 304 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 305 repoApps: []string{}, 306 repoError: fmt.Errorf("error"), 307 expected: []map[string]interface{}{}, 308 expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"), 309 }, 310 } 311 312 for _, testCase := range cases { 313 testCaseCopy := testCase 314 315 t.Run(testCaseCopy.name, func(t *testing.T) { 316 t.Parallel() 317 318 argoCDServiceMock := mocks.Repos{} 319 320 argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError) 321 322 var gitGenerator = NewGitGenerator(&argoCDServiceMock) 323 applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ 324 ObjectMeta: metav1.ObjectMeta{ 325 Name: "set", 326 }, 327 Spec: argoprojiov1alpha1.ApplicationSetSpec{ 328 Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ 329 Git: &argoprojiov1alpha1.GitGenerator{ 330 RepoURL: "RepoURL", 331 Revision: "Revision", 332 Directories: testCaseCopy.directories, 333 PathParamPrefix: testCaseCopy.pathParamPrefix, 334 Values: testCaseCopy.values, 335 }, 336 }}, 337 }, 338 } 339 340 got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo) 341 342 if testCaseCopy.expectedError != nil { 343 assert.EqualError(t, err, testCaseCopy.expectedError.Error()) 344 } else { 345 assert.NoError(t, err) 346 assert.Equal(t, testCaseCopy.expected, got) 347 } 348 349 argoCDServiceMock.AssertExpectations(t) 350 }) 351 } 352 } 353 354 func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) { 355 356 cases := []struct { 357 name string 358 directories []argoprojiov1alpha1.GitDirectoryGeneratorItem 359 pathParamPrefix string 360 repoApps []string 361 repoError error 362 expected []map[string]interface{} 363 expectedError error 364 }{ 365 { 366 name: "happy flow - created apps", 367 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 368 repoApps: []string{ 369 "app1", 370 "app2", 371 "app_3", 372 "p1/app4", 373 }, 374 repoError: nil, 375 expected: []map[string]interface{}{ 376 { 377 "path": map[string]interface{}{ 378 "path": "app1", 379 "basename": "app1", 380 "basenameNormalized": "app1", 381 "segments": []string{ 382 "app1", 383 }, 384 }, 385 }, 386 { 387 "path": map[string]interface{}{ 388 "path": "app2", 389 "basename": "app2", 390 "basenameNormalized": "app2", 391 "segments": []string{ 392 "app2", 393 }, 394 }, 395 }, 396 { 397 "path": map[string]interface{}{ 398 "path": "app_3", 399 "basename": "app_3", 400 "basenameNormalized": "app-3", 401 "segments": []string{ 402 "app_3", 403 }, 404 }, 405 }, 406 }, 407 expectedError: nil, 408 }, 409 { 410 name: "It prefixes path parameters with PathParamPrefix", 411 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 412 pathParamPrefix: "myRepo", 413 repoApps: []string{ 414 "app1", 415 "app2", 416 "app_3", 417 "p1/app4", 418 }, 419 repoError: nil, 420 expected: []map[string]interface{}{ 421 { 422 "myRepo": map[string]interface{}{ 423 "path": map[string]interface{}{ 424 "path": "app1", 425 "basename": "app1", 426 "basenameNormalized": "app1", 427 "segments": []string{ 428 "app1", 429 }, 430 }, 431 }, 432 }, 433 { 434 "myRepo": map[string]interface{}{ 435 "path": map[string]interface{}{ 436 "path": "app2", 437 "basename": "app2", 438 "basenameNormalized": "app2", 439 "segments": []string{ 440 "app2", 441 }, 442 }, 443 }, 444 }, 445 { 446 "myRepo": map[string]interface{}{ 447 "path": map[string]interface{}{ 448 "path": "app_3", 449 "basename": "app_3", 450 "basenameNormalized": "app-3", 451 "segments": []string{ 452 "app_3", 453 }, 454 }, 455 }, 456 }, 457 }, 458 expectedError: nil, 459 }, 460 { 461 name: "It filters application according to the paths", 462 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}}, 463 repoApps: []string{ 464 "app1", 465 "p1/app2", 466 "p1/p2/app3", 467 "p1/p2/p3/app4", 468 }, 469 repoError: nil, 470 expected: []map[string]interface{}{ 471 { 472 "path": map[string]interface{}{ 473 "path": "p1/app2", 474 "basename": "app2", 475 "basenameNormalized": "app2", 476 "segments": []string{ 477 "p1", 478 "app2", 479 }, 480 }, 481 }, 482 { 483 "path": map[string]interface{}{ 484 "path": "p1/p2/app3", 485 "basename": "app3", 486 "basenameNormalized": "app3", 487 "segments": []string{ 488 "p1", 489 "p2", 490 "app3", 491 }, 492 }, 493 }, 494 }, 495 expectedError: nil, 496 }, 497 { 498 name: "It filters application according to the paths with Exclude", 499 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*", Exclude: true}, {Path: "*"}, {Path: "*/*"}}, 500 repoApps: []string{ 501 "app1", 502 "app2", 503 "p1/app2", 504 "p1/app3", 505 "p2/app3", 506 }, 507 repoError: nil, 508 expected: []map[string]interface{}{ 509 { 510 "path": map[string]interface{}{ 511 "path": "app1", 512 "basename": "app1", 513 "basenameNormalized": "app1", 514 "segments": []string{ 515 "app1", 516 }, 517 }, 518 }, 519 { 520 "path": map[string]interface{}{ 521 "path": "app2", 522 "basename": "app2", 523 "basenameNormalized": "app2", 524 "segments": []string{ 525 "app2", 526 }, 527 }, 528 }, 529 { 530 "path": map[string]interface{}{ 531 "path": "p2/app3", 532 "basename": "app3", 533 "basenameNormalized": "app3", 534 "segments": []string{ 535 "p2", 536 "app3", 537 }, 538 }, 539 }, 540 }, 541 expectedError: nil, 542 }, 543 { 544 name: "Expecting same exclude behavior with different order", 545 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}, {Path: "p1/*", Exclude: true}}, 546 repoApps: []string{ 547 "app1", 548 "app2", 549 "p1/app2", 550 "p1/app3", 551 "p2/app3", 552 }, 553 repoError: nil, 554 expected: []map[string]interface{}{ 555 556 { 557 "path": map[string]interface{}{ 558 "path": "app1", 559 "basename": "app1", 560 "basenameNormalized": "app1", 561 "segments": []string{ 562 "app1", 563 }, 564 }, 565 }, 566 { 567 "path": map[string]interface{}{ 568 "path": "app2", 569 "basename": "app2", 570 "basenameNormalized": "app2", 571 "segments": []string{ 572 "app2", 573 }, 574 }, 575 }, 576 { 577 "path": map[string]interface{}{ 578 "path": "p2/app3", 579 "basename": "app3", 580 "basenameNormalized": "app3", 581 "segments": []string{ 582 "p2", 583 "app3", 584 }, 585 }, 586 }, 587 }, 588 expectedError: nil, 589 }, 590 { 591 name: "handles empty response from repo server", 592 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 593 repoApps: []string{}, 594 repoError: nil, 595 expected: []map[string]interface{}{}, 596 expectedError: nil, 597 }, 598 { 599 name: "handles error from repo server", 600 directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, 601 repoApps: []string{}, 602 repoError: fmt.Errorf("error"), 603 expected: []map[string]interface{}{}, 604 expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"), 605 }, 606 } 607 608 for _, testCase := range cases { 609 testCaseCopy := testCase 610 611 t.Run(testCaseCopy.name, func(t *testing.T) { 612 t.Parallel() 613 614 argoCDServiceMock := mocks.Repos{} 615 616 argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError) 617 618 var gitGenerator = NewGitGenerator(&argoCDServiceMock) 619 applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ 620 ObjectMeta: metav1.ObjectMeta{ 621 Name: "set", 622 }, 623 Spec: argoprojiov1alpha1.ApplicationSetSpec{ 624 GoTemplate: true, 625 Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ 626 Git: &argoprojiov1alpha1.GitGenerator{ 627 RepoURL: "RepoURL", 628 Revision: "Revision", 629 Directories: testCaseCopy.directories, 630 PathParamPrefix: testCaseCopy.pathParamPrefix, 631 }, 632 }}, 633 }, 634 } 635 636 got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo) 637 638 if testCaseCopy.expectedError != nil { 639 assert.EqualError(t, err, testCaseCopy.expectedError.Error()) 640 } else { 641 assert.NoError(t, err) 642 assert.Equal(t, testCaseCopy.expected, got) 643 } 644 645 argoCDServiceMock.AssertExpectations(t) 646 }) 647 } 648 649 } 650 651 func TestGitGenerateParamsFromFiles(t *testing.T) { 652 653 cases := []struct { 654 name string 655 // files is the list of paths/globs to match 656 files []argoprojiov1alpha1.GitFileGeneratorItem 657 // repoFileContents maps repo path to the literal contents of that path 658 repoFileContents map[string][]byte 659 // if repoPathsError is non-nil, the call to GetPaths(...) will return this error value 660 repoPathsError error 661 values map[string]string 662 expected []map[string]interface{} 663 expectedError error 664 }{ 665 { 666 name: "happy flow: create params from git files", 667 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 668 repoFileContents: map[string][]byte{ 669 "cluster-config/production/config.json": []byte(`{ 670 "cluster": { 671 "owner": "john.doe@example.com", 672 "name": "production", 673 "address": "https://kubernetes.default.svc" 674 }, 675 "key1": "val1", 676 "key2": { 677 "key2_1": "val2_1", 678 "key2_2": { 679 "key2_2_1": "val2_2_1" 680 } 681 }, 682 "key3": 123 683 }`), 684 "cluster-config/staging/config.json": []byte(`{ 685 "cluster": { 686 "owner": "foo.bar@example.com", 687 "name": "staging", 688 "address": "https://kubernetes.default.svc" 689 } 690 }`), 691 }, 692 repoPathsError: nil, 693 expected: []map[string]interface{}{ 694 { 695 "cluster.owner": "john.doe@example.com", 696 "cluster.name": "production", 697 "cluster.address": "https://kubernetes.default.svc", 698 "key1": "val1", 699 "key2.key2_1": "val2_1", 700 "key2.key2_2.key2_2_1": "val2_2_1", 701 "key3": "123", 702 "path": "cluster-config/production", 703 "path.basename": "production", 704 "path[0]": "cluster-config", 705 "path[1]": "production", 706 "path.basenameNormalized": "production", 707 "path.filename": "config.json", 708 "path.filenameNormalized": "config.json", 709 }, 710 { 711 "cluster.owner": "foo.bar@example.com", 712 "cluster.name": "staging", 713 "cluster.address": "https://kubernetes.default.svc", 714 "path": "cluster-config/staging", 715 "path.basename": "staging", 716 "path[0]": "cluster-config", 717 "path[1]": "staging", 718 "path.basenameNormalized": "staging", 719 "path.filename": "config.json", 720 "path.filenameNormalized": "config.json", 721 }, 722 }, 723 expectedError: nil, 724 }, 725 { 726 name: "Value variable interpolation", 727 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 728 repoFileContents: map[string][]byte{ 729 "cluster-config/production/config.json": []byte(`{ 730 "cluster": { 731 "owner": "john.doe@example.com", 732 "name": "production", 733 "address": "https://kubernetes.default.svc" 734 }, 735 "key1": "val1", 736 "key2": { 737 "key2_1": "val2_1", 738 "key2_2": { 739 "key2_2_1": "val2_2_1" 740 } 741 }, 742 "key3": 123 743 }`), 744 "cluster-config/staging/config.json": []byte(`{ 745 "cluster": { 746 "owner": "foo.bar@example.com", 747 "name": "staging", 748 "address": "https://kubernetes.default.svc" 749 } 750 }`), 751 }, 752 repoPathsError: nil, 753 values: map[string]string{ 754 "aaa": "{{ cluster.owner }}", 755 "no-op": "{{ this-does-not-exist }}", 756 }, 757 expected: []map[string]interface{}{ 758 { 759 "cluster.owner": "john.doe@example.com", 760 "cluster.name": "production", 761 "cluster.address": "https://kubernetes.default.svc", 762 "key1": "val1", 763 "key2.key2_1": "val2_1", 764 "key2.key2_2.key2_2_1": "val2_2_1", 765 "key3": "123", 766 "path": "cluster-config/production", 767 "path.basename": "production", 768 "path[0]": "cluster-config", 769 "path[1]": "production", 770 "path.basenameNormalized": "production", 771 "path.filename": "config.json", 772 "path.filenameNormalized": "config.json", 773 "values.aaa": "john.doe@example.com", 774 "values.no-op": "{{ this-does-not-exist }}", 775 }, 776 { 777 "cluster.owner": "foo.bar@example.com", 778 "cluster.name": "staging", 779 "cluster.address": "https://kubernetes.default.svc", 780 "path": "cluster-config/staging", 781 "path.basename": "staging", 782 "path[0]": "cluster-config", 783 "path[1]": "staging", 784 "path.basenameNormalized": "staging", 785 "path.filename": "config.json", 786 "path.filenameNormalized": "config.json", 787 "values.aaa": "foo.bar@example.com", 788 "values.no-op": "{{ this-does-not-exist }}", 789 }, 790 }, 791 expectedError: nil, 792 }, 793 { 794 name: "handles error during getting repo paths", 795 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 796 repoFileContents: map[string][]byte{}, 797 repoPathsError: fmt.Errorf("paths error"), 798 expected: []map[string]interface{}{}, 799 expectedError: fmt.Errorf("error generating params from git: paths error"), 800 }, 801 { 802 name: "test invalid JSON file returns error", 803 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 804 repoFileContents: map[string][]byte{ 805 "cluster-config/production/config.json": []byte(`invalid json file`), 806 }, 807 repoPathsError: nil, 808 expected: []map[string]interface{}{}, 809 expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"), 810 }, 811 { 812 name: "test JSON array", 813 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 814 repoFileContents: map[string][]byte{ 815 "cluster-config/production/config.json": []byte(` 816 [ 817 { 818 "cluster": { 819 "owner": "john.doe@example.com", 820 "name": "production", 821 "address": "https://kubernetes.default.svc", 822 "inner": { 823 "one" : "two" 824 } 825 } 826 }, 827 { 828 "cluster": { 829 "owner": "john.doe@example.com", 830 "name": "staging", 831 "address": "https://kubernetes.default.svc" 832 } 833 } 834 ]`), 835 }, 836 repoPathsError: nil, 837 expected: []map[string]interface{}{ 838 { 839 "cluster.owner": "john.doe@example.com", 840 "cluster.name": "production", 841 "cluster.address": "https://kubernetes.default.svc", 842 "cluster.inner.one": "two", 843 "path": "cluster-config/production", 844 "path.basename": "production", 845 "path[0]": "cluster-config", 846 "path[1]": "production", 847 "path.basenameNormalized": "production", 848 "path.filename": "config.json", 849 "path.filenameNormalized": "config.json", 850 }, 851 { 852 "cluster.owner": "john.doe@example.com", 853 "cluster.name": "staging", 854 "cluster.address": "https://kubernetes.default.svc", 855 "path": "cluster-config/production", 856 "path.basename": "production", 857 "path[0]": "cluster-config", 858 "path[1]": "production", 859 "path.basenameNormalized": "production", 860 "path.filename": "config.json", 861 "path.filenameNormalized": "config.json", 862 }, 863 }, 864 expectedError: nil, 865 }, 866 { 867 name: "Test YAML flow", 868 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.yaml"}}, 869 repoFileContents: map[string][]byte{ 870 "cluster-config/production/config.yaml": []byte(` 871 cluster: 872 owner: john.doe@example.com 873 name: production 874 address: https://kubernetes.default.svc 875 key1: val1 876 key2: 877 key2_1: val2_1 878 key2_2: 879 key2_2_1: val2_2_1 880 `), 881 "cluster-config/staging/config.yaml": []byte(` 882 cluster: 883 owner: foo.bar@example.com 884 name: staging 885 address: https://kubernetes.default.svc 886 `), 887 }, 888 repoPathsError: nil, 889 expected: []map[string]interface{}{ 890 { 891 "cluster.owner": "john.doe@example.com", 892 "cluster.name": "production", 893 "cluster.address": "https://kubernetes.default.svc", 894 "key1": "val1", 895 "key2.key2_1": "val2_1", 896 "key2.key2_2.key2_2_1": "val2_2_1", 897 "path": "cluster-config/production", 898 "path.basename": "production", 899 "path[0]": "cluster-config", 900 "path[1]": "production", 901 "path.basenameNormalized": "production", 902 "path.filename": "config.yaml", 903 "path.filenameNormalized": "config.yaml", 904 }, 905 { 906 "cluster.owner": "foo.bar@example.com", 907 "cluster.name": "staging", 908 "cluster.address": "https://kubernetes.default.svc", 909 "path": "cluster-config/staging", 910 "path.basename": "staging", 911 "path[0]": "cluster-config", 912 "path[1]": "staging", 913 "path.basenameNormalized": "staging", 914 "path.filename": "config.yaml", 915 "path.filenameNormalized": "config.yaml", 916 }, 917 }, 918 expectedError: nil, 919 }, 920 { 921 name: "test YAML array", 922 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.yaml"}}, 923 repoFileContents: map[string][]byte{ 924 "cluster-config/production/config.yaml": []byte(` 925 - cluster: 926 owner: john.doe@example.com 927 name: production 928 address: https://kubernetes.default.svc 929 inner: 930 one: two 931 - cluster: 932 owner: john.doe@example.com 933 name: staging 934 address: https://kubernetes.default.svc`), 935 }, 936 repoPathsError: nil, 937 expected: []map[string]interface{}{ 938 { 939 "cluster.owner": "john.doe@example.com", 940 "cluster.name": "production", 941 "cluster.address": "https://kubernetes.default.svc", 942 "cluster.inner.one": "two", 943 "path": "cluster-config/production", 944 "path.basename": "production", 945 "path[0]": "cluster-config", 946 "path[1]": "production", 947 "path.basenameNormalized": "production", 948 "path.filename": "config.yaml", 949 "path.filenameNormalized": "config.yaml", 950 }, 951 { 952 "cluster.owner": "john.doe@example.com", 953 "cluster.name": "staging", 954 "cluster.address": "https://kubernetes.default.svc", 955 "path": "cluster-config/production", 956 "path.basename": "production", 957 "path[0]": "cluster-config", 958 "path[1]": "production", 959 "path.basenameNormalized": "production", 960 "path.filename": "config.yaml", 961 "path.filenameNormalized": "config.yaml", 962 }, 963 }, 964 expectedError: nil, 965 }, 966 } 967 968 for _, testCase := range cases { 969 testCaseCopy := testCase 970 971 t.Run(testCaseCopy.name, func(t *testing.T) { 972 t.Parallel() 973 974 argoCDServiceMock := mocks.Repos{} 975 argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). 976 Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError) 977 978 var gitGenerator = NewGitGenerator(&argoCDServiceMock) 979 applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ 980 ObjectMeta: metav1.ObjectMeta{ 981 Name: "set", 982 }, 983 Spec: argoprojiov1alpha1.ApplicationSetSpec{ 984 Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ 985 Git: &argoprojiov1alpha1.GitGenerator{ 986 RepoURL: "RepoURL", 987 Revision: "Revision", 988 Files: testCaseCopy.files, 989 Values: testCaseCopy.values, 990 }, 991 }}, 992 }, 993 } 994 995 got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo) 996 fmt.Println(got, err) 997 998 if testCaseCopy.expectedError != nil { 999 assert.EqualError(t, err, testCaseCopy.expectedError.Error()) 1000 } else { 1001 assert.NoError(t, err) 1002 assert.ElementsMatch(t, testCaseCopy.expected, got) 1003 } 1004 1005 argoCDServiceMock.AssertExpectations(t) 1006 }) 1007 } 1008 } 1009 1010 func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) { 1011 1012 cases := []struct { 1013 name string 1014 // files is the list of paths/globs to match 1015 files []argoprojiov1alpha1.GitFileGeneratorItem 1016 // repoFileContents maps repo path to the literal contents of that path 1017 repoFileContents map[string][]byte 1018 // if repoPathsError is non-nil, the call to GetPaths(...) will return this error value 1019 repoPathsError error 1020 expected []map[string]interface{} 1021 expectedError error 1022 }{ 1023 { 1024 name: "happy flow: create params from git files", 1025 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 1026 repoFileContents: map[string][]byte{ 1027 "cluster-config/production/config.json": []byte(`{ 1028 "cluster": { 1029 "owner": "john.doe@example.com", 1030 "name": "production", 1031 "address": "https://kubernetes.default.svc" 1032 }, 1033 "key1": "val1", 1034 "key2": { 1035 "key2_1": "val2_1", 1036 "key2_2": { 1037 "key2_2_1": "val2_2_1" 1038 } 1039 }, 1040 "key3": 123 1041 }`), 1042 "cluster-config/staging/config.json": []byte(`{ 1043 "cluster": { 1044 "owner": "foo.bar@example.com", 1045 "name": "staging", 1046 "address": "https://kubernetes.default.svc" 1047 } 1048 }`), 1049 }, 1050 repoPathsError: nil, 1051 expected: []map[string]interface{}{ 1052 { 1053 "cluster": map[string]interface{}{ 1054 "owner": "john.doe@example.com", 1055 "name": "production", 1056 "address": "https://kubernetes.default.svc", 1057 }, 1058 "key1": "val1", 1059 "key2": map[string]interface{}{ 1060 "key2_1": "val2_1", 1061 "key2_2": map[string]interface{}{ 1062 "key2_2_1": "val2_2_1", 1063 }, 1064 }, 1065 "key3": float64(123), 1066 "path": map[string]interface{}{ 1067 "path": "cluster-config/production", 1068 "basename": "production", 1069 "filename": "config.json", 1070 "basenameNormalized": "production", 1071 "filenameNormalized": "config.json", 1072 "segments": []string{ 1073 "cluster-config", 1074 "production", 1075 }, 1076 }, 1077 }, 1078 { 1079 "cluster": map[string]interface{}{ 1080 "owner": "foo.bar@example.com", 1081 "name": "staging", 1082 "address": "https://kubernetes.default.svc", 1083 }, 1084 "path": map[string]interface{}{ 1085 "path": "cluster-config/staging", 1086 "basename": "staging", 1087 "filename": "config.json", 1088 "basenameNormalized": "staging", 1089 "filenameNormalized": "config.json", 1090 "segments": []string{ 1091 "cluster-config", 1092 "staging", 1093 }, 1094 }, 1095 }, 1096 }, 1097 expectedError: nil, 1098 }, 1099 { 1100 name: "handles error during getting repo paths", 1101 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 1102 repoFileContents: map[string][]byte{}, 1103 repoPathsError: fmt.Errorf("paths error"), 1104 expected: []map[string]interface{}{}, 1105 expectedError: fmt.Errorf("error generating params from git: paths error"), 1106 }, 1107 { 1108 name: "test invalid JSON file returns error", 1109 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 1110 repoFileContents: map[string][]byte{ 1111 "cluster-config/production/config.json": []byte(`invalid json file`), 1112 }, 1113 repoPathsError: nil, 1114 expected: []map[string]interface{}{}, 1115 expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"), 1116 }, 1117 { 1118 name: "test JSON array", 1119 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, 1120 repoFileContents: map[string][]byte{ 1121 "cluster-config/production/config.json": []byte(` 1122 [ 1123 { 1124 "cluster": { 1125 "owner": "john.doe@example.com", 1126 "name": "production", 1127 "address": "https://kubernetes.default.svc", 1128 "inner": { 1129 "one" : "two" 1130 } 1131 } 1132 }, 1133 { 1134 "cluster": { 1135 "owner": "john.doe@example.com", 1136 "name": "staging", 1137 "address": "https://kubernetes.default.svc" 1138 } 1139 } 1140 ]`), 1141 }, 1142 repoPathsError: nil, 1143 expected: []map[string]interface{}{ 1144 { 1145 "cluster": map[string]interface{}{ 1146 "owner": "john.doe@example.com", 1147 "name": "production", 1148 "address": "https://kubernetes.default.svc", 1149 "inner": map[string]interface{}{ 1150 "one": "two", 1151 }, 1152 }, 1153 "path": map[string]interface{}{ 1154 "path": "cluster-config/production", 1155 "basename": "production", 1156 "filename": "config.json", 1157 "basenameNormalized": "production", 1158 "filenameNormalized": "config.json", 1159 "segments": []string{ 1160 "cluster-config", 1161 "production", 1162 }, 1163 }, 1164 }, 1165 { 1166 "cluster": map[string]interface{}{ 1167 "owner": "john.doe@example.com", 1168 "name": "staging", 1169 "address": "https://kubernetes.default.svc", 1170 }, 1171 "path": map[string]interface{}{ 1172 "path": "cluster-config/production", 1173 "basename": "production", 1174 "filename": "config.json", 1175 "basenameNormalized": "production", 1176 "filenameNormalized": "config.json", 1177 "segments": []string{ 1178 "cluster-config", 1179 "production", 1180 }, 1181 }, 1182 }, 1183 }, 1184 expectedError: nil, 1185 }, 1186 { 1187 name: "Test YAML flow", 1188 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.yaml"}}, 1189 repoFileContents: map[string][]byte{ 1190 "cluster-config/production/config.yaml": []byte(` 1191 cluster: 1192 owner: john.doe@example.com 1193 name: production 1194 address: https://kubernetes.default.svc 1195 key1: val1 1196 key2: 1197 key2_1: val2_1 1198 key2_2: 1199 key2_2_1: val2_2_1 1200 `), 1201 "cluster-config/staging/config.yaml": []byte(` 1202 cluster: 1203 owner: foo.bar@example.com 1204 name: staging 1205 address: https://kubernetes.default.svc 1206 `), 1207 }, 1208 repoPathsError: nil, 1209 expected: []map[string]interface{}{ 1210 { 1211 "cluster": map[string]interface{}{ 1212 "owner": "john.doe@example.com", 1213 "name": "production", 1214 "address": "https://kubernetes.default.svc", 1215 }, 1216 "key1": "val1", 1217 "key2": map[string]interface{}{ 1218 "key2_1": "val2_1", 1219 "key2_2": map[string]interface{}{ 1220 "key2_2_1": "val2_2_1", 1221 }, 1222 }, 1223 "path": map[string]interface{}{ 1224 "path": "cluster-config/production", 1225 "basename": "production", 1226 "filename": "config.yaml", 1227 "basenameNormalized": "production", 1228 "filenameNormalized": "config.yaml", 1229 "segments": []string{ 1230 "cluster-config", 1231 "production", 1232 }, 1233 }, 1234 }, 1235 { 1236 "cluster": map[string]interface{}{ 1237 "owner": "foo.bar@example.com", 1238 "name": "staging", 1239 "address": "https://kubernetes.default.svc", 1240 }, 1241 "path": map[string]interface{}{ 1242 "path": "cluster-config/staging", 1243 "basename": "staging", 1244 "filename": "config.yaml", 1245 "basenameNormalized": "staging", 1246 "filenameNormalized": "config.yaml", 1247 "segments": []string{ 1248 "cluster-config", 1249 "staging", 1250 }, 1251 }, 1252 }, 1253 }, 1254 expectedError: nil, 1255 }, 1256 { 1257 name: "test YAML array", 1258 files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.yaml"}}, 1259 repoFileContents: map[string][]byte{ 1260 "cluster-config/production/config.yaml": []byte(` 1261 - cluster: 1262 owner: john.doe@example.com 1263 name: production 1264 address: https://kubernetes.default.svc 1265 inner: 1266 one: two 1267 - cluster: 1268 owner: john.doe@example.com 1269 name: staging 1270 address: https://kubernetes.default.svc`), 1271 }, 1272 repoPathsError: nil, 1273 expected: []map[string]interface{}{ 1274 { 1275 "cluster": map[string]interface{}{ 1276 "owner": "john.doe@example.com", 1277 "name": "production", 1278 "address": "https://kubernetes.default.svc", 1279 "inner": map[string]interface{}{ 1280 "one": "two", 1281 }, 1282 }, 1283 "path": map[string]interface{}{ 1284 "path": "cluster-config/production", 1285 "basename": "production", 1286 "filename": "config.yaml", 1287 "basenameNormalized": "production", 1288 "filenameNormalized": "config.yaml", 1289 "segments": []string{ 1290 "cluster-config", 1291 "production", 1292 }, 1293 }, 1294 }, 1295 { 1296 "cluster": map[string]interface{}{ 1297 "owner": "john.doe@example.com", 1298 "name": "staging", 1299 "address": "https://kubernetes.default.svc", 1300 }, 1301 "path": map[string]interface{}{ 1302 "path": "cluster-config/production", 1303 "basename": "production", 1304 "filename": "config.yaml", 1305 "basenameNormalized": "production", 1306 "filenameNormalized": "config.yaml", 1307 "segments": []string{ 1308 "cluster-config", 1309 "production", 1310 }, 1311 }, 1312 }, 1313 }, 1314 expectedError: nil, 1315 }, 1316 } 1317 1318 for _, testCase := range cases { 1319 testCaseCopy := testCase 1320 1321 t.Run(testCaseCopy.name, func(t *testing.T) { 1322 t.Parallel() 1323 1324 argoCDServiceMock := mocks.Repos{} 1325 argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). 1326 Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError) 1327 1328 var gitGenerator = NewGitGenerator(&argoCDServiceMock) 1329 applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ 1330 ObjectMeta: metav1.ObjectMeta{ 1331 Name: "set", 1332 }, 1333 Spec: argoprojiov1alpha1.ApplicationSetSpec{ 1334 GoTemplate: true, 1335 Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ 1336 Git: &argoprojiov1alpha1.GitGenerator{ 1337 RepoURL: "RepoURL", 1338 Revision: "Revision", 1339 Files: testCaseCopy.files, 1340 }, 1341 }}, 1342 }, 1343 } 1344 1345 got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo) 1346 fmt.Println(got, err) 1347 1348 if testCaseCopy.expectedError != nil { 1349 assert.EqualError(t, err, testCaseCopy.expectedError.Error()) 1350 } else { 1351 assert.NoError(t, err) 1352 assert.ElementsMatch(t, testCaseCopy.expected, got) 1353 } 1354 1355 argoCDServiceMock.AssertExpectations(t) 1356 }) 1357 } 1358 }