Files
Lucas Bajolet 7cd095062a parser: extract HCP logic out of the HCL parser
Before this commit, some HCP-related logic was embedded in the parser,
making all of it common to all commands.

This complexifies the logic as some parts rely on the template being in
a HCP-valid environment, and the datasources need to be evaluated in
order for some operations to be executed.

To simplify this and avoid those pitfalls, we move this logic to its own
set of functions that will be performed after the parsing is done, on
any valid HCL2 or JSON template.
2022-08-12 11:54:21 -04:00

286 lines
7.1 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
hasHCP := false
for _, build := range pc.Builds {
if build.HCPPackerRegistry != nil {
hasHCP = true
}
}
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
}
if env.IsPAREnabled() {
hasHCP = true
}
if !hasHCP {
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())
}
}
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.IsPAREnabled() {
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()
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
}