terraform: decreasing counts works

This commit is contained in:
Mitchell Hashimoto 2015-02-12 11:40:48 -08:00
parent d7dc0291f5
commit 4ccb12508a
5 changed files with 166 additions and 21 deletions

View File

@ -685,7 +685,6 @@ func TestContext2Plan_countOneIndex(t *testing.T) {
}
}
/*
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
m := testModule(t, "plan-count-dec")
p := testProvider("aws")
@ -741,6 +740,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) {
}
}
/*
func TestContextPlan_countIncreaseFromNotSet(t *testing.T) {
m := testModule(t, "plan-count-inc")
p := testProvider("aws")

59
terraform/eval_count.go Normal file
View File

@ -0,0 +1,59 @@
package terraform
import (
"github.com/hashicorp/terraform/config"
)
// EvalCountFixZeroOneBoundary is an EvalNode that fixes up the state
// when there is a resource count with zero/one boundary, i.e. fixing
// a resource named "aws_instance.foo" to "aws_instance.foo.0" and vice-versa.
type EvalCountFixZeroOneBoundary struct {
Resource *config.Resource
}
func (n *EvalCountFixZeroOneBoundary) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalCountFixZeroOneBoundary) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
// Get the count, important for knowing whether we're supposed to
// be adding the zero, or trimming it.
count, err := n.Resource.Count()
if err != nil {
return nil, err
}
// Figure what to look for and what to replace it with
hunt := n.Resource.Id()
replace := hunt + ".0"
if count < 2 {
hunt, replace = replace, hunt
}
state, lock := ctx.State()
// Get a lock so we can access this instance and potentially make
// changes to it.
lock.Lock()
defer lock.Unlock()
// Look for the module state. If we don't have one, then it doesn't matter.
mod := state.ModuleByPath(ctx.Path())
if mod == nil {
return nil, nil
}
// Look for the resource state. If we don't have one, then it is okay.
if rs, ok := mod.Resources[hunt]; ok {
mod.Resources[replace] = rs
delete(mod.Resources, hunt)
}
return nil, nil
}
func (n *EvalCountFixZeroOneBoundary) Type() EvalType {
return EvalTypeNull
}

View File

@ -171,6 +171,11 @@ func (n *GraphNodeConfigProvider) ProviderName() string {
// GraphNodeConfigResource represents a resource within the config graph.
type GraphNodeConfigResource struct {
Resource *config.Resource
// If set to true, this represents a resource that can only be
// destroyed. It doesn't mean that the resource WILL be destroyed, only
// that logically this node is where it would happen.
Destroy bool
}
func (n *GraphNodeConfigResource) DependableName() []string {
@ -199,19 +204,42 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
}
func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id()
result := n.Resource.Id()
if n.Destroy {
result += " (destroy)"
}
return result
}
// GraphNodeDynamicExpandable impl.
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// Build the graph
b := &BasicGraphBuilder{
Steps: []GraphTransformer{
&ResourceCountTransformer{Resource: n.Resource},
&RootTransformer{},
},
// Start creating the steps
steps := make([]GraphTransformer, 0, 5)
steps = append(steps, &ResourceCountTransformer{
Resource: n.Resource,
Destroy: n.Destroy,
})
// If we're destroying, then we care about adding orphans to
// the graph. Orphans in this case are the leftover resources when
// we decrease count.
if n.Destroy {
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
steps = append(steps, &OrphanTransformer{
State: state,
View: n.Resource.Id(),
})
}
// Always end with the root being added
steps = append(steps, &RootTransformer{})
// Build the graph
b := &BasicGraphBuilder{Steps: steps}
return b.Build(ctx.Path())
}
@ -224,6 +252,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode {
Ops: []walkOperation{walkValidate},
Node: &EvalValidateCount{Resource: n.Resource},
},
&EvalCountFixZeroOneBoundary{Resource: n.Resource},
},
}
}
@ -245,17 +274,16 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
// GraphNodeDestroyable
func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex {
return &GraphNodeConfigResourceDestroy{Resource: n.Resource}
}
// If we're already a destroy node, then don't do anything
if n.Destroy {
return nil
}
// GraphNodeConfigResourceDestroy represents the logical destroy step for
// a resource.
type GraphNodeConfigResourceDestroy struct {
Resource *config.Resource
}
// Just make a copy that is set to destroy
result := *n
result.Destroy = true
func (n *GraphNodeConfigResourceDestroy) Name() string {
return fmt.Sprintf("%s (destroy)", n.Resource.Id())
return &result
}
// graphNodeModuleExpanded represents a module where the graph has

View File

@ -8,6 +8,12 @@ import (
"github.com/hashicorp/terraform/dag"
)
// GraphNodeStateRepresentative is an interface that can be implemented by
// a node to say that it is representing a resource in the state.
type GraphNodeStateRepresentative interface {
StateId() []string
}
// OrphanTransformer is a GraphTransformer that adds orphans to the
// graph. This transformer adds both resource and module orphans.
type OrphanTransformer struct {
@ -18,6 +24,9 @@ type OrphanTransformer struct {
// Module is the root module. We'll look up the proper configuration
// using the graph path.
Module *module.Tree
// View, if non-nil will set a view on the module state.
View string
}
func (t *OrphanTransformer) Transform(g *Graph) error {
@ -26,19 +35,42 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
return nil
}
// Build up all our state representatives
resourceRep := make(map[string]struct{})
for _, v := range g.Vertices() {
if sr, ok := v.(GraphNodeStateRepresentative); ok {
for _, k := range sr.StateId() {
resourceRep[k] = struct{}{}
}
}
}
var config *config.Config
if module := t.Module.Child(g.Path[1:]); module != nil {
config = module.Config()
if t.Module != nil {
if module := t.Module.Child(g.Path[1:]); module != nil {
config = module.Config()
}
}
var resourceVertexes []dag.Vertex
if state := t.State.ModuleByPath(g.Path); state != nil {
// If we have state, then we can have orphan resources
// If we have a view, get the view
if t.View != "" {
state = state.View(t.View)
}
// Go over each resource orphan and add it to the graph.
resourceOrphans := state.Orphans(config)
resourceVertexes = make([]dag.Vertex, len(resourceOrphans))
for i, k := range resourceOrphans {
// If this orphan is represented by some other node somehow,
// then ignore it.
if _, ok := resourceRep[k]; ok {
continue
}
rs := state.Resources[k]
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{

View File

@ -11,6 +11,7 @@ import (
// out for a specific resource.
type ResourceCountTransformer struct {
Resource *config.Resource
Destroy bool
}
func (t *ResourceCountTransformer) Transform(g *Graph) error {
@ -35,13 +36,21 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
index = -1
}
// Save the node for later so we can do connections
nodes[i] = &graphNodeExpandedResource{
// 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{
Index: index,
Resource: t.Resource,
}
if t.Destroy {
node = &graphNodeExpandedResourceDestroy{
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
}
}
// Add the node now
nodes[i] = node
g.Add(nodes[i])
}
@ -185,3 +194,20 @@ func (n *graphNodeExpandedResource) stateId() string {
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
}
// 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())
}
// GraphNodeEvalable impl.
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
// TODO: We need an eval tree that destroys when there is a
// RequiresNew.
return EvalNoop{}
}