github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/resource.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package configs
     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  	hcljson "github.com/hashicorp/hcl/v2/json"
    13  
    14  	"github.com/terramate-io/tf/addrs"
    15  	"github.com/terramate-io/tf/lang"
    16  	"github.com/terramate-io/tf/tfdiags"
    17  )
    18  
    19  // Resource represents a "resource" or "data" block in a module or file.
    20  type Resource struct {
    21  	Mode    addrs.ResourceMode
    22  	Name    string
    23  	Type    string
    24  	Config  hcl.Body
    25  	Count   hcl.Expression
    26  	ForEach hcl.Expression
    27  
    28  	ProviderConfigRef *ProviderConfigRef
    29  	Provider          addrs.Provider
    30  
    31  	Preconditions  []*CheckRule
    32  	Postconditions []*CheckRule
    33  
    34  	DependsOn []hcl.Traversal
    35  
    36  	TriggersReplacement []hcl.Expression
    37  
    38  	// Managed is populated only for Mode = addrs.ManagedResourceMode,
    39  	// containing the additional fields that apply to managed resources.
    40  	// For all other resource modes, this field is nil.
    41  	Managed *ManagedResource
    42  
    43  	// Container links a scoped resource back up to the resources that contains
    44  	// it. This field is referenced during static analysis to check whether any
    45  	// references are also made from within the same container.
    46  	//
    47  	// If this is nil, then this resource is essentially public.
    48  	Container Container
    49  
    50  	DeclRange hcl.Range
    51  	TypeRange hcl.Range
    52  }
    53  
    54  // ManagedResource represents a "resource" block in a module or file.
    55  type ManagedResource struct {
    56  	Connection   *Connection
    57  	Provisioners []*Provisioner
    58  
    59  	CreateBeforeDestroy bool
    60  	PreventDestroy      bool
    61  	IgnoreChanges       []hcl.Traversal
    62  	IgnoreAllChanges    bool
    63  
    64  	CreateBeforeDestroySet bool
    65  	PreventDestroySet      bool
    66  }
    67  
    68  func (r *Resource) moduleUniqueKey() string {
    69  	return r.Addr().String()
    70  }
    71  
    72  // Addr returns a resource address for the receiver that is relative to the
    73  // resource's containing module.
    74  func (r *Resource) Addr() addrs.Resource {
    75  	return addrs.Resource{
    76  		Mode: r.Mode,
    77  		Type: r.Type,
    78  		Name: r.Name,
    79  	}
    80  }
    81  
    82  // ProviderConfigAddr returns the address for the provider configuration that
    83  // should be used for this resource. This function returns a default provider
    84  // config addr if an explicit "provider" argument was not provided.
    85  func (r *Resource) ProviderConfigAddr() addrs.LocalProviderConfig {
    86  	if r.ProviderConfigRef == nil {
    87  		// If no specific "provider" argument is given, we want to look up the
    88  		// provider config where the local name matches the implied provider
    89  		// from the resource type. This may be different from the resource's
    90  		// provider type.
    91  		return addrs.LocalProviderConfig{
    92  			LocalName: r.Addr().ImpliedProvider(),
    93  		}
    94  	}
    95  
    96  	return addrs.LocalProviderConfig{
    97  		LocalName: r.ProviderConfigRef.Name,
    98  		Alias:     r.ProviderConfigRef.Alias,
    99  	}
   100  }
   101  
   102  // HasCustomConditions returns true if and only if the resource has at least
   103  // one author-specified custom condition.
   104  func (r *Resource) HasCustomConditions() bool {
   105  	return len(r.Postconditions) != 0 || len(r.Preconditions) != 0
   106  }
   107  
   108  func decodeResourceBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagnostics) {
   109  	var diags hcl.Diagnostics
   110  	r := &Resource{
   111  		Mode:      addrs.ManagedResourceMode,
   112  		Type:      block.Labels[0],
   113  		Name:      block.Labels[1],
   114  		DeclRange: block.DefRange,
   115  		TypeRange: block.LabelRanges[0],
   116  		Managed:   &ManagedResource{},
   117  	}
   118  
   119  	content, remain, moreDiags := block.Body.PartialContent(ResourceBlockSchema)
   120  	diags = append(diags, moreDiags...)
   121  	r.Config = remain
   122  
   123  	if !hclsyntax.ValidIdentifier(r.Type) {
   124  		diags = append(diags, &hcl.Diagnostic{
   125  			Severity: hcl.DiagError,
   126  			Summary:  "Invalid resource type name",
   127  			Detail:   badIdentifierDetail,
   128  			Subject:  &block.LabelRanges[0],
   129  		})
   130  	}
   131  	if !hclsyntax.ValidIdentifier(r.Name) {
   132  		diags = append(diags, &hcl.Diagnostic{
   133  			Severity: hcl.DiagError,
   134  			Summary:  "Invalid resource name",
   135  			Detail:   badIdentifierDetail,
   136  			Subject:  &block.LabelRanges[1],
   137  		})
   138  	}
   139  
   140  	if attr, exists := content.Attributes["count"]; exists {
   141  		r.Count = attr.Expr
   142  	}
   143  
   144  	if attr, exists := content.Attributes["for_each"]; exists {
   145  		r.ForEach = attr.Expr
   146  		// Cannot have count and for_each on the same resource block
   147  		if r.Count != nil {
   148  			diags = append(diags, &hcl.Diagnostic{
   149  				Severity: hcl.DiagError,
   150  				Summary:  `Invalid combination of "count" and "for_each"`,
   151  				Detail:   `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`,
   152  				Subject:  &attr.NameRange,
   153  			})
   154  		}
   155  	}
   156  
   157  	if attr, exists := content.Attributes["provider"]; exists {
   158  		var providerDiags hcl.Diagnostics
   159  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
   160  		diags = append(diags, providerDiags...)
   161  	}
   162  
   163  	if attr, exists := content.Attributes["depends_on"]; exists {
   164  		deps, depsDiags := decodeDependsOn(attr)
   165  		diags = append(diags, depsDiags...)
   166  		r.DependsOn = append(r.DependsOn, deps...)
   167  	}
   168  
   169  	var seenLifecycle *hcl.Block
   170  	var seenConnection *hcl.Block
   171  	var seenEscapeBlock *hcl.Block
   172  	for _, block := range content.Blocks {
   173  		switch block.Type {
   174  		case "lifecycle":
   175  			if seenLifecycle != nil {
   176  				diags = append(diags, &hcl.Diagnostic{
   177  					Severity: hcl.DiagError,
   178  					Summary:  "Duplicate lifecycle block",
   179  					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
   180  					Subject:  &block.DefRange,
   181  				})
   182  				continue
   183  			}
   184  			seenLifecycle = block
   185  
   186  			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
   187  			diags = append(diags, lcDiags...)
   188  
   189  			if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
   190  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.CreateBeforeDestroy)
   191  				diags = append(diags, valDiags...)
   192  				r.Managed.CreateBeforeDestroySet = true
   193  			}
   194  
   195  			if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
   196  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.PreventDestroy)
   197  				diags = append(diags, valDiags...)
   198  				r.Managed.PreventDestroySet = true
   199  			}
   200  
   201  			if attr, exists := lcContent.Attributes["replace_triggered_by"]; exists {
   202  				exprs, hclDiags := decodeReplaceTriggeredBy(attr.Expr)
   203  				diags = diags.Extend(hclDiags)
   204  
   205  				r.TriggersReplacement = append(r.TriggersReplacement, exprs...)
   206  			}
   207  
   208  			if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
   209  
   210  				// ignore_changes can either be a list of relative traversals
   211  				// or it can be just the keyword "all" to ignore changes to this
   212  				// resource entirely.
   213  				//   ignore_changes = [ami, instance_type]
   214  				//   ignore_changes = all
   215  				// We also allow two legacy forms for compatibility with earlier
   216  				// versions:
   217  				//   ignore_changes = ["ami", "instance_type"]
   218  				//   ignore_changes = ["*"]
   219  
   220  				kw := hcl.ExprAsKeyword(attr.Expr)
   221  
   222  				switch {
   223  				case kw == "all":
   224  					r.Managed.IgnoreAllChanges = true
   225  				default:
   226  					exprs, listDiags := hcl.ExprList(attr.Expr)
   227  					diags = append(diags, listDiags...)
   228  
   229  					var ignoreAllRange hcl.Range
   230  
   231  					for _, expr := range exprs {
   232  
   233  						// our expr might be the literal string "*", which
   234  						// we accept as a deprecated way of saying "all".
   235  						if shimIsIgnoreChangesStar(expr) {
   236  							r.Managed.IgnoreAllChanges = true
   237  							ignoreAllRange = expr.Range()
   238  							diags = append(diags, &hcl.Diagnostic{
   239  								Severity: hcl.DiagError,
   240  								Summary:  "Invalid ignore_changes wildcard",
   241  								Detail:   "The [\"*\"] form of ignore_changes wildcard is was deprecated and is now invalid. Use \"ignore_changes = all\" to ignore changes to all attributes.",
   242  								Subject:  attr.Expr.Range().Ptr(),
   243  							})
   244  							continue
   245  						}
   246  
   247  						expr, shimDiags := shimTraversalInString(expr, false)
   248  						diags = append(diags, shimDiags...)
   249  
   250  						traversal, travDiags := hcl.RelTraversalForExpr(expr)
   251  						diags = append(diags, travDiags...)
   252  						if len(traversal) != 0 {
   253  							r.Managed.IgnoreChanges = append(r.Managed.IgnoreChanges, traversal)
   254  						}
   255  					}
   256  
   257  					if r.Managed.IgnoreAllChanges && len(r.Managed.IgnoreChanges) != 0 {
   258  						diags = append(diags, &hcl.Diagnostic{
   259  							Severity: hcl.DiagError,
   260  							Summary:  "Invalid ignore_changes ruleset",
   261  							Detail:   "Cannot mix wildcard string \"*\" with non-wildcard references.",
   262  							Subject:  &ignoreAllRange,
   263  							Context:  attr.Expr.Range().Ptr(),
   264  						})
   265  					}
   266  
   267  				}
   268  			}
   269  
   270  			for _, block := range lcContent.Blocks {
   271  				switch block.Type {
   272  				case "precondition", "postcondition":
   273  					cr, moreDiags := decodeCheckRuleBlock(block, override)
   274  					diags = append(diags, moreDiags...)
   275  
   276  					moreDiags = cr.validateSelfReferences(block.Type, r.Addr())
   277  					diags = append(diags, moreDiags...)
   278  
   279  					switch block.Type {
   280  					case "precondition":
   281  						r.Preconditions = append(r.Preconditions, cr)
   282  					case "postcondition":
   283  						r.Postconditions = append(r.Postconditions, cr)
   284  					}
   285  				default:
   286  					// The cases above should be exhaustive for all block types
   287  					// defined in the lifecycle schema, so this shouldn't happen.
   288  					panic(fmt.Sprintf("unexpected lifecycle sub-block type %q", block.Type))
   289  				}
   290  			}
   291  
   292  		case "connection":
   293  			if seenConnection != nil {
   294  				diags = append(diags, &hcl.Diagnostic{
   295  					Severity: hcl.DiagError,
   296  					Summary:  "Duplicate connection block",
   297  					Detail:   fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
   298  					Subject:  &block.DefRange,
   299  				})
   300  				continue
   301  			}
   302  			seenConnection = block
   303  
   304  			r.Managed.Connection = &Connection{
   305  				Config:    block.Body,
   306  				DeclRange: block.DefRange,
   307  			}
   308  
   309  		case "provisioner":
   310  			pv, pvDiags := decodeProvisionerBlock(block)
   311  			diags = append(diags, pvDiags...)
   312  			if pv != nil {
   313  				r.Managed.Provisioners = append(r.Managed.Provisioners, pv)
   314  			}
   315  
   316  		case "_":
   317  			if seenEscapeBlock != nil {
   318  				diags = append(diags, &hcl.Diagnostic{
   319  					Severity: hcl.DiagError,
   320  					Summary:  "Duplicate escaping block",
   321  					Detail: fmt.Sprintf(
   322  						"The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each resource block can have only one such block. The first escaping block was at %s.",
   323  						seenEscapeBlock.DefRange,
   324  					),
   325  					Subject: &block.DefRange,
   326  				})
   327  				continue
   328  			}
   329  			seenEscapeBlock = block
   330  
   331  			// When there's an escaping block its content merges with the
   332  			// existing config we extracted earlier, so later decoding
   333  			// will see a blend of both.
   334  			r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body})
   335  
   336  		default:
   337  			// Any other block types are ones we've reserved for future use,
   338  			// so they get a generic message.
   339  			diags = append(diags, &hcl.Diagnostic{
   340  				Severity: hcl.DiagError,
   341  				Summary:  "Reserved block type name in resource block",
   342  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   343  				Subject:  &block.TypeRange,
   344  			})
   345  		}
   346  	}
   347  
   348  	// Now we can validate the connection block references if there are any destroy provisioners.
   349  	// TODO: should we eliminate standalone connection blocks?
   350  	if r.Managed.Connection != nil {
   351  		for _, p := range r.Managed.Provisioners {
   352  			if p.When == ProvisionerWhenDestroy {
   353  				diags = append(diags, onlySelfRefs(r.Managed.Connection.Config)...)
   354  				break
   355  			}
   356  		}
   357  	}
   358  
   359  	return r, diags
   360  }
   361  
   362  func decodeDataBlock(block *hcl.Block, override, nested bool) (*Resource, hcl.Diagnostics) {
   363  	var diags hcl.Diagnostics
   364  	r := &Resource{
   365  		Mode:      addrs.DataResourceMode,
   366  		Type:      block.Labels[0],
   367  		Name:      block.Labels[1],
   368  		DeclRange: block.DefRange,
   369  		TypeRange: block.LabelRanges[0],
   370  	}
   371  
   372  	content, remain, moreDiags := block.Body.PartialContent(dataBlockSchema)
   373  	diags = append(diags, moreDiags...)
   374  	r.Config = remain
   375  
   376  	if !hclsyntax.ValidIdentifier(r.Type) {
   377  		diags = append(diags, &hcl.Diagnostic{
   378  			Severity: hcl.DiagError,
   379  			Summary:  "Invalid data source name",
   380  			Detail:   badIdentifierDetail,
   381  			Subject:  &block.LabelRanges[0],
   382  		})
   383  	}
   384  	if !hclsyntax.ValidIdentifier(r.Name) {
   385  		diags = append(diags, &hcl.Diagnostic{
   386  			Severity: hcl.DiagError,
   387  			Summary:  "Invalid data resource name",
   388  			Detail:   badIdentifierDetail,
   389  			Subject:  &block.LabelRanges[1],
   390  		})
   391  	}
   392  
   393  	if attr, exists := content.Attributes["count"]; exists && !nested {
   394  		r.Count = attr.Expr
   395  	} else if exists && nested {
   396  		// We don't allow count attributes in nested data blocks.
   397  		diags = append(diags, &hcl.Diagnostic{
   398  			Severity: hcl.DiagError,
   399  			Summary:  `Invalid "count" attribute`,
   400  			Detail:   `The "count" and "for_each" meta-arguments are not supported within nested data blocks.`,
   401  			Subject:  &attr.NameRange,
   402  		})
   403  	}
   404  
   405  	if attr, exists := content.Attributes["for_each"]; exists && !nested {
   406  		r.ForEach = attr.Expr
   407  		// Cannot have count and for_each on the same data block
   408  		if r.Count != nil {
   409  			diags = append(diags, &hcl.Diagnostic{
   410  				Severity: hcl.DiagError,
   411  				Summary:  `Invalid combination of "count" and "for_each"`,
   412  				Detail:   `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`,
   413  				Subject:  &attr.NameRange,
   414  			})
   415  		}
   416  	} else if exists && nested {
   417  		// We don't allow for_each attributes in nested data blocks.
   418  		diags = append(diags, &hcl.Diagnostic{
   419  			Severity: hcl.DiagError,
   420  			Summary:  `Invalid "for_each" attribute`,
   421  			Detail:   `The "count" and "for_each" meta-arguments are not supported within nested data blocks.`,
   422  			Subject:  &attr.NameRange,
   423  		})
   424  	}
   425  
   426  	if attr, exists := content.Attributes["provider"]; exists {
   427  		var providerDiags hcl.Diagnostics
   428  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
   429  		diags = append(diags, providerDiags...)
   430  	}
   431  
   432  	if attr, exists := content.Attributes["depends_on"]; exists {
   433  		deps, depsDiags := decodeDependsOn(attr)
   434  		diags = append(diags, depsDiags...)
   435  		r.DependsOn = append(r.DependsOn, deps...)
   436  	}
   437  
   438  	var seenEscapeBlock *hcl.Block
   439  	var seenLifecycle *hcl.Block
   440  	for _, block := range content.Blocks {
   441  		switch block.Type {
   442  
   443  		case "_":
   444  			if seenEscapeBlock != nil {
   445  				diags = append(diags, &hcl.Diagnostic{
   446  					Severity: hcl.DiagError,
   447  					Summary:  "Duplicate escaping block",
   448  					Detail: fmt.Sprintf(
   449  						"The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each data block can have only one such block. The first escaping block was at %s.",
   450  						seenEscapeBlock.DefRange,
   451  					),
   452  					Subject: &block.DefRange,
   453  				})
   454  				continue
   455  			}
   456  			seenEscapeBlock = block
   457  
   458  			// When there's an escaping block its content merges with the
   459  			// existing config we extracted earlier, so later decoding
   460  			// will see a blend of both.
   461  			r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body})
   462  
   463  		case "lifecycle":
   464  			if nested {
   465  				// We don't allow lifecycle arguments in nested data blocks,
   466  				// the lifecycle is managed by the parent block.
   467  				diags = append(diags, &hcl.Diagnostic{
   468  					Severity: hcl.DiagError,
   469  					Summary:  "Invalid lifecycle block",
   470  					Detail:   `Nested data blocks do not support "lifecycle" blocks as the lifecycle is managed by the containing block.`,
   471  					Subject:  block.DefRange.Ptr(),
   472  				})
   473  			}
   474  
   475  			if seenLifecycle != nil {
   476  				diags = append(diags, &hcl.Diagnostic{
   477  					Severity: hcl.DiagError,
   478  					Summary:  "Duplicate lifecycle block",
   479  					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
   480  					Subject:  block.DefRange.Ptr(),
   481  				})
   482  				continue
   483  			}
   484  			seenLifecycle = block
   485  
   486  			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
   487  			diags = append(diags, lcDiags...)
   488  
   489  			// All of the attributes defined for resource lifecycle are for
   490  			// managed resources only, so we can emit a common error message
   491  			// for any given attributes that HCL accepted.
   492  			for name, attr := range lcContent.Attributes {
   493  				diags = append(diags, &hcl.Diagnostic{
   494  					Severity: hcl.DiagError,
   495  					Summary:  "Invalid data resource lifecycle argument",
   496  					Detail:   fmt.Sprintf("The lifecycle argument %q is defined only for managed resources (\"resource\" blocks), and is not valid for data resources.", name),
   497  					Subject:  attr.NameRange.Ptr(),
   498  				})
   499  			}
   500  
   501  			for _, block := range lcContent.Blocks {
   502  				switch block.Type {
   503  				case "precondition", "postcondition":
   504  					cr, moreDiags := decodeCheckRuleBlock(block, override)
   505  					diags = append(diags, moreDiags...)
   506  
   507  					moreDiags = cr.validateSelfReferences(block.Type, r.Addr())
   508  					diags = append(diags, moreDiags...)
   509  
   510  					switch block.Type {
   511  					case "precondition":
   512  						r.Preconditions = append(r.Preconditions, cr)
   513  					case "postcondition":
   514  						r.Postconditions = append(r.Postconditions, cr)
   515  					}
   516  				default:
   517  					// The cases above should be exhaustive for all block types
   518  					// defined in the lifecycle schema, so this shouldn't happen.
   519  					panic(fmt.Sprintf("unexpected lifecycle sub-block type %q", block.Type))
   520  				}
   521  			}
   522  
   523  		default:
   524  			// Any other block types are ones we're reserving for future use,
   525  			// but don't have any defined meaning today.
   526  			diags = append(diags, &hcl.Diagnostic{
   527  				Severity: hcl.DiagError,
   528  				Summary:  "Reserved block type name in data block",
   529  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   530  				Subject:  block.TypeRange.Ptr(),
   531  			})
   532  		}
   533  	}
   534  
   535  	return r, diags
   536  }
   537  
   538  // decodeReplaceTriggeredBy decodes and does basic validation of the
   539  // replace_triggered_by expressions, ensuring they only contains references to
   540  // a single resource, and the only extra variables are count.index or each.key.
   541  func decodeReplaceTriggeredBy(expr hcl.Expression) ([]hcl.Expression, hcl.Diagnostics) {
   542  	// Since we are manually parsing the replace_triggered_by argument, we
   543  	// need to specially handle json configs, in which case the values will
   544  	// be json strings rather than hcl. To simplify parsing however we will
   545  	// decode the individual list elements, rather than the entire expression.
   546  	isJSON := hcljson.IsJSONExpression(expr)
   547  
   548  	exprs, diags := hcl.ExprList(expr)
   549  
   550  	for i, expr := range exprs {
   551  		if isJSON {
   552  			// We can abuse the hcl json api and rely on the fact that calling
   553  			// Value on a json expression with no EvalContext will return the
   554  			// raw string. We can then parse that as normal hcl syntax, and
   555  			// continue with the decoding.
   556  			v, ds := expr.Value(nil)
   557  			diags = diags.Extend(ds)
   558  			if diags.HasErrors() {
   559  				continue
   560  			}
   561  
   562  			expr, ds = hclsyntax.ParseExpression([]byte(v.AsString()), "", expr.Range().Start)
   563  			diags = diags.Extend(ds)
   564  			if diags.HasErrors() {
   565  				continue
   566  			}
   567  			// make sure to swap out the expression we're returning too
   568  			exprs[i] = expr
   569  		}
   570  
   571  		refs, refDiags := lang.ReferencesInExpr(addrs.ParseRef, expr)
   572  		for _, diag := range refDiags {
   573  			severity := hcl.DiagError
   574  			if diag.Severity() == tfdiags.Warning {
   575  				severity = hcl.DiagWarning
   576  			}
   577  
   578  			desc := diag.Description()
   579  
   580  			diags = append(diags, &hcl.Diagnostic{
   581  				Severity: severity,
   582  				Summary:  desc.Summary,
   583  				Detail:   desc.Detail,
   584  				Subject:  expr.Range().Ptr(),
   585  			})
   586  		}
   587  
   588  		if refDiags.HasErrors() {
   589  			continue
   590  		}
   591  
   592  		resourceCount := 0
   593  		for _, ref := range refs {
   594  			switch sub := ref.Subject.(type) {
   595  			case addrs.Resource, addrs.ResourceInstance:
   596  				resourceCount++
   597  
   598  			case addrs.ForEachAttr:
   599  				if sub.Name != "key" {
   600  					diags = append(diags, &hcl.Diagnostic{
   601  						Severity: hcl.DiagError,
   602  						Summary:  "Invalid each reference in replace_triggered_by expression",
   603  						Detail:   "Only each.key may be used in replace_triggered_by.",
   604  						Subject:  expr.Range().Ptr(),
   605  					})
   606  				}
   607  			case addrs.CountAttr:
   608  				if sub.Name != "index" {
   609  					diags = append(diags, &hcl.Diagnostic{
   610  						Severity: hcl.DiagError,
   611  						Summary:  "Invalid count reference in replace_triggered_by expression",
   612  						Detail:   "Only count.index may be used in replace_triggered_by.",
   613  						Subject:  expr.Range().Ptr(),
   614  					})
   615  				}
   616  			default:
   617  				// everything else should be simple traversals
   618  				diags = append(diags, &hcl.Diagnostic{
   619  					Severity: hcl.DiagError,
   620  					Summary:  "Invalid reference in replace_triggered_by expression",
   621  					Detail:   "Only resources, count.index, and each.key may be used in replace_triggered_by.",
   622  					Subject:  expr.Range().Ptr(),
   623  				})
   624  			}
   625  		}
   626  
   627  		switch {
   628  		case resourceCount == 0:
   629  			diags = append(diags, &hcl.Diagnostic{
   630  				Severity: hcl.DiagError,
   631  				Summary:  "Invalid replace_triggered_by expression",
   632  				Detail:   "Missing resource reference in replace_triggered_by expression.",
   633  				Subject:  expr.Range().Ptr(),
   634  			})
   635  		case resourceCount > 1:
   636  			diags = append(diags, &hcl.Diagnostic{
   637  				Severity: hcl.DiagError,
   638  				Summary:  "Invalid replace_triggered_by expression",
   639  				Detail:   "Multiple resource references in replace_triggered_by expression.",
   640  				Subject:  expr.Range().Ptr(),
   641  			})
   642  		}
   643  	}
   644  	return exprs, diags
   645  }
   646  
   647  type ProviderConfigRef struct {
   648  	Name       string
   649  	NameRange  hcl.Range
   650  	Alias      string
   651  	AliasRange *hcl.Range // nil if alias not set
   652  
   653  	// TODO: this may not be set in some cases, so it is not yet suitable for
   654  	// use outside of this package. We currently only use it for internal
   655  	// validation, but once we verify that this can be set in all cases, we can
   656  	// export this so providers don't need to be re-resolved.
   657  	// This same field is also added to the Provider struct.
   658  	providerType addrs.Provider
   659  }
   660  
   661  func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
   662  	var diags hcl.Diagnostics
   663  
   664  	var shimDiags hcl.Diagnostics
   665  	expr, shimDiags = shimTraversalInString(expr, false)
   666  	diags = append(diags, shimDiags...)
   667  
   668  	traversal, travDiags := hcl.AbsTraversalForExpr(expr)
   669  
   670  	// AbsTraversalForExpr produces only generic errors, so we'll discard
   671  	// the errors given and produce our own with extra context. If we didn't
   672  	// get any errors then we might still have warnings, though.
   673  	if !travDiags.HasErrors() {
   674  		diags = append(diags, travDiags...)
   675  	}
   676  
   677  	if len(traversal) < 1 || len(traversal) > 2 {
   678  		// A provider reference was given as a string literal in the legacy
   679  		// configuration language and there are lots of examples out there
   680  		// showing that usage, so we'll sniff for that situation here and
   681  		// produce a specialized error message for it to help users find
   682  		// the new correct form.
   683  		if exprIsNativeQuotedString(expr) {
   684  			diags = append(diags, &hcl.Diagnostic{
   685  				Severity: hcl.DiagError,
   686  				Summary:  "Invalid provider configuration reference",
   687  				Detail:   "A provider configuration reference must not be given in quotes.",
   688  				Subject:  expr.Range().Ptr(),
   689  			})
   690  			return nil, diags
   691  		}
   692  
   693  		diags = append(diags, &hcl.Diagnostic{
   694  			Severity: hcl.DiagError,
   695  			Summary:  "Invalid provider configuration reference",
   696  			Detail:   fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName),
   697  			Subject:  expr.Range().Ptr(),
   698  		})
   699  		return nil, diags
   700  	}
   701  
   702  	// verify that the provider local name is normalized
   703  	name := traversal.RootName()
   704  	nameDiags := checkProviderNameNormalized(name, traversal[0].SourceRange())
   705  	diags = append(diags, nameDiags...)
   706  	if diags.HasErrors() {
   707  		return nil, diags
   708  	}
   709  
   710  	ret := &ProviderConfigRef{
   711  		Name:      name,
   712  		NameRange: traversal[0].SourceRange(),
   713  	}
   714  
   715  	if len(traversal) > 1 {
   716  		aliasStep, ok := traversal[1].(hcl.TraverseAttr)
   717  		if !ok {
   718  			diags = append(diags, &hcl.Diagnostic{
   719  				Severity: hcl.DiagError,
   720  				Summary:  "Invalid provider configuration reference",
   721  				Detail:   "Provider name must either stand alone or be followed by a period and then a configuration alias.",
   722  				Subject:  traversal[1].SourceRange().Ptr(),
   723  			})
   724  			return ret, diags
   725  		}
   726  
   727  		ret.Alias = aliasStep.Name
   728  		ret.AliasRange = aliasStep.SourceRange().Ptr()
   729  	}
   730  
   731  	return ret, diags
   732  }
   733  
   734  // Addr returns the provider config address corresponding to the receiving
   735  // config reference.
   736  //
   737  // This is a trivial conversion, essentially just discarding the source
   738  // location information and keeping just the addressing information.
   739  func (r *ProviderConfigRef) Addr() addrs.LocalProviderConfig {
   740  	return addrs.LocalProviderConfig{
   741  		LocalName: r.Name,
   742  		Alias:     r.Alias,
   743  	}
   744  }
   745  
   746  func (r *ProviderConfigRef) String() string {
   747  	if r == nil {
   748  		return "<nil>"
   749  	}
   750  	if r.Alias != "" {
   751  		return fmt.Sprintf("%s.%s", r.Name, r.Alias)
   752  	}
   753  	return r.Name
   754  }
   755  
   756  var commonResourceAttributes = []hcl.AttributeSchema{
   757  	{
   758  		Name: "count",
   759  	},
   760  	{
   761  		Name: "for_each",
   762  	},
   763  	{
   764  		Name: "provider",
   765  	},
   766  	{
   767  		Name: "depends_on",
   768  	},
   769  }
   770  
   771  // ResourceBlockSchema is the schema for a resource or data resource type within
   772  // Terraform.
   773  //
   774  // This schema is public as it is required elsewhere in order to validate and
   775  // use generated config.
   776  var ResourceBlockSchema = &hcl.BodySchema{
   777  	Attributes: commonResourceAttributes,
   778  	Blocks: []hcl.BlockHeaderSchema{
   779  		{Type: "locals"}, // reserved for future use
   780  		{Type: "lifecycle"},
   781  		{Type: "connection"},
   782  		{Type: "provisioner", LabelNames: []string{"type"}},
   783  		{Type: "_"}, // meta-argument escaping block
   784  	},
   785  }
   786  
   787  var dataBlockSchema = &hcl.BodySchema{
   788  	Attributes: commonResourceAttributes,
   789  	Blocks: []hcl.BlockHeaderSchema{
   790  		{Type: "lifecycle"},
   791  		{Type: "locals"}, // reserved for future use
   792  		{Type: "_"},      // meta-argument escaping block
   793  	},
   794  }
   795  
   796  var resourceLifecycleBlockSchema = &hcl.BodySchema{
   797  	// We tell HCL that these elements are all valid for both "resource"
   798  	// and "data" lifecycle blocks, but the rules are actually more restrictive
   799  	// than that. We deal with that after decoding so that we can return
   800  	// more specific error messages than HCL would typically return itself.
   801  	Attributes: []hcl.AttributeSchema{
   802  		{
   803  			Name: "create_before_destroy",
   804  		},
   805  		{
   806  			Name: "prevent_destroy",
   807  		},
   808  		{
   809  			Name: "ignore_changes",
   810  		},
   811  		{
   812  			Name: "replace_triggered_by",
   813  		},
   814  	},
   815  	Blocks: []hcl.BlockHeaderSchema{
   816  		{Type: "precondition"},
   817  		{Type: "postcondition"},
   818  	},
   819  }