github.com/sfdevops1/terrra4orm@v0.11.12-beta1/configs/resource.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl2/gohcl"
     7  	"github.com/hashicorp/hcl2/hcl"
     8  	"github.com/hashicorp/hcl2/hcl/hclsyntax"
     9  )
    10  
    11  // ManagedResource represents a "resource" block in a module or file.
    12  type ManagedResource struct {
    13  	Name    string
    14  	Type    string
    15  	Config  hcl.Body
    16  	Count   hcl.Expression
    17  	ForEach hcl.Expression
    18  
    19  	ProviderConfigRef *ProviderConfigRef
    20  
    21  	DependsOn []hcl.Traversal
    22  
    23  	Connection   *Connection
    24  	Provisioners []*Provisioner
    25  
    26  	CreateBeforeDestroy bool
    27  	PreventDestroy      bool
    28  	IgnoreChanges       []hcl.Traversal
    29  	IgnoreAllChanges    bool
    30  
    31  	CreateBeforeDestroySet bool
    32  	PreventDestroySet      bool
    33  
    34  	DeclRange hcl.Range
    35  	TypeRange hcl.Range
    36  }
    37  
    38  func (r *ManagedResource) moduleUniqueKey() string {
    39  	return fmt.Sprintf("%s.%s", r.Name, r.Type)
    40  }
    41  
    42  func decodeResourceBlock(block *hcl.Block) (*ManagedResource, hcl.Diagnostics) {
    43  	r := &ManagedResource{
    44  		Type:      block.Labels[0],
    45  		Name:      block.Labels[1],
    46  		DeclRange: block.DefRange,
    47  		TypeRange: block.LabelRanges[0],
    48  	}
    49  
    50  	content, remain, diags := block.Body.PartialContent(resourceBlockSchema)
    51  	r.Config = remain
    52  
    53  	if !hclsyntax.ValidIdentifier(r.Type) {
    54  		diags = append(diags, &hcl.Diagnostic{
    55  			Severity: hcl.DiagError,
    56  			Summary:  "Invalid resource type name",
    57  			Detail:   badIdentifierDetail,
    58  			Subject:  &block.LabelRanges[0],
    59  		})
    60  	}
    61  	if !hclsyntax.ValidIdentifier(r.Name) {
    62  		diags = append(diags, &hcl.Diagnostic{
    63  			Severity: hcl.DiagError,
    64  			Summary:  "Invalid resource name",
    65  			Detail:   badIdentifierDetail,
    66  			Subject:  &block.LabelRanges[1],
    67  		})
    68  	}
    69  
    70  	if attr, exists := content.Attributes["count"]; exists {
    71  		r.Count = attr.Expr
    72  	}
    73  
    74  	if attr, exists := content.Attributes["for_each"]; exists {
    75  		r.Count = attr.Expr
    76  	}
    77  
    78  	if attr, exists := content.Attributes["provider"]; exists {
    79  		var providerDiags hcl.Diagnostics
    80  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
    81  		diags = append(diags, providerDiags...)
    82  	}
    83  
    84  	if attr, exists := content.Attributes["depends_on"]; exists {
    85  		deps, depsDiags := decodeDependsOn(attr)
    86  		diags = append(diags, depsDiags...)
    87  		r.DependsOn = append(r.DependsOn, deps...)
    88  	}
    89  
    90  	var seenLifecycle *hcl.Block
    91  	var seenConnection *hcl.Block
    92  	for _, block := range content.Blocks {
    93  		switch block.Type {
    94  		case "lifecycle":
    95  			if seenLifecycle != nil {
    96  				diags = append(diags, &hcl.Diagnostic{
    97  					Severity: hcl.DiagError,
    98  					Summary:  "Duplicate lifecycle block",
    99  					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
   100  					Subject:  &block.DefRange,
   101  				})
   102  				continue
   103  			}
   104  			seenLifecycle = block
   105  
   106  			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
   107  			diags = append(diags, lcDiags...)
   108  
   109  			if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
   110  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.CreateBeforeDestroy)
   111  				diags = append(diags, valDiags...)
   112  				r.CreateBeforeDestroySet = true
   113  			}
   114  
   115  			if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
   116  				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.PreventDestroy)
   117  				diags = append(diags, valDiags...)
   118  				r.PreventDestroySet = true
   119  			}
   120  
   121  			if attr, exists := lcContent.Attributes["ignore_changes"]; exists {
   122  
   123  				// ignore_changes can either be a list of relative traversals
   124  				// or it can be just the keyword "all" to ignore changes to this
   125  				// resource entirely.
   126  				//   ignore_changes = [ami, instance_type]
   127  				//   ignore_changes = all
   128  				// We also allow two legacy forms for compatibility with earlier
   129  				// versions:
   130  				//   ignore_changes = ["ami", "instance_type"]
   131  				//   ignore_changes = ["*"]
   132  
   133  				kw := hcl.ExprAsKeyword(attr.Expr)
   134  
   135  				switch {
   136  				case kw == "all":
   137  					r.IgnoreAllChanges = true
   138  				default:
   139  					exprs, listDiags := hcl.ExprList(attr.Expr)
   140  					diags = append(diags, listDiags...)
   141  
   142  					var ignoreAllRange hcl.Range
   143  
   144  					for _, expr := range exprs {
   145  
   146  						// our expr might be the literal string "*", which
   147  						// we accept as a deprecated way of saying "all".
   148  						if shimIsIgnoreChangesStar(expr) {
   149  							r.IgnoreAllChanges = true
   150  							ignoreAllRange = expr.Range()
   151  							diags = append(diags, &hcl.Diagnostic{
   152  								Severity: hcl.DiagWarning,
   153  								Summary:  "Deprecated ignore_changes wildcard",
   154  								Detail:   "The [\"*\"] form of ignore_changes wildcard is reprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.",
   155  								Subject:  attr.Expr.Range().Ptr(),
   156  							})
   157  							continue
   158  						}
   159  
   160  						expr, shimDiags := shimTraversalInString(expr, false)
   161  						diags = append(diags, shimDiags...)
   162  
   163  						traversal, travDiags := hcl.RelTraversalForExpr(expr)
   164  						diags = append(diags, travDiags...)
   165  						if len(traversal) != 0 {
   166  							r.IgnoreChanges = append(r.IgnoreChanges, traversal)
   167  						}
   168  					}
   169  
   170  					if r.IgnoreAllChanges && len(r.IgnoreChanges) != 0 {
   171  						diags = append(diags, &hcl.Diagnostic{
   172  							Severity: hcl.DiagError,
   173  							Summary:  "Invalid ignore_changes ruleset",
   174  							Detail:   "Cannot mix wildcard string \"*\" with non-wildcard references.",
   175  							Subject:  &ignoreAllRange,
   176  							Context:  attr.Expr.Range().Ptr(),
   177  						})
   178  					}
   179  
   180  				}
   181  
   182  			}
   183  
   184  		case "connection":
   185  			if seenConnection != nil {
   186  				diags = append(diags, &hcl.Diagnostic{
   187  					Severity: hcl.DiagError,
   188  					Summary:  "Duplicate connection block",
   189  					Detail:   fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
   190  					Subject:  &block.DefRange,
   191  				})
   192  				continue
   193  			}
   194  			seenConnection = block
   195  
   196  			conn, connDiags := decodeConnectionBlock(block)
   197  			diags = append(diags, connDiags...)
   198  			r.Connection = conn
   199  
   200  		case "provisioner":
   201  			pv, pvDiags := decodeProvisionerBlock(block)
   202  			diags = append(diags, pvDiags...)
   203  			if pv != nil {
   204  				r.Provisioners = append(r.Provisioners, pv)
   205  			}
   206  
   207  		default:
   208  			// Should never happen, because the above cases should always be
   209  			// exhaustive for all the types specified in our schema.
   210  			continue
   211  		}
   212  	}
   213  
   214  	return r, diags
   215  }
   216  
   217  // DataResource represents a "data" block in a module or file.
   218  type DataResource struct {
   219  	Name    string
   220  	Type    string
   221  	Config  hcl.Body
   222  	Count   hcl.Expression
   223  	ForEach hcl.Expression
   224  
   225  	ProviderConfigRef *ProviderConfigRef
   226  
   227  	DependsOn []hcl.Traversal
   228  
   229  	DeclRange hcl.Range
   230  	TypeRange hcl.Range
   231  }
   232  
   233  func (r *DataResource) moduleUniqueKey() string {
   234  	return fmt.Sprintf("data.%s.%s", r.Name, r.Type)
   235  }
   236  
   237  func decodeDataBlock(block *hcl.Block) (*DataResource, hcl.Diagnostics) {
   238  	r := &DataResource{
   239  		Type:      block.Labels[0],
   240  		Name:      block.Labels[1],
   241  		DeclRange: block.DefRange,
   242  		TypeRange: block.LabelRanges[0],
   243  	}
   244  
   245  	content, remain, diags := block.Body.PartialContent(dataBlockSchema)
   246  	r.Config = remain
   247  
   248  	if !hclsyntax.ValidIdentifier(r.Type) {
   249  		diags = append(diags, &hcl.Diagnostic{
   250  			Severity: hcl.DiagError,
   251  			Summary:  "Invalid data source name",
   252  			Detail:   badIdentifierDetail,
   253  			Subject:  &block.LabelRanges[0],
   254  		})
   255  	}
   256  	if !hclsyntax.ValidIdentifier(r.Name) {
   257  		diags = append(diags, &hcl.Diagnostic{
   258  			Severity: hcl.DiagError,
   259  			Summary:  "Invalid data resource name",
   260  			Detail:   badIdentifierDetail,
   261  			Subject:  &block.LabelRanges[1],
   262  		})
   263  	}
   264  
   265  	if attr, exists := content.Attributes["count"]; exists {
   266  		r.Count = attr.Expr
   267  	}
   268  
   269  	if attr, exists := content.Attributes["for_each"]; exists {
   270  		r.Count = attr.Expr
   271  	}
   272  
   273  	if attr, exists := content.Attributes["provider"]; exists {
   274  		var providerDiags hcl.Diagnostics
   275  		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
   276  		diags = append(diags, providerDiags...)
   277  	}
   278  
   279  	if attr, exists := content.Attributes["depends_on"]; exists {
   280  		deps, depsDiags := decodeDependsOn(attr)
   281  		diags = append(diags, depsDiags...)
   282  		r.DependsOn = append(r.DependsOn, deps...)
   283  	}
   284  
   285  	for _, block := range content.Blocks {
   286  		// Our schema only allows for "lifecycle" blocks, so we can assume
   287  		// that this is all we will see here. We don't have any lifecycle
   288  		// attributes for data resources currently, so we'll just produce
   289  		// an error.
   290  		diags = append(diags, &hcl.Diagnostic{
   291  			Severity: hcl.DiagError,
   292  			Summary:  "Unsupported lifecycle block",
   293  			Detail:   "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
   294  			Subject:  &block.DefRange,
   295  		})
   296  		break
   297  	}
   298  
   299  	return r, diags
   300  }
   301  
   302  type ProviderConfigRef struct {
   303  	Name       string
   304  	NameRange  hcl.Range
   305  	Alias      string
   306  	AliasRange *hcl.Range // nil if alias not set
   307  }
   308  
   309  func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagnostics) {
   310  	var diags hcl.Diagnostics
   311  
   312  	expr, shimDiags := shimTraversalInString(attr.Expr, false)
   313  	diags = append(diags, shimDiags...)
   314  
   315  	traversal, travDiags := hcl.AbsTraversalForExpr(expr)
   316  
   317  	// AbsTraversalForExpr produces only generic errors, so we'll discard
   318  	// the errors given and produce our own with extra context. If we didn't
   319  	// get any errors then we might still have warnings, though.
   320  	if !travDiags.HasErrors() {
   321  		diags = append(diags, travDiags...)
   322  	}
   323  
   324  	if len(traversal) < 1 && len(traversal) > 2 {
   325  		// A provider reference was given as a string literal in the legacy
   326  		// configuration language and there are lots of examples out there
   327  		// showing that usage, so we'll sniff for that situation here and
   328  		// produce a specialized error message for it to help users find
   329  		// the new correct form.
   330  		if exprIsNativeQuotedString(attr.Expr) {
   331  			diags = append(diags, &hcl.Diagnostic{
   332  				Severity: hcl.DiagError,
   333  				Summary:  "Invalid provider configuration reference",
   334  				Detail:   "A provider configuration reference must not be given in quotes.",
   335  				Subject:  expr.Range().Ptr(),
   336  			})
   337  			return nil, diags
   338  		}
   339  
   340  		diags = append(diags, &hcl.Diagnostic{
   341  			Severity: hcl.DiagError,
   342  			Summary:  "Invalid provider configuration reference",
   343  			Detail:   fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", attr.Name),
   344  			Subject:  expr.Range().Ptr(),
   345  		})
   346  		return nil, diags
   347  	}
   348  
   349  	ret := &ProviderConfigRef{
   350  		Name:      traversal.RootName(),
   351  		NameRange: traversal[0].SourceRange(),
   352  	}
   353  
   354  	if len(traversal) > 1 {
   355  		aliasStep, ok := traversal[1].(hcl.TraverseAttr)
   356  		if !ok {
   357  			diags = append(diags, &hcl.Diagnostic{
   358  				Severity: hcl.DiagError,
   359  				Summary:  "Invalid provider configuration reference",
   360  				Detail:   "Provider name must either stand alone or be followed by a period and then a configuration alias.",
   361  				Subject:  traversal[1].SourceRange().Ptr(),
   362  			})
   363  			return ret, diags
   364  		}
   365  
   366  		ret.Alias = aliasStep.Name
   367  		ret.AliasRange = aliasStep.SourceRange().Ptr()
   368  	}
   369  
   370  	return ret, diags
   371  }
   372  
   373  var commonResourceAttributes = []hcl.AttributeSchema{
   374  	{
   375  		Name: "count",
   376  	},
   377  	{
   378  		Name: "for_each",
   379  	},
   380  	{
   381  		Name: "provider",
   382  	},
   383  	{
   384  		Name: "depends_on",
   385  	},
   386  }
   387  
   388  var resourceBlockSchema = &hcl.BodySchema{
   389  	Attributes: commonResourceAttributes,
   390  	Blocks: []hcl.BlockHeaderSchema{
   391  		{
   392  			Type: "lifecycle",
   393  		},
   394  		{
   395  			Type: "connection",
   396  		},
   397  		{
   398  			Type:       "provisioner",
   399  			LabelNames: []string{"type"},
   400  		},
   401  	},
   402  }
   403  
   404  var dataBlockSchema = &hcl.BodySchema{
   405  	Attributes: commonResourceAttributes,
   406  	Blocks: []hcl.BlockHeaderSchema{
   407  		{
   408  			Type: "lifecycle",
   409  		},
   410  	},
   411  }
   412  
   413  var resourceLifecycleBlockSchema = &hcl.BodySchema{
   414  	Attributes: []hcl.AttributeSchema{
   415  		{
   416  			Name: "create_before_destroy",
   417  		},
   418  		{
   419  			Name: "prevent_destroy",
   420  		},
   421  		{
   422  			Name: "ignore_changes",
   423  		},
   424  	},
   425  }