From bff4e31070fec6687be1c43157bfb221301de19f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Oct 2016 18:26:50 -0700 Subject: [PATCH 01/14] Update go.sh to run destroy tests --- go.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.sh b/go.sh index eb7a1697e..44e2c0198 100755 --- a/go.sh +++ b/go.sh @@ -1 +1 @@ -go test ./terraform | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l +go test ./terraform -Xnew-destroy | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l From db807f4b0f56d9029699e50d5f682d11a9925a69 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Oct 2016 18:28:02 -0700 Subject: [PATCH 02/14] terraform: destroy graph builder, -Xnew-destroy flag --- terraform/context.go | 47 ++++++---- terraform/graph_builder_destroy_apply.go | 109 +++++++++++++++++++++++ terraform/terraform_test.go | 2 + 3 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 terraform/graph_builder_destroy_apply.go diff --git a/terraform/context.go b/terraform/context.go index 7714455c9..9a45a125e 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -21,6 +21,10 @@ var ( // X_newApply will enable the new apply graph. This will be removed // and be on by default in 0.8.0. X_newApply = false + + // X_newDestroy will enable the new destroy graph. This will be removed + // and be on by default in 0.8.0. + X_newDestroy = false ) // InputMode defines what sort of input will be asked for when Input @@ -371,6 +375,8 @@ func (c *Context) Apply() (*State, error) { // Copy our own state c.state = c.state.DeepCopy() + newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply) + // Build the original graph. This is before the new graph builders // coming in 0.8. We do this for shadow graphing. oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true}) @@ -386,18 +392,28 @@ func (c *Context) Apply() (*State, error) { } // Build the new graph. We do this no matter what so we can shadow it. - newGraph, err := (&ApplyGraphBuilder{ - Module: c.module, - Diff: c.diff, - State: c.state, - Providers: c.components.ResourceProviders(), - Provisioners: c.components.ResourceProvisioners(), - }).Build(RootModulePath) - if err != nil && !X_newApply { + var newGraph *Graph + if c.destroy { + newGraph, err = (&DestroyApplyGraphBuilder{ + Module: c.module, + Diff: c.diff, + State: c.state, + Providers: c.providersList(), + }).Build(RootModulePath) + } else { + newGraph, err = (&ApplyGraphBuilder{ + Module: c.module, + Diff: c.diff, + State: c.state, + Providers: c.components.ResourceProviders(), + Provisioners: c.components.ResourceProvisioners(), + }).Build(RootModulePath) + } + if err != nil && !newGraphEnabled { // If we had an error graphing but we're not using this graph, just // set it to nil and record it as a shadow error. c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf( - "Error building new apply graph: %s", err)) + "Error building new graph: %s", err)) newGraph = nil err = nil @@ -418,16 +434,11 @@ func (c *Context) Apply() (*State, error) { // real := oldGraph shadow := newGraph - if c.destroy { - log.Printf("[WARN] terraform: real graph is original, shadow is nil") - shadow = nil + if newGraphEnabled { + log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") + real = shadow } else { - if X_newApply { - log.Printf("[WARN] terraform: real graph is Xnew-apply, shadow is Xnew-apply") - real = shadow - } else { - log.Printf("[WARN] terraform: real graph is original, shadow is Xnew-apply") - } + log.Printf("[WARN] terraform: real graph is original, shadow is experiment") } // For now, always shadow with the real graph for verification. We don't diff --git a/terraform/graph_builder_destroy_apply.go b/terraform/graph_builder_destroy_apply.go new file mode 100644 index 000000000..e6a190e87 --- /dev/null +++ b/terraform/graph_builder_destroy_apply.go @@ -0,0 +1,109 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" +) + +// DestroyApplyGraphBuilder implements GraphBuilder and is responsible for +// applying a pure-destroy plan. +// +// This graph builder is very similar to the ApplyGraphBuilder but +// is slightly simpler. +type DestroyApplyGraphBuilder struct { + // Module is the root module for the graph to build. + Module *module.Tree + + // Diff is the diff to apply. + Diff *Diff + + // State is the current state + State *State + + // Providers is the list of providers supported. + Providers []string + + // DisableReduce, if true, will not reduce the graph. Great for testing. + DisableReduce bool +} + +// See GraphBuilder +func (b *DestroyApplyGraphBuilder) Build(path []string) (*Graph, error) { + return (&BasicGraphBuilder{ + Steps: b.Steps(), + Validate: true, + }).Build(path) +} + +// See GraphBuilder +func (b *DestroyApplyGraphBuilder) Steps() []GraphTransformer { + // Custom factory for creating providers. + providerFactory := func(name string, path []string) GraphNodeProvider { + return &NodeApplyableProvider{ + NameValue: name, + PathValue: path, + } + } + + concreteResource := func(a *NodeAbstractResource) dag.Vertex { + return &NodeApplyableResource{ + NodeAbstractResource: a, + } + } + + steps := []GraphTransformer{ + // Creates all the nodes represented in the diff. + &DiffTransformer{ + Concrete: concreteResource, + + Diff: b.Diff, + Module: b.Module, + State: b.State, + }, + + // Create orphan output nodes + &OrphanOutputTransformer{Module: b.Module, State: b.State}, + + // Attach the configuration to any resources + &AttachResourceConfigTransformer{Module: b.Module}, + + // Attach the state + &AttachStateTransformer{State: b.State}, + + // Destruction ordering. NOTE: For destroys, we don't need to + // do any CBD stuff, so that is explicitly not here. + &DestroyEdgeTransformer{Module: b.Module, State: b.State}, + + // Create all the providers + &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, + &ProviderTransformer{}, + &ParentProviderTransformer{}, + &AttachProviderConfigTransformer{Module: b.Module}, + + // Add root variables + &RootVariableTransformer{Module: b.Module}, + + // Add module variables + &ModuleVariableTransformer{Module: b.Module}, + + // Add the outputs + &OutputTransformer{Module: b.Module}, + + // Connect references so ordering is correct + &ReferenceTransformer{}, + + // Add the node to fix the state count boundaries + &CountBoundaryTransformer{}, + + // Single root + &RootTransformer{}, + } + + if !b.DisableReduce { + // Perform the transitive reduction to make our graph a bit + // more sane if possible (it usually is possible). + steps = append(steps, &TransitiveReductionTransformer{}) + } + + return steps +} diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index f58e3b116..e6f4bdec9 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -24,6 +24,7 @@ const fixtureDir = "./test-fixtures" func TestMain(m *testing.M) { // Experimental features xNewApply := flag.Bool("Xnew-apply", false, "Experiment: new apply graph") + xNewDestroy := flag.Bool("Xnew-destroy", false, "Experiment: new destroy graph") // Normal features shadow := flag.Bool("shadow", true, "Enable shadow graph") @@ -32,6 +33,7 @@ func TestMain(m *testing.M) { // Setup experimental features X_newApply = *xNewApply + X_newDestroy = *xNewDestroy if testing.Verbose() { // if we're verbose, use the logging requested by TF_LOG From 0ed896a313b925ccf408af8838b0fa5117f9bb8e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 19 Oct 2016 00:59:56 -0700 Subject: [PATCH 03/14] terraform: implement destroy planning basics from state --- terraform/context.go | 28 ++++++++--- terraform/graph_builder_destroy_plan.go | 49 +++++++++++++++++++ terraform/node_resource_plan_destroy.go | 55 +++++++++++++++++++++ terraform/transform_state.go | 65 +++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 terraform/graph_builder_destroy_plan.go create mode 100644 terraform/node_resource_plan_destroy.go create mode 100644 terraform/transform_state.go diff --git a/terraform/context.go b/terraform/context.go index 9a45a125e..2bfc7df55 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -516,8 +516,19 @@ func (c *Context) Plan() (*Plan, error) { c.diff.init() c.diffLock.Unlock() - // Build the graph - graph, err := c.Graph(&ContextGraphOpts{Validate: true}) + // Build the graph. We have a branch here since for the pure-destroy + // 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 { return nil, err } @@ -540,11 +551,16 @@ func (c *Context) Plan() (*Plan, error) { p.Diff.DeepCopy() } - // 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. - if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { - return nil, err + // 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 + // and catch any possible cycles during the Plan phase. + if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { + return nil, err + } } + var errs error if len(walker.ValidationErrors) > 0 { errs = multierror.Append(errs, walker.ValidationErrors...) diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go new file mode 100644 index 000000000..047354691 --- /dev/null +++ b/terraform/graph_builder_destroy_plan.go @@ -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 +} diff --git a/terraform/node_resource_plan_destroy.go b/terraform/node_resource_plan_destroy.go new file mode 100644 index 000000000..9416a1afa --- /dev/null +++ b/terraform/node_resource_plan_destroy.go @@ -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, + }, + }, + } +} diff --git a/terraform/transform_state.go b/terraform/transform_state.go new file mode 100644 index 000000000..471cd7465 --- /dev/null +++ b/terraform/transform_state.go @@ -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 +} From e68327e76510864bc3ed70b4986a0ef2eb171002 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 19 Oct 2016 09:44:00 -0700 Subject: [PATCH 04/14] terraform: add config transformer to enable preventDestroy --- terraform/graph_builder_destroy_plan.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go index 047354691..a48c2bb92 100644 --- a/terraform/graph_builder_destroy_plan.go +++ b/terraform/graph_builder_destroy_plan.go @@ -41,6 +41,9 @@ func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer { State: b.State, }, + // Attach the configuration to any resources + &AttachResourceConfigTransformer{Module: b.Module}, + // Single root &RootTransformer{}, } From 480a414c4008d610cf1ff65c6cc8a2bd1f6ecf37 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 19 Oct 2016 10:02:07 -0700 Subject: [PATCH 05/14] terraform: destroy module nodes show up in plan destroy --- terraform/context_plan_test.go | 6 +- terraform/graph_builder_destroy_plan.go | 3 + terraform/graph_config_node_module.go | 2 +- terraform/node_module_destroy.go | 29 ++++++++ terraform/transform_module_destroy.go | 81 ++++++++--------------- terraform/transform_module_destroy_old.go | 62 +++++++++++++++++ 6 files changed, 126 insertions(+), 57 deletions(-) create mode 100644 terraform/node_module_destroy.go create mode 100644 terraform/transform_module_destroy_old.go diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index e58dbdf9f..c0fc555b9 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -1582,7 +1582,7 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanModuleDestroyStr) if actual != expected { - t.Fatalf("bad:\n%s", actual) + t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) } } @@ -1634,7 +1634,7 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) { actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanModuleDestroyCycleStr) if actual != expected { - t.Fatalf("bad:\n%s", actual) + t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) } } @@ -1684,7 +1684,7 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanModuleDestroyMultivarStr) if actual != expected { - t.Fatalf("bad:\n%s", actual) + t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) } } diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go index a48c2bb92..64a7d1c2f 100644 --- a/terraform/graph_builder_destroy_plan.go +++ b/terraform/graph_builder_destroy_plan.go @@ -44,6 +44,9 @@ func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer { // Attach the configuration to any resources &AttachResourceConfigTransformer{Module: b.Module}, + // Module destroy nodes + &ModuleDestroyTransformer{State: b.State}, + // Single root &RootTransformer{}, } diff --git a/terraform/graph_config_node_module.go b/terraform/graph_config_node_module.go index 3e36e1ea5..8da8fb6cd 100644 --- a/terraform/graph_config_node_module.go +++ b/terraform/graph_config_node_module.go @@ -59,7 +59,7 @@ func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error { // Add the destroy marker to the graph - t := &ModuleDestroyTransformer{} + t := &ModuleDestroyTransformerOld{} if err := t.Transform(graph); err != nil { return nil, err } diff --git a/terraform/node_module_destroy.go b/terraform/node_module_destroy.go new file mode 100644 index 000000000..319df1e3a --- /dev/null +++ b/terraform/node_module_destroy.go @@ -0,0 +1,29 @@ +package terraform + +import ( + "fmt" +) + +// NodeDestroyableModule represents a module destruction. +type NodeDestroyableModuleVariable struct { + PathValue []string +} + +func (n *NodeDestroyableModuleVariable) Name() string { + result := "plan-destroy" + if len(n.PathValue) > 1 { + result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result) + } + + return result +} + +// GraphNodeSubPath +func (n *NodeDestroyableModuleVariable) Path() []string { + return n.PathValue +} + +// GraphNodeEvalable +func (n *NodeDestroyableModuleVariable) EvalTree() EvalNode { + return &EvalDiffDestroyModule{Path: n.PathValue} +} diff --git a/terraform/transform_module_destroy.go b/terraform/transform_module_destroy.go index 609873c44..bf6e450f1 100644 --- a/terraform/transform_module_destroy.go +++ b/terraform/transform_module_destroy.go @@ -1,62 +1,37 @@ package terraform -import ( - "fmt" - - "github.com/hashicorp/terraform/dag" -) - // ModuleDestroyTransformer is a GraphTransformer that adds a node -// to the graph that will just mark the full module for destroy in -// the destroy scenario. -type ModuleDestroyTransformer struct{} +// to the graph that will add a module destroy node for all modules in +// the state. +// +// NOTE: This is _completely unnecessary_ in the new graph worlds. This is +// only done to make old tests pass. However, this node does nothing in +// the new apply graph. +type ModuleDestroyTransformer struct { + State *State +} func (t *ModuleDestroyTransformer) Transform(g *Graph) error { - // Create the node - n := &graphNodeModuleDestroy{Path: g.Path} + // If empty do nothing + if t.State.Empty() { + return nil + } - // Add it to the graph. We don't need any edges because - // it can happen whenever. - g.Add(n) + for _, ms := range t.State.Modules { + // Just a silly edge case that is required to get old tests to pass. + // It is probably a bug with the old graph but we mimic it here + // so that old tests pass. + if len(ms.Path) <= 1 { + continue + } + + // Create the node + n := &NodeDestroyableModuleVariable{PathValue: ms.Path} + + // Add it to the graph. We don't need any edges because + // it can happen whenever. + g.Add(n) + } return nil } - -type graphNodeModuleDestroy struct { - Path []string -} - -func (n *graphNodeModuleDestroy) Name() string { - return "plan-destroy" -} - -// GraphNodeEvalable impl. -func (n *graphNodeModuleDestroy) EvalTree() EvalNode { - return &EvalOpFilter{ - Ops: []walkOperation{walkPlanDestroy}, - Node: &EvalDiffDestroyModule{Path: n.Path}, - } -} - -// GraphNodeFlattenable impl. -func (n *graphNodeModuleDestroy) Flatten(p []string) (dag.Vertex, error) { - return &graphNodeModuleDestroyFlat{ - graphNodeModuleDestroy: n, - PathValue: p, - }, nil -} - -type graphNodeModuleDestroyFlat struct { - *graphNodeModuleDestroy - - PathValue []string -} - -func (n *graphNodeModuleDestroyFlat) Name() string { - return fmt.Sprintf( - "%s.%s", modulePrefixStr(n.PathValue), n.graphNodeModuleDestroy.Name()) -} - -func (n *graphNodeModuleDestroyFlat) Path() []string { - return n.PathValue -} diff --git a/terraform/transform_module_destroy_old.go b/terraform/transform_module_destroy_old.go new file mode 100644 index 000000000..e971838f1 --- /dev/null +++ b/terraform/transform_module_destroy_old.go @@ -0,0 +1,62 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/dag" +) + +// ModuleDestroyTransformer is a GraphTransformer that adds a node +// to the graph that will just mark the full module for destroy in +// the destroy scenario. +type ModuleDestroyTransformerOld struct{} + +func (t *ModuleDestroyTransformerOld) Transform(g *Graph) error { + // Create the node + n := &graphNodeModuleDestroy{Path: g.Path} + + // Add it to the graph. We don't need any edges because + // it can happen whenever. + g.Add(n) + + return nil +} + +type graphNodeModuleDestroy struct { + Path []string +} + +func (n *graphNodeModuleDestroy) Name() string { + return "plan-destroy" +} + +// GraphNodeEvalable impl. +func (n *graphNodeModuleDestroy) EvalTree() EvalNode { + return &EvalOpFilter{ + Ops: []walkOperation{walkPlanDestroy}, + Node: &EvalDiffDestroyModule{Path: n.Path}, + } +} + +// GraphNodeFlattenable impl. +func (n *graphNodeModuleDestroy) Flatten(p []string) (dag.Vertex, error) { + return &graphNodeModuleDestroyFlat{ + graphNodeModuleDestroy: n, + PathValue: p, + }, nil +} + +type graphNodeModuleDestroyFlat struct { + *graphNodeModuleDestroy + + PathValue []string +} + +func (n *graphNodeModuleDestroyFlat) Name() string { + return fmt.Sprintf( + "%s.%s", modulePrefixStr(n.PathValue), n.graphNodeModuleDestroy.Name()) +} + +func (n *graphNodeModuleDestroyFlat) Path() []string { + return n.PathValue +} From 2d4f65cc947151cc0e5e24bcd3b1558ccac7d58f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 10:46:32 -0700 Subject: [PATCH 06/14] terraform: disable shadowing destroy graph for now It doesn't fully work so we want to wait until we think its ready before we start the shadowing. --- terraform/context.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/terraform/context.go b/terraform/context.go index 2bfc7df55..41f5e2a36 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -398,7 +398,7 @@ func (c *Context) Apply() (*State, error) { Module: c.module, Diff: c.diff, State: c.state, - Providers: c.providersList(), + Providers: c.components.ResourceProviders(), }).Build(RootModulePath) } else { newGraph, err = (&ApplyGraphBuilder{ @@ -438,6 +438,11 @@ func (c *Context) Apply() (*State, error) { log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") real = shadow } else { + // TODO: remove before branch is done, we're just not ready for this yet + if c.destroy { + shadow = nil + } + log.Printf("[WARN] terraform: real graph is original, shadow is experiment") } From ab4ebcc5c7b9501779d40305cd9094703dd41868 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 18:59:02 -0700 Subject: [PATCH 07/14] terraform: TargetsTransformer on destroy plan This enables targeting to work properly on planning destroys --- terraform/context.go | 5 +++-- terraform/graph_builder_destroy_plan.go | 6 ++++++ terraform/graph_config_node.go | 4 ---- terraform/node_resource_abstract.go | 12 ++++++++++++ terraform/node_resource_destroy.go | 5 +++++ terraform/transform_targets.go | 7 +++++++ 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index 41f5e2a36..8d49d3859 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -528,8 +528,9 @@ func (c *Context) Plan() (*Plan, error) { var err error if c.destroy && X_newDestroy { graph, err = (&DestroyPlanGraphBuilder{ - Module: c.module, - State: c.state, + Module: c.module, + State: c.state, + Targets: c.targets, }).Build(RootModulePath) } else { graph, err = c.Graph(&ContextGraphOpts{Validate: true}) diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go index 64a7d1c2f..2b1444fd9 100644 --- a/terraform/graph_builder_destroy_plan.go +++ b/terraform/graph_builder_destroy_plan.go @@ -16,6 +16,9 @@ type DestroyPlanGraphBuilder struct { // State is the current state State *State + + // Targets are resources to target + Targets []string } // See GraphBuilder @@ -41,6 +44,9 @@ func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer { State: b.State, }, + // Target + &TargetsTransformer{Targets: b.Targets}, + // Attach the configuration to any resources &AttachResourceConfigTransformer{Module: b.Module}, diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index ea4645d72..57d565ca2 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -24,8 +24,6 @@ type graphNodeConfig interface { // configuration graph need to implement in order to be be addressed / targeted // properly. type GraphNodeAddressable interface { - graphNodeConfig - ResourceAddress() *ResourceAddress } @@ -35,7 +33,5 @@ type GraphNodeAddressable interface { // provided will contain every target provided, and each implementing graph // node must filter this list to targets considered relevant. type GraphNodeTargetable interface { - GraphNodeAddressable - SetTargets([]ResourceAddress) } diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index cddccaef2..9ba303a6e 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -28,6 +28,8 @@ type NodeAbstractResource struct { Config *config.Resource // Config is the resource in the config ResourceState *ResourceState // ResourceState is the ResourceState for this + + Targets []ResourceAddress // Set from GraphNodeTargetable } func (n *NodeAbstractResource) Name() string { @@ -111,6 +113,16 @@ func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress { return n.Addr } +// GraphNodeAddressable, TODO: remove, used by target, should unify +func (n *NodeAbstractResource) ResourceAddress() *ResourceAddress { + return n.ResourceAddr() +} + +// GraphNodeTargetable +func (n *NodeAbstractResource) SetTargets(targets []ResourceAddress) { + n.Targets = targets +} + // GraphNodeAttachResourceState func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) { n.ResourceState = s diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index 52c34d1c7..8aed56381 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -63,6 +63,11 @@ func (n *NodeDestroyResource) DynamicExpand(ctx EvalContext) (*Graph, error) { View: n.Config.Id(), }) + // Target + steps = append(steps, &TargetsTransformer{ + ParsedTargets: n.Targets, + }) + // Always end with the root being added steps = append(steps, &RootTransformer{}) diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index 84393796e..0ba98ee15 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -28,8 +28,10 @@ func (t *TargetsTransformer) Transform(g *Graph) error { if err != nil { return err } + t.ParsedTargets = addrs } + if len(t.ParsedTargets) > 0 { targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets) if err != nil { @@ -50,6 +52,7 @@ func (t *TargetsTransformer) Transform(g *Graph) error { } } } + return nil } @@ -62,6 +65,7 @@ func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { } addrs[i] = *ta } + return addrs, nil } @@ -107,6 +111,7 @@ func (t *TargetsTransformer) selectTargetedNodes( } } } + return targetedNodes, nil } @@ -116,12 +121,14 @@ func (t *TargetsTransformer) nodeIsTarget( if !ok { return false } + addr := r.ResourceAddress() for _, targetAddr := range addrs { if targetAddr.Equals(addr) { return true } } + return false } From ceb613d449b7cc2a7eac99f1fecc7c8ef97d91fc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 21:36:43 -0700 Subject: [PATCH 08/14] terraform: don't set destroy module on diff This is something that should be determined and done during an apply. It doesn't make a lot of sense that the plan is doing it (in its current form at least). --- go.sh | 2 +- terraform/context.go | 10 +++---- terraform/diff.go | 4 --- terraform/graph_builder_destroy_plan.go | 3 -- terraform/terraform_test.go | 4 --- terraform/transform_module_destroy.go | 37 ------------------------- 6 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 terraform/transform_module_destroy.go diff --git a/go.sh b/go.sh index 44e2c0198..ca473ad65 100755 --- a/go.sh +++ b/go.sh @@ -1 +1 @@ -go test ./terraform -Xnew-destroy | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l +go test ./terraform -Xnew-apply -Xnew-destroy | grep -E '(FAIL|panic)' | tee /dev/tty | wc -l diff --git a/terraform/context.go b/terraform/context.go index 8d49d3859..eddceccae 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -438,11 +438,6 @@ func (c *Context) Apply() (*State, error) { log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") real = shadow } else { - // TODO: remove before branch is done, we're just not ready for this yet - if c.destroy { - shadow = nil - } - log.Printf("[WARN] terraform: real graph is original, shadow is experiment") } @@ -452,6 +447,11 @@ func (c *Context) Apply() (*State, error) { shadow = real } + // TODO: remove before branch is done, we're just not ready for this yet + if c.destroy { + shadow = nil + } + // Determine the operation operation := walkApply if c.destroy { diff --git a/terraform/diff.go b/terraform/diff.go index 86be4bb5a..89dc947fe 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -238,10 +238,6 @@ func (d *ModuleDiff) IsRoot() bool { func (d *ModuleDiff) String() string { var buf bytes.Buffer - if d.Destroy { - buf.WriteString("DESTROY MODULE\n") - } - names := make([]string, 0, len(d.Resources)) for name, _ := range d.Resources { names = append(names, name) diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go index 2b1444fd9..e0cd231f3 100644 --- a/terraform/graph_builder_destroy_plan.go +++ b/terraform/graph_builder_destroy_plan.go @@ -50,9 +50,6 @@ func (b *DestroyPlanGraphBuilder) Steps() []GraphTransformer { // Attach the configuration to any resources &AttachResourceConfigTransformer{Module: b.Module}, - // Module destroy nodes - &ModuleDestroyTransformer{State: b.State}, - // Single root &RootTransformer{}, } diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index e6f4bdec9..8c4724757 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -1139,7 +1139,6 @@ DIFF: DESTROY: aws_instance.foo module.child: - DESTROY MODULE DESTROY: aws_instance.foo STATE: @@ -1156,10 +1155,8 @@ const testTerraformPlanModuleDestroyCycleStr = ` DIFF: module.a_module: - DESTROY MODULE DESTROY: aws_instance.a module.b_module: - DESTROY MODULE DESTROY: aws_instance.b STATE: @@ -1176,7 +1173,6 @@ const testTerraformPlanModuleDestroyMultivarStr = ` DIFF: module.child: - DESTROY MODULE DESTROY: aws_instance.foo.0 DESTROY: aws_instance.foo.1 diff --git a/terraform/transform_module_destroy.go b/terraform/transform_module_destroy.go deleted file mode 100644 index bf6e450f1..000000000 --- a/terraform/transform_module_destroy.go +++ /dev/null @@ -1,37 +0,0 @@ -package terraform - -// ModuleDestroyTransformer is a GraphTransformer that adds a node -// to the graph that will add a module destroy node for all modules in -// the state. -// -// NOTE: This is _completely unnecessary_ in the new graph worlds. This is -// only done to make old tests pass. However, this node does nothing in -// the new apply graph. -type ModuleDestroyTransformer struct { - State *State -} - -func (t *ModuleDestroyTransformer) Transform(g *Graph) error { - // If empty do nothing - if t.State.Empty() { - return nil - } - - for _, ms := range t.State.Modules { - // Just a silly edge case that is required to get old tests to pass. - // It is probably a bug with the old graph but we mimic it here - // so that old tests pass. - if len(ms.Path) <= 1 { - continue - } - - // Create the node - n := &NodeDestroyableModuleVariable{PathValue: ms.Path} - - // Add it to the graph. We don't need any edges because - // it can happen whenever. - g.Add(n) - } - - return nil -} From ac9a049d19395105b2a2ed9782d05409dee1eaca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 21:39:55 -0700 Subject: [PATCH 09/14] terraform: Diff.Equal ignores ModuleDiff.Destroy This is necessary to get the shadow working properly with the destroy graph since the destroy graph doesn't set this field but the end state is still the same. --- terraform/diff.go | 14 +++++++++++++- terraform/diff_test.go | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/terraform/diff.go b/terraform/diff.go index 89dc947fe..a50c0b82b 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -100,8 +100,20 @@ func (d *Diff) Equal(d2 *Diff) bool { sort.Sort(moduleDiffSort(d.Modules)) sort.Sort(moduleDiffSort(d2.Modules)) + // Copy since we have to modify the module destroy flag to false so + // we don't compare that. TODO: delete this when we get rid of the + // destroy flag on modules. + dCopy := d.DeepCopy() + d2Copy := d2.DeepCopy() + for _, m := range dCopy.Modules { + m.Destroy = false + } + for _, m := range d2Copy.Modules { + m.Destroy = false + } + // Use DeepEqual - return reflect.DeepEqual(d, d2) + return reflect.DeepEqual(dCopy, d2Copy) } // DeepCopy performs a deep copy of all parts of the Diff, making the diff --git a/terraform/diff_test.go b/terraform/diff_test.go index a9cb8b47b..0970609aa 100644 --- a/terraform/diff_test.go +++ b/terraform/diff_test.go @@ -77,6 +77,20 @@ func TestDiffEqual(t *testing.T) { }, true, }, + + "different module diff destroys": { + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, + }, + }, + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: false}, + }, + }, + true, + }, } for name, tc := range cases { From db6d87b16c1fd8ecc205d84af49e1769f1d128fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 22:51:06 -0700 Subject: [PATCH 10/14] terraform: destroy node should understand data sources This makes the new destroy nodes undestand data sourcs and call the correct apply function. --- terraform/node_resource_destroy.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index 8aed56381..4979f9726 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -2,6 +2,8 @@ package terraform import ( "fmt" + + "github.com/hashicorp/terraform/config" ) // NodeDestroyResource represents a resource that is to be destroyed. @@ -147,11 +149,9 @@ func (n *NodeDestroyResource) EvalTree() EvalNode { // Make sure we handle data sources properly. &EvalIf{ If: func(ctx EvalContext) (bool, error) { - /* TODO: data source - if n.Resource.Mode == config.DataResourceMode { + if n.Config.Mode == config.DataResourceMode { return true, nil } - */ return false, nil }, From a4aea3e085fe69e3c092666d7a91f34501e503b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 22:52:42 -0700 Subject: [PATCH 11/14] terraform: destroy apply graph builder should disable providers --- terraform/graph_builder_destroy_apply.go | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/graph_builder_destroy_apply.go b/terraform/graph_builder_destroy_apply.go index e6a190e87..2246e70b9 100644 --- a/terraform/graph_builder_destroy_apply.go +++ b/terraform/graph_builder_destroy_apply.go @@ -77,6 +77,7 @@ func (b *DestroyApplyGraphBuilder) Steps() []GraphTransformer { // Create all the providers &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, &ProviderTransformer{}, + &DisableProviderTransformer{}, &ParentProviderTransformer{}, &AttachProviderConfigTransformer{Module: b.Module}, From 152350464530f6287a265a5b95ebc0c12c211cde Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 23:12:07 -0700 Subject: [PATCH 12/14] terraform: enable shadow graph and destroy resource mode with addr This enables the shadow graph since all tests pass! We also change the destroy node to check the resource type using the addr since that is always available and reliable. The configuration can be nil for orphans. --- terraform/context.go | 5 ----- terraform/context_apply_test.go | 2 +- terraform/node_resource_destroy.go | 8 ++++++-- terraform/transform_attach_config_resource.go | 4 +++- terraform/transform_diff.go | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index eddceccae..5b7777743 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -447,11 +447,6 @@ func (c *Context) Apply() (*State, error) { shadow = real } - // TODO: remove before branch is done, we're just not ready for this yet - if c.destroy { - shadow = nil - } - // Determine the operation operation := walkApply if c.destroy { diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 4451db330..4358c2512 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -739,7 +739,7 @@ func getContextForApply_destroyCrossProviders( }, }, &ModuleState{ - Path: []string{"root", "example"}, + Path: []string{"root", "child"}, Resources: map[string]*ResourceState{ "aws_vpc.bar": &ResourceState{ Type: "aws_vpc", diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index 4979f9726..af52d1e59 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -8,7 +8,7 @@ import ( // NodeDestroyResource represents a resource that is to be destroyed. type NodeDestroyResource struct { - NodeAbstractResource + *NodeAbstractResource } func (n *NodeDestroyResource) Name() string { @@ -149,7 +149,11 @@ func (n *NodeDestroyResource) EvalTree() EvalNode { // Make sure we handle data sources properly. &EvalIf{ If: func(ctx EvalContext) (bool, error) { - if n.Config.Mode == config.DataResourceMode { + if n.Addr == nil { + return false, fmt.Errorf("nil address") + } + + if n.Addr.Mode == config.DataResourceMode { return true, nil } diff --git a/terraform/transform_attach_config_resource.go b/terraform/transform_attach_config_resource.go index 83612fede..f2ee37e56 100644 --- a/terraform/transform_attach_config_resource.go +++ b/terraform/transform_attach_config_resource.go @@ -41,7 +41,9 @@ func (t *AttachResourceConfigTransformer) Transform(g *Graph) error { // Determine what we're looking for addr := arn.ResourceAddr() - log.Printf("[TRACE] AttachResourceConfigTransformer: Attach resource request: %s", addr) + log.Printf( + "[TRACE] AttachResourceConfigTransformer: Attach resource "+ + "config request: %s", addr) // Get the configuration. path := normalizeModulePath(addr.Path) diff --git a/terraform/transform_diff.go b/terraform/transform_diff.go index 7943af685..68e905346 100644 --- a/terraform/transform_diff.go +++ b/terraform/transform_diff.go @@ -59,7 +59,7 @@ func (t *DiffTransformer) Transform(g *Graph) error { // If we're destroying, add the destroy node if inst.Destroy { - abstract := NodeAbstractResource{Addr: addr} + abstract := &NodeAbstractResource{Addr: addr} g.Add(&NodeDestroyResource{NodeAbstractResource: abstract}) } From 5a8ec482a2a35ec08e7a22b55178a05f91a83d00 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 20 Oct 2016 23:22:33 -0700 Subject: [PATCH 13/14] terraform: unify destroy/apply graph builders They're so similar we unify them, they only change in a select few places. This is very similar to the old graph but is still much simpler. --- terraform/context.go | 25 ++---- terraform/graph_builder.go | 4 + terraform/graph_builder_apply.go | 17 +++- terraform/graph_builder_destroy_apply.go | 110 ----------------------- terraform/transform.go | 31 +++++++ 5 files changed, 57 insertions(+), 130 deletions(-) delete mode 100644 terraform/graph_builder_destroy_apply.go diff --git a/terraform/context.go b/terraform/context.go index 5b7777743..176c1e182 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -392,23 +392,14 @@ func (c *Context) Apply() (*State, error) { } // Build the new graph. We do this no matter what so we can shadow it. - var newGraph *Graph - if c.destroy { - newGraph, err = (&DestroyApplyGraphBuilder{ - Module: c.module, - Diff: c.diff, - State: c.state, - Providers: c.components.ResourceProviders(), - }).Build(RootModulePath) - } else { - newGraph, err = (&ApplyGraphBuilder{ - Module: c.module, - Diff: c.diff, - State: c.state, - Providers: c.components.ResourceProviders(), - Provisioners: c.components.ResourceProvisioners(), - }).Build(RootModulePath) - } + newGraph, err := (&ApplyGraphBuilder{ + Module: c.module, + Diff: c.diff, + State: c.state, + Providers: c.components.ResourceProviders(), + Provisioners: c.components.ResourceProvisioners(), + Destroy: c.destroy, + }).Build(RootModulePath) if err != nil && !newGraphEnabled { // If we had an error graphing but we're not using this graph, just // set it to nil and record it as a shadow error. diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index 1c0a2d595..ff3145df0 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -26,6 +26,10 @@ type BasicGraphBuilder struct { func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) { g := &Graph{Path: path} for _, step := range b.Steps { + if step == nil { + continue + } + if err := step.Transform(g); err != nil { return g, err } diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 83cc89374..1d10be39a 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -30,6 +30,9 @@ type ApplyGraphBuilder struct { // DisableReduce, if true, will not reduce the graph. Great for testing. DisableReduce bool + + // Destroy, if true, represents a pure destroy operation + Destroy bool } // See GraphBuilder @@ -77,7 +80,10 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Destruction ordering &DestroyEdgeTransformer{Module: b.Module, State: b.State}, - &CBDEdgeTransformer{Module: b.Module, State: b.State}, + GraphTransformIf( + func() bool { return !b.Destroy }, + &CBDEdgeTransformer{Module: b.Module, State: b.State}, + ), // Create all the providers &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, @@ -87,8 +93,13 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { &AttachProviderConfigTransformer{Module: b.Module}, // Provisioner-related transformations - &MissingProvisionerTransformer{Provisioners: b.Provisioners}, - &ProvisionerTransformer{}, + GraphTransformIf( + func() bool { return !b.Destroy }, + GraphTransformMulti( + &MissingProvisionerTransformer{Provisioners: b.Provisioners}, + &ProvisionerTransformer{}, + ), + ), // Add root variables &RootVariableTransformer{Module: b.Module}, diff --git a/terraform/graph_builder_destroy_apply.go b/terraform/graph_builder_destroy_apply.go deleted file mode 100644 index 2246e70b9..000000000 --- a/terraform/graph_builder_destroy_apply.go +++ /dev/null @@ -1,110 +0,0 @@ -package terraform - -import ( - "github.com/hashicorp/terraform/config/module" - "github.com/hashicorp/terraform/dag" -) - -// DestroyApplyGraphBuilder implements GraphBuilder and is responsible for -// applying a pure-destroy plan. -// -// This graph builder is very similar to the ApplyGraphBuilder but -// is slightly simpler. -type DestroyApplyGraphBuilder struct { - // Module is the root module for the graph to build. - Module *module.Tree - - // Diff is the diff to apply. - Diff *Diff - - // State is the current state - State *State - - // Providers is the list of providers supported. - Providers []string - - // DisableReduce, if true, will not reduce the graph. Great for testing. - DisableReduce bool -} - -// See GraphBuilder -func (b *DestroyApplyGraphBuilder) Build(path []string) (*Graph, error) { - return (&BasicGraphBuilder{ - Steps: b.Steps(), - Validate: true, - }).Build(path) -} - -// See GraphBuilder -func (b *DestroyApplyGraphBuilder) Steps() []GraphTransformer { - // Custom factory for creating providers. - providerFactory := func(name string, path []string) GraphNodeProvider { - return &NodeApplyableProvider{ - NameValue: name, - PathValue: path, - } - } - - concreteResource := func(a *NodeAbstractResource) dag.Vertex { - return &NodeApplyableResource{ - NodeAbstractResource: a, - } - } - - steps := []GraphTransformer{ - // Creates all the nodes represented in the diff. - &DiffTransformer{ - Concrete: concreteResource, - - Diff: b.Diff, - Module: b.Module, - State: b.State, - }, - - // Create orphan output nodes - &OrphanOutputTransformer{Module: b.Module, State: b.State}, - - // Attach the configuration to any resources - &AttachResourceConfigTransformer{Module: b.Module}, - - // Attach the state - &AttachStateTransformer{State: b.State}, - - // Destruction ordering. NOTE: For destroys, we don't need to - // do any CBD stuff, so that is explicitly not here. - &DestroyEdgeTransformer{Module: b.Module, State: b.State}, - - // Create all the providers - &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, - &ProviderTransformer{}, - &DisableProviderTransformer{}, - &ParentProviderTransformer{}, - &AttachProviderConfigTransformer{Module: b.Module}, - - // Add root variables - &RootVariableTransformer{Module: b.Module}, - - // Add module variables - &ModuleVariableTransformer{Module: b.Module}, - - // Add the outputs - &OutputTransformer{Module: b.Module}, - - // Connect references so ordering is correct - &ReferenceTransformer{}, - - // Add the node to fix the state count boundaries - &CountBoundaryTransformer{}, - - // Single root - &RootTransformer{}, - } - - if !b.DisableReduce { - // Perform the transitive reduction to make our graph a bit - // more sane if possible (it usually is possible). - steps = append(steps, &TransitiveReductionTransformer{}) - } - - return steps -} diff --git a/terraform/transform.go b/terraform/transform.go index ca5736940..f4a431a67 100644 --- a/terraform/transform.go +++ b/terraform/transform.go @@ -19,3 +19,34 @@ type GraphTransformer interface { type GraphVertexTransformer interface { Transform(dag.Vertex) (dag.Vertex, error) } + +// GraphTransformIf is a helper function that conditionally returns a +// GraphTransformer given. This is useful for calling inline a sequence +// of transforms without having to split it up into multiple append() calls. +func GraphTransformIf(f func() bool, then GraphTransformer) GraphTransformer { + if f() { + return then + } + + return nil +} + +type graphTransformerMulti struct { + Transforms []GraphTransformer +} + +func (t *graphTransformerMulti) Transform(g *Graph) error { + for _, t := range t.Transforms { + if err := t.Transform(g); err != nil { + return err + } + } + + return nil +} + +// GraphTransformMulti combines multiple graph transformers into a single +// GraphTransformer that runs all the individual graph transformers. +func GraphTransformMulti(ts ...GraphTransformer) GraphTransformer { + return &graphTransformerMulti{Transforms: ts} +} From 1a418c1452b1c47e43c607e06d2871324c7c13a7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 22 Oct 2016 12:36:47 -0700 Subject: [PATCH 14/14] command/apply: -Xnew-destroy --- command/apply.go | 20 ++++++++++++++++++++ command/meta.go | 1 + 2 files changed, 21 insertions(+) diff --git a/command/apply.go b/command/apply.go index 345b2f7ee..2b40eb947 100644 --- a/command/apply.go +++ b/command/apply.go @@ -115,6 +115,26 @@ func (c *ApplyCommand) Run(args []string) int { } } + // Check for the new destroy + if terraform.X_newDestroy { + desc := "Experimental new destroy graph has been enabled. This may still\n" + + "have bugs, and should be used with care. If you'd like to continue,\n" + + "you must enter exactly 'yes' as a response." + v, err := c.UIInput().Input(&terraform.InputOpts{ + Id: "Xnew-destroy", + Query: "Experimental feature enabled: new destroy graph. Continue?", + Description: desc, + }) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) + return 1 + } + if v != "yes" { + c.Ui.Output("Apply cancelled.") + return 1 + } + } + // Build the context based on the arguments given ctx, planned, err := c.Context(contextOpts{ Destroy: c.Destroy, diff --git a/command/meta.go b/command/meta.go index dbacb3854..b8c419c1e 100644 --- a/command/meta.go +++ b/command/meta.go @@ -337,6 +337,7 @@ func (m *Meta) flagSet(n string) *flag.FlagSet { // Experimental features f.BoolVar(&terraform.X_newApply, "Xnew-apply", false, "experiment: new apply") + f.BoolVar(&terraform.X_newDestroy, "Xnew-destroy", false, "experiment: new destroy") // Create an io.Writer that writes to our Ui properly for errors. // This is kind of a hack, but it does the job. Basically: create