terraform/terraform/transform_resource.go

954 lines
22 KiB
Go
Raw Normal View History

package terraform
import (
"fmt"
"strings"
"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
Targets []ResourceAddress
}
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
nodes := make([]dag.Vertex, 0, count)
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,
Resource: t.Resource,
Path: g.Path,
}
2015-02-12 20:40:48 +01:00
if t.Destroy {
node = &graphNodeExpandedResourceDestroy{
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
}
}
// Skip nodes if targeting excludes them
if !t.nodeIsTargeted(node) {
continue
}
// Add the node now
nodes = append(nodes, node)
g.Add(node)
}
// 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
}
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
}
type graphNodeExpandedResource struct {
Index int
Resource *config.Resource
Path []string
}
func (n *graphNodeExpandedResource) Name() string {
if n.Index == -1 {
return n.Resource.Id()
}
return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
}
// 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{
Path: n.Path[1:],
Index: index,
InstanceType: TypePrimary,
Name: n.Resource.Name,
Type: n.Resource.Type,
Mode: n.Resource.Mode,
}
}
// graphNodeConfig impl.
func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
// GraphNodeDependable impl.
func (n *graphNodeExpandedResource) DependableName() []string {
return []string{
n.Resource.Id(),
2015-02-10 23:12:49 +01:00
n.stateId(),
}
}
// GraphNodeDependent impl.
func (n *graphNodeExpandedResource) DependentOn() []string {
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
}
// GraphNodeProviderConsumer
2015-02-11 23:14:05 +01:00
func (n *graphNodeExpandedResource) ProvidedBy() []string {
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
}
func (n *graphNodeExpandedResource) StateDependencies() []string {
depsRaw := n.DependentOn()
deps := make([]string, 0, len(depsRaw))
for _, d := range depsRaw {
// 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], ".")
}
deps = append(deps, d)
}
return deps
}
// 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
// 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-09 20:15:54 +01:00
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Validate the resource
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,
})
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,
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,
}, &EvalInterpolate{
2015-04-09 18:21:38 +02:00
Config: p.RawConfig.Copy(),
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-09 20:15:54 +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})
// 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
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,
Provider: n.Resource.Provider,
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
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,
},
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
// Re-run validation to catch any errors we missed, e.g. type
// mismatches on computed values.
&EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
ResourceMode: n.Resource.Mode,
IgnoreWarnings: true,
},
2015-02-14 07:58:41 +01:00
&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,
Resource: n.Resource,
2015-02-14 07:58:41 +01:00
Provider: &provider,
State: &state,
2016-04-21 21:59:10 +02:00
OutputDiff: &diff,
2015-02-12 23:46:22 +01:00
OutputState: &state,
},
&EvalCheckPreventDestroy{
Resource: n.Resource,
Diff: &diff,
},
2015-02-12 00:22:03 +01:00
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
2015-02-12 23:46:22 +01:00
State: &state,
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
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,
},
&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
var diffApply *InstanceDiff
2015-02-13 18:49:29 +01:00
var err error
2016-04-21 21:59:10 +02:00
var createNew bool
var createBeforeDestroyEnabled bool
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
}
if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
2015-02-13 21:05:34 +01:00
return true, EvalEarlyExitError{}
}
diffApply.SetDestroy(false)
2015-02-13 21:05:34 +01:00
return true, nil
},
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) {
destroy := false
if diffApply != nil {
destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
}
createBeforeDestroyEnabled =
n.Resource.Lifecycle.CreateBeforeDestroy &&
destroy
return createBeforeDestroyEnabled, nil
2015-02-14 00:57:37 +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,
},
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
// Re-run validation to catch any errors we missed, e.g. type
// mismatches on computed values.
&EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
ResourceMode: n.Resource.Mode,
IgnoreWarnings: true,
},
2015-02-13 04:48:57 +01:00
&EvalDiff{
2016-04-21 21:59:10 +02:00
Info: info,
Config: &resourceConfig,
Resource: n.Resource,
2016-04-21 21:59:10 +02:00
Provider: &provider,
Diff: &diffApply,
State: &state,
OutputDiff: &diffApply,
2015-02-12 23:46:22 +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,
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{
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,
Provider: n.Resource.Provider,
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,
CreateNew: &createNew,
2015-02-13 18:49:29 +01:00
Error: &err,
},
2015-02-14 00:57:37 +01:00
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
2016-04-21 21:59:10 +02:00
return createBeforeDestroyEnabled && err != nil, nil
2015-02-14 00:57:37 +01:00
},
Then: &EvalUndeposeState{
2016-04-21 21:59:10 +02:00
Name: n.stateId(),
State: &state,
},
Else: &EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
2015-02-14 00:57:37 +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-02-13 18:52:11 +01:00
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
&EvalUpdateStateHook{},
2015-02-12 23:46:22 +01:00
},
},
})
return nodes
}
func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
//var diff *InstanceDiff
var provider ResourceProvider
var config *ResourceConfig
var diff *InstanceDiff
var state *InstanceState
nodes := make([]EvalNode, 0, 5)
// Refresh the resource
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,
},
// We need to re-interpolate the config here because some
// of the attributes may have become computed during
// earlier planning, due to other resources having
// "requires new resource" diffs.
&EvalInterpolate{
Config: n.Resource.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0
// If the configuration is complete and we
// already have a state then we don't need to
// do any further work during apply, because we
// already populated the state during refresh.
if !computed && state != nil {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
&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,
},
},
},
})
// Diff the resource for destruction
nodes = append(nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlanDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.stateId(),
Output: &state,
},
// Since EvalDiffDestroy doesn't interact with the
// provider at all, we can safely share the same
// implementation for data vs. managed resources.
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&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 diff.GetAttributesLen() == 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,
Previous: &diff,
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{},
},
},
})
return nodes
}
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
// 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())
}
// 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,
},
// 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.GetDestroy() {
2015-02-13 21:05:34 +01:00
return true, nil
}
return true, EvalEarlyExitError{}
},
Then: EvalNoop{},
2015-02-13 21:05:34 +01:00
},
// Load the instance info so we have the module path set
&EvalInstanceInfo{Info: info},
2015-02-13 21:05:34 +01:00
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.stateId(),
Output: &state,
2015-02-13 21:05:34 +01:00
},
&EvalRequireState{
State: &state,
},
// Make sure we handle data sources properly.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if n.Resource.Mode == config.DataResourceMode {
return true, nil
}
return false, nil
},
Then: &EvalReadDataApply{
Info: info,
Diff: &diffApply,
Provider: &provider,
Output: &state,
},
Else: &EvalApply{
Info: info,
State: &state,
Diff: &diffApply,
Provider: &provider,
Output: &state,
Error: &err,
},
2015-02-13 21:05:34 +01:00
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
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
}