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  }