package hcl2template import ( "fmt" "sort" "strconv" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" hcl2shim "github.com/hashicorp/packer/hcl2template/shim" "github.com/zclconf/go-cty/cty" ) // SourceBlock references an HCL 'source' block to be used in a build for // example. type SourceBlock struct { // Type of source; ex: virtualbox-iso Type string // Given name; if any Name string block *hcl.Block // LocalName can be set in a singular source block from a build block, it // allows to give a special name to a build in the logs. LocalName string } // SourceUseBlock is a SourceBlock 'usage' from a config stand point. // For example when one uses `build.sources = ["..."]` or // `build.source "..." {...}`. type SourceUseBlock struct { // reference to an actual source block definition, or SourceBlock. SourceRef // LocalName can be set in a singular source block from a build block, it // allows to give a special name to a build in the logs. LocalName string // Rest of the body, in case the build.source block has more specific // content // Body can be expanded by a dynamic tag. Body hcl.Body } func (b *SourceUseBlock) name() string { if b.LocalName != "" { return b.LocalName } return b.Name } func (b *SourceUseBlock) String() string { return fmt.Sprintf("%s.%s", b.Type, b.name()) } // EvalContext adds the values of the source to the passed eval context. func (b *SourceUseBlock) ctyValues() map[string]cty.Value { return map[string]cty.Value{ "type": cty.StringVal(b.Type), "name": cty.StringVal(b.name()), } } // decodeBuildSource reads a used source block from a build: // build { // source "type.example" { // name = "local_name" // } // } func (p *Parser) decodeBuildSource(block *hcl.Block) (SourceUseBlock, hcl.Diagnostics) { ref := sourceRefFromString(block.Labels[0]) out := SourceUseBlock{SourceRef: ref} var b struct { Name string `hcl:"name,optional"` Rest hcl.Body `hcl:",remain"` } diags := gohcl.DecodeBody(block.Body, nil, &b) if diags.HasErrors() { return out, diags } out.LocalName = b.Name out.Body = b.Rest return out, nil } func (p *Parser) decodeSource(block *hcl.Block) (SourceBlock, hcl.Diagnostics) { source := SourceBlock{ Type: block.Labels[0], Name: block.Labels[1], block: block, } var diags hcl.Diagnostics return source, diags } func (cfg *PackerConfig) startBuilder(source SourceUseBlock, ectx *hcl.EvalContext) (packersdk.Builder, hcl.Diagnostics, []string) { var diags hcl.Diagnostics builder, err := cfg.parser.PluginConfig.Builders.Start(source.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Failed to load " + sourceLabel + " type", Detail: err.Error(), }) return builder, diags, nil } body := source.Body decoded, moreDiags := decodeHCL2Spec(body, ectx, builder) diags = append(diags, moreDiags...) if moreDiags.HasErrors() { return builder, diags, nil } // 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 types matches correctly on decodeHCL2Spec. decoded = hcl2shim.WriteUnknownPlaceholderValues(decoded) // Note: HCL prepares inside of the Start func, but Json does not. Json // builds are instead prepared only in command/build.go // TODO: either make json prepare when plugins are loaded, or make HCL // prepare at a later step, to make builds from different template types // easier to reason about. builderVars := source.builderVariables() builderVars["packer_debug"] = strconv.FormatBool(cfg.debug) builderVars["packer_force"] = strconv.FormatBool(cfg.force) builderVars["packer_on_error"] = cfg.onError generatedVars, warning, err := builder.Prepare(builderVars, decoded) moreDiags = warningErrorsToDiags(cfg.Sources[source.SourceRef].block, warning, err) diags = append(diags, moreDiags...) return builder, diags, generatedVars } // These variables will populate the PackerConfig inside of the builders. func (source *SourceUseBlock) builderVariables() map[string]string { return map[string]string{ "packer_build_name": source.Name, "packer_builder_type": source.Type, } } func (source *SourceBlock) Ref() SourceRef { return SourceRef{ Type: source.Type, Name: source.Name, } } // SourceRef is a nice way to put `virtualbox-iso.source_name` type SourceRef struct { // Type of the source, for example `virtualbox-iso` Type string // Name of the source, for example `source_name` Name string // No other field should be added to the SourceRef because we used that // struct as a map accessor in many places. } // NoSource is the zero value of sourceRef, representing the absense of an // source. var NoSource SourceRef func (r SourceRef) String() string { return fmt.Sprintf("%s.%s", r.Type, r.Name) } func listAvailableSourceNames(srcs map[SourceRef]SourceBlock) []string { res := make([]string, 0, len(srcs)) for k := range srcs { res = append(res, k.String()) } sort.Strings(res) return res }