core: Don't count scaled-out resources twice in the UI

This fixes a bug with the new refresh graph behaviour where a resource
was being counted twice in the UI on part of being scaled out:

 * We are no longer transforming refresh nodes without state to
   plannable resources (the transformer will be removed shortly)
 * A Quiet flag has been added to EvalDiff and InstanceDiff - this
   allows for the flagging of a diff that should not be treated as real
   diff for purposes of planning
 * When there is no state for a refresh node now, a new path is taken
   that is similar to plan, but flags Quiet, and does nothing with the
   diff afterwards.

Tests pending - light testing has confirmed this should fix the double
count issue, but we should have some tests to actually confirm the bug.
This commit is contained in:
Chris Marchesi 2017-06-20 07:37:32 -07:00
parent afe891a80e
commit eef933f2a7
4 changed files with 99 additions and 9 deletions

View File

@ -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

View File

@ -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() }

View File

@ -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 {

View File

@ -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,
},
},
}
}