github.com/hashicorp/packer@v1.14.3/hcl2template/types.build.provisioners.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hcl2template
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    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  // OnlyExcept is a struct that is meant to be embedded that contains the
    19  // logic required for "only" and "except" meta-parameters.
    20  type OnlyExcept struct {
    21  	Only   []string `json:"only,omitempty"`
    22  	Except []string `json:"except,omitempty"`
    23  }
    24  
    25  // Skip says whether or not to skip the build with the given name.
    26  func (o *OnlyExcept) Skip(n string) bool {
    27  	if len(o.Only) > 0 {
    28  		for _, v := range o.Only {
    29  			if v == n {
    30  				return false
    31  			}
    32  		}
    33  
    34  		return true
    35  	}
    36  
    37  	if len(o.Except) > 0 {
    38  		for _, v := range o.Except {
    39  			if v == n {
    40  				return true
    41  			}
    42  		}
    43  
    44  		return false
    45  	}
    46  
    47  	return false
    48  }
    49  
    50  // Validate validates that the OnlyExcept settings are correct for a thing.
    51  func (o *OnlyExcept) Validate() hcl.Diagnostics {
    52  	var diags hcl.Diagnostics
    53  
    54  	if len(o.Only) > 0 && len(o.Except) > 0 {
    55  		diags = diags.Append(&hcl.Diagnostic{
    56  			Summary:  "only one of 'only' or 'except' may be specified",
    57  			Severity: hcl.DiagError,
    58  		})
    59  	}
    60  
    61  	return diags
    62  }
    63  
    64  // ProvisionerBlock references a detected but unparsed provisioner
    65  type ProvisionerBlock struct {
    66  	PType       string
    67  	PName       string
    68  	PauseBefore time.Duration
    69  	MaxRetries  int
    70  	Timeout     time.Duration
    71  	Override    map[string]interface{}
    72  	OnlyExcept  OnlyExcept
    73  	HCL2Ref
    74  }
    75  
    76  func (p *ProvisionerBlock) String() string {
    77  	return fmt.Sprintf(buildProvisionerLabel+"-block %q %q", p.PType, p.PName)
    78  }
    79  
    80  func (p *Parser) decodeProvisioner(block *hcl.Block, ectx *hcl.EvalContext) (*ProvisionerBlock, hcl.Diagnostics) {
    81  	var b struct {
    82  		Name        string    `hcl:"name,optional"`
    83  		PauseBefore string    `hcl:"pause_before,optional"`
    84  		MaxRetries  int       `hcl:"max_retries,optional"`
    85  		Timeout     string    `hcl:"timeout,optional"`
    86  		Only        []string  `hcl:"only,optional"`
    87  		Except      []string  `hcl:"except,optional"`
    88  		Override    cty.Value `hcl:"override,optional"`
    89  		Rest        hcl.Body  `hcl:",remain"`
    90  	}
    91  	diags := gohcl.DecodeBody(block.Body, ectx, &b)
    92  	if diags.HasErrors() {
    93  		return nil, diags
    94  	}
    95  
    96  	provisioner := &ProvisionerBlock{
    97  		PType:      block.Labels[0],
    98  		PName:      b.Name,
    99  		MaxRetries: b.MaxRetries,
   100  		OnlyExcept: OnlyExcept{Only: b.Only, Except: b.Except},
   101  		HCL2Ref:    newHCL2Ref(block, b.Rest),
   102  	}
   103  
   104  	diags = diags.Extend(provisioner.OnlyExcept.Validate())
   105  	if diags.HasErrors() {
   106  		return nil, diags
   107  	}
   108  
   109  	if !b.Override.IsNull() {
   110  		if !b.Override.Type().IsObjectType() {
   111  			return nil, append(diags, &hcl.Diagnostic{
   112  				Severity: hcl.DiagError,
   113  				Summary:  "provisioner's override block must be an HCL object",
   114  				Subject:  block.DefRange.Ptr(),
   115  			})
   116  		}
   117  
   118  		override := make(map[string]interface{})
   119  		for buildName, overrides := range b.Override.AsValueMap() {
   120  			buildOverrides := make(map[string]interface{})
   121  
   122  			if !overrides.Type().IsObjectType() {
   123  				return nil, append(diags, &hcl.Diagnostic{
   124  					Severity: hcl.DiagError,
   125  					Summary: fmt.Sprintf(
   126  						"provisioner's override.'%s' block must be an HCL object",
   127  						buildName),
   128  					Subject: block.DefRange.Ptr(),
   129  				})
   130  			}
   131  
   132  			for option, value := range overrides.AsValueMap() {
   133  				buildOverrides[option] = hcl2shim.ConfigValueFromHCL2(value)
   134  			}
   135  			override[buildName] = buildOverrides
   136  		}
   137  		provisioner.Override = override
   138  	}
   139  
   140  	if b.PauseBefore != "" {
   141  		pauseBefore, err := time.ParseDuration(b.PauseBefore)
   142  		if err != nil {
   143  			return nil, append(diags, &hcl.Diagnostic{
   144  				Summary:  "Failed to parse pause_before duration",
   145  				Severity: hcl.DiagError,
   146  				Detail:   err.Error(),
   147  				Subject:  &block.DefRange,
   148  			})
   149  		}
   150  		provisioner.PauseBefore = pauseBefore
   151  	}
   152  
   153  	if b.Timeout != "" {
   154  		timeout, err := time.ParseDuration(b.Timeout)
   155  		if err != nil {
   156  			return nil, append(diags, &hcl.Diagnostic{
   157  				Summary:  "Failed to parse timeout duration",
   158  				Severity: hcl.DiagError,
   159  				Detail:   err.Error(),
   160  				Subject:  &block.DefRange,
   161  			})
   162  		}
   163  		provisioner.Timeout = timeout
   164  	}
   165  
   166  	return provisioner, diags
   167  }
   168  
   169  func (cfg *PackerConfig) startProvisioner(source SourceUseBlock, pb *ProvisionerBlock, ectx *hcl.EvalContext) (packersdk.Provisioner, hcl.Diagnostics) {
   170  	var diags hcl.Diagnostics
   171  
   172  	provisioner, err := cfg.parser.PluginConfig.Provisioners.Start(pb.PType)
   173  	if err != nil {
   174  		diags = append(diags, &hcl.Diagnostic{
   175  			Severity: hcl.DiagError,
   176  			Summary:  fmt.Sprintf("failed loading %s", pb.PType),
   177  			Subject:  pb.HCL2Ref.LabelsRanges[0].Ptr(),
   178  			Detail:   err.Error(),
   179  		})
   180  		return nil, diags
   181  	}
   182  
   183  	builderVars := source.builderVariables()
   184  	builderVars["packer_core_version"] = cfg.CorePackerVersionString
   185  	builderVars["packer_debug"] = strconv.FormatBool(cfg.debug)
   186  	builderVars["packer_force"] = strconv.FormatBool(cfg.force)
   187  	builderVars["packer_on_error"] = cfg.onError
   188  
   189  	sensitiveVars := make([]string, 0, len(cfg.InputVariables))
   190  
   191  	for key, variable := range cfg.InputVariables {
   192  		if variable.Sensitive {
   193  			sensitiveVars = append(sensitiveVars, key)
   194  		}
   195  	}
   196  
   197  	builderVars["packer_sensitive_variables"] = sensitiveVars
   198  
   199  	hclProvisioner := &HCL2Provisioner{
   200  		Provisioner:      provisioner,
   201  		provisionerBlock: pb,
   202  		evalContext:      ectx,
   203  		builderVariables: builderVars,
   204  	}
   205  
   206  	if pb.Override != nil {
   207  		if override, ok := pb.Override[source.name()]; ok {
   208  			hclProvisioner.override = override.(map[string]interface{})
   209  		}
   210  	}
   211  
   212  	err = hclProvisioner.HCL2Prepare(nil)
   213  	if err != nil {
   214  		diags = append(diags, &hcl.Diagnostic{
   215  			Severity: hcl.DiagError,
   216  			Summary:  fmt.Sprintf("Failed preparing %s", pb),
   217  			Detail:   err.Error(),
   218  			Subject:  pb.HCL2Ref.DefRange.Ptr(),
   219  		})
   220  		return nil, diags
   221  	}
   222  	return hclProvisioner, diags
   223  }