terraform: implement destroy planning basics from state

This commit is contained in:
Mitchell Hashimoto 2016-10-19 00:59:56 -07:00
parent db807f4b0f
commit 0ed896a313
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 191 additions and 6 deletions

View File

@ -516,8 +516,19 @@ func (c *Context) Plan() (*Plan, error) {
c.diff.init() c.diff.init()
c.diffLock.Unlock() c.diffLock.Unlock()
// Build the graph // Build the graph. We have a branch here since for the pure-destroy
graph, err := c.Graph(&ContextGraphOpts{Validate: true}) // plan (c.destroy) we use a much simpler graph builder that simply
// walks the state and reverses edges.
var graph *Graph
var err error
if c.destroy && X_newDestroy {
graph, err = (&DestroyPlanGraphBuilder{
Module: c.module,
State: c.state,
}).Build(RootModulePath)
} else {
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -540,11 +551,16 @@ func (c *Context) Plan() (*Plan, error) {
p.Diff.DeepCopy() p.Diff.DeepCopy()
} }
// We don't do the reverification during the new destroy plan because
// it will use a different apply process.
if !(c.destroy && X_newDestroy) {
// Now that we have a diff, we can build the exact graph that Apply will use // Now that we have a diff, we can build the exact graph that Apply will use
// and catch any possible cycles during the Plan phase. // and catch any possible cycles during the Plan phase.
if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
return nil, err return nil, err
} }
}
var errs error var errs error
if len(walker.ValidationErrors) > 0 { if len(walker.ValidationErrors) > 0 {
errs = multierror.Append(errs, walker.ValidationErrors...) errs = multierror.Append(errs, walker.ValidationErrors...)

View File

@ -0,0 +1,49 @@
package terraform
import (
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// DestroyPlanGraphBuilder implements GraphBuilder and is responsible for
// planning a pure-destroy.
//
// Planning a pure destroy operation is simple because we can ignore most
// ordering configuration and simply reverse the state.
type DestroyPlanGraphBuilder struct {
// Module is the root module for the graph to build.
Module *module.Tree
// State is the current state
State *State
}
// See GraphBuilder
func (b *DestroyPlanGraphBuilder) Build(path []string) (*Graph, error) {
return (&BasicGraphBuilder{
Steps: b.Steps(),
Validate: true,
}).Build(path)
}
// See GraphBuilder
func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer {
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
return &NodePlanDestroyableResource{
NodeAbstractResource: a,
}
}
steps := []GraphTransformer{
// Creates all the nodes represented in the state.
&StateTransformer{
Concrete: concreteResource,
State: b.State,
},
// Single root
&RootTransformer{},
}
return steps
}

View File

@ -0,0 +1,55 @@
package terraform
import (
"fmt"
)
// NodePlanDestroyableResource represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
type NodePlanDestroyableResource struct {
*NodeAbstractResource
}
// GraphNodeEvalable
func (n *NodePlanDestroyableResource) EvalTree() EvalNode {
addr := n.NodeAbstractResource.Addr
// stateId is the ID to put into the state
stateId := addr.stateId()
if addr.Index > -1 {
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
}
// 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 diff *InstanceDiff
var state *InstanceState
return &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&EvalCheckPreventDestroy{
Resource: n.Config,
Diff: &diff,
},
&EvalWriteDiff{
Name: stateId,
Diff: &diff,
},
},
}
}

View File

@ -0,0 +1,65 @@
package terraform
import (
"fmt"
"log"
"github.com/hashicorp/terraform/dag"
)
// StateTransformer is a GraphTransformer that adds the elements of
// the state to the graph.
//
// This transform is used for example by the DestroyPlanGraphBuilder to ensure
// that only resources that are in the state are represented in the graph.
type StateTransformer struct {
Concrete ConcreteResourceNodeFunc
State *State
}
func (t *StateTransformer) Transform(g *Graph) error {
// If the state is nil or empty (nil is empty) then do nothing
if t.State.Empty() {
return nil
}
// Go through all the modules in the diff.
log.Printf("[TRACE] StateTransformer: starting")
var nodes []dag.Vertex
for _, ms := range t.State.Modules {
log.Printf("[TRACE] StateTransformer: Module: %v", ms.Path)
// Go through all the resources in this module.
for name, rs := range ms.Resources {
log.Printf("[TRACE] StateTransformer: Resource %q: %#v", name, rs)
// Add the resource to the graph
addr, err := parseResourceAddressInternal(name)
if err != nil {
panic(fmt.Sprintf(
"Error parsing internal name, this is a bug: %q", name))
}
// Very important: add the module path for this resource to
// the address. Remove "root" from it.
addr.Path = ms.Path[1:]
// Add the resource to the graph
abstract := &NodeAbstractResource{Addr: addr}
var node dag.Vertex = abstract
if f := t.Concrete; f != nil {
node = f(abstract)
}
nodes = append(nodes, node)
}
}
// Add all the nodes to the graph
for _, n := range nodes {
g.Add(n)
}
return nil
}