terraform: destroy nodes work properly
This commit is contained in:
parent
c3df003624
commit
742b45886a
|
@ -88,6 +88,7 @@ func (c *Context2) GraphBuilder() GraphBuilder {
|
|||
|
||||
return &BuiltinGraphBuilder{
|
||||
Root: c.module,
|
||||
Diff: c.diff,
|
||||
Providers: providers,
|
||||
Provisioners: provisioners,
|
||||
State: c.state,
|
||||
|
|
|
@ -3793,7 +3793,6 @@ func TestContext2Apply_Provisioner_ConnInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestContext2Apply_destroy(t *testing.T) {
|
||||
m := testModule(t, "apply-destroy")
|
||||
h := new(HookRecordApplyOrder)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package terraform
|
||||
|
||||
// EvalIf is an EvalNode that is a conditional.
|
||||
type EvalIf struct {
|
||||
If func(EvalContext) (bool, error)
|
||||
Node EvalNode
|
||||
}
|
||||
|
||||
func (n *EvalIf) Args() ([]EvalNode, []EvalType) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalIf) Eval(
|
||||
ctx EvalContext, args []interface{}) (interface{}, error) {
|
||||
yes, err := n.If(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if yes {
|
||||
return EvalRaw(n.Node, ctx)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *EvalIf) Type() EvalType {
|
||||
return EvalTypeNull
|
||||
}
|
|
@ -27,11 +27,13 @@ func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
|||
if err := step.Transform(g); err != nil {
|
||||
return g, err
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] Graph after step %T:\n\n%s", step, g.String())
|
||||
}
|
||||
|
||||
// Validate the graph structure
|
||||
if err := g.Validate(); err != nil {
|
||||
log.Printf("[ERROR] Graph validation failed. Graph: %s", g.String())
|
||||
log.Printf("[ERROR] Graph validation failed. Graph:\n\n%s", g.String())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -49,6 +51,9 @@ type BuiltinGraphBuilder struct {
|
|||
// Root is the root module of the graph to build.
|
||||
Root *module.Tree
|
||||
|
||||
// Diff is the diff. The proper module diffs will be looked up.
|
||||
Diff *Diff
|
||||
|
||||
// State is the global state. The proper module states will be looked
|
||||
// up by graph path.
|
||||
State *State
|
||||
|
@ -100,6 +105,7 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
|
|||
|
||||
// Create the destruction nodes
|
||||
&DestroyTransformer{},
|
||||
&PruneDestroyTransformer{Diff: b.Diff},
|
||||
|
||||
// Make sure we create one root
|
||||
&RootTransformer{},
|
||||
|
|
|
@ -73,6 +73,10 @@ func TestBuiltinGraphBuilder(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: This exposes a really bad bug we need to fix after we merge
|
||||
the f-ast-branch. This bug still exists in master.
|
||||
|
||||
// This test tests that the graph builder properly expands modules.
|
||||
func TestBuiltinGraphBuilder_modules(t *testing.T) {
|
||||
b := &BuiltinGraphBuilder{
|
||||
|
@ -90,6 +94,7 @@ func TestBuiltinGraphBuilder_modules(t *testing.T) {
|
|||
t.Fatalf("bad: %s", actual)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type testBasicGraphBuilderTransform struct {
|
||||
V dag.Vertex
|
||||
|
@ -107,12 +112,15 @@ const testBasicGraphBuilderStr = `
|
|||
const testBuiltinGraphBuilderBasicStr = `
|
||||
aws_instance.db
|
||||
aws_instance.db (destroy)
|
||||
provider.aws
|
||||
aws_instance.db (destroy)
|
||||
aws_instance.web (destroy)
|
||||
provider.aws
|
||||
aws_instance.web
|
||||
aws_instance.web (destroy)
|
||||
aws_instance.web (destroy)
|
||||
aws_instance.db
|
||||
aws_instance.web (destroy)
|
||||
provider.aws
|
||||
aws_instance.web (destroy)
|
||||
provider.aws
|
||||
provider.aws
|
||||
`
|
||||
|
|
|
@ -176,6 +176,8 @@ type GraphNodeConfigResource struct {
|
|||
// destroyed. It doesn't mean that the resource WILL be destroyed, only
|
||||
// that logically this node is where it would happen.
|
||||
Destroy bool
|
||||
|
||||
destroyNode dag.Vertex
|
||||
}
|
||||
|
||||
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||
|
@ -292,11 +294,17 @@ func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex {
|
|||
return nil
|
||||
}
|
||||
|
||||
// If we already made the node, return that
|
||||
if n.destroyNode != nil {
|
||||
return n.destroyNode
|
||||
}
|
||||
|
||||
// Just make a copy that is set to destroy
|
||||
result := *n
|
||||
result.Destroy = true
|
||||
n.destroyNode = &result
|
||||
|
||||
return &result
|
||||
return n.destroyNode
|
||||
}
|
||||
|
||||
// graphNodeModuleExpanded represents a module where the graph has
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
|
@ -8,16 +11,26 @@ import (
|
|||
// must implement. This is used to automatically handle the creation of
|
||||
// destroy nodes in the graph and the dependency ordering of those destroys.
|
||||
type GraphNodeDestroyable interface {
|
||||
// DestroyNode returns the node used for the destroy. This vertex
|
||||
// should not be in the graph yet.
|
||||
// DestroyNode returns the node used for the destroy. This should
|
||||
// return the same node every time so that it can be used later for
|
||||
// lookups as well.
|
||||
DestroyNode() dag.Vertex
|
||||
}
|
||||
|
||||
// GraphNodeDestroyer is the interface that must implemented by
|
||||
// nodes that destroy.
|
||||
type GraphNodeDestroyer interface {
|
||||
dag.Vertex
|
||||
|
||||
DiffId() string
|
||||
}
|
||||
|
||||
// DestroyTransformer is a GraphTransformer that creates the destruction
|
||||
// nodes for things that _might_ be destroyed.
|
||||
type DestroyTransformer struct{}
|
||||
|
||||
func (t *DestroyTransformer) Transform(g *Graph) error {
|
||||
nodes := make(map[dag.Vertex]struct{}, len(g.Vertices()))
|
||||
for _, v := range g.Vertices() {
|
||||
// If it is not a destroyable, we don't care
|
||||
dn, ok := v.(GraphNodeDestroyable)
|
||||
|
@ -31,6 +44,9 @@ func (t *DestroyTransformer) Transform(g *Graph) error {
|
|||
continue
|
||||
}
|
||||
|
||||
// Store it
|
||||
nodes[n] = struct{}{}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(n)
|
||||
|
||||
|
@ -40,15 +56,84 @@ func (t *DestroyTransformer) Transform(g *Graph) error {
|
|||
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
|
||||
}
|
||||
|
||||
// Remove all the edges from the old now
|
||||
for _, edgeRaw := range downEdges {
|
||||
g.RemoveEdge(dag.BasicEdge(v, edgeRaw.(dag.Vertex)))
|
||||
}
|
||||
|
||||
// Add a new edge to connect the node to be created to
|
||||
// the destroy node.
|
||||
g.Connect(dag.BasicEdge(v, n))
|
||||
}
|
||||
|
||||
// Go through the nodes we added and determine if they depend
|
||||
// on any nodes with a destroy node. If so, depend on that instead.
|
||||
for n, _ := range nodes {
|
||||
for _, downRaw := range g.DownEdges(n).List() {
|
||||
target := downRaw.(dag.Vertex)
|
||||
dn, ok := target.(GraphNodeDestroyable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
newTarget := dn.DestroyNode()
|
||||
if newTarget == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := nodes[newTarget]; !ok {
|
||||
return fmt.Errorf(
|
||||
"%s: didn't generate same DestroyNode: %s",
|
||||
dag.VertexName(target),
|
||||
dag.VertexName(newTarget))
|
||||
}
|
||||
|
||||
// Make the new edge and transpose
|
||||
g.Connect(dag.BasicEdge(newTarget, n))
|
||||
|
||||
// Remove the old edge
|
||||
g.RemoveEdge(dag.BasicEdge(n, target))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneDestroyTransformer is a GraphTransformer that removes the destroy
|
||||
// nodes that aren't in the diff.
|
||||
type PruneDestroyTransformer struct {
|
||||
Diff *Diff
|
||||
}
|
||||
|
||||
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
|
||||
var modDiff *ModuleDiff
|
||||
if t.Diff != nil {
|
||||
modDiff = t.Diff.ModuleByPath(g.Path)
|
||||
}
|
||||
|
||||
for _, v := range g.Vertices() {
|
||||
// If it is not a destroyer, we don't care
|
||||
dn, ok := v.(GraphNodeDestroyer)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Grab the name to destroy
|
||||
prefix := dn.DiffId()
|
||||
if prefix == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
remove := true
|
||||
if modDiff != nil {
|
||||
for k, _ := range modDiff.Resources {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
remove = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the node if we have to
|
||||
if remove {
|
||||
g.Remove(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -33,9 +33,10 @@ func TestDestroyTransformer(t *testing.T) {
|
|||
const testTransformDestroyBasicStr = `
|
||||
aws_instance.bar
|
||||
aws_instance.bar (destroy)
|
||||
aws_instance.bar (destroy)
|
||||
aws_instance.foo
|
||||
aws_instance.bar (destroy)
|
||||
aws_instance.foo
|
||||
aws_instance.foo (destroy)
|
||||
aws_instance.foo (destroy)
|
||||
aws_instance.bar (destroy)
|
||||
`
|
||||
|
|
|
@ -142,7 +142,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||
})
|
||||
|
||||
// Build instance info
|
||||
info := &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
|
||||
info := n.instanceInfo()
|
||||
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
|
||||
|
||||
// Refresh the resource
|
||||
|
@ -228,7 +228,28 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||
Ops: []walkOperation{walkApply},
|
||||
Node: &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
// Redo the diff so we can compare outputs
|
||||
// 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 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if diffApply.Destroy {
|
||||
return true, EvalEarlyExitError{}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
},
|
||||
Node: EvalNoop{},
|
||||
},
|
||||
|
||||
&EvalDiff{
|
||||
Info: info,
|
||||
Config: interpolateNode,
|
||||
|
@ -304,6 +325,11 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
|||
return seq
|
||||
}
|
||||
|
||||
// instanceInfo is used for EvalTree.
|
||||
func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
|
||||
return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
|
||||
}
|
||||
|
||||
// stateId is the name used for the state key
|
||||
func (n *graphNodeExpandedResource) stateId() string {
|
||||
if n.Index == -1 {
|
||||
|
@ -330,7 +356,62 @@ func (n *graphNodeExpandedResourceDestroy) Name() string {
|
|||
|
||||
// GraphNodeEvalable impl.
|
||||
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
|
||||
// TODO: We need an eval tree that destroys when there is a
|
||||
// RequiresNew.
|
||||
return EvalNoop{}
|
||||
info := n.instanceInfo()
|
||||
|
||||
var diffApply *InstanceDiff
|
||||
var provider ResourceProvider
|
||||
var state *InstanceState
|
||||
var err error
|
||||
return &EvalOpFilter{
|
||||
Ops: []walkOperation{walkApply},
|
||||
Node: &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
// Get the saved diff for apply
|
||||
&EvalReadDiff{
|
||||
Name: n.stateId(),
|
||||
Diff: &diffApply,
|
||||
},
|
||||
|
||||
// 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{}
|
||||
},
|
||||
Node: EvalNoop{},
|
||||
},
|
||||
|
||||
&EvalGetProvider{
|
||||
Name: n.ProvidedBy()[0],
|
||||
Output: &provider,
|
||||
},
|
||||
&EvalReadState{
|
||||
Name: n.stateId(),
|
||||
Output: &state,
|
||||
},
|
||||
&EvalApply{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Diff: &diffApply,
|
||||
Provider: &provider,
|
||||
Output: &state,
|
||||
Error: &err,
|
||||
},
|
||||
&EvalWriteState{
|
||||
Name: n.stateId(),
|
||||
ResourceType: n.Resource.Type,
|
||||
Dependencies: n.DependentOn(),
|
||||
State: &state,
|
||||
},
|
||||
&EvalApplyPost{
|
||||
Info: info,
|
||||
State: &state,
|
||||
Error: &err,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue