internal: reorganise registry/HCP code

This commit reorganises the code for both the registry/API and the
Orchestrator/Registry.

The main difference with the previous version is how stuff is exposed.
Now we only expose a Registry interface to the outside (previously named
Orchestrator), which has several implementations: null is the default,
and is returned if HCP is not enabled.

The other implementations being HCL/JSON, both private to the hcp
sub-package.

The api (previously `registry') is the set of functionality that
abstracts and calls the HCP API.
It was meant to be merged with the `hcp' package, but because of a
dependency loop with the datasources, both are separated for now.
This commit is contained in:
Wilken Rivera 2022-11-07 14:16:04 -05:00 committed by Lucas Bajolet
parent 1cee460d0d
commit 606e6c48f1
31 changed files with 301 additions and 289 deletions

View file

@ -14,7 +14,7 @@ import (
"github.com/hashicorp/hcl/v2"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/internal/hcp"
"github.com/hashicorp/packer/internal/hcp/registry"
"github.com/hashicorp/packer/packer"
"golang.org/x/sync/semaphore"
@ -90,13 +90,13 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
return ret
}
hcpHandler, diags := hcp.GetOrchestrator(packerStarter)
hcpRegistry, diags := registry.New(packerStarter)
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}
err := hcpHandler.PopulateIteration(buildCtx)
err := hcpRegistry.PopulateIteration(buildCtx)
if err != nil {
return writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
@ -219,14 +219,24 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
defer limitParallel.Release(1)
err := hcpHandler.BuildStart(buildCtx, hcpMap[name])
err := hcpRegistry.StartBuild(buildCtx, hcpMap[name])
// Seems odd to require this error check here. Now that it is an error we can just exit with diag
if err != nil {
if errors.As(err, &hcp.BuildDone{}) {
ui.Say(fmt.Sprintf(
"skipping HCP-enabled build %q: already done.",
name))
// If the build is already done, we skip without a warning
if errors.As(err, &registry.ErrBuildAlreadyDone{}) {
ui.Say(fmt.Sprintf("skipping already done build %q", name))
return
}
writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
Summary: fmt.Sprintf(
"hcp: failed to start build %q",
name),
Severity: hcl.DiagError,
Detail: err.Error(),
},
})
return
}
log.Printf("Starting build run: %s", name)
@ -237,7 +247,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
buildDuration := buildEnd.Sub(buildStart)
fmtBuildDuration := durafmt.Parse(buildDuration).LimitFirstN(2)
runArtifacts, hcperr := hcpHandler.BuildDone(
runArtifacts, hcperr := hcpRegistry.CompleteBuild(
buildCtx,
hcpMap[name],
runArtifacts,

View file

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
packerregistry "github.com/hashicorp/packer/internal/registry"
hcpapi "github.com/hashicorp/packer/internal/hcp/api"
)
type Datasource struct {
@ -139,7 +139,7 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
func (d *Datasource) Execute() (cty.Value, error) {
ctx := context.TODO()
cli, err := packerregistry.NewClient()
cli, err := hcpapi.NewClient()
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}
@ -150,7 +150,7 @@ func (d *Datasource) Execute() (cty.Value, error) {
log.Printf("[INFO] Reading info from HCP Packer registry (%s) [project_id=%s, organization_id=%s, iteration_id=%s]",
d.config.Bucket, cli.ProjectID, cli.OrganizationID, d.config.IterationID)
iter, err := cli.GetIteration(ctx, d.config.Bucket, packerregistry.GetIteration_byID(d.config.IterationID))
iter, err := cli.GetIteration(ctx, d.config.Bucket, hcpapi.GetIteration_byID(d.config.IterationID))
if err != nil {
return cty.NullVal(cty.EmptyObject), fmt.Errorf(
"error retrieving image iteration from HCP Packer registry: %s",

View file

@ -15,7 +15,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
packerregistry "github.com/hashicorp/packer/internal/registry"
hcpapi "github.com/hashicorp/packer/internal/hcp/api"
)
type Datasource struct {
@ -99,7 +99,7 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
func (d *Datasource) Execute() (cty.Value, error) {
ctx := context.TODO()
cli, err := packerregistry.NewClient()
cli, err := hcpapi.NewClient()
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

View file

@ -8,11 +8,7 @@ import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/acctest"
)
const (
HCPClientID string = "HCP_CLIENT_ID"
HCPClientSecret string = "HCP_CLIENT_SECRET"
"github.com/hashicorp/packer/internal/hcp/env"
)
//go:embed test-fixtures/template.pkr.hcl
@ -31,8 +27,8 @@ var testDatasourceBasic string
// as defined above.
func TestAccDatasource_HCPPackerIteration(t *testing.T) {
if os.Getenv(HCPClientID) == "" && os.Getenv(HCPClientSecret) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless envs %q and %q are set", HCPClientID, HCPClientSecret))
if os.Getenv(env.HCPClientID) == "" && os.Getenv(env.HCPClientSecret) == "" {
t.Skipf(fmt.Sprintf("Acceptance tests skipped unless envs %q and %q are set", env.HCPClientID, env.HCPClientSecret))
return
}

View file

@ -16,7 +16,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
packerregistry "github.com/hashicorp/packer/internal/registry"
hcpapi "github.com/hashicorp/packer/internal/hcp/api"
)
// Type for Packer datasource has been renamed temporarily to prevent it from being
@ -136,7 +136,7 @@ func (d *DeactivatedDatasource) OutputSpec() hcldec.ObjectSpec {
func (d *DeactivatedDatasource) Execute() (cty.Value, error) {
ctx := context.TODO()
cli, err := packerregistry.NewClient()
cli, err := hcpapi.NewClient()
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

View file

@ -15,7 +15,6 @@ import (
dnull "github.com/hashicorp/packer/datasource/null"
. "github.com/hashicorp/packer/hcl2template/internal"
hcl2template "github.com/hashicorp/packer/hcl2template/internal"
packerregistry "github.com/hashicorp/packer/internal/registry"
"github.com/hashicorp/packer/packer"
"github.com/zclconf/go-cty/cty"
)
@ -357,19 +356,11 @@ var cmpOpts = []cmp.Option{
packer.CoreBuildProvisioner{},
packer.CoreBuildPostProcessor{},
null.Builder{},
packerregistry.Bucket{},
packerregistry.Iteration{},
),
cmpopts.IgnoreFields(PackerConfig{},
"Cwd", // Cwd will change for every os type
"HCPVars", // HCPVars will not be filled-in during parsing
),
cmpopts.IgnoreFields(packerregistry.Iteration{},
"Fingerprint", // Fingerprint will change everytime
),
cmpopts.IgnoreFields(packerregistry.Bucket{},
"SourceImagesToParentIterations", // Requires execution of datasource at this time
),
cmpopts.IgnoreFields(VariableAssignment{},
"Expr", // its an interface
),

View file

@ -5,7 +5,6 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
packerregistry "github.com/hashicorp/packer/internal/registry"
)
type HCPPackerRegistryBlock struct {
@ -21,20 +20,6 @@ type HCPPackerRegistryBlock struct {
HCL2Ref
}
func (b *HCPPackerRegistryBlock) WriteToBucketConfig(bucket *packerregistry.Bucket) {
if b == nil {
return
}
bucket.Description = b.Description
bucket.BucketLabels = b.BucketLabels
bucket.BuildLabels = b.BuildLabels
// If there's already a Slug this was set from env variable.
// In Packer, env variable overrides config values so we keep it that way for consistency.
if bucket.Slug == "" && b.Slug != "" {
bucket.Slug = b.Slug
}
}
func (p *Parser) decodeHCPRegistry(block *hcl.Block, cfg *PackerConfig) (*HCPPackerRegistryBlock, hcl.Diagnostics) {
par := &HCPPackerRegistryBlock{}
body := block.Body

View file

@ -1,4 +1,5 @@
package registry
// Package api provides access to the HCP Packer Registry API.
package api
import (
"fmt"
@ -8,7 +9,7 @@ import (
projectSvc "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/preview/2019-12-10/client/project_service"
rmmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-resource-manager/preview/2019-12-10/models"
"github.com/hashicorp/hcp-sdk-go/httpclient"
"github.com/hashicorp/packer/internal/registry/env"
"github.com/hashicorp/packer/internal/hcp/env"
"github.com/hashicorp/packer/version"
)

View file

@ -1,4 +1,4 @@
package registry
package api
import (
"fmt"
@ -23,11 +23,11 @@ func (c *ClientError) Error() string {
return fmt.Sprintf("status %d: err %v", c.StatusCode, c.Err)
}
// checkErrorCode checks the error string for err for some code and returns true
// CheckErrorCode checks the error string for err for some code and returns true
// if the code is found. Ideally this function should use status.FromError
// https://pkg.go.dev/google.golang.org/grpc/status#pkg-functions but that
// doesn't appear to work for all of the Cloud Packer Service response errors.
func checkErrorCode(err error, code codes.Code) bool {
func CheckErrorCode(err error, code codes.Code) bool {
if err == nil {
return false
}

View file

@ -1,4 +1,4 @@
package registry
package api
import (
"errors"

View file

@ -1,4 +1,4 @@
package registry
package api
import (
"context"
@ -55,7 +55,7 @@ func (client *Client) UpsertBucket(
// Create bucket if exist we continue as is, eventually we want to treat
// this like an upsert
_, err := client.CreateBucket(ctx, bucketSlug, bucketDescription, bucketLabels)
if err != nil && !checkErrorCode(err, codes.AlreadyExists) {
if err != nil && !CheckErrorCode(err, codes.AlreadyExists) {
return err
}

View file

@ -1,3 +1,4 @@
// Package env provides HCP Packer environment variables.
package env
import (

View file

@ -1,13 +0,0 @@
package hcp
import "fmt"
// BuildDone is the error retuned by an HCP handler when a build cannot be started since it's already marked as DONE.
type BuildDone struct {
Message string
}
// Error returns the message for the BuildDone type
func (b BuildDone) Error() string {
return fmt.Sprintf("BuildDone: %s", b.Message)
}

View file

@ -1,31 +0,0 @@
package hcp
import (
"context"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
)
// noopOrchestrator is a special handler that does nothing
type noopOrchestrator struct{}
func newNoopHandler() Orchestrator {
return noopOrchestrator{}
}
func (h noopOrchestrator) PopulateIteration(context.Context) error {
return nil
}
func (h noopOrchestrator) BuildStart(context.Context, string) error {
return nil
}
func (h noopOrchestrator) BuildDone(
ctx context.Context,
buildName string,
artifacts []sdkpacker.Artifact,
buildErr error,
) ([]sdkpacker.Artifact, error) {
return artifacts, nil
}

View file

@ -1,34 +0,0 @@
package hcp
import (
"context"
"github.com/hashicorp/hcl/v2"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/packer"
)
// Orchestrator is an entity capable to orchestrate a Packer build and upload metadata to HCP
type Orchestrator interface {
PopulateIteration(context.Context) error
BuildStart(context.Context, string) error
BuildDone(ctx context.Context, buildName string, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error)
}
// GetOrchestrator instanciates the appropriate handler for the configuration type given as parameter.
//
// If no HCP-related data is present, it will be a NoopHandler.
func GetOrchestrator(cfg packer.Handler) (Orchestrator, hcl.Diagnostics) {
var handler Orchestrator
var err hcl.Diagnostics
switch cfg := cfg.(type) {
case *hcl2template.PackerConfig:
handler, err = newHCLOrchestrator(cfg)
case *packer.Core:
handler, err = newJSONOrchestrator(cfg)
}
return handler, err
}

View file

@ -0,0 +1,37 @@
package registry
import (
"fmt"
)
const BuilderId = "packer.post-processor.hpc-packer-registry"
type registryArtifact struct {
BucketSlug string
IterationID string
BuildName string
}
func (a *registryArtifact) BuilderId() string {
return BuilderId
}
func (*registryArtifact) Id() string {
return ""
}
func (a *registryArtifact) Files() []string {
return []string{}
}
func (a *registryArtifact) String() string {
return fmt.Sprintf("Published metadata to HCP Packer registry packer/%s/iterations/%s", a.BucketSlug, a.IterationID)
}
func (*registryArtifact) State(name string) interface{} {
return nil
}
func (a *registryArtifact) Destroy() error {
return nil
}

View file

@ -0,0 +1,11 @@
package registry
// ErrBuildAlreadyDone is the error returned by an HCP handler when a build cannot be started since it's already marked as DONE.
type ErrBuildAlreadyDone struct {
Message string
}
// Error returns the message for the ErrBuildAlreadyDone type
func (b ErrBuildAlreadyDone) Error() string {
return b.Message
}

View file

@ -1,4 +1,4 @@
package hcp
package registry
import (
"context"
@ -6,19 +6,17 @@ import (
"github.com/hashicorp/hcl/v2"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
imgds "github.com/hashicorp/packer/datasource/hcp-packer-image"
iterds "github.com/hashicorp/packer/datasource/hcp-packer-iteration"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/internal/registry"
"github.com/hashicorp/packer/internal/registry/env"
hcppackerimagedatasource "github.com/hashicorp/packer/internal/hcp/datasource/hcp-packer-image"
hcppackeriterationdatasource "github.com/hashicorp/packer/internal/hcp/datasource/hcp-packer-iteration"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
// hclOrchestrator is a HCP handler made for handling HCL configurations
type hclOrchestrator struct {
// HCLMetadataRegistry is a HCP handler made for handling HCL configurations
type HCLMetadataRegistry struct {
configuration *hcl2template.PackerConfig
bucket *registry.Bucket
bucket *Bucket
}
const (
@ -29,13 +27,13 @@ const (
)
// PopulateIteration creates the metadata on HCP for a build
func (h *hclOrchestrator) PopulateIteration(ctx context.Context) error {
func (h *HCLMetadataRegistry) PopulateIteration(ctx context.Context) error {
err := h.bucket.Initialize(ctx)
if err != nil {
return err
}
err = h.bucket.PopulateIteration(ctx)
err = h.bucket.populateIteration(ctx)
if err != nil {
return err
}
@ -47,44 +45,22 @@ func (h *hclOrchestrator) PopulateIteration(ctx context.Context) error {
return nil
}
// BuildStart is invoked when one build for the configuration is starting to be processed
func (h *hclOrchestrator) BuildStart(ctx context.Context, buildName string) error {
return h.bucket.BuildStart(ctx, buildName)
// StartBuild is invoked when one build for the configuration is starting to be processed
func (h *HCLMetadataRegistry) StartBuild(ctx context.Context, buildName string) error {
return h.bucket.startBuild(ctx, buildName)
}
// BuildDone is invoked when one build for the configuration has finished
func (h *hclOrchestrator) BuildDone(
// CompleteBuild is invoked when one build for the configuration has finished
func (h *HCLMetadataRegistry) CompleteBuild(
ctx context.Context,
buildName string,
artifacts []sdkpacker.Artifact,
buildErr error,
) ([]sdkpacker.Artifact, error) {
return h.bucket.BuildDone(ctx, buildName, artifacts, buildErr)
return h.bucket.completeBuild(ctx, buildName, artifacts, buildErr)
}
func newHCLOrchestrator(config *hcl2template.PackerConfig) (Orchestrator, hcl.Diagnostics) {
// HCP_PACKER_REGISTRY is explicitly turned off
if env.IsHCPDisabled() {
return newNoopHandler(), nil
}
mode := HCPConfigUnset
for _, build := range config.Builds {
if build.HCPPackerRegistry != nil {
mode = HCPConfigEnabled
}
}
// HCP_PACKER_BUCKET_NAME is set or HCP_PACKER_REGISTRY not toggled off
if mode == HCPConfigUnset && (env.HasPackerRegistryBucket() || env.IsHCPExplicitelyEnabled()) {
mode = HCPEnvEnabled
}
if mode == HCPConfigUnset {
return newNoopHandler(), nil
}
func NewHCLMetadataRegistry(config *hcl2template.PackerConfig) (*HCLMetadataRegistry, hcl.Diagnostics) {
var diags hcl.Diagnostics
if len(config.Builds) > 1 {
diags = append(diags, &hcl.Diagnostic{
@ -100,8 +76,8 @@ func newHCLOrchestrator(config *hcl2template.PackerConfig) (Orchestrator, hcl.Di
}
withHCLBucketConfiguration := func(bb *hcl2template.BuildBlock) bucketConfigurationOpts {
return func(bucket *registry.Bucket) hcl.Diagnostics {
bb.HCPPackerRegistry.WriteToBucketConfig(bucket)
return func(bucket *Bucket) hcl.Diagnostics {
bucket.ReadFromHCLBuildBlock(bb)
// If at this point the bucket.Slug is still empty,
// last try is to use the build.Name if present
if bucket.Slug == "" && bb.Name != "" {
@ -141,14 +117,14 @@ func newHCLOrchestrator(config *hcl2template.PackerConfig) (Orchestrator, hcl.Di
bucket.RegisterBuildForComponent(source.String())
}
return &hclOrchestrator{
return &HCLMetadataRegistry{
configuration: config,
bucket: bucket,
}, nil
}
func imageValueToDSOutput(imageVal map[string]cty.Value) imgds.DatasourceOutput {
dso := imgds.DatasourceOutput{}
func imageValueToDSOutput(imageVal map[string]cty.Value) hcppackerimagedatasource.DatasourceOutput {
dso := hcppackerimagedatasource.DatasourceOutput{}
for k, v := range imageVal {
switch k {
case "id":
@ -182,8 +158,8 @@ func imageValueToDSOutput(imageVal map[string]cty.Value) imgds.DatasourceOutput
return dso
}
func iterValueToDSOutput(iterVal map[string]cty.Value) iterds.DatasourceOutput {
dso := iterds.DatasourceOutput{}
func iterValueToDSOutput(iterVal map[string]cty.Value) hcppackeriterationdatasource.DatasourceOutput {
dso := hcppackeriterationdatasource.DatasourceOutput{}
for k, v := range iterVal {
switch k {
case "author_id":
@ -213,7 +189,7 @@ func iterValueToDSOutput(iterVal map[string]cty.Value) iterds.DatasourceOutput {
}
func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationOpts {
return func(bucket *registry.Bucket) hcl.Diagnostics {
return func(bucket *Bucket) hcl.Diagnostics {
var diags hcl.Diagnostics
imageDS, imageOK := vals[hcpImageDatasourceType]
@ -223,7 +199,7 @@ func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationO
return nil
}
iterations := map[string]iterds.DatasourceOutput{}
iterations := map[string]hcppackeriterationdatasource.DatasourceOutput{}
var err error
if iterOK {
@ -245,7 +221,7 @@ func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationO
}
}
images := map[string]imgds.DatasourceOutput{}
images := map[string]hcppackerimagedatasource.DatasourceOutput{}
if imageOK {
hcpData := map[string]cty.Value{}
@ -267,7 +243,7 @@ func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationO
}
for _, img := range images {
sourceIteration := registry.ParentIteration{}
sourceIteration := ParentIteration{}
sourceIteration.IterationID = img.IterationID

View file

@ -1,11 +1,12 @@
package hcp
package registry
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/internal/registry"
"github.com/hashicorp/packer/internal/registry/env"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/internal/hcp/env"
"github.com/hashicorp/packer/packer"
)
// HCPConfigMode types specify the mode in which HCP configuration
@ -21,12 +22,38 @@ const (
HCPEnvEnabled
)
type bucketConfigurationOpts func(*registry.Bucket) hcl.Diagnostics
type bucketConfigurationOpts func(*Bucket) hcl.Diagnostics
// IsHCPEnabled returns true if HCP integration is enabled for a build
func IsHCPEnabled(cfg packer.Handler) bool {
// HCP_PACKER_REGISTRY is explicitly turned off
if env.IsHCPDisabled() {
return false
}
mode := HCPConfigUnset
switch config := cfg.(type) {
case *hcl2template.PackerConfig:
for _, build := range config.Builds {
if build.HCPPackerRegistry != nil {
mode = HCPConfigEnabled
}
}
}
// HCP_PACKER_BUCKET_NAME is set or HCP_PACKER_REGISTRY not toggled off
if mode == HCPConfigUnset && (env.HasPackerRegistryBucket() || env.IsHCPExplicitelyEnabled()) {
mode = HCPEnvEnabled
}
return mode != HCPConfigUnset
}
// createConfiguredBucket returns a bucket that can be used for connecting to the HCP Packer registry.
// Configuration for the bucket is obtained from the base iteration setting and any addition configuration
// options passed in as opts. All errors during configuration are collected and returned as Diagnostics.
func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts) (*registry.Bucket, hcl.Diagnostics) {
func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts) (*Bucket, hcl.Diagnostics) {
var diags hcl.Diagnostics
if !env.HasHCPCredentials() {
@ -40,7 +67,7 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts)
})
}
bucket := registry.NewBucketWithIteration()
bucket := NewBucketWithIteration()
for _, opt := range opts {
if optDiags := opt(bucket); optDiags.HasErrors() {
@ -60,7 +87,7 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts)
})
}
err := bucket.Iteration.Initialize(registry.IterationOptions{
err := bucket.Iteration.Initialize(IterationOptions{
TemplateBaseDir: templateDir,
})
@ -75,7 +102,7 @@ func createConfiguredBucket(templateDir string, opts ...bucketConfigurationOpts)
return bucket, diags
}
func withPackerEnvConfiguration(bucket *registry.Bucket) hcl.Diagnostics {
func withPackerEnvConfiguration(bucket *Bucket) hcl.Diagnostics {
// Add default values for Packer settings configured via EnvVars.
// TODO look to break this up to be more explicit on what is loaded here.
bucket.LoadDefaultSettingsFromEnv()

View file

@ -1,4 +1,4 @@
package hcp
package registry
import (
"context"
@ -6,23 +6,16 @@ import (
"github.com/hashicorp/hcl/v2"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/internal/registry"
"github.com/hashicorp/packer/internal/registry/env"
"github.com/hashicorp/packer/packer"
)
// jsonOrchestrator is a HCP handler made to process legacy JSON templates
type jsonOrchestrator struct {
// JSONMetadataRegistry is a HCP handler made to process legacy JSON templates
type JSONMetadataRegistry struct {
configuration *packer.Core
bucket *registry.Bucket
bucket *Bucket
}
func newJSONOrchestrator(config *packer.Core) (Orchestrator, hcl.Diagnostics) {
if env.IsHCPDisabled() ||
(!env.HasPackerRegistryBucket() && !env.IsHCPExplicitelyEnabled()) {
return newNoopHandler(), nil
}
func NewJSONMetadataRegistry(config *packer.Core) (*JSONMetadataRegistry, hcl.Diagnostics) {
bucket, diags := createConfiguredBucket(
filepath.Dir(config.Template.Path),
withPackerEnvConfiguration,
@ -37,14 +30,14 @@ func newJSONOrchestrator(config *packer.Core) (Orchestrator, hcl.Diagnostics) {
bucket.RegisterBuildForComponent(packer.HCPName(b))
}
return &jsonOrchestrator{
return &JSONMetadataRegistry{
configuration: config,
bucket: bucket,
}, nil
}
// PopulateIteration creates the metadata on HCP for a build
func (h *jsonOrchestrator) PopulateIteration(ctx context.Context) error {
func (h *JSONMetadataRegistry) PopulateIteration(ctx context.Context) error {
for _, b := range h.configuration.Template.Builders {
// Get all builds slated within config ignoring any only or exclude flags.
h.bucket.RegisterBuildForComponent(b.Name)
@ -59,7 +52,7 @@ func (h *jsonOrchestrator) PopulateIteration(ctx context.Context) error {
return err
}
err = h.bucket.PopulateIteration(ctx)
err = h.bucket.populateIteration(ctx)
if err != nil {
return err
}
@ -67,17 +60,17 @@ func (h *jsonOrchestrator) PopulateIteration(ctx context.Context) error {
return nil
}
// BuildStart is invoked when one build for the configuration is starting to be processed
func (h *jsonOrchestrator) BuildStart(ctx context.Context, buildName string) error {
return h.bucket.BuildStart(ctx, buildName)
// StartBuild is invoked when one build for the configuration is starting to be processed
func (h *JSONMetadataRegistry) StartBuild(ctx context.Context, buildName string) error {
return h.bucket.startBuild(ctx, buildName)
}
// BuildDone is invoked when one build for the configuration has finished
func (h *jsonOrchestrator) BuildDone(
// CompleteBuild is invoked when one build for the configuration has finished
func (h *JSONMetadataRegistry) CompleteBuild(
ctx context.Context,
buildName string,
artifacts []sdkpacker.Artifact,
buildErr error,
) ([]sdkpacker.Artifact, error) {
return h.bucket.BuildDone(ctx, buildName, artifacts, buildErr)
return h.bucket.completeBuild(ctx, buildName, artifacts, buildErr)
}

View file

@ -0,0 +1,27 @@
package registry
import (
"context"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
)
// nullRegistry is a special handler that does nothing
type nullRegistry struct{}
func (r nullRegistry) PopulateIteration(context.Context) error {
return nil
}
func (r nullRegistry) StartBuild(context.Context, string) error {
return nil
}
func (r nullRegistry) CompleteBuild(
ctx context.Context,
buildName string,
artifacts []sdkpacker.Artifact,
buildErr error,
) ([]sdkpacker.Artifact, error) {
return artifacts, nil
}

View file

@ -0,0 +1,45 @@
// Package registry provides access to the HCP registry.
package registry
import (
"context"
"github.com/hashicorp/hcl/v2"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/packer"
)
// Registry is an entity capable to orchestrate a Packer build and upload metadata to HCP
type Registry interface {
//Configure(packer.Handler)
PopulateIteration(context.Context) error
StartBuild(context.Context, string) error
CompleteBuild(ctx context.Context, buildName string, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error)
}
// New instanciates the appropriate registry for the Packer configuration template type.
// A nullRegistry is returned for non-HCP Packer registry enabled templates.
func New(cfg packer.Handler) (Registry, hcl.Diagnostics) {
if !IsHCPEnabled(cfg) {
return &nullRegistry{}, nil
}
switch config := cfg.(type) {
case *hcl2template.PackerConfig:
// Maybe rename to what it represents....
return NewHCLMetadataRegistry(config)
case *packer.Core:
return NewJSONMetadataRegistry(config)
}
return nil, hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unknown Config type",
Detail: "The config type %s does not match a Packer-known template type. " +
"This is a Packer error and should be brought up to the Packer " +
"team via a GitHub Issue.",
},
}
}

View file

@ -13,7 +13,9 @@ import (
"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
"github.com/hashicorp/packer-plugin-sdk/packer"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
"github.com/hashicorp/packer/internal/registry/env"
"github.com/hashicorp/packer/hcl2template"
"github.com/hashicorp/packer/internal/hcp/api"
"github.com/hashicorp/packer/internal/hcp/env"
"github.com/mitchellh/mapstructure"
"google.golang.org/grpc/codes"
)
@ -32,7 +34,7 @@ type Bucket struct {
SourceImagesToParentIterations map[string]ParentIteration
RunningBuilds map[string]chan struct{}
Iteration *Iteration
client *Client
client *api.Client
}
type ParentIteration struct {
@ -61,6 +63,27 @@ func (b *Bucket) Validate() error {
return nil
}
// ReadFromHCLBuildBlock reads the information for initialising a Bucket from a HCL2 build block
func (b *Bucket) ReadFromHCLBuildBlock(hcpBlock *hcl2template.BuildBlock) {
if b == nil {
return
}
b.Description = hcpBlock.Description
hcp := hcpBlock.HCPPackerRegistry
if hcp == nil {
return
}
b.BucketLabels = hcp.BucketLabels
b.BuildLabels = hcp.BuildLabels
// If there's already a Slug this was set from env variable.
// In Packer, env variable overrides config values so we keep it that way for consistency.
if b.Slug == "" && hcp.Slug != "" {
b.Slug = hcp.Slug
}
}
// connect initializes a client connection to a remote HCP Packer Registry service on HCP.
// Upon a successful connection the initialized client is persisted on the Bucket b for later usage.
func (b *Bucket) connect() error {
@ -68,7 +91,7 @@ func (b *Bucket) connect() error {
return nil
}
registryClient, err := NewClient()
registryClient, err := api.NewClient()
if err != nil {
return errors.New("Failed to create client connection to artifact registry: " + err.Error())
}
@ -186,10 +209,10 @@ func (b *Bucket) UpdateBuildStatus(ctx context.Context, name string, status mode
return nil
}
// CompleteBuild should be called to set a build on the HCP Packer registry to DONE.
// markBuildComplete should be called to set a build on the HCP Packer registry to DONE.
// Upon a successful call markBuildComplete will publish all images created by the named build,
// and set the registry build to done. A build with no images can not be set to DONE.
func (b *Bucket) CompleteBuild(ctx context.Context, name string) error {
func (b *Bucket) markBuildComplete(ctx context.Context, name string) error {
buildToUpdate, err := b.Iteration.Build(name)
if err != nil {
return err
@ -297,8 +320,8 @@ func (b *Bucket) createIteration() (*models.HashicorpCloudPackerIteration, error
func (b *Bucket) initializeIteration(ctx context.Context) error {
// load existing iteration using fingerprint.
iteration, err := b.client.GetIteration(ctx, b.Slug, GetIteration_byFingerprint(b.Iteration.Fingerprint))
if checkErrorCode(err, codes.Aborted) {
iteration, err := b.client.GetIteration(ctx, b.Slug, api.GetIteration_byFingerprint(b.Iteration.Fingerprint))
if api.CheckErrorCode(err, codes.Aborted) {
// probably means Iteration doesn't exist need a way to check the error
iteration, err = b.createIteration()
}
@ -325,11 +348,11 @@ func (b *Bucket) initializeIteration(ctx context.Context) error {
return nil
}
// PopulateIteration populates the bucket iteration with the details needed for tracking builds for a Packer run.
// populateIteration populates the bucket iteration with the details needed for tracking builds for a Packer run.
// If an existing Packer registry iteration exists for the said iteration fingerprint, calling initialize on iteration
// that doesn't yet exist will call createIteration to create the entry on the HCP packer registry for the given bucket.
// All build details will be created (if they don't exists) and added to b.Iteration.builds for tracking during runtime.
func (b *Bucket) PopulateIteration(ctx context.Context) error {
func (b *Bucket) populateIteration(ctx context.Context) error {
// list all this iteration's builds so we can figure out which ones
// we want to run against. TODO: pagination?
existingBuilds, err := b.client.ListBuilds(ctx, b.Slug, b.Iteration.ID)
@ -387,7 +410,7 @@ func (b *Bucket) PopulateIteration(ctx context.Context) error {
log.Printf("[TRACE] registering build with iteration for %q.", name)
err := b.CreateInitialBuildForIteration(ctx, name)
if checkErrorCode(err, codes.AlreadyExists) {
if api.CheckErrorCode(err, codes.AlreadyExists) {
log.Printf("[TRACE] build %s already exists in Packer registry, continuing...", name)
return
}
@ -473,9 +496,11 @@ func (b *Bucket) HeartbeatBuild(ctx context.Context, build string) (func(), erro
}, nil
}
func (b *Bucket) BuildStart(ctx context.Context, buildName string) error {
func (b *Bucket) startBuild(ctx context.Context, buildName string) error {
if !b.IsExpectingBuildForComponent(buildName) {
return fmt.Errorf("already done")
return &ErrBuildAlreadyDone{
Message: "build is already done",
}
}
err := b.UpdateBuildStatus(ctx, buildName, models.HashicorpCloudPackerBuildStatusRUNNING)
@ -515,7 +540,7 @@ func (b *Bucket) BuildStart(ctx context.Context, buildName string) error {
return nil
}
func (b *Bucket) BuildDone(
func (b *Bucket) completeBuild(
ctx context.Context,
buildName string,
artifacts []packer.Artifact,
@ -568,7 +593,7 @@ func (b *Bucket) BuildDone(
}
}
parErr := b.CompleteBuild(ctx, buildName)
parErr := b.markBuildComplete(ctx, buildName)
if parErr != nil {
return artifacts, fmt.Errorf(
"failed to update Packer registry with image artifacts for %q: %s",
@ -576,7 +601,7 @@ func (b *Bucket) BuildDone(
parErr)
}
return append(artifacts, &RegistryArtifact{
return append(artifacts, &registryArtifact{
BuildName: buildName,
BucketSlug: b.Slug,
IterationID: b.Iteration.ID,

View file

@ -5,15 +5,16 @@ import (
"testing"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
"github.com/hashicorp/packer/internal/hcp/api"
)
func TestInitialize_NewBucketNewIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -47,7 +48,7 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) {
t.Errorf("expected an iteration to created but it didn't")
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -63,12 +64,12 @@ func TestInitialize_NewBucketNewIteration(t *testing.T) {
func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -101,7 +102,7 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
t.Errorf("expected an iteration to created but it didn't")
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -117,13 +118,13 @@ func TestInitialize_ExistingBucketNewIteration(t *testing.T) {
func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -141,7 +142,7 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -170,7 +171,7 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
t.Errorf("expected an iteration to created but it didn't")
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -187,7 +188,7 @@ func TestInitialize_ExistingBucketExistingIteration(t *testing.T) {
func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
mockService.IterationCompleted = true
@ -195,7 +196,7 @@ func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -233,13 +234,13 @@ func TestInitialize_ExistingBucketCompleteIteration(t *testing.T) {
func TestUpdateBuildStatus(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -256,7 +257,7 @@ func TestUpdateBuildStatus(t *testing.T) {
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
@ -287,13 +288,13 @@ func TestUpdateBuildStatus(t *testing.T) {
func TestUpdateBuildStatus_DONENoImages(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "testnumber")
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
b := &Bucket{
Slug: "TestBucket",
client: &Client{
client: &api.Client{
Packer: mockService,
},
}
@ -311,7 +312,7 @@ func TestUpdateBuildStatus_DONENoImages(t *testing.T) {
if err != nil {
t.Errorf("unexpected failure: %v", err)
}
err = b.PopulateIteration(context.TODO())
err = b.populateIteration(context.TODO())
if err != nil {
t.Errorf("unexpected failure: %v", err)
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/internal/hcp/api"
)
func createInitialTestBucket(t testing.TB) *Bucket {
@ -19,10 +20,10 @@ func createInitialTestBucket(t testing.TB) *Bucket {
return nil
}
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.TrackCalledServiceMethods = false
bucket.Slug = "TestBucket"
bucket.client = &Client{
bucket.client = &api.Client{
Packer: mockService,
}
@ -164,7 +165,7 @@ func TestBucket_UpdateLabelsForBuild_withMultipleBuilds(t *testing.T) {
secondComponent := "happycloud.image2"
bucket.RegisterBuildForComponent(secondComponent)
err := bucket.PopulateIteration(context.TODO())
err := bucket.populateIteration(context.TODO())
checkError(t, err)
err = bucket.UpdateLabelsForBuild(firstComponent, map[string]string{
@ -282,7 +283,7 @@ func TestBucket_PopulateIteration(t *testing.T) {
t.Setenv("HCP_PACKER_BUILD_FINGERPRINT", "test-run-"+strconv.Itoa(i))
mockService := NewMockPackerClientService()
mockService := api.NewMockPackerClientService()
mockService.BucketAlreadyExist = true
mockService.IterationAlreadyExist = true
mockService.BuildAlreadyDone = tt.buildCompleted
@ -294,7 +295,7 @@ func TestBucket_PopulateIteration(t *testing.T) {
}
bucket.Slug = "TestBucket"
bucket.client = &Client{
bucket.client = &api.Client{
Packer: mockService,
}
for k, v := range tt.bucketBuildLabels {
@ -307,7 +308,7 @@ func TestBucket_PopulateIteration(t *testing.T) {
mockService.ExistingBuilds = append(mockService.ExistingBuilds, componentName)
mockService.ExistingBuildLabels = tt.buildLabels
err = bucket.PopulateIteration(context.TODO())
err = bucket.populateIteration(context.TODO())
checkError(t, err)
if mockService.CreateBuildCalled {

View file

@ -8,7 +8,7 @@ import (
git "github.com/go-git/go-git/v5"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
"github.com/hashicorp/packer/internal/registry/env"
"github.com/hashicorp/packer/internal/hcp/env"
)
type Iteration struct {

View file

@ -1,37 +0,0 @@
package registry
import (
"fmt"
)
const BuilderId = "packer.post-processor.packer-registry"
type RegistryArtifact struct {
BucketSlug string
IterationID string
BuildName string
}
func (a *RegistryArtifact) BuilderId() string {
return BuilderId
}
func (*RegistryArtifact) Id() string {
return ""
}
func (a *RegistryArtifact) Files() []string {
return []string{}
}
func (a *RegistryArtifact) String() string {
return fmt.Sprintf("Published metadata to HCP Packer registry packer/%s/iterations/%s", a.BucketSlug, a.IterationID)
}
func (*RegistryArtifact) State(name string) interface{} {
return nil
}
func (a *RegistryArtifact) Destroy() error {
return nil
}