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  }