github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/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/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/tfdiags"
    10  	"github.com/zclconf/go-cty/cty"
    11  	"github.com/zclconf/go-cty/cty/gocty"
    12  )
    13  
    14  // evaluateCountExpression is our standard mechanism for interpreting an
    15  // expression given for a "count" argument on a resource or a module. This
    16  // should be called during expansion in order to determine the final count
    17  // value.
    18  //
    19  // evaluateCountExpression differs from evaluateCountExpressionValue by
    20  // returning an error if the count value is not known, and converting the
    21  // cty.Value to an integer.
    22  func evaluateCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) {
    23  	countVal, diags := evaluateCountExpressionValue(expr, ctx)
    24  	if !countVal.IsKnown() {
    25  		// Currently this is a rather bad outcome from a UX standpoint, since we have
    26  		// no real mechanism to deal with this situation and all we can do is produce
    27  		// an error message.
    28  		// FIXME: In future, implement a built-in mechanism for deferring changes that
    29  		// can't yet be predicted, and use it to guide the user through several
    30  		// plan/apply steps until the desired configuration is eventually reached.
    31  		diags = diags.Append(&hcl.Diagnostic{
    32  			Severity: hcl.DiagError,
    33  			Summary:  "Invalid count argument",
    34  			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.`,
    35  			Subject:  expr.Range().Ptr(),
    36  		})
    37  	}
    38  
    39  	if countVal.IsNull() || !countVal.IsKnown() {
    40  		return -1, diags
    41  	}
    42  
    43  	count, _ := countVal.AsBigFloat().Int64()
    44  	return int(count), diags
    45  }
    46  
    47  // evaluateCountExpressionValue is like evaluateCountExpression
    48  // except that it returns a cty.Value which must be a cty.Number and can be
    49  // unknown.
    50  func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) {
    51  	var diags tfdiags.Diagnostics
    52  	nullCount := cty.NullVal(cty.Number)
    53  	if expr == nil {
    54  		return nullCount, nil
    55  	}
    56  
    57  	countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
    58  	diags = diags.Append(countDiags)
    59  	if diags.HasErrors() {
    60  		return nullCount, diags
    61  	}
    62  
    63  	// Unmark the count value, sensitive values are allowed in count but not for_each,
    64  	// as using it here will not disclose the sensitive value
    65  	countVal, _ = countVal.Unmark()
    66  
    67  	switch {
    68  	case countVal.IsNull():
    69  		diags = diags.Append(&hcl.Diagnostic{
    70  			Severity: hcl.DiagError,
    71  			Summary:  "Invalid count argument",
    72  			Detail:   `The given "count" argument value is null. An integer is required.`,
    73  			Subject:  expr.Range().Ptr(),
    74  		})
    75  		return nullCount, diags
    76  
    77  	case !countVal.IsKnown():
    78  		return cty.UnknownVal(cty.Number), diags
    79  	}
    80  
    81  	var count int
    82  	err := gocty.FromCtyValue(countVal, &count)
    83  	if err != nil {
    84  		diags = diags.Append(&hcl.Diagnostic{
    85  			Severity: hcl.DiagError,
    86  			Summary:  "Invalid count argument",
    87  			Detail:   fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
    88  			Subject:  expr.Range().Ptr(),
    89  		})
    90  		return nullCount, diags
    91  	}
    92  	if count < 0 {
    93  		diags = diags.Append(&hcl.Diagnostic{
    94  			Severity: hcl.DiagError,
    95  			Summary:  "Invalid count argument",
    96  			Detail:   `The given "count" argument value is unsuitable: negative numbers are not supported.`,
    97  			Subject:  expr.Range().Ptr(),
    98  		})
    99  		return nullCount, diags
   100  	}
   101  
   102  	return countVal, diags
   103  }
   104  
   105  // fixResourceCountSetTransition is a helper function to fix up the state when a
   106  // resource transitions its "count" from being set to unset or vice-versa,
   107  // treating a 0-key and a no-key instance as aliases for one another across
   108  // the transition.
   109  //
   110  // The correct time to call this function is in the DynamicExpand method for
   111  // a node representing a resource, just after evaluating the count with
   112  // evaluateCountExpression, and before any other analysis of the
   113  // state such as orphan detection.
   114  //
   115  // This function calls methods on the given EvalContext to update the current
   116  // state in-place, if necessary. It is a no-op if there is no count transition
   117  // taking place.
   118  //
   119  // Since the state is modified in-place, this function must take a writer lock
   120  // on the state. The caller must therefore not also be holding a state lock,
   121  // or this function will block forever awaiting the lock.
   122  func fixResourceCountSetTransition(ctx EvalContext, addr addrs.ConfigResource, countEnabled bool) {
   123  	state := ctx.State()
   124  	if state.MaybeFixUpResourceInstanceAddressForCount(addr, countEnabled) {
   125  		log.Printf("[TRACE] renamed first %s instance in transient state due to count argument change", addr)
   126  	}
   127  
   128  	refreshState := ctx.RefreshState()
   129  	if refreshState != nil && refreshState.MaybeFixUpResourceInstanceAddressForCount(addr, countEnabled) {
   130  		log.Printf("[TRACE] renamed first %s instance in transient state due to count argument change", addr)
   131  	}
   132  }