2015-02-08 23:00:13 +01:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-05-01 01:27:20 +02:00
|
|
|
"strings"
|
2015-02-08 23:00:13 +01:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
|
|
"github.com/hashicorp/terraform/dag"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ResourceCountTransformer is a GraphTransformer that expands the count
|
|
|
|
// out for a specific resource.
|
|
|
|
type ResourceCountTransformer struct {
|
|
|
|
Resource *config.Resource
|
2015-02-12 20:40:48 +01:00
|
|
|
Destroy bool
|
2015-03-31 02:02:36 +02:00
|
|
|
Targets []ResourceAddress
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
|
|
|
// Expand the resource count
|
|
|
|
count, err := t.Resource.Count()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow the count to be negative
|
|
|
|
if count < 0 {
|
|
|
|
return fmt.Errorf("negative count: %d", count)
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each count, build and add the node
|
2015-03-31 02:02:36 +02:00
|
|
|
nodes := make([]dag.Vertex, 0, count)
|
2015-02-08 23:00:13 +01:00
|
|
|
for i := 0; i < count; i++ {
|
2015-02-12 02:18:16 +01:00
|
|
|
// Set the index. If our count is 1 we special case it so that
|
|
|
|
// we handle the "resource.0" and "resource" boundary properly.
|
|
|
|
index := i
|
|
|
|
if count == 1 {
|
|
|
|
index = -1
|
|
|
|
}
|
|
|
|
|
2015-02-12 20:40:48 +01:00
|
|
|
// Save the node for later so we can do connections. Make the
|
|
|
|
// proper node depending on if we're just a destroy node or if
|
|
|
|
// were a regular node.
|
|
|
|
var node dag.Vertex = &graphNodeExpandedResource{
|
2015-02-12 02:18:16 +01:00
|
|
|
Index: index,
|
2015-02-08 23:00:13 +01:00
|
|
|
Resource: t.Resource,
|
2015-05-01 18:01:49 +02:00
|
|
|
Path: g.Path,
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
2015-02-12 20:40:48 +01:00
|
|
|
if t.Destroy {
|
|
|
|
node = &graphNodeExpandedResourceDestroy{
|
|
|
|
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
|
|
|
|
}
|
|
|
|
}
|
2015-02-08 23:00:13 +01:00
|
|
|
|
2015-03-31 02:02:36 +02:00
|
|
|
// Skip nodes if targeting excludes them
|
|
|
|
if !t.nodeIsTargeted(node) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-02-08 23:00:13 +01:00
|
|
|
// Add the node now
|
2015-03-31 02:02:36 +02:00
|
|
|
nodes = append(nodes, node)
|
|
|
|
g.Add(node)
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make the dependency connections
|
|
|
|
for _, n := range nodes {
|
|
|
|
// Connect the dependents. We ignore the return value for missing
|
|
|
|
// dependents since that should've been caught at a higher level.
|
|
|
|
g.ConnectDependent(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-31 02:02:36 +02:00
|
|
|
func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool {
|
|
|
|
// no targets specified, everything stays in the graph
|
|
|
|
if len(t.Targets) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
addressable, ok := node.(GraphNodeAddressable)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
addr := addressable.ResourceAddress()
|
|
|
|
for _, targetAddr := range t.Targets {
|
|
|
|
if targetAddr.Equals(addr) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-02-08 23:00:13 +01:00
|
|
|
type graphNodeExpandedResource struct {
|
|
|
|
Index int
|
|
|
|
Resource *config.Resource
|
2015-05-01 18:01:49 +02:00
|
|
|
Path []string
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *graphNodeExpandedResource) Name() string {
|
2015-02-12 02:20:05 +01:00
|
|
|
if n.Index == -1 {
|
|
|
|
return n.Resource.Id()
|
|
|
|
}
|
|
|
|
|
2015-02-08 23:00:13 +01:00
|
|
|
return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
|
|
|
|
}
|
|
|
|
|
2015-03-31 02:02:36 +02:00
|
|
|
// GraphNodeAddressable impl.
|
|
|
|
func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress {
|
|
|
|
// We want this to report the logical index properly, so we must undo the
|
|
|
|
// special case from the expand
|
|
|
|
index := n.Index
|
|
|
|
if index == -1 {
|
|
|
|
index = 0
|
|
|
|
}
|
|
|
|
return &ResourceAddress{
|
2015-05-01 18:01:49 +02:00
|
|
|
Path: n.Path[1:],
|
|
|
|
Index: index,
|
2015-03-31 02:02:36 +02:00
|
|
|
InstanceType: TypePrimary,
|
|
|
|
Name: n.Resource.Name,
|
|
|
|
Type: n.Resource.Type,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-09 06:25:17 +02:00
|
|
|
// graphNodeConfig impl.
|
|
|
|
func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
|
|
|
|
return GraphNodeConfigTypeResource
|
|
|
|
}
|
|
|
|
|
2015-02-08 23:00:13 +01:00
|
|
|
// GraphNodeDependable impl.
|
|
|
|
func (n *graphNodeExpandedResource) DependableName() []string {
|
|
|
|
return []string{
|
|
|
|
n.Resource.Id(),
|
2015-02-10 23:12:49 +01:00
|
|
|
n.stateId(),
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GraphNodeDependent impl.
|
|
|
|
func (n *graphNodeExpandedResource) DependentOn() []string {
|
2015-04-19 00:56:43 +02:00
|
|
|
configNode := &GraphNodeConfigResource{Resource: n.Resource}
|
|
|
|
result := configNode.DependentOn()
|
|
|
|
|
|
|
|
// Walk the variables to find any count-specific variables we depend on.
|
|
|
|
configNode.VarWalk(func(v config.InterpolatedVariable) {
|
|
|
|
rv, ok := v.(*config.ResourceVariable)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// We only want ourselves
|
|
|
|
if rv.ResourceId() != n.Resource.Id() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this isn't a multi-access (which shouldn't be allowed but
|
|
|
|
// is verified elsewhere), then we depend on the specific count
|
|
|
|
// of this resource, ignoring ourself (which again should be
|
|
|
|
// validated elsewhere).
|
|
|
|
if rv.Index > -1 {
|
|
|
|
id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index)
|
|
|
|
if id != n.stateId() && id != n.stateId()+".0" {
|
|
|
|
result = append(result, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return result
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// GraphNodeProviderConsumer
|
2015-02-11 23:14:05 +01:00
|
|
|
func (n *graphNodeExpandedResource) ProvidedBy() []string {
|
2015-03-23 23:36:53 +01:00
|
|
|
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
|
|
|
|
2015-05-01 01:27:20 +02:00
|
|
|
func (n *graphNodeExpandedResource) StateDependencies() []string {
|
|
|
|
depsRaw := n.DependentOn()
|
|
|
|
deps := make([]string, 0, len(depsRaw))
|
|
|
|
for _, d := range depsRaw {
|
2015-05-01 02:19:01 +02:00
|
|
|
// Ignore any variable dependencies
|
|
|
|
if strings.HasPrefix(d, "var.") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is sad. The dependencies are currently in the format of
|
|
|
|
// "module.foo.bar" (the full field). This strips the field off.
|
|
|
|
if strings.HasPrefix(d, "module.") {
|
|
|
|
parts := strings.SplitN(d, ".", 3)
|
|
|
|
d = strings.Join(parts[0:2], ".")
|
2015-05-01 01:27:20 +02:00
|
|
|
}
|
2015-05-01 02:19:01 +02:00
|
|
|
deps = append(deps, d)
|
2015-05-01 01:27:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return deps
|
|
|
|
}
|
|
|
|
|
2015-02-08 23:00:13 +01:00
|
|
|
// GraphNodeEvalable impl.
|
|
|
|
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
2015-02-14 07:58:41 +01:00
|
|
|
var provider ResourceProvider
|
|
|
|
var resourceConfig *ResourceConfig
|
2015-02-12 23:46:22 +01:00
|
|
|
|
2015-02-12 07:57:50 +01:00
|
|
|
// Build the resource. If we aren't part of a multi-resource, then
|
|
|
|
// we still consider ourselves as count index zero.
|
|
|
|
index := n.Index
|
|
|
|
if index < 0 {
|
|
|
|
index = 0
|
|
|
|
}
|
2015-02-23 23:56:02 +01:00
|
|
|
resource := &Resource{
|
|
|
|
Name: n.Resource.Name,
|
|
|
|
Type: n.Resource.Type,
|
|
|
|
CountIndex: index,
|
|
|
|
}
|
2015-02-12 07:56:35 +01:00
|
|
|
|
2015-02-09 20:15:54 +01:00
|
|
|
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
|
|
|
|
|
|
|
// Validate the resource
|
2015-02-12 03:13:15 +01:00
|
|
|
vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
2015-02-14 07:58:41 +01:00
|
|
|
vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
})
|
|
|
|
vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
|
2015-04-09 18:21:38 +02:00
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
2015-02-14 07:58:41 +01:00
|
|
|
Resource: resource,
|
|
|
|
Output: &resourceConfig,
|
|
|
|
})
|
2015-02-12 03:13:15 +01:00
|
|
|
vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
|
2015-02-14 07:58:41 +01:00
|
|
|
Provider: &provider,
|
|
|
|
Config: &resourceConfig,
|
2015-02-09 20:15:54 +01:00
|
|
|
ResourceName: n.Resource.Name,
|
|
|
|
ResourceType: n.Resource.Type,
|
2016-05-02 04:01:48 +02:00
|
|
|
ResourceMode: n.Resource.Mode,
|
2015-02-09 20:15:54 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// Validate all the provisioners
|
|
|
|
for _, p := range n.Resource.Provisioners {
|
2015-02-14 07:58:41 +01:00
|
|
|
var provisioner ResourceProvisioner
|
|
|
|
vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{
|
|
|
|
Name: p.Type,
|
|
|
|
Output: &provisioner,
|
2015-02-17 04:15:14 +01:00
|
|
|
}, &EvalInterpolate{
|
2015-04-09 18:21:38 +02:00
|
|
|
Config: p.RawConfig.Copy(),
|
2015-02-17 04:15:14 +01:00
|
|
|
Resource: resource,
|
|
|
|
Output: &resourceConfig,
|
2015-02-14 07:58:41 +01:00
|
|
|
}, &EvalValidateProvisioner{
|
|
|
|
Provisioner: &provisioner,
|
|
|
|
Config: &resourceConfig,
|
2015-02-09 20:15:54 +01:00
|
|
|
})
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
2015-02-09 20:15:54 +01:00
|
|
|
|
2015-02-12 03:13:15 +01:00
|
|
|
// Add the validation operations
|
|
|
|
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
|
|
|
|
Ops: []walkOperation{walkValidate},
|
|
|
|
Node: vseq,
|
|
|
|
})
|
|
|
|
|
2015-02-11 22:43:07 +01:00
|
|
|
// Build instance info
|
2015-02-13 21:05:34 +01:00
|
|
|
info := n.instanceInfo()
|
2015-02-11 22:43:07 +01:00
|
|
|
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
|
|
|
|
|
2016-05-02 06:38:44 +02:00
|
|
|
// Each resource mode has its own lifecycle
|
|
|
|
switch n.Resource.Mode {
|
|
|
|
case config.ManagedResourceMode:
|
|
|
|
seq.Nodes = append(
|
|
|
|
seq.Nodes,
|
|
|
|
n.managedResourceEvalNodes(resource, info, resourceConfig)...,
|
|
|
|
)
|
|
|
|
case config.DataResourceMode:
|
|
|
|
seq.Nodes = append(
|
|
|
|
seq.Nodes,
|
|
|
|
n.dataResourceEvalNodes(resource, info, resourceConfig)...,
|
|
|
|
)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unsupported resource mode %s", n.Resource.Mode))
|
|
|
|
}
|
|
|
|
|
|
|
|
return seq
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
|
|
|
|
var diff *InstanceDiff
|
|
|
|
var provider ResourceProvider
|
|
|
|
var state *InstanceState
|
|
|
|
|
|
|
|
nodes := make([]EvalNode, 0, 5)
|
|
|
|
|
2015-02-11 17:48:45 +01:00
|
|
|
// Refresh the resource
|
2016-05-02 06:38:44 +02:00
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
2015-02-11 17:48:45 +01:00
|
|
|
Ops: []walkOperation{walkRefresh},
|
2015-02-12 23:46:22 +01:00
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
2015-02-14 07:58:41 +01:00
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
2015-02-12 23:46:22 +01:00
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
&EvalRefresh{
|
|
|
|
Info: info,
|
2015-02-14 07:58:41 +01:00
|
|
|
Provider: &provider,
|
2015-02-12 23:46:22 +01:00
|
|
|
State: &state,
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-02-12 23:46:22 +01:00
|
|
|
State: &state,
|
|
|
|
},
|
2015-02-11 17:48:45 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2015-02-12 00:22:03 +01:00
|
|
|
// Diff the resource
|
2016-05-02 06:38:44 +02:00
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
2015-02-12 00:22:03 +01:00
|
|
|
Ops: []walkOperation{walkPlan},
|
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
2015-02-14 07:58:41 +01:00
|
|
|
&EvalInterpolate{
|
2015-04-09 18:21:38 +02:00
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
2015-02-14 07:58:41 +01:00
|
|
|
Resource: resource,
|
|
|
|
Output: &resourceConfig,
|
|
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
2015-02-12 23:46:22 +01:00
|
|
|
&EvalDiff{
|
|
|
|
Info: info,
|
2015-02-14 07:58:41 +01:00
|
|
|
Config: &resourceConfig,
|
|
|
|
Provider: &provider,
|
|
|
|
State: &state,
|
2015-02-12 23:46:22 +01:00
|
|
|
Output: &diff,
|
|
|
|
OutputState: &state,
|
|
|
|
},
|
2015-04-17 00:57:18 +02:00
|
|
|
&EvalCheckPreventDestroy{
|
|
|
|
Resource: n.Resource,
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
2015-08-09 10:02:28 +02:00
|
|
|
&EvalIgnoreChanges{
|
|
|
|
Resource: n.Resource,
|
2015-10-15 17:21:20 +02:00
|
|
|
Diff: &diff,
|
2015-08-09 10:02:28 +02:00
|
|
|
},
|
2015-02-12 00:22:03 +01:00
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-02-12 23:46:22 +01:00
|
|
|
State: &state,
|
2015-02-12 00:22:03 +01:00
|
|
|
},
|
2015-02-12 22:10:21 +01:00
|
|
|
&EvalDiffTainted{
|
|
|
|
Diff: &diff,
|
|
|
|
Name: n.stateId(),
|
|
|
|
},
|
2015-02-12 00:22:03 +01:00
|
|
|
&EvalWriteDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2015-02-12 21:42:33 +01:00
|
|
|
// Diff the resource for destruction
|
2016-05-02 06:38:44 +02:00
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
2015-02-12 21:42:33 +01:00
|
|
|
Ops: []walkOperation{walkPlanDestroy},
|
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
2015-02-14 07:58:41 +01:00
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
2015-02-12 21:42:33 +01:00
|
|
|
&EvalDiffDestroy{
|
|
|
|
Info: info,
|
2015-02-14 07:58:41 +01:00
|
|
|
State: &state,
|
2015-02-12 21:42:33 +01:00
|
|
|
Output: &diff,
|
|
|
|
},
|
2015-04-17 00:57:18 +02:00
|
|
|
&EvalCheckPreventDestroy{
|
|
|
|
Resource: n.Resource,
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
2015-02-12 21:42:33 +01:00
|
|
|
&EvalWriteDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2015-02-17 04:15:58 +01:00
|
|
|
// Apply
|
2015-02-13 04:57:27 +01:00
|
|
|
var diffApply *InstanceDiff
|
2015-02-13 18:49:29 +01:00
|
|
|
var err error
|
2015-02-13 19:09:35 +01:00
|
|
|
var createNew, tainted bool
|
2015-02-17 06:06:09 +01:00
|
|
|
var createBeforeDestroyEnabled bool
|
2016-03-15 16:03:01 +01:00
|
|
|
var wasChangeType DiffChangeType
|
2016-05-02 06:38:44 +02:00
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
2015-10-02 20:18:33 +02:00
|
|
|
Ops: []walkOperation{walkApply, walkDestroy},
|
2015-02-12 23:46:22 +01:00
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
2015-02-13 21:05:34 +01:00
|
|
|
// Get the saved diff for apply
|
|
|
|
&EvalReadDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diffApply,
|
|
|
|
},
|
|
|
|
|
|
|
|
// We don't want to do any destroys
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
if diffApply == nil {
|
2015-02-13 21:13:39 +01:00
|
|
|
return true, EvalEarlyExitError{}
|
2015-02-13 21:05:34 +01:00
|
|
|
}
|
|
|
|
|
2015-02-13 23:03:17 +01:00
|
|
|
if diffApply.Destroy && len(diffApply.Attributes) == 0 {
|
2015-02-13 21:05:34 +01:00
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
}
|
|
|
|
|
2016-03-15 16:03:01 +01:00
|
|
|
wasChangeType = diffApply.ChangeType()
|
2015-02-13 23:03:17 +01:00
|
|
|
diffApply.Destroy = false
|
2015-02-13 21:05:34 +01:00
|
|
|
return true, nil
|
|
|
|
},
|
2015-02-27 18:38:17 +01:00
|
|
|
Then: EvalNoop{},
|
2015-02-13 21:05:34 +01:00
|
|
|
},
|
|
|
|
|
2015-02-14 00:57:37 +01:00
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
2015-02-17 06:06:09 +01:00
|
|
|
destroy := false
|
|
|
|
if diffApply != nil {
|
|
|
|
destroy = diffApply.Destroy || diffApply.RequiresNew()
|
|
|
|
}
|
|
|
|
|
|
|
|
createBeforeDestroyEnabled =
|
|
|
|
n.Resource.Lifecycle.CreateBeforeDestroy &&
|
|
|
|
destroy
|
|
|
|
|
|
|
|
return createBeforeDestroyEnabled, nil
|
2015-02-14 00:57:37 +01:00
|
|
|
},
|
2015-02-27 18:38:17 +01:00
|
|
|
Then: &EvalDeposeState{
|
2015-02-14 00:57:37 +01:00
|
|
|
Name: n.stateId(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-02-14 07:58:41 +01:00
|
|
|
&EvalInterpolate{
|
2015-04-09 18:21:38 +02:00
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
2015-02-14 07:58:41 +01:00
|
|
|
Resource: resource,
|
|
|
|
Output: &resourceConfig,
|
|
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
|
2015-02-13 04:48:57 +01:00
|
|
|
&EvalDiff{
|
|
|
|
Info: info,
|
2015-02-14 07:58:41 +01:00
|
|
|
Config: &resourceConfig,
|
|
|
|
Provider: &provider,
|
|
|
|
State: &state,
|
2015-02-13 04:57:27 +01:00
|
|
|
Output: &diffApply,
|
2015-02-12 23:46:22 +01:00
|
|
|
},
|
2016-02-02 23:23:25 +01:00
|
|
|
&EvalIgnoreChanges{
|
2016-03-15 16:03:01 +01:00
|
|
|
Resource: n.Resource,
|
|
|
|
Diff: &diffApply,
|
|
|
|
WasChangeType: &wasChangeType,
|
2016-02-02 23:23:25 +01:00
|
|
|
},
|
2015-02-13 04:48:57 +01:00
|
|
|
|
|
|
|
// Get the saved diff
|
2015-02-12 23:46:22 +01:00
|
|
|
&EvalReadDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
2015-02-13 04:48:57 +01:00
|
|
|
|
|
|
|
// Compare the diffs
|
|
|
|
&EvalCompareDiff{
|
|
|
|
Info: info,
|
|
|
|
One: &diff,
|
2015-02-13 04:57:27 +01:00
|
|
|
Two: &diffApply,
|
2015-02-13 04:48:57 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
2015-02-12 23:46:22 +01:00
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
&EvalApply{
|
2015-02-13 19:09:35 +01:00
|
|
|
Info: info,
|
|
|
|
State: &state,
|
|
|
|
Diff: &diffApply,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &state,
|
|
|
|
Error: &err,
|
|
|
|
CreateNew: &createNew,
|
2015-02-12 23:46:22 +01:00
|
|
|
},
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-02-12 23:46:22 +01:00
|
|
|
State: &state,
|
|
|
|
},
|
2015-02-13 18:49:29 +01:00
|
|
|
&EvalApplyProvisioners{
|
|
|
|
Info: info,
|
|
|
|
State: &state,
|
|
|
|
Resource: n.Resource,
|
|
|
|
InterpResource: resource,
|
2015-02-13 19:09:35 +01:00
|
|
|
CreateNew: &createNew,
|
2015-02-13 18:49:29 +01:00
|
|
|
Tainted: &tainted,
|
|
|
|
Error: &err,
|
|
|
|
},
|
2015-02-14 00:57:37 +01:00
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
2015-02-17 06:06:09 +01:00
|
|
|
if createBeforeDestroyEnabled {
|
2015-02-14 01:27:23 +01:00
|
|
|
tainted = err != nil
|
|
|
|
}
|
|
|
|
|
2015-02-14 01:05:44 +01:00
|
|
|
failure := tainted || err != nil
|
2015-02-17 06:06:09 +01:00
|
|
|
return createBeforeDestroyEnabled && failure, nil
|
2015-02-14 00:57:37 +01:00
|
|
|
},
|
2015-02-27 18:38:17 +01:00
|
|
|
Then: &EvalUndeposeState{
|
2015-02-14 00:57:37 +01:00
|
|
|
Name: n.stateId(),
|
|
|
|
},
|
|
|
|
},
|
2015-02-25 07:45:47 +01:00
|
|
|
|
|
|
|
// We clear the diff out here so that future nodes
|
|
|
|
// don't see a diff that is already complete. There
|
|
|
|
// is no longer a diff!
|
|
|
|
&EvalWriteDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: nil,
|
|
|
|
},
|
|
|
|
|
2015-03-02 22:34:05 +01:00
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
return tainted, nil
|
|
|
|
},
|
|
|
|
Then: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
&EvalWriteStateTainted{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-03-02 22:34:05 +01:00
|
|
|
State: &state,
|
2015-03-04 19:15:53 +01:00
|
|
|
Index: -1,
|
2015-03-02 22:34:05 +01:00
|
|
|
},
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
|
|
|
|
},
|
|
|
|
Then: &EvalClearPrimaryState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Else: &EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-03-02 22:34:05 +01:00
|
|
|
State: &state,
|
|
|
|
},
|
2015-02-13 18:49:29 +01:00
|
|
|
},
|
2015-02-13 18:52:11 +01:00
|
|
|
&EvalApplyPost{
|
|
|
|
Info: info,
|
|
|
|
State: &state,
|
|
|
|
Error: &err,
|
|
|
|
},
|
2015-02-24 04:13:25 +01:00
|
|
|
&EvalUpdateStateHook{},
|
2015-02-12 23:46:22 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2016-05-02 06:38:44 +02:00
|
|
|
return nodes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
|
|
|
|
//var diff *InstanceDiff
|
2016-05-08 08:41:27 +02:00
|
|
|
var provider ResourceProvider
|
|
|
|
var config *ResourceConfig
|
|
|
|
var diff *InstanceDiff
|
|
|
|
var state *InstanceState
|
2016-05-02 06:38:44 +02:00
|
|
|
|
|
|
|
nodes := make([]EvalNode, 0, 5)
|
|
|
|
|
2016-05-08 08:41:27 +02:00
|
|
|
// Refresh the resource
|
|
|
|
// TODO: Interpolate and then check if the config has any computed stuff.
|
|
|
|
// If it doesn't, then do the diff/apply/writestate steps here so we
|
|
|
|
// can get this data resource populated early enough for its values to
|
|
|
|
// be visible during plan.
|
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
|
|
Ops: []walkOperation{walkRefresh},
|
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
|
|
|
|
// Always destroy the existing state first, since we must
|
|
|
|
// make sure that values from a previous read will not
|
|
|
|
// get interpolated if we end up needing to defer our
|
|
|
|
// loading until apply time.
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
|
|
|
Provider: n.Resource.Provider,
|
|
|
|
Dependencies: n.StateDependencies(),
|
|
|
|
State: &state, // state is nil here
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalInterpolate{
|
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
|
|
|
Resource: resource,
|
|
|
|
Output: &config,
|
|
|
|
},
|
|
|
|
|
|
|
|
// The rest of this pass can proceed only if there are no
|
|
|
|
// computed values in our config.
|
|
|
|
// (If there are, we'll deal with this during the plan and
|
|
|
|
// apply phases.)
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
|
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
},
|
|
|
|
Then: EvalNoop{},
|
|
|
|
},
|
|
|
|
|
|
|
|
// The remainder of this pass is the same as running
|
|
|
|
// a "plan" pass immediately followed by an "apply" pass,
|
|
|
|
// populating the state early so it'll be available to
|
|
|
|
// provider configurations that need this data during
|
|
|
|
// refresh/plan.
|
|
|
|
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalReadDataDiff{
|
|
|
|
Info: info,
|
|
|
|
Config: &config,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &diff,
|
|
|
|
OutputState: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalReadDataApply{
|
|
|
|
Info: info,
|
|
|
|
Diff: &diff,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
|
|
|
Provider: n.Resource.Provider,
|
|
|
|
Dependencies: n.StateDependencies(),
|
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalUpdateStateHook{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Diff the resource
|
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
|
|
Ops: []walkOperation{walkPlan},
|
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
|
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
// If we already have a state (created either during refresh
|
|
|
|
// or on a previous apply) then we don't need to do any
|
|
|
|
// more work on it during apply.
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
if state != nil {
|
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
},
|
|
|
|
Then: EvalNoop{},
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalInterpolate{
|
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
|
|
|
Resource: resource,
|
|
|
|
Output: &config,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalReadDataDiff{
|
|
|
|
Info: info,
|
|
|
|
Config: &config,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &diff,
|
|
|
|
OutputState: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
|
|
|
Provider: n.Resource.Provider,
|
|
|
|
Dependencies: n.StateDependencies(),
|
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalWriteDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Apply
|
|
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
|
|
Ops: []walkOperation{walkApply, walkDestroy},
|
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
// Get the saved diff for apply
|
|
|
|
&EvalReadDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diff,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Stop here if we don't actually have a diff
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
if diff == nil {
|
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(diff.Attributes) == 0 {
|
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
},
|
|
|
|
Then: EvalNoop{},
|
|
|
|
},
|
|
|
|
|
|
|
|
// We need to re-interpolate the config here, rather than
|
|
|
|
// just using the diff's values directly, because we've
|
|
|
|
// potentially learned more variable values during the
|
|
|
|
// apply pass that weren't known when the diff was produced.
|
|
|
|
&EvalInterpolate{
|
|
|
|
Config: n.Resource.RawConfig.Copy(),
|
|
|
|
Resource: resource,
|
|
|
|
Output: &config,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Make a new diff with our newly-interpolated config.
|
|
|
|
&EvalReadDataDiff{
|
|
|
|
Info: info,
|
|
|
|
Config: &config,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &diff,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalReadDataApply{
|
|
|
|
Info: info,
|
|
|
|
Diff: &diff,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
|
|
|
Provider: n.Resource.Provider,
|
|
|
|
Dependencies: n.StateDependencies(),
|
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Clear the diff now that we've applied it, so
|
|
|
|
// later nodes won't see a diff that's now a no-op.
|
|
|
|
&EvalWriteDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: nil,
|
|
|
|
},
|
|
|
|
|
|
|
|
&EvalUpdateStateHook{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2016-05-02 06:38:44 +02:00
|
|
|
return nodes
|
2015-02-08 23:00:13 +01:00
|
|
|
}
|
2015-02-10 23:12:49 +01:00
|
|
|
|
2015-02-13 21:05:34 +01:00
|
|
|
// instanceInfo is used for EvalTree.
|
|
|
|
func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
|
|
|
|
return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
|
|
|
|
}
|
|
|
|
|
2015-02-10 23:12:49 +01:00
|
|
|
// stateId is the name used for the state key
|
|
|
|
func (n *graphNodeExpandedResource) stateId() string {
|
2015-02-12 02:18:16 +01:00
|
|
|
if n.Index == -1 {
|
2015-02-11 17:48:45 +01:00
|
|
|
return n.Resource.Id()
|
|
|
|
}
|
|
|
|
|
2015-02-10 23:12:49 +01:00
|
|
|
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
|
|
|
|
}
|
2015-02-12 20:40:48 +01:00
|
|
|
|
2015-02-12 20:57:15 +01:00
|
|
|
// GraphNodeStateRepresentative impl.
|
|
|
|
func (n *graphNodeExpandedResource) StateId() []string {
|
|
|
|
return []string{n.stateId()}
|
|
|
|
}
|
|
|
|
|
2015-02-12 20:40:48 +01:00
|
|
|
// graphNodeExpandedResourceDestroy represents an expanded resource that
|
|
|
|
// is to be destroyed.
|
|
|
|
type graphNodeExpandedResourceDestroy struct {
|
|
|
|
*graphNodeExpandedResource
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *graphNodeExpandedResourceDestroy) Name() string {
|
|
|
|
return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
|
|
|
|
}
|
|
|
|
|
2015-04-09 06:25:17 +02:00
|
|
|
// graphNodeConfig impl.
|
|
|
|
func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
|
|
|
|
return GraphNodeConfigTypeResource
|
|
|
|
}
|
|
|
|
|
2015-02-12 20:40:48 +01:00
|
|
|
// GraphNodeEvalable impl.
|
|
|
|
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
|
2015-02-13 21:05:34 +01:00
|
|
|
info := n.instanceInfo()
|
|
|
|
|
|
|
|
var diffApply *InstanceDiff
|
|
|
|
var provider ResourceProvider
|
|
|
|
var state *InstanceState
|
|
|
|
var err error
|
|
|
|
return &EvalOpFilter{
|
2015-10-02 20:18:33 +02:00
|
|
|
Ops: []walkOperation{walkApply, walkDestroy},
|
2015-02-13 21:05:34 +01:00
|
|
|
Node: &EvalSequence{
|
|
|
|
Nodes: []EvalNode{
|
|
|
|
// Get the saved diff for apply
|
|
|
|
&EvalReadDiff{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Diff: &diffApply,
|
|
|
|
},
|
|
|
|
|
2015-02-25 07:45:47 +01:00
|
|
|
// Filter the diff so we only get the destroy
|
|
|
|
&EvalFilterDiff{
|
|
|
|
Diff: &diffApply,
|
|
|
|
Output: &diffApply,
|
|
|
|
Destroy: true,
|
|
|
|
},
|
|
|
|
|
2015-02-13 21:05:34 +01:00
|
|
|
// If we're not destroying, then compare diffs
|
|
|
|
&EvalIf{
|
|
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
|
|
if diffApply != nil && diffApply.Destroy {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, EvalEarlyExitError{}
|
|
|
|
},
|
2015-02-27 18:38:17 +01:00
|
|
|
Then: EvalNoop{},
|
2015-02-13 21:05:34 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
&EvalGetProvider{
|
|
|
|
Name: n.ProvidedBy()[0],
|
|
|
|
Output: &provider,
|
|
|
|
},
|
2015-04-14 01:31:11 +02:00
|
|
|
&EvalReadState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
Output: &state,
|
2015-02-13 21:05:34 +01:00
|
|
|
},
|
2015-02-25 07:45:47 +01:00
|
|
|
&EvalRequireState{
|
|
|
|
State: &state,
|
|
|
|
},
|
2015-02-13 21:05:34 +01:00
|
|
|
&EvalApply{
|
|
|
|
Info: info,
|
|
|
|
State: &state,
|
|
|
|
Diff: &diffApply,
|
|
|
|
Provider: &provider,
|
|
|
|
Output: &state,
|
|
|
|
Error: &err,
|
|
|
|
},
|
|
|
|
&EvalWriteState{
|
|
|
|
Name: n.stateId(),
|
|
|
|
ResourceType: n.Resource.Type,
|
2015-03-23 23:36:53 +01:00
|
|
|
Provider: n.Resource.Provider,
|
2015-05-01 01:27:20 +02:00
|
|
|
Dependencies: n.StateDependencies(),
|
2015-02-13 21:05:34 +01:00
|
|
|
State: &state,
|
|
|
|
},
|
|
|
|
&EvalApplyPost{
|
|
|
|
Info: info,
|
|
|
|
State: &state,
|
|
|
|
Error: &err,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2015-02-12 20:40:48 +01:00
|
|
|
}
|