github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/provisioner.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  )
     8  
     9  // Provisioner represents a "provisioner" block when used within a
    10  // "resource" block in a module or file.
    11  type Provisioner struct {
    12  	Type       string
    13  	Config     hcl.Body
    14  	Connection *Connection
    15  	When       ProvisionerWhen
    16  	OnFailure  ProvisionerOnFailure
    17  
    18  	DeclRange hcl.Range
    19  	TypeRange hcl.Range
    20  }
    21  
    22  func decodeProvisionerBlock(block *hcl.Block) (*Provisioner, hcl.Diagnostics) {
    23  	pv := &Provisioner{
    24  		Type:      block.Labels[0],
    25  		TypeRange: block.LabelRanges[0],
    26  		DeclRange: block.DefRange,
    27  		When:      ProvisionerWhenCreate,
    28  		OnFailure: ProvisionerOnFailureFail,
    29  	}
    30  
    31  	content, config, diags := block.Body.PartialContent(provisionerBlockSchema)
    32  	pv.Config = config
    33  
    34  	if attr, exists := content.Attributes["when"]; exists {
    35  		expr, shimDiags := shimTraversalInString(attr.Expr, true)
    36  		diags = append(diags, shimDiags...)
    37  
    38  		switch hcl.ExprAsKeyword(expr) {
    39  		case "create":
    40  			pv.When = ProvisionerWhenCreate
    41  		case "destroy":
    42  			pv.When = ProvisionerWhenDestroy
    43  		default:
    44  			diags = append(diags, &hcl.Diagnostic{
    45  				Severity: hcl.DiagError,
    46  				Summary:  "Invalid \"when\" keyword",
    47  				Detail:   "The \"when\" argument requires one of the following keywords: create or destroy.",
    48  				Subject:  expr.Range().Ptr(),
    49  			})
    50  		}
    51  	}
    52  
    53  	// destroy provisioners can only refer to self
    54  	if pv.When == ProvisionerWhenDestroy {
    55  		diags = append(diags, onlySelfRefs(config)...)
    56  	}
    57  
    58  	if attr, exists := content.Attributes["on_failure"]; exists {
    59  		expr, shimDiags := shimTraversalInString(attr.Expr, true)
    60  		diags = append(diags, shimDiags...)
    61  
    62  		switch hcl.ExprAsKeyword(expr) {
    63  		case "continue":
    64  			pv.OnFailure = ProvisionerOnFailureContinue
    65  		case "fail":
    66  			pv.OnFailure = ProvisionerOnFailureFail
    67  		default:
    68  			diags = append(diags, &hcl.Diagnostic{
    69  				Severity: hcl.DiagError,
    70  				Summary:  "Invalid \"on_failure\" keyword",
    71  				Detail:   "The \"on_failure\" argument requires one of the following keywords: continue or fail.",
    72  				Subject:  attr.Expr.Range().Ptr(),
    73  			})
    74  		}
    75  	}
    76  
    77  	var seenConnection *hcl.Block
    78  	for _, block := range content.Blocks {
    79  		switch block.Type {
    80  
    81  		case "connection":
    82  			if seenConnection != nil {
    83  				diags = append(diags, &hcl.Diagnostic{
    84  					Severity: hcl.DiagError,
    85  					Summary:  "Duplicate connection block",
    86  					Detail:   fmt.Sprintf("This provisioner already has a connection block at %s.", seenConnection.DefRange),
    87  					Subject:  &block.DefRange,
    88  				})
    89  				continue
    90  			}
    91  			seenConnection = block
    92  
    93  			// destroy provisioners can only refer to self
    94  			if pv.When == ProvisionerWhenDestroy {
    95  				diags = append(diags, onlySelfRefs(block.Body)...)
    96  			}
    97  
    98  			pv.Connection = &Connection{
    99  				Config:    block.Body,
   100  				DeclRange: block.DefRange,
   101  			}
   102  
   103  		default:
   104  			// Any other block types are ones we've reserved for future use,
   105  			// so they get a generic message.
   106  			diags = append(diags, &hcl.Diagnostic{
   107  				Severity: hcl.DiagError,
   108  				Summary:  "Reserved block type name in provisioner block",
   109  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   110  				Subject:  &block.TypeRange,
   111  			})
   112  		}
   113  	}
   114  
   115  	return pv, diags
   116  }
   117  
   118  func onlySelfRefs(body hcl.Body) hcl.Diagnostics {
   119  	var diags hcl.Diagnostics
   120  
   121  	// Provisioners currently do not use any blocks in their configuration.
   122  	// Blocks are likely to remain solely for meta parameters, but in the case
   123  	// that blocks are supported for provisioners, we will want to extend this
   124  	// to find variables in nested blocks.
   125  	attrs, _ := body.JustAttributes()
   126  	for _, attr := range attrs {
   127  		for _, v := range attr.Expr.Variables() {
   128  			valid := false
   129  			switch v.RootName() {
   130  			case "self", "path", "terraform":
   131  				valid = true
   132  			case "count":
   133  				// count must use "index"
   134  				if len(v) == 2 {
   135  					if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "index" {
   136  						valid = true
   137  					}
   138  				}
   139  
   140  			case "each":
   141  				if len(v) == 2 {
   142  					if t, ok := v[1].(hcl.TraverseAttr); ok && t.Name == "key" {
   143  						valid = true
   144  					}
   145  				}
   146  			}
   147  
   148  			if !valid {
   149  				diags = append(diags, &hcl.Diagnostic{
   150  					Severity: hcl.DiagWarning,
   151  					Summary:  "External references from destroy provisioners are deprecated",
   152  					Detail: "Destroy-time provisioners and their connection configurations may only " +
   153  						"reference attributes of the related resource, via 'self', 'count.index', " +
   154  						"or 'each.key'.\n\nReferences to other resources during the destroy phase " +
   155  						"can cause dependency cycles and interact poorly with create_before_destroy.",
   156  					Subject: attr.Expr.Range().Ptr(),
   157  				})
   158  			}
   159  		}
   160  	}
   161  	return diags
   162  }
   163  
   164  // Connection represents a "connection" block when used within either a
   165  // "resource" or "provisioner" block in a module or file.
   166  type Connection struct {
   167  	Config hcl.Body
   168  
   169  	DeclRange hcl.Range
   170  }
   171  
   172  // ProvisionerWhen is an enum for valid values for when to run provisioners.
   173  type ProvisionerWhen int
   174  
   175  //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerWhen
   176  
   177  const (
   178  	ProvisionerWhenInvalid ProvisionerWhen = iota
   179  	ProvisionerWhenCreate
   180  	ProvisionerWhenDestroy
   181  )
   182  
   183  // ProvisionerOnFailure is an enum for valid values for on_failure options
   184  // for provisioners.
   185  type ProvisionerOnFailure int
   186  
   187  //go:generate go run golang.org/x/tools/cmd/stringer -type ProvisionerOnFailure
   188  
   189  const (
   190  	ProvisionerOnFailureInvalid ProvisionerOnFailure = iota
   191  	ProvisionerOnFailureContinue
   192  	ProvisionerOnFailureFail
   193  )
   194  
   195  var provisionerBlockSchema = &hcl.BodySchema{
   196  	Attributes: []hcl.AttributeSchema{
   197  		{Name: "when"},
   198  		{Name: "on_failure"},
   199  	},
   200  	Blocks: []hcl.BlockHeaderSchema{
   201  		{Type: "connection"},
   202  		{Type: "lifecycle"}, // reserved for future use
   203  	},
   204  }