From db807f4b0f56d9029699e50d5f682d11a9925a69 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Oct 2016 18:28:02 -0700 Subject: [PATCH] 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