2017-01-21 13:22:40 -06:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config"
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
"github.com/hashicorp/terraform/dag"
|
2017-01-21 13:22:40 -06:00
|
|
|
)
|
|
|
|
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
// NodeRefreshableManagedResource represents a resource that is expanabled into
|
|
|
|
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
|
|
|
|
type NodeRefreshableManagedResource struct {
|
|
|
|
*NodeAbstractCountResource
|
|
|
|
}
|
|
|
|
|
|
|
|
// GraphNodeDynamicExpandable
|
|
|
|
func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
|
|
// Grab the state which we read
|
|
|
|
state, lock := ctx.State()
|
|
|
|
lock.RLock()
|
|
|
|
defer lock.RUnlock()
|
|
|
|
|
|
|
|
// Expand the resource count which must be available by now from EvalTree
|
|
|
|
count, err := n.Config.Count()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The concrete resource factory we'll use
|
|
|
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
|
|
|
// Add the config and state since we don't do that via transforms
|
|
|
|
a.Config = n.Config
|
|
|
|
|
|
|
|
return &NodeRefreshableManagedResourceInstance{
|
|
|
|
NodeAbstractResource: a,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start creating the steps
|
|
|
|
steps := []GraphTransformer{
|
|
|
|
// Expand the count.
|
|
|
|
&ResourceCountTransformer{
|
|
|
|
Concrete: concreteResource,
|
|
|
|
Count: count,
|
|
|
|
Addr: n.ResourceAddr(),
|
|
|
|
},
|
|
|
|
|
|
|
|
// Add the count orphans to make sure these resources are accounted for
|
|
|
|
// during a scale in.
|
|
|
|
&OrphanResourceCountTransformer{
|
|
|
|
Concrete: concreteResource,
|
|
|
|
Count: count,
|
|
|
|
Addr: n.ResourceAddr(),
|
|
|
|
State: state,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Attach the state
|
|
|
|
&AttachStateTransformer{State: state},
|
|
|
|
|
|
|
|
// Targeting
|
|
|
|
&TargetsTransformer{ParsedTargets: n.Targets},
|
|
|
|
|
|
|
|
// Connect references so ordering is correct
|
|
|
|
&ReferenceTransformer{},
|
|
|
|
|
|
|
|
// Make sure there is a single root
|
|
|
|
&RootTransformer{},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the graph
|
|
|
|
b := &BasicGraphBuilder{
|
|
|
|
Steps: steps,
|
|
|
|
Validate: true,
|
|
|
|
Name: "NodeRefreshableManagedResource",
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.Build(ctx.Path())
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
|
2017-01-21 13:22:40 -06:00
|
|
|
// it is ready to be applied and is represented by a diff.
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
type NodeRefreshableManagedResourceInstance struct {
|
2017-01-21 13:22:40 -06:00
|
|
|
*NodeAbstractResource
|
|
|
|
}
|
|
|
|
|
|
|
|
// GraphNodeDestroyer
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *ResourceAddress {
|
2017-01-21 13:22:40 -06:00
|
|
|
return n.Addr
|
|
|
|
}
|
|
|
|
|
|
|
|
// GraphNodeEvalable
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
|
2017-01-21 13:22:40 -06:00
|
|
|
// Eval info is different depending on what kind of resource this is
|
|
|
|
switch mode := n.Addr.Mode; mode {
|
|
|
|
case config.ManagedResourceMode:
|
2017-06-20 09:37:32 -05:00
|
|
|
if n.ResourceState == nil {
|
2017-06-22 05:43:05 -05:00
|
|
|
return n.evalTreeManagedScaleOutResource()
|
2017-06-20 09:37:32 -05:00
|
|
|
}
|
2017-01-21 13:22:40 -06:00
|
|
|
return n.evalTreeManagedResource()
|
2017-02-03 13:58:03 -06:00
|
|
|
|
2017-01-21 13:22:40 -06:00
|
|
|
case config.DataResourceMode:
|
2017-02-03 13:58:03 -06:00
|
|
|
// Get the data source node. If we don't have a configuration
|
|
|
|
// then it is an orphan so we destroy it (remove it from the state).
|
|
|
|
var dn GraphNodeEvalable
|
|
|
|
if n.Config != nil {
|
|
|
|
dn = &NodeRefreshableDataResourceInstance{
|
|
|
|
NodeAbstractResource: n.NodeAbstractResource,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dn = &NodeDestroyableDataResource{
|
|
|
|
NodeAbstractResource: n.NodeAbstractResource,
|
|
|
|
}
|
2017-01-22 18:05:10 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return dn.EvalTree()
|
2017-01-21 13:22:40 -06:00
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unsupported resource mode %s", mode))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
core: New refresh graph building behaviour
Currently, the refresh graph uses the resources from state as a base,
with data sources then layered on. Config is not consulted for resources
and hence new resources that are added with count (or any new resource
from config, for that matter) do not get added to the graph during
refresh.
This is leading to issues with scale in and scale out when the same
value for count is used in both resources, and data sources that may
depend on that resource (and possibly vice versa). While the resources
exist in config and can be used, the fact that ConfigTransformer for
resources is missing means that they don't get added into the graph,
leading to "index out of range" errors and what not.
Further to that, if we add these new resources to the graph for scale
out, considerations need to be taken for scale in as well, which are not
being caught 100% by the current implementation of
NodeRefreshableDataResource. Scale-in resources should be treated as
orphans, which according to the instance-form NodeRefreshableResource
node, should be NodeDestroyableDataResource nodes, but this this logic
is currently not rolled into NodeRefreshableDataResource. This causes
issues on scale-in in the form of race-ish "index out of range" errors
again.
This commit updates the refresh graph so that StateTransformer is no
longer used as the base of the graph. Instead, we add resources from the
state and config in a hybrid fashion:
* First off, resource nodes are added from config, but only if
resources currently exist in state. NodeRefreshableManagedResource
is a new expandable resource node that will expand count and add
orphans from state. Any count-expanded node that has config but no
state is also transformed into a plannable resource, via a new
ResourceRefreshPlannableTransformer.
* The NodeRefreshableDataResource node type will now add count orphans
as NodeDestroyableDataResource nodes. This achieves the same effect
as if the data sources were added by StateTransformer, but ensures
there are no races in the dependency chain, with the added benefit of
directing these nodes straight to the proper
NodeDestroyableDataResource node.
* Finally, config orphans (nodes that don't exist in config anymore
period) are then added, to complete the graph.
This should ensure as much as possible that there is a refresh graph
that best represents both the current state and config with updated
variables and counts.
2017-04-30 01:07:01 -05:00
|
|
|
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
|
2017-01-21 13:22:40 -06:00
|
|
|
addr := n.NodeAbstractResource.Addr
|
|
|
|
|
|
|
|
// stateId is the ID to put into the state
|
|
|
|
stateId := addr.stateId()
|
|
|
|
|
|
|
|
// Build the instance info. More of this will be populated during eval
|
|
|
|
info := &InstanceInfo{
|
|
|
|
Id: stateId,
|
|
|
|
Type: addr.Type,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// This happened during initial development. All known cases were
|
|
|
|
// fixed and tested but as a sanity check let's assert here.
|
|
|
|
if n.ResourceState == nil {
|
|
|
|
err := fmt.Errorf(
|
|
|
|
"No resource state attached for addr: %s\n\n"+
|
|
|
|
"This is a bug. Please report this to Terraform with your configuration\n"+
|
|
|
|
"and state attached. Please be careful to scrub any sensitive information.",
|
|
|
|
addr)
|
|
|
|
return &EvalReturnError{Error: &err}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
&EvalReadState{
|
|
|
|
Name: stateId,
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
&EvalRefresh{
|
|
|
|
Info: info,
|
|
|
|
Provider: &provider,
|
|
|
|
State: &state,
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: stateId,
|
|
|
|
ResourceType: n.ResourceState.Type,
|
|
|
|
Provider: n.ResourceState.Provider,
|
|
|
|
Dependencies: n.ResourceState.Dependencies,
|
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2017-06-20 09:37:32 -05:00
|
|
|
|
2017-06-22 05:43:05 -05:00
|
|
|
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedScaleOutResource() EvalNode {
|
2017-06-20 09:37:32 -05:00
|
|
|
// 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,
|
2017-06-21 11:04:01 -05:00
|
|
|
Stub: true,
|
2017-06-20 09:37:32 -05:00
|
|
|
},
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: stateID,
|
|
|
|
ResourceType: n.Config.Type,
|
|
|
|
Provider: n.Config.Provider,
|
|
|
|
Dependencies: stateDeps,
|
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|