From 119a82529627de84dd81b3de4a9b27abe04c3ecb Mon Sep 17 00:00:00 2001 From: Wilken Rivera Date: Tue, 22 Feb 2022 15:34:09 -0500 Subject: [PATCH] Update CreateIntialBuildForIteration to initialize each build its own map (#11574) * Rename mock bucket test file * Add failing tests for reproducing #11573 ``` === RUN TestBucket_CreateInitialBuildForIteration 2022/02/16 16:22:21 [TRACE] creating initial build for component happycloud.image --- PASS: TestBucket_CreateInitialBuildForIteration (0.00s) === RUN TestBucket_UpdateLabelsForBuild 2022/02/16 16:22:21 [TRACE] creating initial build for component happycloud.image types.bucket_test.go:87: expected the initial build to have an additional build label but thee is no diff: "" --- FAIL: TestBucket_UpdateLabelsForBuild (0.00s) === RUN TestBucket_UpdateLabelsForBuild_withMultipleBuilds 2022/02/16 16:22:21 [TRACE] creating initial build for component happycloud.image 2022/02/16 16:22:21 [TRACE] creating initial build for component happycloud.image2 types.bucket_test.go:125: Comparing component build labels: map[based_off:alpine source_image:another-happycloud-image version:1.7.0] against global build labels: map[based_off:alpine source_image:another-happycloud-image version:1.7.0] types.bucket_test.go:128: expected the initial build to have an additional build label but they are equal types.bucket_test.go:125: Comparing component build labels: map[based_off:alpine source_image:another-happycloud-image version:1.7.0] against global build labels: map[based_off:alpine source_image:another-happycloud-image version:1.7.0] types.bucket_test.go:128: expected the initial build to have an additional build label but they are equal --- FAIL: TestBucket_UpdateLabelsForBuild_withMultipleBuilds (0.00s) FAIL FAIL github.com/hashicorp/packer/internal/registry 0.646s ``` * Update CreateIntialBuildForIteration to initialize each build with a new map Previously upon creating the initial build the same map, which was initialized for the build_labels argument was being shared across all build images. This was causing an issue with labels being backed by the same map for all builds. This change ensures that all builds get their own map with any global build labels copied over during the initial creation. Closes #11573 Passing tests with changes on branch ``` RUN TestBucket_CreateInitialBuildForIteration 2022/02/16 16:37:40 [TRACE] creating initial build for component happycloud.image --- PASS: TestBucket_CreateInitialBuildForIteration (0.00s) === RUN TestBucket_UpdateLabelsForBuild 2022/02/16 16:37:40 [TRACE] creating initial build for component happycloud.image --- PASS: TestBucket_UpdateLabelsForBuild (0.00s) === RUN TestBucket_UpdateLabelsForBuild_withMultipleBuilds 2022/02/16 16:37:40 [TRACE] creating initial build for component happycloud.image 2022/02/16 16:37:40 [TRACE] creating initial build for component happycloud.image2 types.bucket_test.go:125: Comparing component build labels: map[based_off:alpine source_image:another-happycloud-image version:1.7.0] against global build labels: map[based_off:alpine version:1.7.0] types.bucket_test.go:125: Comparing component build labels: map[based_off:alpine source_image:the-original-happycloud-image version:1.7.0] against global build labels: map[based_off:alpine version:1.7.0] --- PASS: TestBucket_UpdateLabelsForBuild_withMultipleBuilds (0.00s) ``` * Handle errors from bucket methods * Update test cases Initialize maps for bucket when calling NewBucketWithIteration --- internal/registry/types.bucket.go | 19 +- .../registry/types.bucket_service_test.go | 385 +++++++++++++++ internal/registry/types.bucket_test.go | 453 +++++------------- 3 files changed, 528 insertions(+), 329 deletions(-) create mode 100644 internal/registry/types.bucket_service_test.go diff --git a/internal/registry/types.bucket.go b/internal/registry/types.bucket.go index 4b40b2ef3..7060c566c 100644 --- a/internal/registry/types.bucket.go +++ b/internal/registry/types.bucket.go @@ -29,7 +29,10 @@ type Bucket struct { // NewBucketWithIteration initializes a simple Bucket that can be used publishing Packer build // images to the HCP Packer registry. func NewBucketWithIteration(opts IterationOptions) (*Bucket, error) { - b := Bucket{} + b := Bucket{ + BucketLabels: make(map[string]string), + BuildLabels: make(map[string]string), + } i, err := NewIteration(opts) if err != nil { @@ -114,21 +117,21 @@ func (b *Bucket) CreateInitialBuildForIteration(ctx context.Context, componentTy } id := resp.Payload.Build.ID - if b.BuildLabels == nil { - b.BuildLabels = make(map[string]string) - } - - build := &Build{ + build := Build{ ID: id, ComponentType: componentType, RunUUID: b.Iteration.RunUUID, Status: status, - Labels: b.BuildLabels, + Labels: make(map[string]string), Images: make(map[string]registryimage.Image), } + for k, v := range b.BuildLabels { + build.Labels[k] = v + } + log.Println("[TRACE] creating initial build for component", componentType) - b.Iteration.builds.Store(componentType, build) + b.Iteration.builds.Store(componentType, &build) return nil } diff --git a/internal/registry/types.bucket_service_test.go b/internal/registry/types.bucket_service_test.go new file mode 100644 index 000000000..5a8cbdf7d --- /dev/null +++ b/internal/registry/types.bucket_service_test.go @@ -0,0 +1,385 @@ +package registry + +import ( + "context" + "os" + "testing" + + "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/models" +) + +func TestInitialize_NewBucketNewIteration(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + if !mockService.CreateBucketCalled { + t.Errorf("expected a call to CreateBucket but it didn't happen") + } + + if !mockService.CreateIterationCalled { + t.Errorf("expected a call to CreateIteration but it didn't happen") + } + + if mockService.CreateBuildCalled { + t.Errorf("Didn't expect a call to CreateBuild") + } + + if b.Iteration.ID != "iteration-id" { + t.Errorf("expected an iteration to created but it didn't") + } + + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + if !mockService.CreateBuildCalled { + t.Errorf("Expected a call to CreateBuild but it didn't happen") + } + + if _, ok := b.Iteration.builds.Load("happycloud.image"); !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + +} + +func TestInitialize_ExistingBucketNewIteration(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + mockService.BucketAlreadyExist = true + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + if !mockService.UpdateBucketCalled { + t.Errorf("expected call to UpdateBucket but it didn't happen") + } + + if !mockService.CreateIterationCalled { + t.Errorf("expected a call to CreateIteration but it didn't happen") + } + + if mockService.CreateBuildCalled { + t.Errorf("Didn't expect a call to CreateBuild") + } + + if b.Iteration.ID != "iteration-id" { + t.Errorf("expected an iteration to created but it didn't") + } + + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + if !mockService.CreateBuildCalled { + t.Errorf("Expected a call to CreateBuild but it didn't happen") + } + + if _, ok := b.Iteration.builds.Load("happycloud.image"); !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + +} + +func TestInitialize_ExistingBucketExistingIteration(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + mockService.BucketAlreadyExist = true + mockService.IterationAlreadyExist = true + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + if mockService.CreateBucketCalled { + t.Errorf("unexpected call to CreateBucket") + } + + if !mockService.UpdateBucketCalled { + t.Errorf("expected call to UpdateBucket but it didn't happen") + } + + if mockService.CreateIterationCalled { + t.Errorf("unexpected a call to CreateIteration") + } + + if !mockService.GetIterationCalled { + t.Errorf("expected a call to GetIteration but it didn't happen") + } + + if mockService.CreateBuildCalled { + t.Errorf("unexpected a call to CreateBuild") + } + + if b.Iteration.ID != "iteration-id" { + t.Errorf("expected an iteration to created but it didn't") + } + + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + if !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + + existingBuild, ok := loadedBuild.(*Build) + if !ok { + t.Errorf("expected the existing build loaded from an existing bucket to be valid") + } + + if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + t.Errorf("expected the existing build to be in the default state") + } +} + +func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + mockService.BucketAlreadyExist = true + mockService.IterationAlreadyExist = true + mockService.IterationCompleted = true + mockService.BuildAlreadyDone = true + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err == nil { + t.Errorf("unexpected failure: %v", err) + } + + if mockService.CreateIterationCalled { + t.Errorf("unexpected call to CreateIteration") + } + + if !mockService.GetIterationCalled { + t.Errorf("expected a call to GetIteration but it didn't happen") + } + + if mockService.CreateBuildCalled { + t.Errorf("unexpected call to CreateBuild") + } + + if b.Iteration.ID != "iteration-id" { + t.Errorf("expected an iteration to be returned but it wasn't") + } +} + +func TestUpdateBuildStatus(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + mockService.BucketAlreadyExist = true + mockService.IterationAlreadyExist = true + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + if !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + + existingBuild, ok := loadedBuild.(*Build) + if !ok { + t.Errorf("expected the existing build loaded from an existing bucket to be valid") + } + + if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + t.Errorf("expected the existing build to be in the default state") + } + + err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) + if err != nil { + t.Errorf("unexpected failure for PublishBuildStatus: %v", err) + } + + reloadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + if !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + + existingBuild, ok = reloadedBuild.(*Build) + if !ok { + t.Errorf("expected the existing build loaded from an existing bucket to be valid") + } + + if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { + t.Errorf("expected the existing build to be in the running state") + } +} + +func TestUpdateBuildStatus_DONENoImages(t *testing.T) { + //nolint:errcheck + os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") + defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") + mockService := NewMockPackerClientService() + mockService.BucketAlreadyExist = true + mockService.IterationAlreadyExist = true + + b := &Bucket{ + Slug: "TestBucket", + client: &Client{ + Packer: mockService, + }, + } + + var err error + b.Iteration, err = NewIteration(IterationOptions{}) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") + mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + + err = b.Initialize(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + err = b.PopulateIteration(context.TODO()) + if err != nil { + t.Errorf("unexpected failure: %v", err) + } + + loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + if !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + + existingBuild, ok := loadedBuild.(*Build) + if !ok { + t.Errorf("expected the existing build loaded from an existing bucket to be valid") + } + + if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { + t.Errorf("expected the existing build to be in the default state") + } + + //nolint:errcheck + b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) + + err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusDONE) + if err == nil { + t.Errorf("expected failure for PublishBuildStatus when setting status to DONE with no images") + } + + reloadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + if !ok { + t.Errorf("expected a basic build entry to be created but it didn't") + } + + existingBuild, ok = reloadedBuild.(*Build) + if !ok { + t.Errorf("expected the existing build loaded from an existing bucket to be valid") + } + + if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { + t.Errorf("expected the existing build to be in the running state") + } +} + +//func (b *Bucket) PublishBuildStatus(ctx context.Context, name string, status models.HashicorpCloudPackerBuildStatus) error {} diff --git a/internal/registry/types.bucket_test.go b/internal/registry/types.bucket_test.go index 5a8cbdf7d..ba287930e 100644 --- a/internal/registry/types.bucket_test.go +++ b/internal/registry/types.bucket_test.go @@ -5,381 +5,192 @@ import ( "os" "testing" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/preview/2021-04-30/models" + "github.com/google/go-cmp/cmp" ) -func TestInitialize_NewBucketNewIteration(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() +func createInitialBucket(t testing.TB) *Bucket { + oldEnv := os.Getenv("HCP_PACKER_BUILD_FINGERPRINT") + os.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "no-fingerprint-here") + defer func() { + os.Setenv("HCP_PACKER_BUILD_FINGERPRINT", oldEnv) + }() - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, - }, - } - - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - - err = b.Initialize(context.TODO()) + t.Helper() + subject, err := NewBucketWithIteration(IterationOptions{}) if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - if !mockService.CreateBucketCalled { - t.Errorf("expected a call to CreateBucket but it didn't happen") - } - - if !mockService.CreateIterationCalled { - t.Errorf("expected a call to CreateIteration but it didn't happen") - } - - if mockService.CreateBuildCalled { - t.Errorf("Didn't expect a call to CreateBuild") - } - - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") + t.Fatalf("failed when calling NewBucketWithIteration: %s", err) } - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - if !mockService.CreateBuildCalled { - t.Errorf("Expected a call to CreateBuild but it didn't happen") + subject.Slug = "TestBucket" + subject.BuildLabels = map[string]string{ + "version": "1.7.0", + "based_off": "alpine", } - - if _, ok := b.Iteration.builds.Load("happycloud.image"); !ok { - t.Errorf("expected a basic build entry to be created but it didn't") + subject.client = &Client{ + Packer: NewMockPackerClientService(), } - + return subject } -func TestInitialize_ExistingBucketNewIteration(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() - mockService.BucketAlreadyExist = true - - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, - }, - } - - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - - err = b.Initialize(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - if !mockService.UpdateBucketCalled { - t.Errorf("expected call to UpdateBucket but it didn't happen") - } - - if !mockService.CreateIterationCalled { - t.Errorf("expected a call to CreateIteration but it didn't happen") - } - - if mockService.CreateBuildCalled { - t.Errorf("Didn't expect a call to CreateBuild") - } - - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") - } +func checkError(t testing.TB, err error) { + t.Helper() - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - if !mockService.CreateBuildCalled { - t.Errorf("Expected a call to CreateBuild but it didn't happen") - } - - if _, ok := b.Iteration.builds.Load("happycloud.image"); !ok { - t.Errorf("expected a basic build entry to be created but it didn't") + if err == nil { + return } + t.Errorf("received an error during testing %s", err) } -func TestInitialize_ExistingBucketExistingIteration(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() - mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true - - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, - }, - } - - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - - err = b.Initialize(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } +func TestBucket_CreateInitialBuildForIteration(t *testing.T) { + subject := createInitialBucket(t) - if mockService.CreateBucketCalled { - t.Errorf("unexpected call to CreateBucket") - } + componentName := "happycloud.image" + subject.RegisterBuildForComponent(componentName) + err := subject.CreateInitialBuildForIteration(context.TODO(), componentName) + checkError(t, err) - if !mockService.UpdateBucketCalled { - t.Errorf("expected call to UpdateBucket but it didn't happen") - } - - if mockService.CreateIterationCalled { - t.Errorf("unexpected a call to CreateIteration") - } - - if !mockService.GetIterationCalled { - t.Errorf("expected a call to GetIteration but it didn't happen") - } - - if mockService.CreateBuildCalled { - t.Errorf("unexpected a call to CreateBuild") - } - - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to created but it didn't") - } - - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") + // Assert that a build stored on the iteration + iBuild, ok := subject.Iteration.builds.Load(componentName) if !ok { - t.Errorf("expected a basic build entry to be created but it didn't") + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) } - existingBuild, ok := loadedBuild.(*Build) + build, ok := iBuild.(*Build) if !ok { - t.Errorf("expected the existing build loaded from an existing bucket to be valid") - } - - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { - t.Errorf("expected the existing build to be in the default state") - } -} - -func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() - mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true - mockService.IterationCompleted = true - mockService.BuildAlreadyDone = true - - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, - }, - } - - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") - - err = b.Initialize(context.TODO()) - if err == nil { - t.Errorf("unexpected failure: %v", err) - } - - if mockService.CreateIterationCalled { - t.Errorf("unexpected call to CreateIteration") - } - - if !mockService.GetIterationCalled { - t.Errorf("expected a call to GetIteration but it didn't happen") + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) } - if mockService.CreateBuildCalled { - t.Errorf("unexpected call to CreateBuild") + if build.ComponentType != componentName { + t.Errorf("expected the initial build to have the defined component type") } - if b.Iteration.ID != "iteration-id" { - t.Errorf("expected an iteration to be returned but it wasn't") + if diff := cmp.Diff(build.Labels, subject.BuildLabels); diff != "" { + t.Errorf("expected the initial build to have the defined build labels %v", diff) } } -func TestUpdateBuildStatus(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() - mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true - - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, +func TestBucket_UpdateLabelsForBuild(t *testing.T) { + tc := []struct { + desc string + components []string + labels map[string]string + labelsCount int + noDiffExpected bool + }{ + { + desc: "only global build labels", + components: []string{"happcloud.image"}, + labelsCount: 2, + noDiffExpected: true, + }, + { + desc: "global build labels and one additional build specific label", + components: []string{"happcloud.image"}, + labels: map[string]string{ + "source_image": "another-happycloud-image", + }, + labelsCount: 3, + noDiffExpected: false, }, } - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } + for _, tt := range tc { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + subject := createInitialBucket(t) - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + for _, componentName := range tt.components { + subject.RegisterBuildForComponent(componentName) + err := subject.CreateInitialBuildForIteration(context.TODO(), componentName) + checkError(t, err) - err = b.Initialize(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } + err = subject.UpdateLabelsForBuild(componentName, tt.labels) + checkError(t, err) - loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") - if !ok { - t.Errorf("expected a basic build entry to be created but it didn't") - } + // Assert that the build is stored on the iteration + iBuild, ok := subject.Iteration.builds.Load(componentName) + if !ok { + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) + } - existingBuild, ok := loadedBuild.(*Build) - if !ok { - t.Errorf("expected the existing build loaded from an existing bucket to be valid") - } + build, ok := iBuild.(*Build) + if !ok { + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) + } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { - t.Errorf("expected the existing build to be in the default state") - } + if build.ComponentType != componentName { + t.Errorf("expected the build to have the defined component type") + } - err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) - if err != nil { - t.Errorf("unexpected failure for PublishBuildStatus: %v", err) - } + if len(build.Labels) != tt.labelsCount { + t.Errorf("expected the build to have %d build labels but there is only: %d", tt.labelsCount, len(build.Labels)) + } - reloadedBuild, ok := b.Iteration.builds.Load("happycloud.image") - if !ok { - t.Errorf("expected a basic build entry to be created but it didn't") - } + diff := cmp.Diff(build.Labels, subject.BuildLabels) + if (diff == "") != tt.noDiffExpected { + t.Errorf("expected the build to have an additional build label but there is no diff: %q", diff) + } - existingBuild, ok = reloadedBuild.(*Build) - if !ok { - t.Errorf("expected the existing build loaded from an existing bucket to be valid") - } - - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { - t.Errorf("expected the existing build to be in the running state") + } + }) } } -func TestUpdateBuildStatus_DONENoImages(t *testing.T) { - //nolint:errcheck - os.Setenv("HCP_PACKER_BUILD_FINGEPRINT", "testnumber") - defer os.Unsetenv("HCP_PACKER_BUILD_FINGERPRINT") - mockService := NewMockPackerClientService() - mockService.BucketAlreadyExist = true - mockService.IterationAlreadyExist = true - - b := &Bucket{ - Slug: "TestBucket", - client: &Client{ - Packer: mockService, - }, - } +func TestBucket_UpdateLabelsForBuild_withMultipleBuilds(t *testing.T) { + subject := createInitialBucket(t) - var err error - b.Iteration, err = NewIteration(IterationOptions{}) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } + firstComponent := "happycloud.image" + subject.RegisterBuildForComponent(firstComponent) + err := subject.CreateInitialBuildForIteration(context.TODO(), firstComponent) + checkError(t, err) - b.Iteration.expectedBuilds = append(b.Iteration.expectedBuilds, "happycloud.image") - mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + err = subject.UpdateLabelsForBuild(firstComponent, map[string]string{ + "source_image": "another-happycloud-image", + }) + checkError(t, err) - err = b.Initialize(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } - err = b.PopulateIteration(context.TODO()) - if err != nil { - t.Errorf("unexpected failure: %v", err) - } + secondComponent := "happycloud.image2" + subject.RegisterBuildForComponent(secondComponent) + err = subject.CreateInitialBuildForIteration(context.TODO(), secondComponent) + checkError(t, err) - loadedBuild, ok := b.Iteration.builds.Load("happycloud.image") - if !ok { - t.Errorf("expected a basic build entry to be created but it didn't") - } + err = subject.UpdateLabelsForBuild(secondComponent, map[string]string{ + "source_image": "the-original-happycloud-image", + "role_name": "no-role-is-a-good-role", + }) + checkError(t, err) - existingBuild, ok := loadedBuild.(*Build) - if !ok { - t.Errorf("expected the existing build loaded from an existing bucket to be valid") - } + var registeredBuilds []*Build + expectedComponents := []string{firstComponent, secondComponent} + for _, componentName := range expectedComponents { + // Assert that a build stored on the iteration + iBuild, ok := subject.Iteration.builds.Load(componentName) + if !ok { + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) + } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusUNSET { - t.Errorf("expected the existing build to be in the default state") - } + build, ok := iBuild.(*Build) + if !ok { + t.Errorf("expected an initial build for %s to be created, but it failed", componentName) + } + registeredBuilds = append(registeredBuilds, build) - //nolint:errcheck - b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusRUNNING) + if build.ComponentType != componentName { + t.Errorf("expected the initial build to have the defined component type") + } - err = b.UpdateBuildStatus(context.TODO(), "happycloud.image", models.HashicorpCloudPackerBuildStatusDONE) - if err == nil { - t.Errorf("expected failure for PublishBuildStatus when setting status to DONE with no images") + if ok := cmp.Equal(build.Labels, subject.BuildLabels); ok { + t.Errorf("expected the build to have an additional build label but they are equal") + } } - reloadedBuild, ok := b.Iteration.builds.Load("happycloud.image") - if !ok { - t.Errorf("expected a basic build entry to be created but it didn't") + if len(registeredBuilds) != 2 { + t.Errorf("expected the bucket to have 2 registered builds but got %d", len(registeredBuilds)) } - existingBuild, ok = reloadedBuild.(*Build) - if !ok { - t.Errorf("expected the existing build loaded from an existing bucket to be valid") + if ok := cmp.Equal(registeredBuilds[0].Labels, registeredBuilds[1].Labels); ok { + t.Errorf("expected registered builds to have different labels but they are equal") } - if existingBuild.Status != models.HashicorpCloudPackerBuildStatusRUNNING { - t.Errorf("expected the existing build to be in the running state") - } } - -//func (b *Bucket) PublishBuildStatus(ctx context.Context, name string, status models.HashicorpCloudPackerBuildStatus) error {}