github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/service/batch_test.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package service 18 19 import ( 20 "context" 21 "fmt" 22 "os/exec" 23 "path" 24 "path/filepath" 25 "testing" 26 27 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository/testutil" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "google.golang.org/grpc/status" 32 "google.golang.org/protobuf/testing/protocmp" 33 34 api "github.com/freiheit-com/kuberpult/pkg/api/v1" 35 "github.com/freiheit-com/kuberpult/pkg/auth" 36 "github.com/freiheit-com/kuberpult/pkg/ptr" 37 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/config" 38 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository" 39 ) 40 41 // Used to compare two error message strings, needed because errors.Is(fmt.Errorf(text),fmt.Errorf(text)) == false 42 type errMatcher struct { 43 msg string 44 } 45 46 func (e errMatcher) Error() string { 47 return e.msg 48 } 49 50 func (e errMatcher) Is(err error) bool { 51 return e.Error() == err.Error() 52 } 53 54 func getBatchActions() []*api.BatchAction { 55 opDeploy := &api.BatchAction_Deploy{ 56 Deploy: &api.DeployRequest{ 57 Environment: "production", 58 Application: "test", 59 Version: 1, 60 LockBehavior: api.LockBehavior_FAIL, 61 }, 62 } 63 opCreateEnvLock := &api.BatchAction_CreateEnvironmentLock{ 64 CreateEnvironmentLock: &api.CreateEnvironmentLockRequest{ 65 Environment: "production", 66 LockId: "envlock", 67 Message: "please", 68 }, 69 } 70 opCreateAppLock := &api.BatchAction_CreateEnvironmentApplicationLock{ 71 CreateEnvironmentApplicationLock: &api.CreateEnvironmentApplicationLockRequest{ 72 Environment: "production", 73 Application: "test", 74 LockId: "applock", 75 Message: "please", 76 }, 77 } 78 opDeleteEnvLock := &api.BatchAction_DeleteEnvironmentLock{ // this deletes the existing lock in the transformers 79 DeleteEnvironmentLock: &api.DeleteEnvironmentLockRequest{ 80 Environment: "production", 81 LockId: "1234", 82 }, 83 } 84 opDeleteAppLock := &api.BatchAction_DeleteEnvironmentApplicationLock{ // this deletes the existing lock in the transformers 85 DeleteEnvironmentApplicationLock: &api.DeleteEnvironmentApplicationLockRequest{ 86 Environment: "production", 87 Application: "test", 88 LockId: "5678", 89 }, 90 } 91 ops := []*api.BatchAction{ // it works through the batch in order 92 {Action: opDeleteEnvLock}, 93 {Action: opDeleteAppLock}, 94 {Action: opDeploy}, 95 {Action: opCreateEnvLock}, 96 {Action: opCreateAppLock}, 97 } 98 return ops 99 } 100 101 func getNBatchActions(N int) []*api.BatchAction { 102 var ops []*api.BatchAction 103 for i := 1; i <= N; i++ { 104 deploy := api.DeployRequest{ 105 Environment: "production", 106 Application: "test", 107 Version: 1, 108 LockBehavior: api.LockBehavior_FAIL, 109 } 110 if i%2 == 0 { 111 deploy.Version = 2 112 } 113 ops = append(ops, &api.BatchAction{ 114 Action: &api.BatchAction_Deploy{ 115 Deploy: &deploy, 116 }, 117 }) 118 } 119 return ops 120 } 121 122 func TestBatchServiceWorks(t *testing.T) { 123 const prod = "production" 124 tcs := []struct { 125 Name string 126 Batch []*api.BatchAction 127 Setup []repository.Transformer 128 context context.Context 129 svc *BatchServer 130 expectedError error 131 }{ 132 { 133 Name: "5 sample actions", 134 Setup: []repository.Transformer{ 135 &repository.CreateEnvironment{ 136 Environment: prod, 137 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Latest: true}}, 138 }, 139 &repository.CreateApplicationVersion{ 140 Application: "test", 141 Manifests: map[string]string{ 142 prod: "manifest", 143 }, 144 }, 145 &repository.CreateEnvironmentLock{ // will be deleted by the batch actions 146 Environment: prod, 147 LockId: "1234", 148 Message: "EnvLock", 149 }, 150 &repository.CreateEnvironmentApplicationLock{ // will be deleted by the batch actions 151 Environment: prod, 152 Application: "test", 153 LockId: "5678", 154 Message: "AppLock", 155 }, 156 }, 157 Batch: getBatchActions(), 158 context: testutil.MakeTestContext(), 159 svc: &BatchServer{}, 160 }, 161 { 162 Name: "testing Dex setup with permissions", 163 Setup: []repository.Transformer{ 164 &repository.CreateEnvironment{ 165 Environment: "production", 166 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Latest: true}}, 167 }, 168 &repository.CreateApplicationVersion{ 169 Application: "test", 170 Manifests: map[string]string{ 171 "production": "manifest", 172 }, 173 }, 174 &repository.CreateEnvironmentLock{ 175 Environment: "production", 176 LockId: "1234", 177 Message: "EnvLock", 178 }, 179 &repository.CreateEnvironmentApplicationLock{ 180 Environment: "production", 181 Application: "test", 182 LockId: "5678", 183 Message: "no message", 184 }, 185 }, 186 Batch: getBatchActions(), 187 context: testutil.MakeTestContextDexEnabled(), 188 svc: &BatchServer{ 189 RBACConfig: auth.RBACConfig{ 190 DexEnabled: true, 191 Policy: map[string]*auth.Permission{ 192 "developer,DeployRelease,production:production,*,allow": {Role: "Developer"}, 193 "developer,CreateLock,production:production,*,allow": {Role: "Developer"}, 194 "developer,DeleteLock,production:production,*,allow": {Role: "Developer"}, 195 }, 196 }, 197 }, 198 }, 199 } 200 for _, tc := range tcs { 201 tc := tc 202 t.Run(tc.Name, func(t *testing.T) { 203 repo, err := setupRepositoryTest(t) 204 if err != nil { 205 t.Fatal(err) 206 } 207 for _, tr := range tc.Setup { 208 err := repo.Apply(tc.context, tr) 209 if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { 210 t.Fatalf("error mismatch (-want, +got):\n%s", diff) 211 } 212 } 213 214 tc.svc.Repository = repo 215 resp, err := tc.svc.ProcessBatch( 216 tc.context, 217 &api.BatchRequest{ 218 Actions: tc.Batch, 219 }, 220 ) 221 if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { 222 t.Fatalf("error mismatch (-want, +got):\n%s", diff) 223 } 224 if tc.expectedError != nil { 225 return 226 } 227 228 if len(resp.Results) != len(tc.Batch) { 229 t.Errorf("got wrong number of batch results, expected %d but got %d", len(tc.Batch), len(resp.Results)) 230 } 231 // check deployment version 232 { 233 version, err := tc.svc.Repository.State().GetEnvironmentApplicationVersion("production", "test") 234 if err != nil { 235 t.Fatal(err) 236 } 237 if version == nil { 238 t.Errorf("unexpected version: expected 1, actual: %d", version) 239 } 240 if *version != 1 { 241 t.Errorf("unexpected version: expected 1, actual: %d", *version) 242 } 243 } 244 // check that the envlock was created/deleted 245 { 246 envLocks, err := tc.svc.Repository.State().GetEnvironmentLocks("production") 247 if err != nil { 248 t.Fatal(err) 249 } 250 lock, exists := envLocks["envlock"] 251 if !exists { 252 t.Error("lock was not created") 253 } 254 if lock.Message != "please" { 255 t.Errorf("unexpected lock message: expected \"please\", actual: %q", lock.Message) 256 } 257 _, exists = envLocks["1234"] 258 if exists { 259 t.Error("lock was not deleted") 260 } 261 } 262 // check that the applock was created/deleted 263 { 264 appLocks, err := tc.svc.Repository.State().GetEnvironmentApplicationLocks("production", "test") 265 if err != nil { 266 t.Fatal(err) 267 } 268 lock, exists := appLocks["applock"] 269 if !exists { 270 t.Error("lock was not created") 271 } 272 if lock.Message != "please" { 273 t.Errorf("unexpected lock message: expected \"please\", actual: %q", lock.Message) 274 } 275 _, exists = appLocks["5678"] 276 if exists { 277 t.Error("lock was not deleted") 278 } 279 } 280 281 }) 282 } 283 } 284 285 func TestBatchServiceFails(t *testing.T) { 286 tcs := []struct { 287 Name string 288 Batch []*api.BatchAction 289 Setup []repository.Transformer 290 context context.Context 291 svc *BatchServer 292 expectedError error 293 expectedSetupError error 294 }{ 295 { 296 Name: "testing Dex setup without permissions", 297 Setup: []repository.Transformer{ 298 &repository.CreateEnvironment{ 299 Environment: "production", 300 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Latest: true}}, 301 }, 302 &repository.CreateApplicationVersion{ 303 Application: "test", 304 Manifests: map[string]string{ 305 "production": "manifest", 306 }, 307 }, 308 &repository.CreateEnvironmentLock{ // will be deleted by the batch actions 309 Environment: "production", 310 LockId: "1234", 311 Message: "EnvLock", 312 Authentication: repository.Authentication{RBACConfig: auth.RBACConfig{DexEnabled: true}}, 313 }, 314 }, 315 Batch: []*api.BatchAction{}, 316 context: testutil.MakeTestContextDexEnabled(), 317 svc: &BatchServer{}, 318 // expectedSetupError: errMatcher{"error at index 0 of transformer batch: rpc error: code = PermissionDenied desc = PermissionDenied: The user 'test tester' with role 'developer' is not allowed to perform the action 'CreateLock' on environment 'production'"}, 319 expectedSetupError: &repository.TransformerBatchApplyError{ 320 Index: 0, 321 TransformerError: auth.PermissionError{ 322 User: "test tester", 323 Role: "developer", 324 Action: "CreateLock", 325 Environment: "production", 326 }, 327 }, 328 }, 329 } 330 for _, tc := range tcs { 331 tc := tc 332 t.Run(tc.Name, func(t *testing.T) { 333 repo, err := setupRepositoryTest(t) 334 if err != nil { 335 t.Fatal(err) 336 } 337 errSetupObserved := false 338 for _, tr := range tc.Setup { 339 err := repo.Apply(tc.context, tr) 340 if err != nil { 341 if diff := cmp.Diff(tc.expectedSetupError, err, cmpopts.EquateErrors()); diff != "" { 342 t.Fatalf("error during setup mismatch (-want, +got):\n%s", diff) 343 } else { 344 errSetupObserved = true 345 } 346 } 347 } 348 if tc.expectedSetupError != nil && !errSetupObserved { 349 // ensure we fail on unobserved error 350 t.Errorf("did not oberve error during setup: %s", tc.expectedSetupError.Error()) 351 } 352 353 tc.svc.Repository = repo 354 resp, err := tc.svc.ProcessBatch( 355 tc.context, 356 &api.BatchRequest{ 357 Actions: tc.Batch, 358 }, 359 ) 360 if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { 361 t.Fatalf("error mismatch (-want, +got):\n%s", diff) 362 } 363 364 if len(resp.Results) != len(tc.Batch) { 365 t.Errorf("got wrong number of batch results, expected %d but got %d", len(tc.Batch), len(resp.Results)) 366 } 367 }) 368 } 369 } 370 371 func TestBatchServiceErrors(t *testing.T) { 372 tcs := []struct { 373 Name string 374 Batch []*api.BatchAction 375 Setup []repository.Transformer 376 ExpectedResponse *api.BatchResponse 377 ExpectedError error 378 }{ 379 { 380 // tests that in ProcessBatch, transformer errors are returned without wrapping them in a 381 // not so helpful "internal error" 382 Name: "forwards transformers error to caller: cannot open manifest", 383 Setup: []repository.Transformer{}, 384 Batch: []*api.BatchAction{ 385 { 386 Action: &api.BatchAction_Deploy{ 387 Deploy: &api.DeployRequest{ 388 Environment: "dev", 389 Application: "myapp", 390 Version: 666, 391 LockBehavior: 0, 392 }, 393 }, 394 }}, 395 ExpectedResponse: nil, 396 ExpectedError: &repository.TransformerBatchApplyError{ 397 Index: 0, 398 TransformerError: errMatcher{"deployment failed: could not open manifest for app myapp with release 666 on env dev 'applications/myapp/releases/666/environments/dev/manifests.yaml': file does not exist"}, 399 }, 400 }, 401 { 402 Name: "create release endpoint fails app validity check", 403 Setup: []repository.Transformer{}, 404 Batch: []*api.BatchAction{ 405 { 406 Action: &api.BatchAction_CreateRelease{ 407 CreateRelease: &api.CreateReleaseRequest{ 408 Environment: "dev", 409 Application: "myappIsWayTooLongDontYouThink", 410 Team: "team1", 411 Manifests: nil, 412 Version: 666, 413 SourceCommitId: "1", 414 SourceAuthor: "2", 415 SourceMessage: "3", 416 SourceRepoUrl: "4", 417 }, 418 }, 419 }, 420 }, 421 ExpectedResponse: &api.BatchResponse{ 422 Results: []*api.BatchResult{ 423 { 424 Result: &api.BatchResult_CreateReleaseResponse{ 425 CreateReleaseResponse: &api.CreateReleaseResponse{ 426 Response: &api.CreateReleaseResponse_TooLong{ 427 TooLong: &api.CreateReleaseResponseAppNameTooLong{ 428 AppName: "myappIsWayTooLongDontYouThink", 429 RegExp: "\\A[a-z0-9]+(?:-[a-z0-9]+)*\\z", 430 MaxLen: 39, 431 }, 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 }, 439 } 440 for _, tc := range tcs { 441 tc := tc 442 t.Run(tc.Name, func(t *testing.T) { 443 repo, err := setupRepositoryTest(t) 444 if err != nil { 445 t.Fatal(err) 446 } 447 for _, tr := range tc.Setup { 448 if err := repo.Apply(testutil.MakeTestContext(), tr); err != nil { 449 t.Fatal(err) 450 } 451 } 452 svc := &BatchServer{ 453 Repository: repo, 454 } 455 response, processErr := svc.ProcessBatch( 456 testutil.MakeTestContext(), 457 &api.BatchRequest{ 458 Actions: tc.Batch, 459 }, 460 ) 461 if diff := cmp.Diff(tc.ExpectedError, processErr, cmpopts.EquateErrors()); diff != "" { 462 t.Errorf("error mismatch (-want, +got):\n%s", diff) 463 } 464 if diff := cmp.Diff(tc.ExpectedResponse, response, protocmp.Transform()); diff != "" { 465 t.Fatalf("response mismatch, diff (-want, +got):\n%s", diff) 466 } 467 }) 468 } 469 } 470 471 func TestBatchServiceLimit(t *testing.T) { 472 transformers := []repository.Transformer{ 473 &repository.CreateEnvironment{ 474 Environment: "production", 475 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Latest: true}}, 476 }, 477 &repository.CreateApplicationVersion{ 478 Application: "test", 479 Manifests: map[string]string{ 480 "production": "manifest", 481 }, 482 }, 483 &repository.CreateApplicationVersion{ 484 Application: "test", 485 Manifests: map[string]string{ 486 "production": "manifest2", 487 }, 488 }, 489 } 490 var two uint64 = 2 491 tcs := []struct { 492 Name string 493 Batch []*api.BatchAction 494 Setup []repository.Transformer 495 ShouldSucceed bool 496 ExpectedVersion *uint64 497 }{ 498 { 499 Name: "exactly the maximum number of actions", 500 Setup: transformers, 501 ShouldSucceed: true, 502 Batch: getNBatchActions(maxBatchActions), 503 ExpectedVersion: &two, 504 }, 505 { 506 Name: "more than the maximum number of actions", 507 Setup: transformers, 508 ShouldSucceed: false, 509 Batch: getNBatchActions(maxBatchActions + 1), // more than max 510 ExpectedVersion: nil, 511 }, 512 } 513 for _, tc := range tcs { 514 tc := tc 515 t.Run(tc.Name, func(t *testing.T) { 516 repo, err := setupRepositoryTest(t) 517 if err != nil { 518 t.Fatal(err) 519 } 520 for _, tr := range tc.Setup { 521 if err := repo.Apply(testutil.MakeTestContext(), tr); err != nil { 522 t.Fatal(err) 523 } 524 } 525 svc := &BatchServer{ 526 Repository: repo, 527 } 528 _, err = svc.ProcessBatch( 529 testutil.MakeTestContext(), 530 &api.BatchRequest{ 531 Actions: tc.Batch, 532 }, 533 ) 534 if !tc.ShouldSucceed { 535 if err == nil { 536 t.Fatal("expected an error but got none") 537 } 538 s, ok := status.FromError(err) 539 if !ok { 540 t.Fatalf("error is not a status error, got: %#v", err) 541 } 542 expectedMessage := fmt.Sprintf("cannot process batch: too many actions. limit is %d", maxBatchActions) 543 if s.Message() != expectedMessage { 544 t.Errorf("invalid error message: expected %q, actual: %q", expectedMessage, s.Message()) 545 } 546 } else { 547 if err != nil { 548 t.Fatal(err) 549 } 550 version, err := svc.Repository.State().GetEnvironmentApplicationVersion("production", "test") 551 if err != nil { 552 t.Fatal(err) 553 } 554 if version == nil { 555 t.Errorf("unexpected version: expected %d, actual: %d", *tc.ExpectedVersion, version) 556 } 557 if *version != *tc.ExpectedVersion { 558 t.Errorf("unexpected version: expected %d, actual: %d", *tc.ExpectedVersion, *version) 559 } 560 } 561 }) 562 } 563 } 564 565 func setupRepositoryTest(t *testing.T) (repository.Repository, error) { 566 t.Parallel() 567 dir := t.TempDir() 568 remoteDir := path.Join(dir, "remote") 569 localDir := path.Join(dir, "local") 570 cmd := exec.Command("git", "init", "--bare", remoteDir) 571 cmd.Start() 572 cmd.Wait() 573 t.Logf("test created dir: %s", localDir) 574 repo, err := repository.New( 575 testutil.MakeTestContext(), 576 repository.RepositoryConfig{ 577 URL: remoteDir, 578 Path: localDir, 579 CommitterEmail: "kuberpult@freiheit.com", 580 CommitterName: "kuberpult", 581 EnvironmentConfigsPath: filepath.Join(remoteDir, "..", "environment_configs.json"), 582 }, 583 ) 584 if err != nil { 585 t.Fatal(err) 586 } 587 return repo, nil 588 } 589 590 func TestReleaseTrain(t *testing.T) { 591 tcs := []struct { 592 Name string 593 Setup []repository.Transformer 594 Request *api.BatchRequest 595 ExpectedResponse *api.BatchResponse 596 }{ 597 { 598 Name: "Get Upstream env and TargetEnv", 599 Setup: []repository.Transformer{ 600 &repository.CreateEnvironment{ 601 Environment: "acceptance", 602 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Environment: "production"}}, 603 }, 604 &repository.CreateApplicationVersion{ 605 Application: "test", 606 Manifests: map[string]string{ 607 "acceptance": "manifest", 608 }, 609 }, 610 }, 611 Request: &api.BatchRequest{ 612 Actions: []*api.BatchAction{ 613 { 614 Action: &api.BatchAction_ReleaseTrain{ 615 ReleaseTrain: &api.ReleaseTrainRequest{ 616 Target: "acceptance", 617 618 Team: "team", 619 }, 620 }, 621 }, 622 }, 623 }, 624 ExpectedResponse: &api.BatchResponse{ 625 Results: []*api.BatchResult{ 626 { 627 Result: &api.BatchResult_ReleaseTrain{ 628 ReleaseTrain: &api.ReleaseTrainResponse{ 629 Target: "acceptance", 630 Team: "team", 631 }, 632 }, 633 }, 634 }, 635 }, 636 }, 637 { 638 Name: "Get Upstream (latest) and TargetEnv", 639 Setup: []repository.Transformer{ 640 &repository.CreateEnvironment{ 641 Environment: "acceptance", 642 Config: config.EnvironmentConfig{Upstream: &config.EnvironmentConfigUpstream{Latest: true}}, 643 }, 644 &repository.CreateApplicationVersion{ 645 Application: "test", 646 Manifests: map[string]string{ 647 "acceptance": "manifest", 648 }, 649 }, 650 }, 651 Request: &api.BatchRequest{ 652 Actions: []*api.BatchAction{ 653 { 654 Action: &api.BatchAction_ReleaseTrain{ 655 ReleaseTrain: &api.ReleaseTrainRequest{ 656 Target: "acceptance", 657 658 Team: "team", 659 }, 660 }, 661 }, 662 }, 663 }, 664 ExpectedResponse: &api.BatchResponse{ 665 Results: []*api.BatchResult{ 666 { 667 Result: &api.BatchResult_ReleaseTrain{ 668 ReleaseTrain: &api.ReleaseTrainResponse{ 669 Target: "acceptance", 670 Team: "team", 671 }, 672 }, 673 }, 674 }, 675 }, 676 }, 677 } 678 for _, tc := range tcs { 679 tc := tc 680 t.Run(tc.Name, func(t *testing.T) { 681 repo, err := setupRepositoryTest(t) 682 if err != nil { 683 t.Fatal(err) 684 } 685 for _, tr := range tc.Setup { 686 if err := repo.Apply(testutil.MakeTestContext(), tr); err != nil { 687 t.Fatal(err) 688 } 689 } 690 svc := &BatchServer{ 691 Repository: repo, 692 } 693 resp, err := svc.ProcessBatch( 694 testutil.MakeTestContext(), 695 tc.Request, 696 ) 697 if err != nil { 698 t.Errorf("unexpected error: %q", err) 699 } 700 if d := cmp.Diff(tc.ExpectedResponse, resp, protocmp.Transform()); d != "" { 701 t.Errorf("batch response mismatch: %s", d) 702 } 703 }) 704 } 705 } 706 707 func TestCreateEnvironmentTrain(t *testing.T) { 708 tcs := []struct { 709 Name string 710 Setup []repository.Transformer 711 Request *api.BatchRequest 712 ExpectedResponse *api.BatchResponse 713 ExpectedEnvironments map[string]config.EnvironmentConfig 714 }{ 715 { 716 Name: "Minimal test case", 717 Setup: []repository.Transformer{}, 718 Request: &api.BatchRequest{ 719 Actions: []*api.BatchAction{ 720 { 721 Action: &api.BatchAction_CreateEnvironment{ 722 CreateEnvironment: &api.CreateEnvironmentRequest{ 723 Environment: "env", 724 }, 725 }, 726 }, 727 }, 728 }, 729 ExpectedResponse: &api.BatchResponse{ 730 Results: []*api.BatchResult{ 731 nil, 732 }, 733 }, 734 ExpectedEnvironments: map[string]config.EnvironmentConfig{ 735 "env": config.EnvironmentConfig{}, 736 }, 737 }, 738 { 739 Name: "With upstream latest", 740 Setup: []repository.Transformer{}, 741 Request: &api.BatchRequest{ 742 Actions: []*api.BatchAction{ 743 { 744 Action: &api.BatchAction_CreateEnvironment{ 745 CreateEnvironment: &api.CreateEnvironmentRequest{ 746 Environment: "env", 747 Config: &api.EnvironmentConfig{ 748 Upstream: &api.EnvironmentConfig_Upstream{ 749 Latest: ptr.Bool(true), 750 }, 751 }, 752 }, 753 }, 754 }, 755 }, 756 }, 757 ExpectedResponse: &api.BatchResponse{ 758 Results: []*api.BatchResult{ 759 nil, 760 }, 761 }, 762 ExpectedEnvironments: map[string]config.EnvironmentConfig{ 763 "env": config.EnvironmentConfig{ 764 Upstream: &config.EnvironmentConfigUpstream{Latest: true}, 765 }, 766 }, 767 }, 768 { 769 Name: "With upstream env", 770 Setup: []repository.Transformer{}, 771 Request: &api.BatchRequest{ 772 Actions: []*api.BatchAction{ 773 { 774 Action: &api.BatchAction_CreateEnvironment{ 775 CreateEnvironment: &api.CreateEnvironmentRequest{ 776 Environment: "env", 777 Config: &api.EnvironmentConfig{ 778 Upstream: &api.EnvironmentConfig_Upstream{ 779 Environment: ptr.FromString("other-env"), 780 }, 781 }, 782 }, 783 }, 784 }, 785 }, 786 }, 787 ExpectedResponse: &api.BatchResponse{ 788 Results: []*api.BatchResult{ 789 nil, 790 }, 791 }, 792 ExpectedEnvironments: map[string]config.EnvironmentConfig{ 793 "env": config.EnvironmentConfig{ 794 Upstream: &config.EnvironmentConfigUpstream{Environment: "other-env"}, 795 }, 796 }, 797 }, 798 { 799 Name: "With minimal argocd config", 800 Setup: []repository.Transformer{}, 801 Request: &api.BatchRequest{ 802 Actions: []*api.BatchAction{ 803 { 804 Action: &api.BatchAction_CreateEnvironment{ 805 CreateEnvironment: &api.CreateEnvironmentRequest{ 806 Environment: "env", 807 Config: &api.EnvironmentConfig{ 808 Argocd: &api.EnvironmentConfig_ArgoCD{}, 809 }, 810 }, 811 }, 812 }, 813 }, 814 }, 815 ExpectedResponse: &api.BatchResponse{ 816 Results: []*api.BatchResult{ 817 nil, 818 }, 819 }, 820 ExpectedEnvironments: map[string]config.EnvironmentConfig{ 821 "env": config.EnvironmentConfig{ 822 ArgoCd: &config.EnvironmentConfigArgoCd{}, 823 }, 824 }, 825 }, 826 { 827 Name: "With full argocd config", 828 Setup: []repository.Transformer{}, 829 Request: &api.BatchRequest{ 830 Actions: []*api.BatchAction{ 831 { 832 Action: &api.BatchAction_CreateEnvironment{ 833 CreateEnvironment: &api.CreateEnvironmentRequest{ 834 Environment: "env", 835 Config: &api.EnvironmentConfig{ 836 Argocd: &api.EnvironmentConfig_ArgoCD{ 837 Destination: &api.EnvironmentConfig_ArgoCD_Destination{ 838 Name: "name", 839 Server: "server", 840 Namespace: ptr.FromString("namespace"), 841 AppProjectNamespace: ptr.FromString("app-project-namespace"), 842 ApplicationNamespace: ptr.FromString("app-namespace"), 843 }, 844 SyncWindows: []*api.EnvironmentConfig_ArgoCD_SyncWindows{ 845 &api.EnvironmentConfig_ArgoCD_SyncWindows{ 846 Schedule: "schedule", 847 Duration: "duration", 848 Kind: "kind", 849 Applications: []string{"applications"}, 850 }, 851 }, 852 AccessList: []*api.EnvironmentConfig_ArgoCD_AccessEntry{ 853 &api.EnvironmentConfig_ArgoCD_AccessEntry{ 854 Group: "group", 855 Kind: "kind", 856 }, 857 }, 858 SyncOptions: []string{"sync-option"}, 859 IgnoreDifferences: []*api.EnvironmentConfig_ArgoCD_IgnoreDifferences{ 860 { 861 Group: "group", 862 Kind: "kind", 863 Name: "name", 864 Namespace: "namespace", 865 JsonPointers: []string{"/json"}, 866 JqPathExpressions: []string{".jq"}, 867 ManagedFieldsManagers: []string{"manager"}, 868 }, 869 }, 870 }, 871 }, 872 }, 873 }, 874 }, 875 }, 876 }, 877 ExpectedResponse: &api.BatchResponse{ 878 Results: []*api.BatchResult{ 879 nil, 880 }, 881 }, 882 ExpectedEnvironments: map[string]config.EnvironmentConfig{ 883 "env": config.EnvironmentConfig{ 884 ArgoCd: &config.EnvironmentConfigArgoCd{ 885 Destination: config.ArgoCdDestination{ 886 Name: "name", 887 Server: "server", 888 Namespace: ptr.FromString("namespace"), 889 AppProjectNamespace: ptr.FromString("app-project-namespace"), 890 ApplicationNamespace: ptr.FromString("app-namespace"), 891 }, 892 SyncWindows: []config.ArgoCdSyncWindow{ 893 { 894 Schedule: "schedule", 895 Duration: "duration", 896 Kind: "kind", 897 Apps: []string{"applications"}, 898 }, 899 }, 900 ClusterResourceWhitelist: []config.AccessEntry{{Group: "group", Kind: "kind"}}, 901 SyncOptions: []string{"sync-option"}, 902 IgnoreDifferences: []config.ArgoCdIgnoreDifference{ 903 { 904 Group: "group", 905 Kind: "kind", 906 Name: "name", 907 Namespace: "namespace", 908 JSONPointers: []string{"/json"}, 909 JqPathExpressions: []string{".jq"}, 910 ManagedFieldsManagers: []string{"manager"}, 911 }, 912 }, 913 }, 914 }, 915 }, 916 }, 917 } 918 for _, tc := range tcs { 919 tc := tc 920 t.Run(tc.Name, func(t *testing.T) { 921 repo, err := setupRepositoryTest(t) 922 if err != nil { 923 t.Fatal(err) 924 } 925 for _, tr := range tc.Setup { 926 if err := repo.Apply(testutil.MakeTestContext(), tr); err != nil { 927 t.Fatal(err) 928 } 929 } 930 svc := &BatchServer{ 931 Repository: repo, 932 } 933 resp, err := svc.ProcessBatch( 934 testutil.MakeTestContext(), 935 tc.Request, 936 ) 937 if err != nil { 938 t.Errorf("unexpected error: %q", err) 939 } 940 if d := cmp.Diff(tc.ExpectedResponse, resp, protocmp.Transform()); d != "" { 941 t.Errorf("batch response mismatch: %s", d) 942 } 943 envs, err := repo.State().GetEnvironmentConfigs() 944 if err != nil { 945 t.Errorf("unexpected error: %q", err) 946 } 947 if d := cmp.Diff(tc.ExpectedEnvironments, envs); d != "" { 948 t.Errorf("batch response mismatch: %s", d) 949 } 950 }) 951 } 952 }