github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/resource.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/gohcl"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  
    10  	"github.com/muratcelep/terraform/not-internal/addrs"
    11  )
    12  
    13  // Resource represents a "resource" or "data" block in a module or file.
    14  type Resource struct {
    15  	Mode    addrs.ResourceMode
    16  	Name    string
    17  	Type    string
    18  	Config  hcl.Body
    19  	Count   hcl.Expression
    20  	ForEach hcl.Expression
    21  
    22  	ProviderConfigRef *ProviderConfigRef
    23  	Provider          addrs.Provider
    24  
    25  	DependsOn []hcl.Traversal
    26  
    27  	// Managed is populated only for Mode = addrs.ManagedResourceMode,
    28  	// containing the additional fields that apply to managed resources.
    29  	// For all other resource modes, this field is nil.
    30  	Managed *ManagedResource
    31  
    32  	DeclRange hcl.Range
    33  	TypeRange hcl.Range
    34  }
    35  
    36  // ManagedResource represents a "resource" block in a module or file.
    37  type ManagedResource struct {
    38  	Connection   *Connection
    39  	Provisioners []*Provisioner
    40  
    41  	CreateBeforeDestroy bool
    42  	PreventDestroy      bool
    43  	IgnoreChanges       []hcl.Traversal
    44  	IgnoreAllChanges    bool
    45  
    46  	CreateBeforeDestroySet bool
    47  	PreventDestroySet      bool
    48  }
    49  
    50  func (r *Resource) moduleUniqueKey() string {
    51  	return r.Addr().String()
    52  }
    53  
    54  // Addr returns a resource address for the receiver that is relative to the
    55  // resource's containing module.
    56  func (r *Resource) Addr() addrs.Resource {
    57  	return addrs.Resource{
    58  		Mode: r.Mode,
    59  		Type: r.Type,
    60  		Name: r.Name,
    61  	}
    62  }
    63  
    64  // ProviderConfigAddr returns the address for the provider configuration that
    65  // should be used for this resource. This function returns a default provider
    66  // config addr if an explicit "provider" argument was not provided.
    67  func (r *Resource) ProviderConfigAddr() addrs.LocalProviderConfig {
    68  	if r.ProviderConfigRef == nil {
    69  		// If no specific "provider" argument is given, we want to look up the
    70  		// provider config where the local name matches the implied provider
    71  		// from the resource type. This may be different from the resource's
    72  		// provider type.
    73  		return addrs.LocalProviderConfig{
    74  			LocalName: r.Addr().ImpliedProvider(),
    75  		}
    76  	}
    77  
    78  	return addrs.LocalProviderConfig{
    79  		LocalName: r.ProviderConfigRef.Name,
    80  		Alias:     r.ProviderConfigRef.Alias,
    81  	}
    82  }
    83  
    84  func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
    85  	var diags hcl.Diagnostics
    86  	r := &Resource{
    87  		Mode:      addrs.ManagedResourceMode,
    88  		Type:      block.Labels[0],
    89  		Name:      block.Labels[1],
    90  		DeclRange: block.DefRange,
    91  		TypeRange: block.LabelRanges[0],
    92  		Managed:   &ManagedResource{},
    93  	}
    94  
    95  	content, remain, moreDiags := block.Body.PartialContent(resourceBlockSchema)
    96  	diags = append(diags, moreDiags...)
    97  	r.Config = remain
    98  
    99  	if !hclsyntax.ValidIdentifier(r.Type) {
   100  		diags = append(diags, &hcl.Diagnostic{
   101  			Severity: hcl.DiagError,
   102  			Summary:  "Invalid resource type name",
   103  			Detail:   badIdentifierDetail,
   104  			Subject:  &block.LabelRanges[0],
   105  		})
   106  	}
   107  	if !hclsyntax.ValidIdentifier(r.Name) {
   108  		diags = append(diags, &hcl.Diagnostic{
   109  			Severity: hcl.DiagError,
   110  			Summary:  "Invalid resource name",
   111  			Detail:   badIdentifierDetail,
   112  			Subject:  &block.LabelRanges[1],
   113  		})
   114  	}
   115  
   116  	if attr, exists := content.Attributes["count"]; exists {
   117  		r.Count = attr.Expr
   118  	}
   119  
   120  	if attr, exists := content.Attributes["for_each"]; exists {
   121  		r.ForEach = attr.Expr
   122  		// Cannot have count and for_each on the same resource block
   123  		if r.Count != nil {
   124  			diags = append(diags, &hcl.Diagnostic{
   125  				Severity: hcl.DiagError,
   126  				Summary:  `Invalid combination of "count" and "for_each"`,
   127  				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.`,
   128  				Subject:  &attr.NameRange,
   129  			})
   130  		}
   131  	}
   132  
   133  	if attr, exists := content.Attributes["provider"]; exists {
   134  		var providerDiags hcl.Diagnostics
   135  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
   136  		diags = append(diags, providerDiags...)
   137  	}
   138  
   139  	if attr, exists := content.Attributes["depends_on"]; exists {
   140  		deps, depsDiags := decodeDependsOn(attr)
   141  		diags = append(diags, depsDiags...)
   142  		r.DependsOn = append(r.DependsOn, deps...)
   143  	}
   144  
   145  	var seenLifecycle *hcl.Block
   146  	var seenConnection *hcl.Block
   147  	var seenEscapeBlock *hcl.Block
   148  	for _, block := range content.Blocks {
   149  		switch block.Type {
   150  		case "lifecycle":
   151  			if seenLifecycle != nil {
   152  				diags = append(diags, &hcl.Diagnostic{
   153  					Severity: hcl.DiagError,
   154  					Summary:  "Duplicate lifecycle block",
   155  					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
   156  					Subject:  &block.DefRange,
   157  				})
   158  				continue
   159  			}
   160  			seenLifecycle = block
   161  
   162  			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
   163  			diags = append(diags, lcDiags...)
   164  
   165  			if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
   166  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.CreateBeforeDestroy)
   167  				diags = append(diags, valDiags...)
   168  				r.Managed.CreateBeforeDestroySet = true
   169  			}
   170  
   171  			if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
   172  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.Managed.PreventDestroy)
   173  				diags = append(diags, valDiags...)
   174  				r.Managed.PreventDestroySet = true
   175  			}
   176  
   177  			if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
   178  
   179  				// ignore_changes can either be a list of relative traversals
   180  				// or it can be just the keyword "all" to ignore changes to this
   181  				// resource entirely.
   182  				//   ignore_changes = [ami, instance_type]
   183  				//   ignore_changes = all
   184  				// We also allow two legacy forms for compatibility with earlier
   185  				// versions:
   186  				//   ignore_changes = ["ami", "instance_type"]
   187  				//   ignore_changes = ["*"]
   188  
   189  				kw := hcl.ExprAsKeyword(attr.Expr)
   190  
   191  				switch {
   192  				case kw == "all":
   193  					r.Managed.IgnoreAllChanges = true
   194  				default:
   195  					exprs, listDiags := hcl.ExprList(attr.Expr)
   196  					diags = append(diags, listDiags...)
   197  
   198  					var ignoreAllRange hcl.Range
   199  
   200  					for _, expr := range exprs {
   201  
   202  						// our expr might be the literal string "*", which
   203  						// we accept as a deprecated way of saying "all".
   204  						if shimIsIgnoreChangesStar(expr) {
   205  							r.Managed.IgnoreAllChanges = true
   206  							ignoreAllRange = expr.Range()
   207  							diags = append(diags, &hcl.Diagnostic{
   208  								Severity: hcl.DiagError,
   209  								Summary:  "Invalid ignore_changes wildcard",
   210  								Detail:   "The [\"*\"] form of ignore_changes wildcard is was deprecated and is now invalid. Use \"ignore_changes = all\" to ignore changes to all attributes.",
   211  								Subject:  attr.Expr.Range().Ptr(),
   212  							})
   213  							continue
   214  						}
   215  
   216  						expr, shimDiags := shimTraversalInString(expr, false)
   217  						diags = append(diags, shimDiags...)
   218  
   219  						traversal, travDiags := hcl.RelTraversalForExpr(expr)
   220  						diags = append(diags, travDiags...)
   221  						if len(traversal) != 0 {
   222  							r.Managed.IgnoreChanges = append(r.Managed.IgnoreChanges, traversal)
   223  						}
   224  					}
   225  
   226  					if r.Managed.IgnoreAllChanges && len(r.Managed.IgnoreChanges) != 0 {
   227  						diags = append(diags, &hcl.Diagnostic{
   228  							Severity: hcl.DiagError,
   229  							Summary:  "Invalid ignore_changes ruleset",
   230  							Detail:   "Cannot mix wildcard string \"*\" with non-wildcard references.",
   231  							Subject:  &ignoreAllRange,
   232  							Context:  attr.Expr.Range().Ptr(),
   233  						})
   234  					}
   235  
   236  				}
   237  
   238  			}
   239  
   240  		case "connection":
   241  			if seenConnection != nil {
   242  				diags = append(diags, &hcl.Diagnostic{
   243  					Severity: hcl.DiagError,
   244  					Summary:  "Duplicate connection block",
   245  					Detail:   fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
   246  					Subject:  &block.DefRange,
   247  				})
   248  				continue
   249  			}
   250  			seenConnection = block
   251  
   252  			r.Managed.Connection = &Connection{
   253  				Config:    block.Body,
   254  				DeclRange: block.DefRange,
   255  			}
   256  
   257  		case "provisioner":
   258  			pv, pvDiags := decodeProvisionerBlock(block)
   259  			diags = append(diags, pvDiags...)
   260  			if pv != nil {
   261  				r.Managed.Provisioners = append(r.Managed.Provisioners, pv)
   262  			}
   263  
   264  		case "_":
   265  			if seenEscapeBlock != nil {
   266  				diags = append(diags, &hcl.Diagnostic{
   267  					Severity: hcl.DiagError,
   268  					Summary:  "Duplicate escaping block",
   269  					Detail: fmt.Sprintf(
   270  						"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.",
   271  						seenEscapeBlock.DefRange,
   272  					),
   273  					Subject: &block.DefRange,
   274  				})
   275  				continue
   276  			}
   277  			seenEscapeBlock = block
   278  
   279  			// When there's an escaping block its content merges with the
   280  			// existing config we extracted earlier, so later decoding
   281  			// will see a blend of both.
   282  			r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body})
   283  
   284  		default:
   285  			// Any other block types are ones we've reserved for future use,
   286  			// so they get a generic message.
   287  			diags = append(diags, &hcl.Diagnostic{
   288  				Severity: hcl.DiagError,
   289  				Summary:  "Reserved block type name in resource block",
   290  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   291  				Subject:  &block.TypeRange,
   292  			})
   293  		}
   294  	}
   295  
   296  	// Now we can validate the connection block references if there are any destroy provisioners.
   297  	// TODO: should we eliminate standalone connection blocks?
   298  	if r.Managed.Connection != nil {
   299  		for _, p := range r.Managed.Provisioners {
   300  			if p.When == ProvisionerWhenDestroy {
   301  				diags = append(diags, onlySelfRefs(r.Managed.Connection.Config)...)
   302  				break
   303  			}
   304  		}
   305  	}
   306  
   307  	return r, diags
   308  }
   309  
   310  func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
   311  	var diags hcl.Diagnostics
   312  	r := &Resource{
   313  		Mode:      addrs.DataResourceMode,
   314  		Type:      block.Labels[0],
   315  		Name:      block.Labels[1],
   316  		DeclRange: block.DefRange,
   317  		TypeRange: block.LabelRanges[0],
   318  	}
   319  
   320  	content, remain, moreDiags := block.Body.PartialContent(dataBlockSchema)
   321  	diags = append(diags, moreDiags...)
   322  	r.Config = remain
   323  
   324  	if !hclsyntax.ValidIdentifier(r.Type) {
   325  		diags = append(diags, &hcl.Diagnostic{
   326  			Severity: hcl.DiagError,
   327  			Summary:  "Invalid data source name",
   328  			Detail:   badIdentifierDetail,
   329  			Subject:  &block.LabelRanges[0],
   330  		})
   331  	}
   332  	if !hclsyntax.ValidIdentifier(r.Name) {
   333  		diags = append(diags, &hcl.Diagnostic{
   334  			Severity: hcl.DiagError,
   335  			Summary:  "Invalid data resource name",
   336  			Detail:   badIdentifierDetail,
   337  			Subject:  &block.LabelRanges[1],
   338  		})
   339  	}
   340  
   341  	if attr, exists := content.Attributes["count"]; exists {
   342  		r.Count = attr.Expr
   343  	}
   344  
   345  	if attr, exists := content.Attributes["for_each"]; exists {
   346  		r.ForEach = attr.Expr
   347  		// Cannot have count and for_each on the same data block
   348  		if r.Count != nil {
   349  			diags = append(diags, &hcl.Diagnostic{
   350  				Severity: hcl.DiagError,
   351  				Summary:  `Invalid combination of "count" and "for_each"`,
   352  				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.`,
   353  				Subject:  &attr.NameRange,
   354  			})
   355  		}
   356  	}
   357  
   358  	if attr, exists := content.Attributes["provider"]; exists {
   359  		var providerDiags hcl.Diagnostics
   360  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
   361  		diags = append(diags, providerDiags...)
   362  	}
   363  
   364  	if attr, exists := content.Attributes["depends_on"]; exists {
   365  		deps, depsDiags := decodeDependsOn(attr)
   366  		diags = append(diags, depsDiags...)
   367  		r.DependsOn = append(r.DependsOn, deps...)
   368  	}
   369  
   370  	var seenEscapeBlock *hcl.Block
   371  	for _, block := range content.Blocks {
   372  		switch block.Type {
   373  
   374  		case "_":
   375  			if seenEscapeBlock != nil {
   376  				diags = append(diags, &hcl.Diagnostic{
   377  					Severity: hcl.DiagError,
   378  					Summary:  "Duplicate escaping block",
   379  					Detail: fmt.Sprintf(
   380  						"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.",
   381  						seenEscapeBlock.DefRange,
   382  					),
   383  					Subject: &block.DefRange,
   384  				})
   385  				continue
   386  			}
   387  			seenEscapeBlock = block
   388  
   389  			// When there's an escaping block its content merges with the
   390  			// existing config we extracted earlier, so later decoding
   391  			// will see a blend of both.
   392  			r.Config = hcl.MergeBodies([]hcl.Body{r.Config, block.Body})
   393  
   394  		// The rest of these are just here to reserve block type names for future use.
   395  		case "lifecycle":
   396  			diags = append(diags, &hcl.Diagnostic{
   397  				Severity: hcl.DiagError,
   398  				Summary:  "Unsupported lifecycle block",
   399  				Detail:   "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
   400  				Subject:  &block.DefRange,
   401  			})
   402  
   403  		default:
   404  			diags = append(diags, &hcl.Diagnostic{
   405  				Severity: hcl.DiagError,
   406  				Summary:  "Reserved block type name in data block",
   407  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   408  				Subject:  &block.TypeRange,
   409  			})
   410  		}
   411  	}
   412  
   413  	return r, diags
   414  }
   415  
   416  type ProviderConfigRef struct {
   417  	Name       string
   418  	NameRange  hcl.Range
   419  	Alias      string
   420  	AliasRange *hcl.Range // nil if alias not set
   421  
   422  	// TODO: this may not be set in some cases, so it is not yet suitable for
   423  	// use outside of this package. We currently only use it for not-internal
   424  	// validation, but once we verify that this can be set in all cases, we can
   425  	// export this so providers don't need to be re-resolved.
   426  	// This same field is also added to the Provider struct.
   427  	providerType addrs.Provider
   428  }
   429  
   430  func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
   431  	var diags hcl.Diagnostics
   432  
   433  	var shimDiags hcl.Diagnostics
   434  	expr, shimDiags = shimTraversalInString(expr, false)
   435  	diags = append(diags, shimDiags...)
   436  
   437  	traversal, travDiags := hcl.AbsTraversalForExpr(expr)
   438  
   439  	// AbsTraversalForExpr produces only generic errors, so we'll discard
   440  	// the errors given and produce our own with extra context. If we didn't
   441  	// get any errors then we might still have warnings, though.
   442  	if !travDiags.HasErrors() {
   443  		diags = append(diags, travDiags...)
   444  	}
   445  
   446  	if len(traversal) < 1 || len(traversal) > 2 {
   447  		// A provider reference was given as a string literal in the legacy
   448  		// configuration language and there are lots of examples out there
   449  		// showing that usage, so we'll sniff for that situation here and
   450  		// produce a specialized error message for it to help users find
   451  		// the new correct form.
   452  		if exprIsNativeQuotedString(expr) {
   453  			diags = append(diags, &hcl.Diagnostic{
   454  				Severity: hcl.DiagError,
   455  				Summary:  "Invalid provider configuration reference",
   456  				Detail:   "A provider configuration reference must not be given in quotes.",
   457  				Subject:  expr.Range().Ptr(),
   458  			})
   459  			return nil, diags
   460  		}
   461  
   462  		diags = append(diags, &hcl.Diagnostic{
   463  			Severity: hcl.DiagError,
   464  			Summary:  "Invalid provider configuration reference",
   465  			Detail:   fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName),
   466  			Subject:  expr.Range().Ptr(),
   467  		})
   468  		return nil, diags
   469  	}
   470  
   471  	// verify that the provider local name is normalized
   472  	name := traversal.RootName()
   473  	nameDiags := checkProviderNameNormalized(name, traversal[0].SourceRange())
   474  	diags = append(diags, nameDiags...)
   475  	if diags.HasErrors() {
   476  		return nil, diags
   477  	}
   478  
   479  	ret := &ProviderConfigRef{
   480  		Name:      name,
   481  		NameRange: traversal[0].SourceRange(),
   482  	}
   483  
   484  	if len(traversal) > 1 {
   485  		aliasStep, ok := traversal[1].(hcl.TraverseAttr)
   486  		if !ok {
   487  			diags = append(diags, &hcl.Diagnostic{
   488  				Severity: hcl.DiagError,
   489  				Summary:  "Invalid provider configuration reference",
   490  				Detail:   "Provider name must either stand alone or be followed by a period and then a configuration alias.",
   491  				Subject:  traversal[1].SourceRange().Ptr(),
   492  			})
   493  			return ret, diags
   494  		}
   495  
   496  		ret.Alias = aliasStep.Name
   497  		ret.AliasRange = aliasStep.SourceRange().Ptr()
   498  	}
   499  
   500  	return ret, diags
   501  }
   502  
   503  // Addr returns the provider config address corresponding to the receiving
   504  // config reference.
   505  //
   506  // This is a trivial conversion, essentially just discarding the source
   507  // location information and keeping just the addressing information.
   508  func (r *ProviderConfigRef) Addr() addrs.LocalProviderConfig {
   509  	return addrs.LocalProviderConfig{
   510  		LocalName: r.Name,
   511  		Alias:     r.Alias,
   512  	}
   513  }
   514  
   515  func (r *ProviderConfigRef) String() string {
   516  	if r == nil {
   517  		return "<nil>"
   518  	}
   519  	if r.Alias != "" {
   520  		return fmt.Sprintf("%s.%s", r.Name, r.Alias)
   521  	}
   522  	return r.Name
   523  }
   524  
   525  var commonResourceAttributes = []hcl.AttributeSchema{
   526  	{
   527  		Name: "count",
   528  	},
   529  	{
   530  		Name: "for_each",
   531  	},
   532  	{
   533  		Name: "provider",
   534  	},
   535  	{
   536  		Name: "depends_on",
   537  	},
   538  }
   539  
   540  var resourceBlockSchema = &hcl.BodySchema{
   541  	Attributes: commonResourceAttributes,
   542  	Blocks: []hcl.BlockHeaderSchema{
   543  		{Type: "locals"}, // reserved for future use
   544  		{Type: "lifecycle"},
   545  		{Type: "connection"},
   546  		{Type: "provisioner", LabelNames: []string{"type"}},
   547  		{Type: "_"}, // meta-argument escaping block
   548  	},
   549  }
   550  
   551  var dataBlockSchema = &hcl.BodySchema{
   552  	Attributes: commonResourceAttributes,
   553  	Blocks: []hcl.BlockHeaderSchema{
   554  		{Type: "lifecycle"}, // reserved for future use
   555  		{Type: "locals"},    // reserved for future use
   556  		{Type: "_"},         // meta-argument escaping block
   557  	},
   558  }
   559  
   560  var resourceLifecycleBlockSchema = &hcl.BodySchema{
   561  	Attributes: []hcl.AttributeSchema{
   562  		{
   563  			Name: "create_before_destroy",
   564  		},
   565  		{
   566  			Name: "prevent_destroy",
   567  		},
   568  		{
   569  			Name: "ignore_changes",
   570  		},
   571  	},
   572  }