terraform/terraform/node_resource_destroy.go

289 lines
6.4 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
// NodeDestroyResource represents a resource that is to be destroyed.
type NodeDestroyResource struct {
*NodeAbstractResource
}
func (n *NodeDestroyResource) Name() string {
return n.NodeAbstractResource.Name() + " (destroy)"
}
// GraphNodeDestroyer
func (n *NodeDestroyResource) DestroyAddr() *ResourceAddress {
return n.Addr
}
// GraphNodeDestroyerCBD
func (n *NodeDestroyResource) CreateBeforeDestroy() bool {
// If we have no config, we just assume no
if n.Config == nil {
return false
}
return n.Config.Lifecycle.CreateBeforeDestroy
}
// GraphNodeDestroyerCBD
func (n *NodeDestroyResource) ModifyCreateBeforeDestroy(v bool) error {
// If we have no config, do nothing since it won't affect the
// create step anyways.
if n.Config == nil {
return nil
}
// Set CBD to true
n.Config.Lifecycle.CreateBeforeDestroy = true
return nil
}
// GraphNodeReferenceable, overriding NodeAbstractResource
func (n *NodeDestroyResource) ReferenceableName() []string {
// We modify our referenceable name to have the suffix of ".destroy"
// since depending on the creation side doesn't necessarilly mean
// depending on destruction.
suffix := ".destroy"
// If we're CBD, we also append "-cbd". This is because CBD will setup
// its own edges (in CBDEdgeTransformer). Depending on the "destroy"
// side generally doesn't mean depending on CBD as well. See GH-11349
if n.CreateBeforeDestroy() {
suffix += "-cbd"
}
result := n.NodeAbstractResource.ReferenceableName()
for i, v := range result {
result[i] = v + suffix
}
return result
}
// GraphNodeReferencer, overriding NodeAbstractResource
func (n *NodeDestroyResource) References() []string {
// If we have a config, then we need to include destroy-time dependencies
if c := n.Config; c != nil {
var result []string
for _, p := range c.Provisioners {
// We include conn info and config for destroy time provisioners
// as dependencies that we have.
if p.When == config.ProvisionerWhenDestroy {
result = append(result, ReferencesFromConfig(p.ConnInfo)...)
result = append(result, ReferencesFromConfig(p.RawConfig)...)
}
}
return result
}
return nil
}
// GraphNodeDynamicExpandable
func (n *NodeDestroyResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// If we have no config we do nothing
if n.Addr == nil {
return nil, nil
}
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
// Start creating the steps
steps := make([]GraphTransformer, 0, 5)
// We want deposed resources in the state to be destroyed
steps = append(steps, &DeposedTransformer{
State: state,
View: n.Addr.stateId(),
})
// Target
steps = append(steps, &TargetsTransformer{
ParsedTargets: n.Targets,
})
// Always end with the root being added
steps = append(steps, &RootTransformer{})
// Build the graph
b := &BasicGraphBuilder{
Steps: steps,
Name: "NodeResourceDestroy",
}
return b.Build(ctx.Path())
}
// GraphNodeEvalable
func (n *NodeDestroyResource) EvalTree() EvalNode {
// stateId is the ID to put into the state
stateId := n.Addr.stateId()
// Build the instance info. More of this will be populated during eval
info := &InstanceInfo{
Id: stateId,
Type: n.Addr.Type,
uniqueExtra: "destroy",
}
// Build the resource for eval
addr := n.Addr
resource := &Resource{
Name: addr.Name,
Type: addr.Type,
CountIndex: addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// Get our state
rs := n.ResourceState
if rs == nil {
rs = &ResourceState{}
}
var diffApply *InstanceDiff
var provider ResourceProvider
var state *InstanceState
var err error
return &EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
// Get the saved diff for apply
&EvalReadDiff{
Name: stateId,
Diff: &diffApply,
},
// Filter the diff so we only get the destroy
&EvalFilterDiff{
Diff: &diffApply,
Output: &diffApply,
Destroy: true,
},
// If we're not destroying, then compare diffs
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diffApply != nil && diffApply.GetDestroy() {
return true, nil
}
return true, EvalEarlyExitError{}
},
Then: EvalNoop{},
},
// Load the instance info so we have the module path set
&EvalInstanceInfo{Info: info},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalRequireState{
State: &state,
},
// Call pre-apply hook
&EvalApplyPre{
Info: info,
State: &state,
Diff: &diffApply,
},
// Run destroy provisioners if not tainted
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if state != nil && state.Tainted {
return false, nil
}
return true, nil
},
Then: &EvalApplyProvisioners{
Info: info,
State: &state,
Resource: n.Config,
InterpResource: resource,
Error: &err,
When: config.ProvisionerWhenDestroy,
},
},
// If we have a provisioning error, then we just call
// the post-apply hook now.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return err != nil, nil
},
Then: &EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
},
// Make sure we handle data sources properly.
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if n.Addr == nil {
return false, fmt.Errorf("nil address")
}
if n.Addr.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,
},
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Addr.Type,
Provider: rs.Provider,
Dependencies: rs.Dependencies,
State: &state,
},
&EvalApplyPost{
Info: info,
State: &state,
Error: &err,
},
&EvalUpdateStateHook{},
},
},
}
}