diff --git a/internal/hcp/api/mock_service.go b/internal/hcp/api/mock_service.go index db2f64a7a..72718a814 100644 --- a/internal/hcp/api/mock_service.go +++ b/internal/hcp/api/mock_service.go @@ -22,6 +22,7 @@ type MockPackerClientService struct { CreateBucketCalled, UpdateBucketCalled, GetBucketCalled, BucketNotFound bool CreateVersionCalled, GetVersionCalled, VersionAlreadyExist, VersionCompleted bool CreateBuildCalled, UpdateBuildCalled, ListBuildsCalled, BuildAlreadyDone bool + UpdateChannelCalled bool TrackCalledServiceMethods bool // Mock Creates @@ -289,3 +290,34 @@ func (svc *MockPackerClientService) PackerServiceListBuilds( return ok, nil } + +func (svc *MockPackerClientService) PackerServiceUpdateChannel( + params *hcpPackerService.PackerServiceUpdateChannelParams, _ runtime.ClientAuthInfoWriter, + opts ...hcpPackerService.ClientOption, +) (*hcpPackerService.PackerServiceUpdateChannelOK, error) { + if params.BucketName == "" { + return nil, errors.New("no valid BucketName was passed in") + } + + if params.ChannelName == "" { + return nil, errors.New("no valid ChannelName was passed in") + } + + if params.Body == nil { + return nil, errors.New("no valid update body was passed in") + } + + if svc.TrackCalledServiceMethods { + svc.UpdateChannelCalled = true + } + + ok := hcpPackerService.NewPackerServiceUpdateChannelOK() + ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101UpdateChannelResponse{ + Channel: &hcpPackerModels.HashicorpCloudPacker20230101Channel{ + Name: params.ChannelName, + BucketName: params.BucketName, + }, + } + + return ok, nil +} diff --git a/internal/hcp/registry/types.bucket_test.go b/internal/hcp/registry/types.bucket_test.go index 6cd49503c..fe5c1bd4f 100644 --- a/internal/hcp/registry/types.bucket_test.go +++ b/internal/hcp/registry/types.bucket_test.go @@ -373,6 +373,34 @@ func TestReadFromHCLBuildBlock(t *testing.T) { "version": "1.7.0", "based_off": "alpine", }, + Channels: nil, + }, + }, + { + desc: "configure bucket with channels", + buildBlock: &hcl2template.BuildBlock{ + HCPPackerRegistry: &hcl2template.HCPPackerRegistryBlock{ + Slug: "channel-test-bucket", + Description: "bucket with channel configuration", + Channels: []string{"production", "staging", "development"}, + BucketLabels: map[string]string{ + "team": "infrastructure", + }, + BuildLabels: map[string]string{ + "version": "2.0.0", + }, + }, + }, + expectedBucket: &Bucket{ + Name: "channel-test-bucket", + Description: "bucket with channel configuration", + Channels: []string{"production", "staging", "development"}, + BucketLabels: map[string]string{ + "team": "infrastructure", + }, + BuildLabels: map[string]string{ + "version": "2.0.0", + }, }, }, } @@ -509,3 +537,132 @@ func TestCompleteBuild(t *testing.T) { }) } } + +func TestBucket_UpdateChannels(t *testing.T) { + tests := []struct { + name string + channels []string + wantErr bool + wantCalled bool + }{ + { + name: "no channels", + channels: []string{}, + wantErr: false, + wantCalled: false, + }, + { + name: "single channel", + channels: []string{"production"}, + wantErr: false, + wantCalled: true, + }, + { + name: "multiple channels", + channels: []string{"staging", "production", "dev"}, + wantErr: false, + wantCalled: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() + mockService.TrackCalledServiceMethods = true + + b := &Bucket{ + Name: "test-bucket", + Channels: tt.channels, + client: &hcpPackerAPI.Client{ + Packer: mockService, + }, + } + + // Initialize version + b.Version = &Version{ + ID: "test-version-id", + Fingerprint: "test-fingerprint", + } + + err := b.updateChannels(context.Background()) + + if (err != nil) != tt.wantErr { + t.Errorf("updateChannels() error = %v, wantErr %v", err, tt.wantErr) + } + + if mockService.UpdateChannelCalled != tt.wantCalled { + t.Errorf("UpdateChannelCalled = %v, want %v", mockService.UpdateChannelCalled, tt.wantCalled) + } + }) + } +} + +func TestBucket_DoCompleteBuild_WithChannels(t *testing.T) { + mockService := hcpPackerAPI.NewMockPackerClientService() + mockService.VersionAlreadyExist = true + mockService.TrackCalledServiceMethods = true + + b := &Bucket{ + Name: "TestBucket", + Channels: []string{"production", "staging"}, + client: &hcpPackerAPI.Client{ + Packer: mockService, + }, + } + + b.Version = NewVersion() + err := b.Version.Initialize() + if err != nil { + t.Fatalf("unexpected failure initializing version: %v", err) + } + + b.Version.expectedBuilds = append(b.Version.expectedBuilds, "happycloud.image") + mockService.ExistingBuilds = append(mockService.ExistingBuilds, "happycloud.image") + + err = b.Initialize(context.TODO(), models.HashicorpCloudPacker20230101TemplateTypeHCL2) + if err != nil { + t.Fatalf("unexpected failure initializing bucket: %v", err) + } + + err = b.populateVersion(context.TODO()) + if err != nil { + t.Fatalf("unexpected failure populating version: %v", err) + } + + // Create mock HCP-compatible artifacts + mockArtifacts := []packer.Artifact{ + &packer.MockArtifact{ + BuilderIdValue: "builder.test", + FilesValue: []string{"file.one"}, + IdValue: "test-artifact", + StateValues: map[string]interface{}{ + "builder.test": "OK", + image.ArtifactStateURI: &image.Image{ + ImageID: "hcp-test-image", + ProviderName: "test-provider", + ProviderRegion: "test-region", + Labels: map[string]string{}, + SourceImageID: "", + }, + }, + DestroyCalled: false, + StringValue: "", + }, + } + + // Complete the build + _, err = b.doCompleteBuild(context.TODO(), "happycloud.image", mockArtifacts, nil) + if err != nil { + t.Errorf("doCompleteBuild() should have completed successfully for build happycloud.image, got err: %v", err) + } + + // Verify that UpdateChannel was called for channel updates + if !mockService.UpdateChannelCalled { + t.Error("UpdateChannelCalled should be true after completing build with channels") + } + + // Verify that UpdateBuild was called for marking build complete + if !mockService.UpdateBuildCalled { + t.Error("UpdateBuildCalled should be true after completing build") + } +}