github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_count.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 9 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 10 "github.com/zclconf/go-cty/cty" 11 "github.com/zclconf/go-cty/cty/gocty" 12 ) 13 14 // evaluateResourceCountExpression is our standard mechanism for interpreting an 15 // expression given for a "count" argument on a resource. This should be called 16 // from the DynamicExpand of a node representing a resource in order to 17 // determine the final count value. 18 // 19 // If the result is zero or positive and no error diagnostics are returned, then 20 // the result is the literal count value to use. 21 // 22 // If the result is -1, this indicates that the given expression is nil and so 23 // the "count" behavior should not be enabled for this resource at all. 24 // 25 // If error diagnostics are returned then the result is always the meaningless 26 // placeholder value -1. 27 func evaluateResourceCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) { 28 count, known, diags := evaluateResourceCountExpressionKnown(expr, ctx) 29 if !known { 30 // Currently this is a rather bad outcome from a UX standpoint, since we have 31 // no real mechanism to deal with this situation and all we can do is produce 32 // an error message. 33 // FIXME: In future, implement a built-in mechanism for deferring changes that 34 // can't yet be predicted, and use it to guide the user through several 35 // plan/apply steps until the desired configuration is eventually reached. 36 diags = diags.Append(&hcl.Diagnostic{ 37 Severity: hcl.DiagError, 38 Summary: "Invalid count argument", 39 Detail: `The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.`, 40 Subject: expr.Range().Ptr(), 41 }) 42 } 43 return count, diags 44 } 45 46 // evaluateResourceCountExpressionKnown is like evaluateResourceCountExpression 47 // except that it handles an unknown result by returning count = 0 and 48 // a known = false, rather than by reporting the unknown value as an error 49 // diagnostic. 50 func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) (count int, known bool, diags tfdiags.Diagnostics) { 51 if expr == nil { 52 return -1, true, nil 53 } 54 55 countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil) 56 diags = diags.Append(countDiags) 57 if diags.HasErrors() { 58 return -1, true, diags 59 } 60 61 switch { 62 case countVal.IsNull(): 63 diags = diags.Append(&hcl.Diagnostic{ 64 Severity: hcl.DiagError, 65 Summary: "Invalid count argument", 66 Detail: `The given "count" argument value is null. An integer is required.`, 67 Subject: expr.Range().Ptr(), 68 }) 69 return -1, true, diags 70 case !countVal.IsKnown(): 71 return 0, false, diags 72 } 73 74 err := gocty.FromCtyValue(countVal, &count) 75 if err != nil { 76 diags = diags.Append(&hcl.Diagnostic{ 77 Severity: hcl.DiagError, 78 Summary: "Invalid count argument", 79 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err), 80 Subject: expr.Range().Ptr(), 81 }) 82 return -1, true, diags 83 } 84 if count < 0 { 85 diags = diags.Append(&hcl.Diagnostic{ 86 Severity: hcl.DiagError, 87 Summary: "Invalid count argument", 88 Detail: `The given "count" argument value is unsuitable: negative numbers are not supported.`, 89 Subject: expr.Range().Ptr(), 90 }) 91 return -1, true, diags 92 } 93 94 return count, true, diags 95 } 96 97 // fixResourceCountSetTransition is a helper function to fix up the state when a 98 // resource transitions its "count" from being set to unset or vice-versa, 99 // treating a 0-key and a no-key instance as aliases for one another across 100 // the transition. 101 // 102 // The correct time to call this function is in the DynamicExpand method for 103 // a node representing a resource, just after evaluating the count with 104 // evaluateResourceCountExpression, and before any other analysis of the 105 // state such as orphan detection. 106 // 107 // This function calls methods on the given EvalContext to update the current 108 // state in-place, if necessary. It is a no-op if there is no count transition 109 // taking place. 110 // 111 // Since the state is modified in-place, this function must take a writer lock 112 // on the state. The caller must therefore not also be holding a state lock, 113 // or this function will block forever awaiting the lock. 114 func fixResourceCountSetTransition(ctx EvalContext, addr addrs.AbsResource, countEnabled bool) { 115 state := ctx.State() 116 changed := state.MaybeFixUpResourceInstanceAddressForCount(addr, countEnabled) 117 if changed { 118 log.Printf("[TRACE] renamed first %s instance in transient state due to count argument change", addr) 119 } 120 }