github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/evaluate_triggers.go (about)

     1  package terraform
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/hclsyntax"
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/instances"
    10  	"github.com/hashicorp/terraform/internal/tfdiags"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  func evalReplaceTriggeredByExpr(expr hcl.Expression, keyData instances.RepetitionData) (*addrs.Reference, tfdiags.Diagnostics) {
    15  	var ref *addrs.Reference
    16  	var diags tfdiags.Diagnostics
    17  
    18  	traversal, diags := triggersExprToTraversal(expr, keyData)
    19  	if diags.HasErrors() {
    20  		return nil, diags
    21  	}
    22  
    23  	// We now have a static traversal, so we can just turn it into an addrs.Reference.
    24  	ref, ds := addrs.ParseRef(traversal)
    25  	diags = diags.Append(ds)
    26  
    27  	return ref, diags
    28  }
    29  
    30  // trggersExprToTraversal takes an hcl expression limited to the syntax allowed
    31  // in replace_triggered_by, and converts it to a static traversal. The
    32  // RepetitionData contains the data necessary to evaluate the only allowed
    33  // variables in the expression, count.index and each.key.
    34  func triggersExprToTraversal(expr hcl.Expression, keyData instances.RepetitionData) (hcl.Traversal, tfdiags.Diagnostics) {
    35  	var trav hcl.Traversal
    36  	var diags tfdiags.Diagnostics
    37  
    38  	switch e := expr.(type) {
    39  	case *hclsyntax.RelativeTraversalExpr:
    40  		t, d := triggersExprToTraversal(e.Source, keyData)
    41  		diags = diags.Append(d)
    42  		trav = append(trav, t...)
    43  		trav = append(trav, e.Traversal...)
    44  
    45  	case *hclsyntax.ScopeTraversalExpr:
    46  		// a static reference, we can just append the traversal
    47  		trav = append(trav, e.Traversal...)
    48  
    49  	case *hclsyntax.IndexExpr:
    50  		// Get the collection from the index expression
    51  		t, d := triggersExprToTraversal(e.Collection, keyData)
    52  		diags = diags.Append(d)
    53  		if diags.HasErrors() {
    54  			return nil, diags
    55  		}
    56  		trav = append(trav, t...)
    57  
    58  		// The index key is the only place where we could have variables that
    59  		// reference count and each, so we need to parse those independently.
    60  		idx, hclDiags := parseIndexKeyExpr(e.Key, keyData)
    61  		diags = diags.Append(hclDiags)
    62  
    63  		trav = append(trav, idx)
    64  
    65  	default:
    66  		// Something unexpected got through config validation. We're not sure
    67  		// what it is, but we'll point it out in the diagnostics for the user
    68  		// to fix.
    69  		diags = diags.Append(&hcl.Diagnostic{
    70  			Severity: hcl.DiagError,
    71  			Summary:  "Invalid replace_triggered_by expression",
    72  			Detail:   "Unexpected expression found in replace_triggered_by.",
    73  			Subject:  e.Range().Ptr(),
    74  		})
    75  	}
    76  
    77  	return trav, diags
    78  }
    79  
    80  // parseIndexKeyExpr takes an hcl.Expression and parses it as an index key, while
    81  // evaluating any references to count.index or each.key.
    82  func parseIndexKeyExpr(expr hcl.Expression, keyData instances.RepetitionData) (hcl.TraverseIndex, hcl.Diagnostics) {
    83  	idx := hcl.TraverseIndex{
    84  		SrcRange: expr.Range(),
    85  	}
    86  
    87  	trav, diags := hcl.RelTraversalForExpr(expr)
    88  	if diags.HasErrors() {
    89  		return idx, diags
    90  	}
    91  
    92  	keyParts := []string{}
    93  
    94  	for _, t := range trav {
    95  		attr, ok := t.(hcl.TraverseAttr)
    96  		if !ok {
    97  			diags = append(diags, &hcl.Diagnostic{
    98  				Severity: hcl.DiagError,
    99  				Summary:  "Invalid index expression",
   100  				Detail:   "Only constant values, count.index or each.key are allowed in index expressions.",
   101  				Subject:  expr.Range().Ptr(),
   102  			})
   103  			return idx, diags
   104  		}
   105  		keyParts = append(keyParts, attr.Name)
   106  	}
   107  
   108  	switch strings.Join(keyParts, ".") {
   109  	case "count.index":
   110  		if keyData.CountIndex == cty.NilVal {
   111  			diags = diags.Append(&hcl.Diagnostic{
   112  				Severity: hcl.DiagError,
   113  				Summary:  `Reference to "count" in non-counted context`,
   114  				Detail:   `The "count" object can only be used in "resource" blocks when the "count" argument is set.`,
   115  				Subject:  expr.Range().Ptr(),
   116  			})
   117  		}
   118  		idx.Key = keyData.CountIndex
   119  
   120  	case "each.key":
   121  		if keyData.EachKey == cty.NilVal {
   122  			diags = diags.Append(&hcl.Diagnostic{
   123  				Severity: hcl.DiagError,
   124  				Summary:  `Reference to "each" in context without for_each`,
   125  				Detail:   `The "each" object can be used only in "resource" blocks when the "for_each" argument is set.`,
   126  				Subject:  expr.Range().Ptr(),
   127  			})
   128  		}
   129  		idx.Key = keyData.EachKey
   130  	default:
   131  		// Something may have slipped through validation, probably from a json
   132  		// configuration.
   133  		diags = append(diags, &hcl.Diagnostic{
   134  			Severity: hcl.DiagError,
   135  			Summary:  "Invalid index expression",
   136  			Detail:   "Only constant values, count.index or each.key are allowed in index expressions.",
   137  			Subject:  expr.Range().Ptr(),
   138  		})
   139  	}
   140  
   141  	return idx, diags
   142  
   143  }