Files
Packer-Cn/command/registry.go
T
Lucas Bajolet 779a31a25f hcp: use HCP_PACKER_REGISTRY to disable HCP
The current behaviour for HCP integration is based on the presence of
the HCP_PACKER_REGISTRY environment variable, where if it is either not
"0" or "off", the HCP integration is enabled.

This commit changes the behaviour to only use this variable to
explicitely disable HCP, and the HCP_PACKER_BUCKET_NAME variable will
condition if the HCP integration is enabled for a build or not in JSON
templates.

For HCL templates, the integration can also be disabled if the
HCP_PACKER_REGISTRY variable is set to "0" or "off", and will be
considered enabled if the HCP_PACKER_BUCKET_NAME is set, or there is a
"hcp_packer_registry" block in a build.
2022-09-19 11:22:13 -04:00

310 lines
7.7 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"
)
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 setup the HCP-related structures if HCP is enabled
// for the command
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: "SetupRegistry 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 {
var diags hcl.Diagnostics
if env.IsHCPDisabled() {
return nil
}
hasHCP := false
for _, build := range pc.Builds {
if build.HCPPackerRegistry != nil {
hasHCP = true
}
}
if env.HasPackerRegistryBucket() {
hasHCP = true
}
if !hasHCP {
return nil
}
if hasHCP && 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
}
var err error
pc.Bucket, err = registry.NewBucketWithIteration(registry.IterationOptions{
TemplateBaseDir: pc.Basedir,
})
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unable to create a valid bucket object for HCP Packer Registry",
Detail: fmt.Sprintf("%s", err),
Severity: hcl.DiagError,
})
return diags
}
pc.Bucket.LoadDefaultSettingsFromEnv()
for _, build := range pc.Builds {
build.HCPPackerRegistry.WriteToBucketConfig(pc.Bucket)
// If at this point the bucket.Slug is still empty,
// last try is to use the build.Name if present
if pc.Bucket.Slug == "" && build.Name != "" {
pc.Bucket.Slug = build.Name
}
// If the description is empty, use the one from the build block
if pc.Bucket.Description == "" {
pc.Bucket.Description = build.Description
}
for _, source := range build.Sources {
pc.Bucket.RegisterBuildForComponent(source.String())
}
}
if pc.Bucket.Slug == "" {
return append(diags, &hcl.Diagnostic{
Summary: "bucket name cannot be empty",
Detail: "empty bucket name, please set it with the HCP_PACKER_BUCKET_NAME environment variable, or in a `hcp_packer_registry` block",
Severity: hcl.DiagError,
})
}
vals, diags := pc.Datasources.Values()
if diags != nil {
return diags
}
imageDS, imageOK := vals[hcpImageDatasourceType]
iterDS, iterOK := vals[hcpIterationDatasourceType]
// If we don't have any image or iteration defined, we can return directly
if !imageOK && !iterOK {
return nil
}
iterations := map[string]iterds.DatasourceOutput{}
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
}
}
}
pc.Bucket.SourceImagesToParentIterations[img.ID] = sourceIteration
}
return diags
}
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 setupRegistryForPackerCore(cfg *CoreWrapper) hcl.Diagnostics {
if env.IsHCPDisabled() {
return nil
}
if !env.HasPackerRegistryBucket() {
return nil
}
var diags hcl.Diagnostics
var err error
core := cfg.Core
core.Bucket, err = registry.NewBucketWithIteration(registry.IterationOptions{
TemplateBaseDir: filepath.Dir(core.Template.Path),
})
if err != nil {
return append(diags, &hcl.Diagnostic{
Summary: "bucket creation failure",
Detail: fmt.Sprintf("failed to create Bucket: %s", err),
Severity: hcl.DiagError,
})
}
core.Bucket.LoadDefaultSettingsFromEnv()
if core.Bucket.Slug == "" {
return append(diags, &hcl.Diagnostic{
Summary: "bucket name cannot be empty",
Detail: "empty bucket name, please set it with the HCP_PACKER_BUCKET_NAME environment variable",
Severity: hcl.DiagError,
})
}
for _, b := range core.Template.Builders {
// Get all builds slated within config ignoring any only or exclude flags.
core.Bucket.RegisterBuildForComponent(b.Name)
}
return nil
}