diff --git a/backend/local/hook_count.go b/backend/local/hook_count.go index 4708159dc..0981ec1c0 100644 --- a/backend/local/hook_count.go +++ b/backend/local/hook_count.go @@ -100,6 +100,11 @@ func (h *CountHook) PostDiff( return terraform.HookActionContinue, nil } + // Don't count anything for a Quiet diff + if d.Quiet { + return terraform.HookActionContinue, nil + } + switch d.ChangeType() { case terraform.DiffDestroyCreate: h.ToRemoveAndAdd += 1 diff --git a/terraform/diff.go b/terraform/diff.go index a9fae6c2c..b179a2766 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -373,6 +373,11 @@ type InstanceDiff struct { // mean to be used for additional data a resource may want to pass through. // The value here must only contain Go primitives and collections. Meta map[string]interface{} + + // Quiet should be set when this diff exists only for purposes of providing a + // diff to various pre-plan or dry-run steps in the graph. A diff with this + // enabled should not be acted on in the plan. + Quiet bool } func (d *InstanceDiff) Lock() { d.mu.Lock() } diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index 6f09526a4..b5258c9fb 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -81,6 +81,11 @@ type EvalDiff struct { // Resource is needed to fetch the ignore_changes list so we can // filter user-requested ignored attributes from the diff. Resource *config.Resource + + // Quiet is used to indicate that this count should not be used in the UI. + // This is used with refresh nodes on scale-out so that resources do not get + // counted twice in the UI output. + Quiet bool } // TODO: test @@ -157,6 +162,9 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { return nil, err } + // Flag quiet to ensure that this resource is skipped in post-diff hooks, such as count, etc. + diff.Quiet = n.Quiet + // Call post-refresh hook err = ctx.Hook(func(h Hook) (HookAction, error) { return h.PostDiff(n.Info, diff) @@ -165,8 +173,10 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { return nil, err } - // Update our output - *n.OutputDiff = diff + // Update our output if we care + if n.OutputDiff != nil { + *n.OutputDiff = diff + } // Update the state if we care if n.OutputState != nil { diff --git a/terraform/node_resource_refresh.go b/terraform/node_resource_refresh.go index 6ab9df7a2..a9e60b168 100644 --- a/terraform/node_resource_refresh.go +++ b/terraform/node_resource_refresh.go @@ -45,13 +45,6 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, Addr: n.ResourceAddr(), }, - // Switch up any node missing state to a plannable resource. This helps - // catch cases where data sources depend on the counts from this resource - // during a scale out. - &ResourceRefreshPlannableTransformer{ - State: state, - }, - // Add the count orphans to make sure these resources are accounted for // during a scale in. &OrphanResourceCountTransformer{ @@ -100,6 +93,9 @@ func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode { // Eval info is different depending on what kind of resource this is switch mode := n.Addr.Mode; mode { case config.ManagedResourceMode: + if n.ResourceState == nil { + return n.evalTreeManagedScaleInResource() + } return n.evalTreeManagedResource() case config.DataResourceMode: @@ -176,3 +172,77 @@ func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalN }, } } + +func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedScaleInResource() EvalNode { + // Declare a bunch of variables that are used for state during + // evaluation. Most of this are written to by-address below. + var provider ResourceProvider + var state *InstanceState + var resourceConfig *ResourceConfig + + addr := n.NodeAbstractResource.Addr + stateID := addr.stateId() + info := &InstanceInfo{ + Id: stateID, + Type: addr.Type, + ModulePath: normalizeModulePath(addr.Path), + } + + // Build the resource for eval + resource := &Resource{ + Name: addr.Name, + Type: addr.Type, + CountIndex: addr.Index, + } + if resource.CountIndex < 0 { + resource.CountIndex = 0 + } + + // Determine the dependencies for the state. + stateDeps := n.StateReferences() + + return &EvalSequence{ + Nodes: []EvalNode{ + &EvalInterpolate{ + Config: n.Config.RawConfig.Copy(), + Resource: resource, + Output: &resourceConfig, + }, + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + // Re-run validation to catch any errors we missed, e.g. type + // mismatches on computed values. + &EvalValidateResource{ + Provider: &provider, + Config: &resourceConfig, + ResourceName: n.Config.Name, + ResourceType: n.Config.Type, + ResourceMode: n.Config.Mode, + IgnoreWarnings: true, + }, + &EvalReadState{ + Name: stateID, + Output: &state, + }, + &EvalDiff{ + Name: stateID, + Info: info, + Config: &resourceConfig, + Resource: n.Config, + Provider: &provider, + State: &state, + OutputState: &state, + Quiet: true, + }, + &EvalWriteState{ + Name: stateID, + ResourceType: n.Config.Type, + Provider: n.Config.Provider, + Dependencies: stateDeps, + State: &state, + }, + }, + } +}