packer/hcl2template/types.datasource.go
Lucas Bajolet 9c3ec44379 hcl2template: use refString for local/data deps
When registering dependencies for datasources and locals, we now use
refString.

This allows for the functions that detect dependencies to not only be
able to register the same types as dependencies, but instead generalises
it to any type that refString supports, so data, local and var.

This can then be leveraged for orchestrating evaluation of those
components in a non-phased way (i.e. with a DAG for dependency
management).
2024-10-29 16:10:29 -04:00

166 lines
4.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package hcl2template
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
"github.com/zclconf/go-cty/cty"
)
// DatasourceBlock references an HCL 'data' block.
type DatasourceBlock struct {
Type string
DSName string
Dependencies []refString
value cty.Value
block *hcl.Block
}
type DatasourceRef struct {
Type string
Name string
}
type Datasources map[DatasourceRef]DatasourceBlock
func (data DatasourceBlock) Name() string {
return fmt.Sprintf("%s.%s", data.Type, data.DSName)
}
func (data *DatasourceBlock) Ref() DatasourceRef {
return DatasourceRef{
Type: data.Type,
Name: data.DSName,
}
}
func (ds *Datasources) Values() (map[string]cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
res := map[string]cty.Value{}
valuesMap := map[string]map[string]cty.Value{}
for ref, datasource := range *ds {
if datasource.value == (cty.Value{}) {
diags = append(diags, &hcl.Diagnostic{
Summary: "empty value",
Subject: &datasource.block.DefRange,
Severity: hcl.DiagError,
})
continue
}
inner := valuesMap[ref.Type]
if inner == nil {
inner = map[string]cty.Value{}
}
inner[ref.Name] = datasource.value
res[ref.Type] = cty.MapVal(inner)
// Keeps values of different datasources from same type
valuesMap[ref.Type] = inner
}
return res, diags
}
func (cfg *PackerConfig) startDatasource(ds DatasourceBlock) (packersdk.Datasource, hcl.Diagnostics) {
var diags hcl.Diagnostics
block := ds.block
dataSourceStore := cfg.parser.PluginConfig.DataSources
if dataSourceStore == nil {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unknown " + dataSourceLabel + " type " + ds.Type,
Subject: block.LabelRanges[0].Ptr(),
Detail: "packer does not currently know any data source.",
Severity: hcl.DiagError,
})
return nil, diags
}
if !dataSourceStore.Has(ds.Type) {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unknown " + dataSourceLabel + " type " + ds.Type,
Subject: block.LabelRanges[0].Ptr(),
Detail: fmt.Sprintf("known data sources: %v", dataSourceStore.List()),
Severity: hcl.DiagError,
})
return nil, diags
}
datasource, err := dataSourceStore.Start(ds.Type)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: err.Error(),
Subject: &block.DefRange,
Severity: hcl.DiagError,
})
}
if datasource == nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("failed to start datasource plugin %q", ds.Name()),
Subject: &block.DefRange,
Severity: hcl.DiagError,
})
}
var decoded cty.Value
var moreDiags hcl.Diagnostics
body := block.Body
decoded, moreDiags = decodeHCL2Spec(body, cfg.EvalContext(DatasourceContext, nil), datasource)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
return nil, diags
}
// In case of cty.Unknown values, this will write a equivalent placeholder
// of the same type. Unknown types are not recognized by the json marshal
// during the RPC call and we have to do this here to avoid json parsing
// failures when running the validate command. We don't do this before so
// we can validate if variable type matches correctly on decodeHCL2Spec.
decoded = hcl2shim.WriteUnknownPlaceholderValues(decoded)
if err := datasource.Configure(decoded); err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: err.Error(),
Subject: &block.DefRange,
Severity: hcl.DiagError,
})
}
return datasource, diags
}
func (p *Parser) decodeDataBlock(block *hcl.Block) (*DatasourceBlock, hcl.Diagnostics) {
var diags hcl.Diagnostics
r := &DatasourceBlock{
Type: block.Labels[0],
DSName: block.Labels[1],
block: block,
}
if !hclsyntax.ValidIdentifier(r.Type) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid data source name",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[0],
})
}
if !hclsyntax.ValidIdentifier(r.DSName) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid data resource name",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[1],
})
}
return r, diags
}