github.com/hashicorp/packer@v1.14.3/hcl2template/types.build.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package hcl2template 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/gohcl" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 const ( 16 buildFromLabel = "from" 17 18 buildSourceLabel = "source" 19 20 buildProvisionerLabel = "provisioner" 21 22 buildErrorCleanupProvisionerLabel = "error-cleanup-provisioner" 23 24 buildPostProcessorLabel = "post-processor" 25 26 buildPostProcessorsLabel = "post-processors" 27 28 buildHCPPackerRegistryLabel = "hcp_packer_registry" 29 ) 30 31 var buildSchema = &hcl.BodySchema{ 32 Blocks: []hcl.BlockHeaderSchema{ 33 {Type: buildFromLabel, LabelNames: []string{"type"}}, 34 {Type: sourceLabel, LabelNames: []string{"reference"}}, 35 {Type: buildProvisionerLabel, LabelNames: []string{"type"}}, 36 {Type: buildErrorCleanupProvisionerLabel, LabelNames: []string{"type"}}, 37 {Type: buildPostProcessorLabel, LabelNames: []string{"type"}}, 38 {Type: buildPostProcessorsLabel, LabelNames: []string{}}, 39 {Type: buildHCPPackerRegistryLabel}, 40 }, 41 } 42 43 var postProcessorsSchema = &hcl.BodySchema{ 44 Blocks: []hcl.BlockHeaderSchema{ 45 {Type: buildPostProcessorLabel, LabelNames: []string{"type"}}, 46 }, 47 } 48 49 // BuildBlock references an HCL 'build' block and it content, for example : 50 // 51 // build { 52 // sources = [ 53 // ... 54 // ] 55 // provisioner "" { ... } 56 // post-processor "" { ... } 57 // } 58 type BuildBlock struct { 59 // Name is a string representing the named build to show in the logs 60 Name string 61 62 // A description of what this build does, it could be used in a inspect 63 // call for example. 64 Description string 65 66 // HCPPackerRegistry contains the configuration for publishing the image to the HCP Packer Registry. 67 HCPPackerRegistry *HCPPackerRegistryBlock 68 69 // Sources is the list of sources that we want to start in this build block. 70 Sources []SourceUseBlock 71 72 // ProvisionerBlocks references a list of HCL provisioner block that will 73 // will be ran against the sources. 74 ProvisionerBlocks []*ProvisionerBlock 75 76 // ErrorCleanupProvisionerBlock references a special provisioner block that 77 // will be ran only if the provision step fails. 78 ErrorCleanupProvisionerBlock *ProvisionerBlock 79 80 // PostProcessorLists references the lists of lists of HCL post-processors 81 // block that will be run against the artifacts from the provisioning 82 // steps. 83 PostProcessorsLists [][]*PostProcessorBlock 84 85 HCL2Ref HCL2Ref 86 } 87 88 type Builds []*BuildBlock 89 90 // decodeBuildConfig is called when a 'build' block has been detected. It will 91 // load the references to the contents of the build block. 92 func (p *Parser) decodeBuildConfig(block *hcl.Block, cfg *PackerConfig) (*BuildBlock, hcl.Diagnostics) { 93 var b struct { 94 Name string `hcl:"name,optional"` 95 Description string `hcl:"description,optional"` 96 FromSources []string `hcl:"sources,optional"` 97 Config hcl.Body `hcl:",remain"` 98 } 99 100 body := block.Body 101 diags := gohcl.DecodeBody(body, cfg.EvalContext(LocalContext, nil), &b) 102 if diags.HasErrors() { 103 return nil, diags 104 } 105 106 build := &BuildBlock{ 107 HCL2Ref: newHCL2Ref(block, b.Config), 108 } 109 110 build.Name = b.Name 111 build.Description = b.Description 112 build.HCL2Ref.DefRange = block.DefRange 113 114 // Expose build.name during parsing of pps and provisioners 115 ectx := cfg.EvalContext(BuildContext, nil) 116 ectx.Variables[buildAccessor] = cty.ObjectVal(map[string]cty.Value{ 117 "name": cty.StringVal(b.Name), 118 }) 119 120 // We rely on `hadSource` to determine which error to proc. 121 // 122 // If a source block is referenced in the build block, but isn't valid, we 123 // cannot rely on the `build.Sources' since it's only populated when a valid 124 // source is processed. 125 hadSource := false 126 127 for _, buildFrom := range b.FromSources { 128 hadSource = true 129 130 ref := sourceRefFromString(buildFrom) 131 132 if ref == NoSource || 133 !hclsyntax.ValidIdentifier(ref.Type) || 134 !hclsyntax.ValidIdentifier(ref.Name) { 135 diags = append(diags, &hcl.Diagnostic{ 136 Severity: hcl.DiagError, 137 Summary: "Invalid " + sourceLabel + " reference", 138 Detail: "A " + sourceLabel + " type is made of three parts that are " + 139 "split by a dot `.`; each part must start with a letter and " + 140 "may contain only letters, digits, underscores, and dashes. " + 141 "A valid source reference looks like: `source.type.name`", 142 Subject: block.DefRange.Ptr(), 143 }) 144 continue 145 } 146 147 // source with no body 148 build.Sources = append(build.Sources, SourceUseBlock{SourceRef: ref}) 149 } 150 151 body = b.Config 152 content, moreDiags := body.Content(buildSchema) 153 diags = append(diags, moreDiags...) 154 if diags.HasErrors() { 155 return nil, diags 156 } 157 for _, block := range content.Blocks { 158 switch block.Type { 159 case buildHCPPackerRegistryLabel: 160 if build.HCPPackerRegistry != nil { 161 diags = append(diags, &hcl.Diagnostic{ 162 Severity: hcl.DiagError, 163 Summary: fmt.Sprintf("Only one " + buildHCPPackerRegistryLabel + " is allowed"), 164 Subject: block.DefRange.Ptr(), 165 }) 166 continue 167 } 168 hcpPackerRegistry, moreDiags := p.decodeHCPRegistry(block, cfg) 169 diags = append(diags, moreDiags...) 170 if moreDiags.HasErrors() { 171 continue 172 } 173 build.HCPPackerRegistry = hcpPackerRegistry 174 case sourceLabel: 175 hadSource = true 176 ref, moreDiags := p.decodeBuildSource(block) 177 diags = append(diags, moreDiags...) 178 if moreDiags.HasErrors() { 179 continue 180 } 181 build.Sources = append(build.Sources, ref) 182 case buildProvisionerLabel: 183 p, moreDiags := p.decodeProvisioner(block, ectx) 184 diags = append(diags, moreDiags...) 185 if moreDiags.HasErrors() { 186 continue 187 } 188 build.ProvisionerBlocks = append(build.ProvisionerBlocks, p) 189 case buildErrorCleanupProvisionerLabel: 190 if build.ErrorCleanupProvisionerBlock != nil { 191 diags = append(diags, &hcl.Diagnostic{ 192 Severity: hcl.DiagError, 193 Summary: fmt.Sprintf("Only one " + buildErrorCleanupProvisionerLabel + " is allowed"), 194 Subject: block.DefRange.Ptr(), 195 }) 196 continue 197 } 198 p, moreDiags := p.decodeProvisioner(block, ectx) 199 diags = append(diags, moreDiags...) 200 if moreDiags.HasErrors() { 201 continue 202 } 203 build.ErrorCleanupProvisionerBlock = p 204 case buildPostProcessorLabel: 205 pp, moreDiags := p.decodePostProcessor(block, ectx) 206 diags = append(diags, moreDiags...) 207 if moreDiags.HasErrors() { 208 continue 209 } 210 build.PostProcessorsLists = append(build.PostProcessorsLists, []*PostProcessorBlock{pp}) 211 case buildPostProcessorsLabel: 212 213 content, moreDiags := block.Body.Content(postProcessorsSchema) 214 diags = append(diags, moreDiags...) 215 if moreDiags.HasErrors() { 216 continue 217 } 218 219 errored := false 220 postProcessors := []*PostProcessorBlock{} 221 for _, block := range content.Blocks { 222 pp, moreDiags := p.decodePostProcessor(block, ectx) 223 diags = append(diags, moreDiags...) 224 if moreDiags.HasErrors() { 225 errored = true 226 break 227 } 228 postProcessors = append(postProcessors, pp) 229 } 230 if errored == false { 231 build.PostProcessorsLists = append(build.PostProcessorsLists, postProcessors) 232 } 233 } 234 } 235 236 if !hadSource { 237 diags = append(diags, &hcl.Diagnostic{ 238 Summary: "missing source reference", 239 Detail: "a build block must reference at least one source to be built", 240 Severity: hcl.DiagError, 241 Subject: block.DefRange.Ptr(), 242 }) 243 } 244 245 return build, diags 246 }