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  }