Files
Packer-Cn/internal/registry/types.iterations.go
T
Wilken RiveraandSylvia Moss dd525fb814 Update HCP Packer build labels when re-running Packer on an incomplete build (#11584)
* Update HCP Packer build labels argument

Previously when running a partial build on multi-cloud build template it
was found that build labels were only being applied at the creation for
the partially executed build. Leaving all other completed builds with
no HCP Packer build labels. This updates how incomplete builds are
loaded from the registry and ensure that any defined
hcp_packer_registry.build_labels are assigned to the build before
starting an actual Packer build.

Related to: #11573

* Add test case for overwriting build labels

* Update tests to call CreateInitialBuild for non-existing builds

* Rename test case to TestBucket_PopulateIteration

* Fix data race in PopulateIteration against mock service

Before Change
```
WARNING: DATA RACE
Write at 0x00c0005421b0 by goroutine 47:
  github.com/hashicorp/packer/internal/registry.(*MockPackerClientService).PackerServiceCreateBuild()
      /Users/scrubbed/Development/packer/internal/registry/mock_service.go:173 +0x2b6
  github.com/hashicorp/packer/internal/registry.(*Client).CreateBuild()
      /Users/scrubbed/Development/packer/internal/registry/service.go:169 +0x592
  github.com/hashicorp/packer/internal/registry.(*Bucket).CreateInitialBuildForIteration()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:107 +0x204
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration.func1()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:368 +0x14e
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration·dwrap·1()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:376 +0x58

Previous write at 0x00c0005421b0 by goroutine 46:
  github.com/hashicorp/packer/internal/registry.(*MockPackerClientService).PackerServiceCreateBuild()
      /Users/scrubbed/Development/packer/internal/registry/mock_service.go:173 +0x2b6
  github.com/hashicorp/packer/internal/registry.(*Client).CreateBuild()
      /Users/scrubbed/Development/packer/internal/registry/service.go:169 +0x592
  github.com/hashicorp/packer/internal/registry.(*Bucket).CreateInitialBuildForIteration()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:107 +0x204
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration.func1()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:368 +0x14e
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration·dwrap·1()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:376 +0x58

Goroutine 47 (running) created at:
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:362 +0x5c7
  github.com/hashicorp/packer/internal/registry.TestBucket_UpdateLabelsForBuild_withMultipleBuilds()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket_test.go:179 +0xf7
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1259 +0x22f
  testing.(*T).Run·dwrap·21()
      /usr/local/go/src/testing/testing.go:1306 +0x47

Goroutine 46 (finished) created at:
  github.com/hashicorp/packer/internal/registry.(*Bucket).PopulateIteration()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket.go:362 +0x5c7
  github.com/hashicorp/packer/internal/registry.TestBucket_UpdateLabelsForBuild_withMultipleBuilds()
      /Users/scrubbed/Development/packer/internal/registry/types.bucket_test.go:179 +0xf7
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1259 +0x22f
  testing.(*T).Run·dwrap·21()
      /usr/local/go/src/testing/testing.go:1306 +0x47
==================
```

* Add methods for managing builds on an Iteration

* Update Mock Service to be a bit more concurrent

It is a simple set of changes to make it work for the current testing
use cases. If we need to we can move the Called fields to counters or
re-architect the mock.

This change also fixes a race condition when appending to the Slice of
errs when calling PopulateIteration.

* Update test case descriptions

* Apply suggestions from code review

Only append to errs if err is not nil

Co-authored-by: Sylvia Moss <[email protected]>

* Fix linting issues

Co-authored-by: Sylvia Moss <[email protected]>
2022-02-25 15:50:41 -05:00

140 lines
4.0 KiB
Go

package registry
import (
"errors"
"fmt"
"os"
"sync"
git "github.com/go-git/go-git/v5"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
)
type Iteration struct {
ID string
AncestorSlug string
Fingerprint string
RunUUID string
Labels map[string]string
builds sync.Map
expectedBuilds []string
}
type IterationOptions struct {
TemplateBaseDir string
}
// NewIteration returns a pointer to an Iteration that can be used for storing Packer build details needed by PAR.
func NewIteration(opts IterationOptions) (*Iteration, error) {
i := Iteration{
expectedBuilds: make([]string, 0),
}
// By default we try to load a Fingerprint from the environment variable.
// If no variable is defined we should try to load a fingerprint from Git, or other VCS.
i.Fingerprint = os.Getenv("HCP_PACKER_BUILD_FINGERPRINT")
// get a Git SHA
if i.Fingerprint != "" {
return &i, nil
}
fp, err := GetGitFingerprint(opts)
if err != nil {
return nil, err
}
i.Fingerprint = fp
return &i, nil
}
// GetGitFingerprint returns the HEAD commit for some template dir defined in opt.TemplateBaseDir.
// If the base directory is not under version control an error is returned.
func GetGitFingerprint(opts IterationOptions) (string, error) {
r, err := git.PlainOpenWithOptions(opts.TemplateBaseDir, &git.PlainOpenOptions{
DetectDotGit: true,
})
if err != nil {
return "", fmt.Errorf("Packer was unable to load a git sha. "+
"If your Packer template is not in a git repo, please add a unique "+
"template fingerprint using the env var HCP_PACKER_BUILD_FINGERPRINT. "+
"Error: %s", err)
}
// The config can be used to retrieve user identity. for example,
// c.User.Email. Leaving in but commented because I'm not sure we care
// about this identity right now. - Megan
//
// c, err := r.ConfigScoped(config.GlobalScope)
// if err != nil {
// return "", fmt.Errorf("Error setting git scope", err)
// }
ref, err := r.Head()
if err != nil {
return "", fmt.Errorf("Packer encountered an issue reading the git info for the path %q.\n"+
"If your Packer template is not in a git repo, please add a unique "+
"template fingerprint using the env var HCP_PACKER_BUILD_FINGERPRINT. "+
"Error: %s", opts.TemplateBaseDir, err)
}
// log.Printf("Author: %v, Commit: %v\n", c.User.Email, ref.Hash())
return ref.Hash().String(), nil
}
//StoreBuild stores a build for buildName to an active iteration.
func (i *Iteration) StoreBuild(buildName string, build *Build) {
i.builds.Store(buildName, build)
}
//Build gets the store build associated with buildName in the active iteration.
func (i *Iteration) Build(buildName string) (*Build, error) {
build, ok := i.builds.Load(buildName)
if !ok {
return nil, errors.New("no associated build found for the name " + buildName)
}
b, ok := build.(*Build)
if !ok {
return nil, fmt.Errorf("the build for the component %q does not appear to be a valid registry Build", buildName)
}
return b, nil
}
//HasBuild checks if iteration has a stored build associated with buildName.
func (i *Iteration) HasBuild(buildName string) bool {
_, ok := i.builds.Load(buildName)
return ok
}
// AddImageToBuild appends one or more images artifacts to the build referred to by buildName.
func (i *Iteration) AddImageToBuild(buildName string, images ...registryimage.Image) error {
build, err := i.Build(buildName)
if err != nil {
return fmt.Errorf("AddImageToBuild: %w", err)
}
err = build.AddImages(images...)
if err != nil {
return fmt.Errorf("AddImageToBuild: %w", err)
}
i.StoreBuild(buildName, build)
return nil
}
// AddLabelsToBuild merges the contents of data to the labels associated with the build referred to by buildName.
func (i *Iteration) AddLabelsToBuild(buildName string, data map[string]string) error {
build, err := i.Build(buildName)
if err != nil {
return fmt.Errorf("AddLabelsToBuild: %w", err)
}
build.MergeLabels(data)
i.StoreBuild(buildName, build)
return nil
}