mirror of
https://github.com/hashicorp/packer.git
synced 2026-03-06 23:41:14 -05:00
The HCP error messages were sometimes a bit terse in terms of the information it bubbles up to the user. This made them useful for people who already knew what to look for, but newcomers would almost certainly be lost because of the lack of information in those. To improve on this situation, we reword most of those error messages in this commit.
369 lines
10 KiB
Go
369 lines
10 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
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"
|
|
"github.com/hashicorp/packer/packer"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/gocty"
|
|
)
|
|
|
|
// HCPConfigMode types specify the mode in which HCP configuration
|
|
// is defined for a given Packer build execution.
|
|
type HCPConfigMode int
|
|
|
|
const (
|
|
// HCPConfigUnset mode is set when no HCP configuration has been found for the Packer execution.
|
|
HCPConfigUnset HCPConfigMode = iota
|
|
// HCPConfigEnabled mode is set when the HCP configuration is codified in the template.
|
|
HCPConfigEnabled
|
|
// HCPEnvEnabled mode is set when the HCP configuration is read from environment variables.
|
|
HCPEnvEnabled
|
|
)
|
|
|
|
const (
|
|
// Known HCP Packer Image Datasource, whose id is the SourceImageId for some build.
|
|
hcpImageDatasourceType string = "hcp-packer-image"
|
|
hcpIterationDatasourceType string = "hcp-packer-iteration"
|
|
buildLabel string = "build"
|
|
)
|
|
|
|
// TrySetupHCP attempts to configure the HCP-related structures if
|
|
// Packer has been configured to publish to a HCP Packer registry.
|
|
func TrySetupHCP(cfg packer.Handler) hcl.Diagnostics {
|
|
switch cfg := cfg.(type) {
|
|
case *hcl2template.PackerConfig:
|
|
return setupRegistryForPackerConfig(cfg)
|
|
case *CoreWrapper:
|
|
return setupRegistryForPackerCore(cfg)
|
|
}
|
|
|
|
return hcl.Diagnostics{
|
|
&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "unknown Handler type",
|
|
Detail: "TrySetupHCP called with an unknown Handler. " +
|
|
"This is a Packer bug and should be brought to the attention " +
|
|
"of the Packer team, please consider opening an issue for this.",
|
|
},
|
|
}
|
|
}
|
|
|
|
func setupRegistryForPackerConfig(pc *hcl2template.PackerConfig) hcl.Diagnostics {
|
|
|
|
// HCP_PACKER_REGISTRY is explicitly turned off
|
|
if env.IsHCPDisabled() {
|
|
return nil
|
|
}
|
|
|
|
mode := HCPConfigUnset
|
|
|
|
for _, build := range pc.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 nil
|
|
}
|
|
|
|
var diags hcl.Diagnostics
|
|
if len(pc.Builds) > 1 {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Multiple " + buildLabel + " blocks",
|
|
Detail: fmt.Sprintf("For Packer Registry enabled builds, only one " + buildLabel +
|
|
" block can be defined. Please remove any additional " + buildLabel +
|
|
" block(s). If this " + buildLabel + " is not meant for the Packer registry please " +
|
|
"clear any HCP_PACKER_* environment variables."),
|
|
})
|
|
|
|
return diags
|
|
}
|
|
|
|
withHCLBucketConfiguration := func(bb *hcl2template.BuildBlock) bucketConfigurationOpts {
|
|
return func(bucket *registry.Bucket) hcl.Diagnostics {
|
|
bb.HCPPackerRegistry.WriteToBucketConfig(bucket)
|
|
// 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 != "" {
|
|
bucket.Slug = bb.Name
|
|
}
|
|
|
|
// If the description is empty, use the one from the build block
|
|
if bucket.Description == "" && bb.Description != "" {
|
|
bucket.Description = bb.Description
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Capture Datasource configuration data
|
|
vals, dsDiags := pc.Datasources.Values()
|
|
if dsDiags != nil {
|
|
diags = append(diags, dsDiags...)
|
|
}
|
|
|
|
build := pc.Builds[0]
|
|
pc.Bucket, diags = createConfiguredBucket(
|
|
pc.Basedir,
|
|
withPackerEnvConfiguration,
|
|
withHCLBucketConfiguration(build),
|
|
withDatasourceConfiguration(vals),
|
|
)
|
|
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
for _, source := range build.Sources {
|
|
pc.Bucket.RegisterBuildForComponent(source.String())
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func setupRegistryForPackerCore(cfg *CoreWrapper) hcl.Diagnostics {
|
|
if env.IsHCPDisabled() {
|
|
return nil
|
|
}
|
|
|
|
if !env.HasPackerRegistryBucket() && !env.IsHCPExplicitelyEnabled() {
|
|
return nil
|
|
}
|
|
|
|
bucket, diags := createConfiguredBucket(
|
|
filepath.Dir(cfg.Core.Template.Path),
|
|
withPackerEnvConfiguration,
|
|
)
|
|
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
cfg.Core.Bucket = bucket
|
|
for _, b := range cfg.Core.Template.Builders {
|
|
// Get all builds slated within config ignoring any only or exclude flags.
|
|
cfg.Core.Bucket.RegisterBuildForComponent(packer.HCPName(b))
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
type bucketConfigurationOpts func(*registry.Bucket) hcl.Diagnostics
|
|
|
|
// 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) {
|
|
var diags hcl.Diagnostics
|
|
|
|
if !env.HasHCPCredentials() {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: "HCP authentication information required",
|
|
Detail: fmt.Sprintf("The client authentication requires both %s and %s environment "+
|
|
"variables to be set for authenticating with HCP.",
|
|
env.HCPClientID,
|
|
env.HCPClientSecret),
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
|
|
bucket := registry.NewBucketWithIteration()
|
|
|
|
for _, opt := range opts {
|
|
if optDiags := opt(bucket); optDiags.HasErrors() {
|
|
diags = append(diags, optDiags...)
|
|
}
|
|
}
|
|
|
|
if bucket.Slug == "" {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: "Image bucket name required",
|
|
Detail: "You must provide an image bucket name for HCP Packer builds. " +
|
|
"You can set the HCP_PACKER_BUCKET_NAME environment variable. " +
|
|
"For HCL2 templates, the registry either uses the name of your " +
|
|
"template's build block, or you can set the bucket_name argument " +
|
|
"in an hcp_packer_registry block.",
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
|
|
err := bucket.Iteration.Initialize(registry.IterationOptions{
|
|
TemplateBaseDir: templateDir,
|
|
})
|
|
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: "Iteration initialization failed",
|
|
Detail: fmt.Sprintf("Initialization of the iteration failed with "+
|
|
"the following error message: %s", err),
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
return bucket, diags
|
|
}
|
|
|
|
func withPackerEnvConfiguration(bucket *registry.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()
|
|
|
|
return nil
|
|
}
|
|
|
|
func imageValueToDSOutput(imageVal map[string]cty.Value) imgds.DatasourceOutput {
|
|
dso := imgds.DatasourceOutput{}
|
|
for k, v := range imageVal {
|
|
switch k {
|
|
case "id":
|
|
dso.ID = v.AsString()
|
|
case "region":
|
|
dso.Region = v.AsString()
|
|
case "labels":
|
|
labels := map[string]string{}
|
|
lbls := v.AsValueMap()
|
|
for k, v := range lbls {
|
|
labels[k] = v.AsString()
|
|
}
|
|
dso.Labels = labels
|
|
case "packer_run_uuid":
|
|
dso.PackerRunUUID = v.AsString()
|
|
case "channel_id":
|
|
dso.ChannelID = v.AsString()
|
|
case "iteration_id":
|
|
dso.IterationID = v.AsString()
|
|
case "build_id":
|
|
dso.BuildID = v.AsString()
|
|
case "created_at":
|
|
dso.CreatedAt = v.AsString()
|
|
case "component_type":
|
|
dso.ComponentType = v.AsString()
|
|
case "cloud_provider":
|
|
dso.CloudProvider = v.AsString()
|
|
}
|
|
}
|
|
|
|
return dso
|
|
}
|
|
|
|
func iterValueToDSOutput(iterVal map[string]cty.Value) iterds.DatasourceOutput {
|
|
dso := iterds.DatasourceOutput{}
|
|
for k, v := range iterVal {
|
|
switch k {
|
|
case "author_id":
|
|
dso.AuthorID = v.AsString()
|
|
case "bucket_name":
|
|
dso.BucketName = v.AsString()
|
|
case "complete":
|
|
// For all intents and purposes, cty.Value.True() acts
|
|
// like a AsBool() would.
|
|
dso.Complete = v.True()
|
|
case "created_at":
|
|
dso.CreatedAt = v.AsString()
|
|
case "fingerprint":
|
|
dso.Fingerprint = v.AsString()
|
|
case "id":
|
|
dso.ID = v.AsString()
|
|
case "incremental_version":
|
|
// Maybe when cty provides a good way to AsInt() a cty.Value
|
|
// we can consider implementing this.
|
|
case "updated_at":
|
|
dso.UpdatedAt = v.AsString()
|
|
case "channel_id":
|
|
dso.ChannelID = v.AsString()
|
|
}
|
|
}
|
|
return dso
|
|
}
|
|
|
|
func withDatasourceConfiguration(vals map[string]cty.Value) bucketConfigurationOpts {
|
|
return func(bucket *registry.Bucket) hcl.Diagnostics {
|
|
var diags hcl.Diagnostics
|
|
|
|
imageDS, imageOK := vals[hcpImageDatasourceType]
|
|
iterDS, iterOK := vals[hcpIterationDatasourceType]
|
|
|
|
if !imageOK && !iterOK {
|
|
return nil
|
|
}
|
|
|
|
iterations := map[string]iterds.DatasourceOutput{}
|
|
|
|
var err error
|
|
if iterOK {
|
|
hcpData := map[string]cty.Value{}
|
|
err = gocty.FromCtyValue(iterDS, &hcpData)
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid HCP datasources",
|
|
Detail: fmt.Sprintf("Failed to decode hcp_packer_iteration datasources: %s", err),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
for k, v := range hcpData {
|
|
iterVals := v.AsValueMap()
|
|
dso := iterValueToDSOutput(iterVals)
|
|
iterations[k] = dso
|
|
}
|
|
}
|
|
|
|
images := map[string]imgds.DatasourceOutput{}
|
|
|
|
if imageOK {
|
|
hcpData := map[string]cty.Value{}
|
|
err = gocty.FromCtyValue(imageDS, &hcpData)
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid HCP datasources",
|
|
Detail: fmt.Sprintf("Failed to decode hcp_packer_image datasources: %s", err),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
for k, v := range hcpData {
|
|
imageVals := v.AsValueMap()
|
|
dso := imageValueToDSOutput(imageVals)
|
|
images[k] = dso
|
|
}
|
|
}
|
|
|
|
for _, img := range images {
|
|
sourceIteration := registry.ParentIteration{}
|
|
|
|
sourceIteration.IterationID = img.IterationID
|
|
|
|
if img.ChannelID != "" {
|
|
sourceIteration.ChannelID = img.ChannelID
|
|
} else {
|
|
for _, it := range iterations {
|
|
if it.ID == img.IterationID {
|
|
sourceIteration.ChannelID = it.ChannelID
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
bucket.SourceImagesToParentIterations[img.ID] = sourceIteration
|
|
}
|
|
|
|
return diags
|
|
}
|
|
}
|