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 }