From b1bc7a792b9076ad7397a8ec00be888151de844e Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 8 Apr 2020 17:09:17 -0400 Subject: [PATCH] rename and cleanup use of count/for_each eval func Stop evaluating count and for each if they aren't set in the config. Remove "Resource" from the function names, as they are also now used with modules. --- terraform/eval_apply.go | 4 +- terraform/eval_count.go | 45 ++++++++++-------- terraform/eval_diff.go | 2 +- terraform/eval_for_each.go | 74 ++++++++++++++++-------------- terraform/eval_for_each_test.go | 24 ++++------ terraform/eval_read_data.go | 2 +- terraform/eval_state.go | 46 +++++++++---------- terraform/eval_validate.go | 11 +++-- terraform/node_data_refresh.go | 65 +++++++++++++------------- terraform/node_module_expand.go | 4 +- terraform/node_resource_refresh.go | 44 +++++++++--------- 11 files changed, 165 insertions(+), 156 deletions(-) diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index 788542a44..ed8e7bc10 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -61,7 +61,7 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { configVal := cty.NullVal(cty.DynamicPseudoType) if n.Config != nil { var configDiags tfdiags.Diagnostics - forEach, _ := evaluateResourceForEachExpression(n.Config.ForEach, ctx) + forEach, _ := evaluateForEachExpression(n.Config.ForEach, ctx) keyData := EvalDataForInstanceKey(n.Addr.Key, forEach) configVal, _, configDiags = ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData) diags = diags.Append(configDiags) @@ -595,7 +595,7 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisio // in its result. That's okay because each.value is prohibited for // destroy-time provisioners. if n.When != configs.ProvisionerWhenDestroy { - m, forEachDiags := evaluateResourceForEachExpression(n.ResourceConfig.ForEach, ctx) + m, forEachDiags := evaluateForEachExpression(n.ResourceConfig.ForEach, ctx) diags = diags.Append(forEachDiags) forEach = m } diff --git a/terraform/eval_count.go b/terraform/eval_count.go index 3da947f1f..99ddeafee 100644 --- a/terraform/eval_count.go +++ b/terraform/eval_count.go @@ -11,7 +11,7 @@ import ( "github.com/zclconf/go-cty/cty/gocty" ) -// evaluateResourceCountExpression is our standard mechanism for interpreting an +// evaluateCountExpression is our standard mechanism for interpreting an // expression given for a "count" argument on a resource. This should be called // from the DynamicExpand of a node representing a resource in order to // determine the final count value. @@ -24,9 +24,9 @@ import ( // // If error diagnostics are returned then the result is always the meaningless // placeholder value -1. -func evaluateResourceCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) { - count, known, diags := evaluateResourceCountExpressionKnown(expr, ctx) - if !known { +func evaluateCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) { + countVal, diags := evaluateCountExpressionValue(expr, ctx) + if !countVal.IsKnown() { // Currently this is a rather bad outcome from a UX standpoint, since we have // no real mechanism to deal with this situation and all we can do is produce // an error message. @@ -40,22 +40,29 @@ func evaluateResourceCountExpression(expr hcl.Expression, ctx EvalContext) (int, Subject: expr.Range().Ptr(), }) } - return count, diags + + if countVal.IsNull() || !countVal.IsKnown() { + return -1, diags + } + + count, _ := countVal.AsBigFloat().Int64() + return int(count), diags } -// evaluateResourceCountExpressionKnown is like evaluateResourceCountExpression -// except that it handles an unknown result by returning count = 0 and -// a known = false, rather than by reporting the unknown value as an error -// diagnostic. -func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) (count int, known bool, diags tfdiags.Diagnostics) { +// evaluateCountExpressionValue is like evaluateCountExpression +// except that it returns a cty.Value which must be a cty.Number and can be +// unknown. +func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + nullCount := cty.NullVal(cty.Number) if expr == nil { - return -1, true, nil + return nullCount, nil } countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil) diags = diags.Append(countDiags) if diags.HasErrors() { - return -1, true, diags + return nullCount, diags } switch { @@ -66,11 +73,13 @@ func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) Detail: `The given "count" argument value is null. An integer is required.`, Subject: expr.Range().Ptr(), }) - return -1, true, diags + return nullCount, diags + case !countVal.IsKnown(): - return 0, false, diags + return cty.UnknownVal(cty.Number), diags } + var count int err := gocty.FromCtyValue(countVal, &count) if err != nil { diags = diags.Append(&hcl.Diagnostic{ @@ -79,7 +88,7 @@ func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err), Subject: expr.Range().Ptr(), }) - return -1, true, diags + return nullCount, diags } if count < 0 { diags = diags.Append(&hcl.Diagnostic{ @@ -88,10 +97,10 @@ func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) Detail: `The given "count" argument value is unsuitable: negative numbers are not supported.`, Subject: expr.Range().Ptr(), }) - return -1, true, diags + return nullCount, diags } - return count, true, diags + return countVal, diags } // fixResourceCountSetTransition is a helper function to fix up the state when a @@ -101,7 +110,7 @@ func evaluateResourceCountExpressionKnown(expr hcl.Expression, ctx EvalContext) // // The correct time to call this function is in the DynamicExpand method for // a node representing a resource, just after evaluating the count with -// evaluateResourceCountExpression, and before any other analysis of the +// evaluateCountExpression, and before any other analysis of the // state such as orphan detection. // // This function calls methods on the given EvalContext to update the current diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index c5c24b49e..509bd5bbc 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -133,7 +133,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { // Should be caught during validation, so we don't bother with a pretty error here return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) } - forEach, _ := evaluateResourceForEachExpression(n.Config.ForEach, ctx) + forEach, _ := evaluateForEachExpression(n.Config.ForEach, ctx) keyData := EvalDataForInstanceKey(n.Addr.Key, forEach) configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData) diags = diags.Append(configDiags) diff --git a/terraform/eval_for_each.go b/terraform/eval_for_each.go index 599995728..978e034d9 100644 --- a/terraform/eval_for_each.go +++ b/terraform/eval_for_each.go @@ -8,14 +8,14 @@ import ( "github.com/zclconf/go-cty/cty" ) -// evaluateResourceForEachExpression interprets a "for_each" argument on a resource. +// evaluateForEachExpression interprets a "for_each" argument on a resource. // // Returns a cty.Value map, and diagnostics if necessary. It will return nil if // the expression is nil, and is used to distinguish between an unset for_each and an // empty map -func evaluateResourceForEachExpression(expr hcl.Expression, ctx EvalContext) (forEach map[string]cty.Value, diags tfdiags.Diagnostics) { - forEachMap, known, diags := evaluateResourceForEachExpressionKnown(expr, ctx) - if !known { +func evaluateForEachExpression(expr hcl.Expression, ctx EvalContext) (forEach map[string]cty.Value, diags tfdiags.Diagnostics) { + forEachVal, diags := evaluateForEachExpressionValue(expr, ctx) + if !forEachVal.IsKnown() { // Attach a diag as we do with count, with the same downsides diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -24,23 +24,31 @@ func evaluateResourceForEachExpression(expr hcl.Expression, ctx EvalContext) (fo Subject: expr.Range().Ptr(), }) } - return forEachMap, diags + + if forEachVal.IsNull() || !forEachVal.IsKnown() || forEachVal.LengthInt() == 0 { + // we check length, because an empty set return a nil map + return map[string]cty.Value{}, diags + } + + return forEachVal.AsValueMap(), diags } -// evaluateResourceForEachExpressionKnown is like evaluateResourceForEachExpression -// except that it handles an unknown result by returning an empty map and -// a known = false, rather than by reporting the unknown value as an error -// diagnostic. -func evaluateResourceForEachExpressionKnown(expr hcl.Expression, ctx EvalContext) (forEach map[string]cty.Value, known bool, diags tfdiags.Diagnostics) { +// evaluateForEachExpressionValue is like evaluateForEachExpression +// except that it returns a cty.Value map or set which can be unknown. +func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + nullMap := cty.NullVal(cty.Map(cty.DynamicPseudoType)) + if expr == nil { - return nil, true, nil + return nullMap, diags } forEachVal, forEachDiags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil) diags = diags.Append(forEachDiags) if diags.HasErrors() { - return nil, true, diags + return nullMap, diags } + ty := forEachVal.Type() switch { case forEachVal.IsNull(): @@ -50,44 +58,42 @@ func evaluateResourceForEachExpressionKnown(expr hcl.Expression, ctx EvalContext Detail: `The given "for_each" argument value is unsuitable: the given "for_each" argument value is null. A map, or set of strings is allowed.`, Subject: expr.Range().Ptr(), }) - return nil, true, diags + return nullMap, diags case !forEachVal.IsKnown(): - return map[string]cty.Value{}, false, diags - } + // ensure that we have a map, and not a DynamicValue + return cty.UnknownVal(cty.Map(cty.DynamicPseudoType)), diags - if !forEachVal.CanIterateElements() || forEachVal.Type().IsListType() || forEachVal.Type().IsTupleType() { + case !(ty.IsMapType() || ty.IsSetType() || ty.IsObjectType()): diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid for_each argument", - Detail: fmt.Sprintf(`The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type %s.`, forEachVal.Type().FriendlyName()), + Detail: fmt.Sprintf(`The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type %s.`, ty.FriendlyName()), Subject: expr.Range().Ptr(), }) - return nil, true, diags + return nullMap, diags + + case forEachVal.LengthInt() == 0: + // If the map is empty ({}), return an empty map, because cty will + // return nil when representing {} AsValueMap This also covers an empty + // set (toset([])) + return forEachVal, diags } - // If the map is empty ({}), return an empty map, because cty will return nil when representing {} AsValueMap - // This also covers an empty set (toset([])) - if forEachVal.LengthInt() == 0 { - return map[string]cty.Value{}, true, diags - } - - if forEachVal.Type().IsSetType() { - if forEachVal.Type().ElementType() != cty.String { + if ty.IsSetType() { + if ty.ElementType() != cty.String { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid for_each set argument", Detail: fmt.Sprintf(`The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type %s.`, forEachVal.Type().ElementType().FriendlyName()), Subject: expr.Range().Ptr(), }) - return nil, true, diags + return cty.NullVal(ty), diags } - // A set may contain unknown values that must be - // discovered by checking with IsWhollyKnown (which iterates through the - // structure), while for maps in cty, keys can never be unknown or null, - // thus the earlier IsKnown check suffices for maps + // since we can't use a set values that are unknown, we treat the + // entire set as unknown if !forEachVal.IsWhollyKnown() { - return map[string]cty.Value{}, false, diags + return cty.UnknownVal(ty), diags } // A set of strings may contain null, which makes it impossible to @@ -102,10 +108,10 @@ func evaluateResourceForEachExpressionKnown(expr hcl.Expression, ctx EvalContext Detail: fmt.Sprintf(`The given "for_each" argument value is unsuitable: "for_each" sets must not contain null values.`), Subject: expr.Range().Ptr(), }) - return nil, true, diags + return cty.NullVal(ty), diags } } } - return forEachVal.AsValueMap(), true, nil + return forEachVal, nil } diff --git a/terraform/eval_for_each_test.go b/terraform/eval_for_each_test.go index 5f0cabcf0..83aa4595e 100644 --- a/terraform/eval_for_each_test.go +++ b/terraform/eval_for_each_test.go @@ -12,7 +12,7 @@ import ( "github.com/zclconf/go-cty/cty" ) -func TestEvaluateResourceForEachExpression_valid(t *testing.T) { +func TestEvaluateForEachExpression_valid(t *testing.T) { tests := map[string]struct { Expr hcl.Expression ForEachMap map[string]cty.Value @@ -58,7 +58,7 @@ func TestEvaluateResourceForEachExpression_valid(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() - forEachMap, diags := evaluateResourceForEachExpression(test.Expr, ctx) + forEachMap, diags := evaluateForEachExpression(test.Expr, ctx) if len(diags) != 0 { t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) @@ -75,7 +75,7 @@ func TestEvaluateResourceForEachExpression_valid(t *testing.T) { } } -func TestEvaluateResourceForEachExpression_errors(t *testing.T) { +func TestEvaluateForEachExpression_errors(t *testing.T) { tests := map[string]struct { Expr hcl.Expression Summary, DetailSubstring string @@ -131,7 +131,7 @@ func TestEvaluateResourceForEachExpression_errors(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() - _, diags := evaluateResourceForEachExpression(test.Expr, ctx) + _, diags := evaluateForEachExpression(test.Expr, ctx) if len(diags) != 1 { t.Fatalf("got %d diagnostics; want 1", diags) @@ -149,7 +149,7 @@ func TestEvaluateResourceForEachExpression_errors(t *testing.T) { } } -func TestEvaluateResourceForEachExpressionKnown(t *testing.T) { +func TestEvaluateForEachExpressionKnown(t *testing.T) { tests := map[string]hcl.Expression{ "unknown string set": hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))), "unknown map": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))), @@ -159,23 +159,15 @@ func TestEvaluateResourceForEachExpressionKnown(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() - forEachMap, known, diags := evaluateResourceForEachExpressionKnown(expr, ctx) + forEachVal, diags := evaluateForEachExpressionValue(expr, ctx) if len(diags) != 0 { t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) } - if known { - t.Errorf("got %v known, want false", known) + if forEachVal.IsKnown() { + t.Error("got known, want unknown") } - - if len(forEachMap) != 0 { - t.Errorf( - "expected empty map\ngot: %s", - spew.Sdump(forEachMap), - ) - } - }) } } diff --git a/terraform/eval_read_data.go b/terraform/eval_read_data.go index ef9fad038..b3f7fdbe5 100644 --- a/terraform/eval_read_data.go +++ b/terraform/eval_read_data.go @@ -96,7 +96,7 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) { objTy := schema.ImpliedType() priorVal := cty.NullVal(objTy) // for data resources, prior is always null because we start fresh every time - forEach, _ := evaluateResourceForEachExpression(n.Config.ForEach, ctx) + forEach, _ := evaluateForEachExpression(n.Config.ForEach, ctx) keyData := EvalDataForInstanceKey(n.Addr.Key, forEach) var configDiags tfdiags.Diagnostics diff --git a/terraform/eval_state.go b/terraform/eval_state.go index 7f626e96f..d12adf374 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -466,36 +466,32 @@ func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) { // can refer to it. Since this node represents the abstract module, we need // to expand the module here to create all resources. expander := ctx.InstanceExpander() - count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) - diags = diags.Append(countDiags) - if countDiags.HasErrors() { - return nil, diags.Err() - } - eachMode := states.NoEach - if count >= 0 { // -1 signals "count not set" - eachMode = states.EachList - } + switch { + case n.Config.Count != nil: + count, countDiags := evaluateCountExpression(n.Config.Count, ctx) + diags = diags.Append(countDiags) + if countDiags.HasErrors() { + return nil, diags.Err() + } - forEach, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx) - diags = diags.Append(forEachDiags) - if forEachDiags.HasErrors() { - return nil, diags.Err() - } - - if forEach != nil { - eachMode = states.EachMap - } - // This method takes care of all of the business logic of updating this - // while ensuring that any existing instances are preserved, etc. - state.SetResourceMeta(n.Addr, eachMode, n.ProviderAddr) - - switch eachMode { - case states.EachList: + state.SetResourceMeta(n.Addr, states.EachList, n.ProviderAddr) expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count) - case states.EachMap: + + case n.Config.ForEach != nil: + forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) + diags = diags.Append(forEachDiags) + if forEachDiags.HasErrors() { + return nil, diags.Err() + } + + // This method takes care of all of the business logic of updating this + // while ensuring that any existing instances are preserved, etc. + state.SetResourceMeta(n.Addr, states.EachMap, n.ProviderAddr) expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEach) + default: + state.SetResourceMeta(n.Addr, states.NoEach, n.ProviderAddr) expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource) } diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 9146d5577..00b80d37b 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -375,7 +375,9 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { mode := cfg.Mode keyData := EvalDataForNoInstanceKey - if n.Config.Count != nil { + + switch { + case n.Config.Count != nil: // If the config block has count, we'll evaluate with an unknown // number as count.index so we can still type check even though // we won't expand count until the plan phase. @@ -387,9 +389,8 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { // of this will happen when we DynamicExpand during the plan walk. countDiags := n.validateCount(ctx, n.Config.Count) diags = diags.Append(countDiags) - } - if n.Config.ForEach != nil { + case n.Config.ForEach != nil: keyData = InstanceKeyEvalData{ EachKey: cty.UnknownVal(cty.String), EachValue: cty.UnknownVal(cty.DynamicPseudoType), @@ -608,10 +609,10 @@ func (n *EvalValidateResource) validateCount(ctx EvalContext, expr hcl.Expressio } func (n *EvalValidateResource) validateForEach(ctx EvalContext, expr hcl.Expression) (diags tfdiags.Diagnostics) { - _, known, forEachDiags := evaluateResourceForEachExpressionKnown(expr, ctx) + val, forEachDiags := evaluateForEachExpressionValue(expr, ctx) // If the value isn't known then that's the best we can do for now, but // we'll check more thoroughly during the plan walk - if !known { + if !val.IsKnown() { return diags } diff --git a/terraform/node_data_refresh.go b/terraform/node_data_refresh.go index a01334c86..8e1b9d5f0 100644 --- a/terraform/node_data_refresh.go +++ b/terraform/node_data_refresh.go @@ -65,43 +65,46 @@ func (n *NodeRefreshableDataResource) Path() addrs.ModuleInstance { func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) { var diags tfdiags.Diagnostics - count, countKnown, countDiags := evaluateResourceCountExpressionKnown(n.Config.Count, ctx) - diags = diags.Append(countDiags) - if countDiags.HasErrors() { - return nil, diags.Err() - } - if !countKnown { - // If the count isn't known yet, we'll skip refreshing and try expansion - // again during the plan walk. - return nil, nil - } + expander := ctx.InstanceExpander() - forEachMap, forEachKnown, forEachDiags := evaluateResourceForEachExpressionKnown(n.Config.ForEach, ctx) - diags = diags.Append(forEachDiags) - if forEachDiags.HasErrors() { - return nil, diags.Err() - } - if !forEachKnown { - // If the for_each isn't known yet, we'll skip refreshing and try expansion - // again during the plan walk. - return nil, nil + switch { + case n.Config.Count != nil: + count, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx) + diags = diags.Append(countDiags) + if countDiags.HasErrors() { + return nil, diags.Err() + } + if !count.IsKnown() { + // If the count isn't known yet, we'll skip refreshing and try expansion + // again during the plan walk. + return nil, nil + } + + c, _ := count.AsBigFloat().Int64() + expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, int(c)) + + case n.Config.ForEach != nil: + forEachVal, forEachDiags := evaluateForEachExpressionValue(n.Config.ForEach, ctx) + diags = diags.Append(forEachDiags) + if forEachDiags.HasErrors() { + return nil, diags.Err() + } + if !forEachVal.IsKnown() { + // If the for_each isn't known yet, we'll skip refreshing and try expansion + // again during the plan walk. + return nil, nil + } + + expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachVal.AsValueMap()) + + default: + expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource) } // Next we need to potentially rename an instance address in the state // if we're transitioning whether "count" is set at all. - fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) + fixResourceCountSetTransition(ctx, n.ResourceAddr(), n.Config.Count != nil) - // Inform our instance expander about our expansion results above, - // and then use it to calculate the instance addresses we'll expand for. - expander := ctx.InstanceExpander() - switch { - case count >= 0: - expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count) - case forEachMap != nil: - expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachMap) - default: - expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource) - } instanceAddrs := expander.ExpandResource(n.Addr) // Our graph transformers require access to the full state, so we'll diff --git a/terraform/node_module_expand.go b/terraform/node_module_expand.go index a0669b7d4..057ac2614 100644 --- a/terraform/node_module_expand.go +++ b/terraform/node_module_expand.go @@ -229,14 +229,14 @@ func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error) switch { case n.ModuleCall.Count != nil: - count, diags := evaluateResourceCountExpression(n.ModuleCall.Count, ctx) + count, diags := evaluateCountExpression(n.ModuleCall.Count, ctx) if diags.HasErrors() { return nil, diags.Err() } expander.SetModuleCount(module, call, count) case n.ModuleCall.ForEach != nil: - forEach, diags := evaluateResourceForEachExpression(n.ModuleCall.ForEach, ctx) + forEach, diags := evaluateForEachExpression(n.ModuleCall.ForEach, ctx) if diags.HasErrors() { return nil, diags.Err() } diff --git a/terraform/node_resource_refresh.go b/terraform/node_resource_refresh.go index fa73b275b..0956bcd88 100644 --- a/terraform/node_resource_refresh.go +++ b/terraform/node_resource_refresh.go @@ -89,32 +89,34 @@ func (n *NodeRefreshableManagedResource) Path() addrs.ModuleInstance { func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { var diags tfdiags.Diagnostics - count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) - diags = diags.Append(countDiags) - if countDiags.HasErrors() { - return nil, diags.Err() - } + expander := ctx.InstanceExpander() + // Inform our instance expander about our expansion results, and then use + // it to calculate the instance addresses we'll expand for. + switch { + case n.Config.Count != nil: + count, countDiags := evaluateCountExpression(n.Config.Count, ctx) + diags = diags.Append(countDiags) + if countDiags.HasErrors() { + return nil, diags.Err() + } - forEachMap, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx) - if forEachDiags.HasErrors() { - return nil, diags.Err() + expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count) + + case n.Config.ForEach != nil: + forEachMap, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) + if forEachDiags.HasErrors() { + return nil, diags.Err() + } + + expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachMap) + + default: + expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource) } // Next we need to potentially rename an instance address in the state // if we're transitioning whether "count" is set at all. - fixResourceCountSetTransition(ctx, n.Addr.Config(), count != -1) - - // Inform our instance expander about our expansion results above, - // and then use it to calculate the instance addresses we'll expand for. - expander := ctx.InstanceExpander() - switch { - case count >= 0: - expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count) - case forEachMap != nil: - expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachMap) - default: - expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource) - } + fixResourceCountSetTransition(ctx, n.Addr.Config(), n.Config.Count != nil) instanceAddrs := expander.ExpandResource(n.Addr) // Our graph transformers require access to the full state, so we'll