github.com/hashicorp/packer@v1.14.3/hcl2template/types.source.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hcl2template
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/hashicorp/hcl/v2/gohcl"
    13  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    14  	hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  // SourceBlock references an HCL 'source' block to be used in a build for
    19  // example.
    20  type SourceBlock struct {
    21  	// Type of source; ex: virtualbox-iso
    22  	Type string
    23  	// Given name; if any
    24  	Name string
    25  
    26  	block *hcl.Block
    27  
    28  	// LocalName can be set in a singular source block from a build block, it
    29  	// allows to give a special name to a build in the logs.
    30  	LocalName string
    31  }
    32  
    33  // SourceUseBlock is a SourceBlock 'usage' from a config stand point.
    34  // For example when one uses `build.sources = ["..."]` or
    35  // `build.source "..." {...}`.
    36  type SourceUseBlock struct {
    37  	// reference to an actual source block definition, or SourceBlock.
    38  	SourceRef
    39  
    40  	// LocalName can be set in a singular source block from a build block, it
    41  	// allows to give a special name to a build in the logs.
    42  	LocalName string
    43  
    44  	// Rest of the body, in case the build.source block has more specific
    45  	// content
    46  	// Body can be expanded by a dynamic tag.
    47  	Body hcl.Body
    48  }
    49  
    50  func (b *SourceUseBlock) name() string {
    51  	if b.LocalName != "" {
    52  		return b.LocalName
    53  	}
    54  	return b.Name
    55  }
    56  
    57  func (b *SourceUseBlock) String() string {
    58  	return fmt.Sprintf("%s.%s", b.Type, b.name())
    59  }
    60  
    61  // EvalContext adds the values of the source to the passed eval context.
    62  func (b *SourceUseBlock) ctyValues() map[string]cty.Value {
    63  	return map[string]cty.Value{
    64  		"type": cty.StringVal(b.Type),
    65  		"name": cty.StringVal(b.name()),
    66  	}
    67  }
    68  
    69  // decodeBuildSource reads a used source block from a build:
    70  //
    71  //	build {
    72  //	  source "type.example" {
    73  //	    name = "local_name"
    74  //	  }
    75  //	}
    76  func (p *Parser) decodeBuildSource(block *hcl.Block) (SourceUseBlock, hcl.Diagnostics) {
    77  	ref := sourceRefFromString(block.Labels[0])
    78  	out := SourceUseBlock{SourceRef: ref}
    79  	var b struct {
    80  		Name string   `hcl:"name,optional"`
    81  		Rest hcl.Body `hcl:",remain"`
    82  	}
    83  	diags := gohcl.DecodeBody(block.Body, nil, &b)
    84  	if diags.HasErrors() {
    85  		return out, diags
    86  	}
    87  	out.LocalName = b.Name
    88  	out.Body = b.Rest
    89  	return out, nil
    90  }
    91  
    92  func (p *Parser) decodeSource(block *hcl.Block) (SourceBlock, hcl.Diagnostics) {
    93  	source := SourceBlock{
    94  		Type:  block.Labels[0],
    95  		Name:  block.Labels[1],
    96  		block: block,
    97  	}
    98  	var diags hcl.Diagnostics
    99  
   100  	return source, diags
   101  }
   102  
   103  func (cfg *PackerConfig) startBuilder(source SourceUseBlock, ectx *hcl.EvalContext) (packersdk.Builder, hcl.Diagnostics, []string) {
   104  	var diags hcl.Diagnostics
   105  
   106  	builder, err := cfg.parser.PluginConfig.Builders.Start(source.Type)
   107  	if err != nil {
   108  		diags = append(diags, &hcl.Diagnostic{
   109  			Severity: hcl.DiagError,
   110  			Summary:  "Failed to load " + sourceLabel + " type",
   111  			Detail:   err.Error(),
   112  		})
   113  		return builder, diags, nil
   114  	}
   115  
   116  	body := source.Body
   117  	// Add known values to source accessor in eval context.
   118  	ectx.Variables[sourcesAccessor] = cty.ObjectVal(source.ctyValues())
   119  
   120  	decoded, moreDiags := decodeHCL2Spec(body, ectx, builder)
   121  	diags = append(diags, moreDiags...)
   122  	if moreDiags.HasErrors() {
   123  		return builder, diags, nil
   124  	}
   125  
   126  	// In case of cty.Unknown values, this will write a equivalent placeholder of the same type
   127  	// Unknown types are not recognized by the json marshal during the RPC call and we have to do this here
   128  	// to avoid json parsing failures when running the validate command.
   129  	// We don't do this before so we can validate if variable types matches correctly on decodeHCL2Spec.
   130  	decoded = hcl2shim.WriteUnknownPlaceholderValues(decoded)
   131  
   132  	// Note: HCL prepares inside of the Start func, but Json does not. Json
   133  	// builds are instead prepared only in command/build.go
   134  	// TODO: either make json prepare when plugins are loaded, or make HCL
   135  	// prepare at a later step, to make builds from different template types
   136  	// easier to reason about.
   137  	builderVars := source.builderVariables()
   138  	builderVars["packer_core_version"] = cfg.CorePackerVersionString
   139  	builderVars["packer_debug"] = strconv.FormatBool(cfg.debug)
   140  	builderVars["packer_force"] = strconv.FormatBool(cfg.force)
   141  	builderVars["packer_on_error"] = cfg.onError
   142  
   143  	generatedVars, warning, err := builder.Prepare(builderVars, decoded)
   144  	moreDiags = warningErrorsToDiags(cfg.Sources[source.SourceRef].block, warning, err)
   145  	diags = append(diags, moreDiags...)
   146  	return builder, diags, generatedVars
   147  }
   148  
   149  // These variables will populate the PackerConfig inside of the builders.
   150  func (source *SourceUseBlock) builderVariables() map[string]interface{} {
   151  	return map[string]interface{}{
   152  		"packer_build_name":   source.Name,
   153  		"packer_builder_type": source.Type,
   154  	}
   155  }
   156  
   157  func (source *SourceBlock) Ref() SourceRef {
   158  	return SourceRef{
   159  		Type: source.Type,
   160  		Name: source.Name,
   161  	}
   162  }
   163  
   164  // SourceRef is a nice way to put `virtualbox-iso.source_name`
   165  type SourceRef struct {
   166  	// Type of the source, for example `virtualbox-iso`
   167  	Type string
   168  	// Name of the source, for example `source_name`
   169  	Name string
   170  
   171  	// No other field should be added to the SourceRef because we used that
   172  	// struct as a map accessor in many places.
   173  }
   174  
   175  // NoSource is the zero value of sourceRef, representing the absense of an
   176  // source.
   177  var NoSource SourceRef
   178  
   179  func (r SourceRef) String() string {
   180  	return fmt.Sprintf("%s.%s", r.Type, r.Name)
   181  }
   182  
   183  func listAvailableSourceNames(srcs map[SourceRef]SourceBlock) []string {
   184  	res := make([]string, 0, len(srcs))
   185  	for k := range srcs {
   186  		res = append(res, k.String())
   187  	}
   188  	sort.Strings(res)
   189  	return res
   190  }