github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/eval_count.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/terraform/internal/tfdiags" 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/gocty" 10 ) 11 12 // evaluateCountExpression is our standard mechanism for interpreting an 13 // expression given for a "count" argument on a resource or a module. This 14 // should be called during expansion in order to determine the final count 15 // value. 16 // 17 // evaluateCountExpression differs from evaluateCountExpressionValue by 18 // returning an error if the count value is not known, and converting the 19 // cty.Value to an integer. 20 func evaluateCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) { 21 countVal, diags := evaluateCountExpressionValue(expr, ctx) 22 if !countVal.IsKnown() { 23 // Currently this is a rather bad outcome from a UX standpoint, since we have 24 // no real mechanism to deal with this situation and all we can do is produce 25 // an error message. 26 // FIXME: In future, implement a built-in mechanism for deferring changes that 27 // can't yet be predicted, and use it to guide the user through several 28 // plan/apply steps until the desired configuration is eventually reached. 29 diags = diags.Append(&hcl.Diagnostic{ 30 Severity: hcl.DiagError, 31 Summary: "Invalid count argument", 32 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.`, 33 Subject: expr.Range().Ptr(), 34 35 // TODO: Also populate Expression and EvalContext in here, but 36 // we can't easily do that right now because the hcl.EvalContext 37 // (which is not the same as the ctx we have in scope here) is 38 // hidden away inside evaluateCountExpressionValue. 39 Extra: diagnosticCausedByUnknown(true), 40 }) 41 } 42 43 if countVal.IsNull() || !countVal.IsKnown() { 44 return -1, diags 45 } 46 47 count, _ := countVal.AsBigFloat().Int64() 48 return int(count), diags 49 } 50 51 // evaluateCountExpressionValue is like evaluateCountExpression 52 // except that it returns a cty.Value which must be a cty.Number and can be 53 // unknown. 54 func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) { 55 var diags tfdiags.Diagnostics 56 nullCount := cty.NullVal(cty.Number) 57 if expr == nil { 58 return nullCount, nil 59 } 60 61 countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil) 62 diags = diags.Append(countDiags) 63 if diags.HasErrors() { 64 return nullCount, diags 65 } 66 67 // Unmark the count value, sensitive values are allowed in count but not for_each, 68 // as using it here will not disclose the sensitive value 69 countVal, _ = countVal.Unmark() 70 71 switch { 72 case countVal.IsNull(): 73 diags = diags.Append(&hcl.Diagnostic{ 74 Severity: hcl.DiagError, 75 Summary: "Invalid count argument", 76 Detail: `The given "count" argument value is null. An integer is required.`, 77 Subject: expr.Range().Ptr(), 78 }) 79 return nullCount, diags 80 81 case !countVal.IsKnown(): 82 return cty.UnknownVal(cty.Number), diags 83 } 84 85 var count int 86 err := gocty.FromCtyValue(countVal, &count) 87 if err != nil { 88 diags = diags.Append(&hcl.Diagnostic{ 89 Severity: hcl.DiagError, 90 Summary: "Invalid count argument", 91 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err), 92 Subject: expr.Range().Ptr(), 93 }) 94 return nullCount, diags 95 } 96 if count < 0 { 97 diags = diags.Append(&hcl.Diagnostic{ 98 Severity: hcl.DiagError, 99 Summary: "Invalid count argument", 100 Detail: `The given "count" argument value is unsuitable: must be greater than or equal to zero.`, 101 Subject: expr.Range().Ptr(), 102 }) 103 return nullCount, diags 104 } 105 106 return countVal, diags 107 }