github.com/hashicorp/packer@v1.14.3/packer/build.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package packer 5 6 import ( 7 "context" 8 "fmt" 9 "log" 10 "sync" 11 12 hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models" 13 "github.com/hashicorp/packer-plugin-sdk/common" 14 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 15 "github.com/hashicorp/packer-plugin-sdk/packerbuilderdata" 16 "github.com/hashicorp/packer/version" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 // A CoreBuild struct represents a single build job, the result of which should 21 // be a single machine image artifact. This artifact may be comprised of 22 // multiple files, of course, but it should be for only a single provider (such 23 // as VirtualBox, EC2, etc.). 24 type CoreBuild struct { 25 BuildName string 26 Type string 27 Builder packersdk.Builder 28 // BuilderConfig is the config for the builder. 29 // 30 // Is is deserialised directly from the JSON template, 31 // and is only populated for legacy JSON templates. 32 BuilderConfig interface{} 33 // HCLConfig is the HCL config for the builder 34 // 35 // Its only use is for telemetry, since we use it to extract the 36 // field names from it. 37 HCLConfig cty.Value 38 BuilderType string 39 hooks map[string][]packersdk.Hook 40 Provisioners []CoreBuildProvisioner 41 PostProcessors [][]CoreBuildPostProcessor 42 CleanupProvisioner CoreBuildProvisioner 43 TemplatePath string 44 Variables map[string]string 45 SensitiveVars []string 46 47 // Indicates whether the build is already initialized before calling Prepare(..) 48 Prepared bool 49 50 debug bool 51 force bool 52 onError string 53 l sync.Mutex 54 prepareCalled bool 55 56 SBOMs []SBOM 57 } 58 59 type SBOM struct { 60 Name string 61 Format hcpPackerModels.HashicorpCloudPacker20230101SbomFormat 62 CompressedData []byte 63 } 64 65 type BuildMetadata struct { 66 PackerVersion string 67 Plugins map[string]PluginDetails 68 SBOMs []SBOM 69 } 70 71 func (b *CoreBuild) getPluginsMetadata() map[string]PluginDetails { 72 resp := map[string]PluginDetails{} 73 74 builderPlugin, builderPluginOk := GlobalPluginsDetailsStore.GetBuilder(b.BuilderType) 75 if builderPluginOk { 76 resp[builderPlugin.Name] = builderPlugin 77 } 78 79 for _, pp := range b.PostProcessors { 80 for _, p := range pp { 81 postprocessorsPlugin, postprocessorsPluginOk := GlobalPluginsDetailsStore.GetPostProcessor(p.PType) 82 if postprocessorsPluginOk { 83 resp[postprocessorsPlugin.Name] = postprocessorsPlugin 84 } 85 } 86 } 87 88 for _, pv := range b.Provisioners { 89 provisionerPlugin, provisionerPluginOk := GlobalPluginsDetailsStore.GetProvisioner(pv.PType) 90 if provisionerPluginOk { 91 resp[provisionerPlugin.Name] = provisionerPlugin 92 } 93 } 94 95 return resp 96 } 97 98 func (b *CoreBuild) GetMetadata() BuildMetadata { 99 metadata := BuildMetadata{ 100 PackerVersion: version.FormattedVersion(), 101 Plugins: b.getPluginsMetadata(), 102 SBOMs: b.SBOMs, 103 } 104 return metadata 105 } 106 107 // CoreBuildPostProcessor Keeps track of the post-processor and the 108 // configuration of the post-processor used within a build. 109 type CoreBuildPostProcessor struct { 110 PostProcessor packersdk.PostProcessor 111 PType string 112 PName string 113 // HCLConfig is the HCL config for the post-processor 114 // 115 // Its only use is for telemetry, since we use it to extract the 116 // field names from it. 117 HCLConfig cty.Value 118 // config is JSON-specific, the configuration for the post-processor 119 // deserialised directly from the JSON template 120 config map[string]interface{} 121 KeepInputArtifact *bool 122 } 123 124 // CoreBuildProvisioner keeps track of the provisioner and the configuration of 125 // the provisioner within the build. 126 type CoreBuildProvisioner struct { 127 PType string 128 PName string 129 Provisioner packersdk.Provisioner 130 // HCLConfig is the HCL config for the provisioner 131 // 132 // Its only use is for telemetry, since we use it to extract the 133 // field names from it. 134 HCLConfig cty.Value 135 // config is JSON-specific, and is the configuration of the 136 // provisioner, with overrides 137 config []interface{} 138 } 139 140 // Returns the name of the build. 141 func (b *CoreBuild) Name() string { 142 if b.BuildName != "" { 143 return b.BuildName + "." + b.Type 144 } 145 return b.Type 146 } 147 148 // Prepare prepares the build by doing some initialization for the builder 149 // and any hooks. This _must_ be called prior to Run. The parameter is the 150 // overrides for the variables within the template (if any). 151 func (b *CoreBuild) Prepare() (warn []string, err error) { 152 // For HCL2 templates, the builder and hooks are initialized when the 153 // template is parsed. Calling Prepare(...) is not necessary 154 if b.Prepared { 155 b.prepareCalled = true 156 return 157 } 158 159 b.l.Lock() 160 defer b.l.Unlock() 161 162 if b.prepareCalled { 163 panic("prepare already called") 164 } 165 166 // Templates loaded from HCL2 will never get here. TODO: move this code into 167 // a custom json area instead of just aborting early for HCL. 168 b.prepareCalled = true 169 170 packerConfig := map[string]interface{}{ 171 common.BuildNameConfigKey: b.Type, 172 common.BuilderTypeConfigKey: b.BuilderType, 173 common.CoreVersionConfigKey: version.FormattedVersion(), 174 common.DebugConfigKey: b.debug, 175 common.ForceConfigKey: b.force, 176 common.OnErrorConfigKey: b.onError, 177 common.TemplatePathKey: b.TemplatePath, 178 common.UserVariablesConfigKey: b.Variables, 179 common.SensitiveVarsConfigKey: b.SensitiveVars, 180 } 181 182 // Prepare the builder 183 generatedVars, warn, err := b.Builder.Prepare(b.BuilderConfig, packerConfig) 184 if err != nil { 185 log.Printf("Build '%s' prepare failure: %s\n", b.Type, err) 186 return 187 } 188 189 // If the builder has provided a list of to-be-generated variables that 190 // should be made accessible to provisioners, pass that list into 191 // the provisioner prepare() so that the provisioner can appropriately 192 // validate user input against what will become available. 193 generatedPlaceholderMap := BasicPlaceholderData() 194 for _, k := range generatedVars { 195 generatedPlaceholderMap[k] = fmt.Sprintf("Build_%s. "+ 196 packerbuilderdata.PlaceholderMsg, k) 197 } 198 199 // Prepare the provisioners 200 for _, coreProv := range b.Provisioners { 201 configs := make([]interface{}, len(coreProv.config), len(coreProv.config)+1) 202 copy(configs, coreProv.config) 203 configs = append(configs, packerConfig) 204 configs = append(configs, generatedPlaceholderMap) 205 206 if err = coreProv.Provisioner.Prepare(configs...); err != nil { 207 return 208 } 209 } 210 211 // Prepare the on-error-cleanup provisioner 212 if b.CleanupProvisioner.PType != "" { 213 configs := make([]interface{}, len(b.CleanupProvisioner.config), len(b.CleanupProvisioner.config)+1) 214 copy(configs, b.CleanupProvisioner.config) 215 configs = append(configs, packerConfig) 216 configs = append(configs, generatedPlaceholderMap) 217 err = b.CleanupProvisioner.Provisioner.Prepare(configs...) 218 if err != nil { 219 return 220 } 221 } 222 223 // Prepare the post-processors 224 for _, ppSeq := range b.PostProcessors { 225 for _, corePP := range ppSeq { 226 err = corePP.PostProcessor.Configure(corePP.config, packerConfig, generatedPlaceholderMap) 227 if err != nil { 228 return 229 } 230 } 231 } 232 233 return 234 } 235 236 // Runs the actual build. Prepare must be called prior to running this. 237 func (b *CoreBuild) Run(ctx context.Context, originalUi packersdk.Ui) ([]packersdk.Artifact, error) { 238 if !b.prepareCalled { 239 panic("Prepare must be called first") 240 } 241 242 // Copy the hooks 243 hooks := make(map[string][]packersdk.Hook) 244 for hookName, hookList := range b.hooks { 245 hooks[hookName] = make([]packersdk.Hook, len(hookList)) 246 copy(hooks[hookName], hookList) 247 } 248 249 // Add a hook for the provisioners if we have provisioners 250 if len(b.Provisioners) > 0 { 251 hookedProvisioners := make([]*HookedProvisioner, len(b.Provisioners)) 252 for i, p := range b.Provisioners { 253 var pConfig interface{} 254 if len(p.config) > 0 { 255 pConfig = p.config[0] 256 } else { 257 pConfig = p.HCLConfig 258 } 259 if b.debug { 260 hookedProvisioners[i] = &HookedProvisioner{ 261 &DebuggedProvisioner{Provisioner: p.Provisioner}, 262 pConfig, 263 p.PType, 264 } 265 } else { 266 hookedProvisioners[i] = &HookedProvisioner{ 267 p.Provisioner, 268 pConfig, 269 p.PType, 270 } 271 } 272 } 273 274 if _, ok := hooks[packersdk.HookProvision]; !ok { 275 hooks[packersdk.HookProvision] = make([]packersdk.Hook, 0, 1) 276 } 277 278 hooks[packersdk.HookProvision] = append(hooks[packersdk.HookProvision], &ProvisionHook{ 279 Provisioners: hookedProvisioners, 280 }) 281 } 282 283 if b.CleanupProvisioner.PType != "" { 284 hookedCleanupProvisioner := &HookedProvisioner{ 285 b.CleanupProvisioner.Provisioner, 286 b.CleanupProvisioner.config, 287 b.CleanupProvisioner.PType, 288 } 289 hooks[packersdk.HookCleanupProvision] = []packersdk.Hook{&ProvisionHook{ 290 Provisioners: []*HookedProvisioner{hookedCleanupProvisioner}, 291 }} 292 } 293 294 hook := &packersdk.DispatchHook{Mapping: hooks} 295 artifacts := make([]packersdk.Artifact, 0, 1) 296 297 // The builder just has a normal Ui, but targeted 298 builderUi := &TargetedUI{ 299 Target: b.Name(), 300 Ui: originalUi, 301 } 302 303 var ts *TelemetrySpan 304 log.Printf("Running builder: %s", b.BuilderType) 305 if b.BuilderConfig != nil { 306 ts = CheckpointReporter.AddSpan(b.Type, "builder", b.BuilderConfig) 307 } else { 308 ts = CheckpointReporter.AddSpan(b.Type, "builder", b.HCLConfig) 309 } 310 builderArtifact, err := b.Builder.Run(ctx, builderUi, hook) 311 ts.End(err) 312 if err != nil { 313 return nil, err 314 } 315 316 for _, p := range b.Provisioners { 317 sbomInternalProvisioner, ok := p.Provisioner.(*SBOMInternalProvisioner) 318 if ok { 319 sbom := SBOM{ 320 Name: sbomInternalProvisioner.SBOMName, 321 Format: sbomInternalProvisioner.SBOMFormat, 322 CompressedData: sbomInternalProvisioner.CompressedData, 323 } 324 b.SBOMs = append(b.SBOMs, sbom) 325 } 326 } 327 328 // If there was no result, don't worry about running post-processors 329 // because there is nothing they can do, just return. 330 if builderArtifact == nil { 331 return nil, nil 332 } 333 334 errors := make([]error, 0) 335 keepOriginalArtifact := len(b.PostProcessors) == 0 336 337 select { 338 case <-ctx.Done(): 339 log.Println("Build was cancelled. Skipping post-processors.") 340 return nil, ctx.Err() 341 default: 342 } 343 344 // Run the post-processors 345 PostProcessorRunSeqLoop: 346 for _, ppSeq := range b.PostProcessors { 347 priorArtifact := builderArtifact 348 for i, corePP := range ppSeq { 349 ppUi := &TargetedUI{ 350 Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.PType), 351 Ui: originalUi, 352 } 353 354 if corePP.PName == corePP.PType { 355 builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.PType)) 356 } else { 357 builderUi.Say(fmt.Sprintf("Running post-processor: %s (type %s)", corePP.PName, corePP.PType)) 358 } 359 var ts *TelemetrySpan 360 if corePP.config != nil { 361 ts = CheckpointReporter.AddSpan(corePP.PType, "post-processor", corePP.config) 362 } else { 363 ts = CheckpointReporter.AddSpan(corePP.PType, "post-processor", corePP.HCLConfig) 364 } 365 artifact, defaultKeep, forceOverride, err := corePP.PostProcessor.PostProcess(ctx, ppUi, priorArtifact) 366 ts.End(err) 367 if err != nil { 368 errors = append(errors, fmt.Errorf("Post-processor failed: %s", err)) 369 continue PostProcessorRunSeqLoop 370 } 371 372 if artifact == nil { 373 log.Println("Nil artifact, halting post-processor chain.") 374 continue PostProcessorRunSeqLoop 375 } 376 377 keep := defaultKeep 378 // When user has not set keep_input_artifact 379 // corePP.keepInputArtifact is nil. 380 // In this case, use the keepDefault provided by the postprocessor. 381 // When user _has_ set keep_input_artifact, go with that instead. 382 // Exception: for postprocessors that will fail/become 383 // useless if keep isn't true, heed forceOverride and keep the 384 // input artifact regardless of user preference. 385 if corePP.KeepInputArtifact != nil { 386 if defaultKeep && *corePP.KeepInputArtifact == false && forceOverride { 387 log.Printf("The %s post-processor forces "+ 388 "keep_input_artifact=true to preserve integrity of the"+ 389 "build chain. User-set keep_input_artifact=false will be"+ 390 "ignored.", corePP.PType) 391 } else { 392 // User overrides default. 393 keep = *corePP.KeepInputArtifact 394 } 395 } 396 if i == 0 { 397 // This is the first post-processor. We handle deleting 398 // previous artifacts a bit different because multiple 399 // post-processors may be using the original and need it. 400 if !keepOriginalArtifact && keep { 401 log.Printf( 402 "Flagging to keep original artifact from post-processor '%s'", 403 corePP.PType) 404 keepOriginalArtifact = true 405 } 406 } else { 407 // We have a prior artifact. If we want to keep it, we append 408 // it to the results list. Otherwise, we destroy it. 409 if keep { 410 artifacts = append(artifacts, priorArtifact) 411 } else { 412 log.Printf("Deleting prior artifact from post-processor '%s'", corePP.PType) 413 if err := priorArtifact.Destroy(); err != nil { 414 log.Printf("Error is %#v", err) 415 errors = append(errors, fmt.Errorf("Failed cleaning up prior artifact: %s; pp is %s", err, corePP.PType)) 416 } 417 } 418 } 419 420 priorArtifact = artifact 421 } 422 423 // Add on the last artifact to the results 424 if priorArtifact != nil { 425 artifacts = append(artifacts, priorArtifact) 426 } 427 } 428 429 if keepOriginalArtifact { 430 artifacts = append(artifacts, nil) 431 copy(artifacts[1:], artifacts) 432 artifacts[0] = builderArtifact 433 } else { 434 log.Printf("Deleting original artifact for build '%s'", b.Type) 435 if err := builderArtifact.Destroy(); err != nil { 436 errors = append(errors, fmt.Errorf("Error destroying builder artifact: %s; bad artifact: %#v", err, builderArtifact.Files())) 437 } 438 } 439 440 if len(errors) > 0 { 441 err = &packersdk.MultiError{Errors: errors} 442 return artifacts, err 443 } 444 445 return artifacts, nil 446 } 447 448 func (b *CoreBuild) SetDebug(val bool) { 449 if b.prepareCalled { 450 panic("prepare has already been called") 451 } 452 453 b.debug = val 454 } 455 456 func (b *CoreBuild) SetForce(val bool) { 457 if b.prepareCalled { 458 panic("prepare has already been called") 459 } 460 461 b.force = val 462 } 463 464 func (b *CoreBuild) SetOnError(val string) { 465 if b.prepareCalled { 466 panic("prepare has already been called") 467 } 468 469 b.onError = val 470 }