]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
107c1cdb ND |
4 | "fmt" |
5 | "log" | |
6 | ||
7 | "github.com/hashicorp/hcl2/hcl" | |
8 | "github.com/hashicorp/terraform/addrs" | |
9 | "github.com/hashicorp/terraform/tfdiags" | |
10 | "github.com/zclconf/go-cty/cty" | |
11 | "github.com/zclconf/go-cty/cty/gocty" | |
bae9f6d2 JC |
12 | ) |
13 | ||
107c1cdb ND |
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 | |
bae9f6d2 JC |
44 | } |
45 | ||
107c1cdb ND |
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 | |
bae9f6d2 JC |
53 | } |
54 | ||
107c1cdb ND |
55 | countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil) |
56 | diags = diags.Append(countDiags) | |
57 | if diags.HasErrors() { | |
58 | return -1, true, diags | |
bae9f6d2 JC |
59 | } |
60 | ||
107c1cdb ND |
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 | |
bae9f6d2 JC |
72 | } |
73 | ||
107c1cdb ND |
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 | |
bae9f6d2 | 83 | } |
107c1cdb ND |
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 | |
bae9f6d2 JC |
92 | } |
93 | ||
107c1cdb ND |
94 | return count, true, diags |
95 | } | |
bae9f6d2 | 96 | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 120 | } |