From d7aa59be3c0e2fdba97c3de9a13dc183e18d098e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Nov 2016 00:00:05 -0700 Subject: [PATCH] terraform: begin NodePlannableResource --- terraform/context.go | 14 ++- terraform/context_plan_test.go | 2 +- terraform/graph_builder_plan.go | 2 +- terraform/node_resource_plan.go | 207 ++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 terraform/node_resource_plan.go diff --git a/terraform/context.go b/terraform/context.go index 5b7fd7075d..5403ec41db 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -487,7 +487,7 @@ func (c *Context) Plan() (*Plan, error) { c.diffLock.Unlock() // Used throughout below - X_newApply := experiment.Enabled(experiment.X_newDestroy) + X_newApply := experiment.Enabled(experiment.X_newApply) X_newDestroy := experiment.Enabled(experiment.X_newDestroy) newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply) @@ -515,8 +515,11 @@ func (c *Context) Plan() (*Plan, error) { Targets: c.targets, }).Build(RootModulePath) } else { - // TODO: new plan graph when its ready - newGraph = nil + newGraph, err = (&PlanGraphBuilder{ + Module: c.module, + State: c.state, + Providers: c.components.ResourceProviders(), + }).Build(RootModulePath) } if err != nil && !newGraphEnabled { // If we had an error graphing but we're not using this graph, just @@ -554,6 +557,9 @@ func (c *Context) Plan() (*Plan, error) { shadow = nil } + // TODO: remove when we're ready + shadow = nil + // Do the walk walker, err := c.walk(real, shadow, operation) if err != nil { @@ -574,7 +580,7 @@ func (c *Context) Plan() (*Plan, error) { // We don't do the reverification during the new destroy plan because // it will use a different apply process. - if !(c.destroy && X_newDestroy) { + if !newGraphEnabled { // 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 { diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index c791b8b5c8..e42e83ff29 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -11,7 +11,7 @@ import ( "testing" ) -func TestContext2Plan(t *testing.T) { +func TestContext2Plan_basic(t *testing.T) { m := testModule(t, "plan-good") p := testProvider("aws") p.DiffFn = testDiffFn diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 56d23fa049..e25749299b 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -50,7 +50,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { } concreteResource := func(a *NodeAbstractResource) dag.Vertex { - return &NodeApplyableResource{ + return &NodePlannableResource{ NodeAbstractResource: a, } } diff --git a/terraform/node_resource_plan.go b/terraform/node_resource_plan.go new file mode 100644 index 0000000000..b708715a4e --- /dev/null +++ b/terraform/node_resource_plan.go @@ -0,0 +1,207 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/config" +) + +// NodePlannableResource represents a resource that is "plannable": +// it is ready to be planned in order to create a diff. +type NodePlannableResource struct { + *NodeAbstractResource +} + +// GraphNodeEvalable +func (n *NodePlannableResource) 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, + } + + // Build the resource for eval + resource := &Resource{ + Name: addr.Name, + Type: addr.Type, + CountIndex: addr.Index, + } + if resource.CountIndex < 0 { + resource.CountIndex = 0 + } + + // Determine the dependencies for the state. We use some older + // code for this that we've used for a long time. + var stateDeps []string + { + oldN := &graphNodeExpandedResource{Resource: n.Config} + stateDeps = oldN.StateDependencies() + } + + // Eval info is different depending on what kind of resource this is + switch n.Config.Mode { + case config.ManagedResourceMode: + return n.evalTreeManagedResource( + stateId, info, resource, stateDeps, + ) + case config.DataResourceMode: + return n.evalTreeDataResource( + stateId, info, resource, stateDeps) + default: + panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) + } +} + +func (n *NodePlannableResource) evalTreeDataResource( + stateId string, info *InstanceInfo, + resource *Resource, stateDeps []string) EvalNode { + var provider ResourceProvider + var config *ResourceConfig + var diff *InstanceDiff + var state *InstanceState + + return &EvalSequence{ + Nodes: []EvalNode{ + // Get the saved diff for apply + &EvalReadDiff{ + Name: stateId, + Diff: &diff, + }, + + // Stop here if we don't actually have a diff + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + if diff == nil { + return true, EvalEarlyExitError{} + } + + if diff.GetAttributesLen() == 0 { + return true, EvalEarlyExitError{} + } + + return true, nil + }, + Then: EvalNoop{}, + }, + + // We need to re-interpolate the config here, rather than + // just using the diff's values directly, because we've + // potentially learned more variable values during the + // apply pass that weren't known when the diff was produced. + &EvalInterpolate{ + Config: n.Config.RawConfig.Copy(), + Resource: resource, + Output: &config, + }, + + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + + // Make a new diff with our newly-interpolated config. + &EvalReadDataDiff{ + Info: info, + Config: &config, + Previous: &diff, + Provider: &provider, + Output: &diff, + }, + + &EvalReadDataApply{ + Info: info, + Diff: &diff, + Provider: &provider, + Output: &state, + }, + + &EvalWriteState{ + Name: stateId, + ResourceType: n.Config.Type, + Provider: n.Config.Provider, + Dependencies: stateDeps, + State: &state, + }, + + // Clear the diff now that we've applied it, so + // later nodes won't see a diff that's now a no-op. + &EvalWriteDiff{ + Name: stateId, + Diff: nil, + }, + + &EvalUpdateStateHook{}, + }, + } +} + +func (n *NodePlannableResource) evalTreeManagedResource( + stateId string, info *InstanceInfo, + resource *Resource, stateDeps []string) EvalNode { + // Declare a bunch of variables that are used for state during + // evaluation. Most of this are written to by-address below. + var provider ResourceProvider + var diff *InstanceDiff + var state *InstanceState + var resourceConfig *ResourceConfig + + return &EvalSequence{ + Nodes: []EvalNode{ + &EvalInterpolate{ + Config: n.Config.RawConfig.Copy(), + Resource: resource, + Output: &resourceConfig, + }, + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + // Re-run validation to catch any errors we missed, e.g. type + // mismatches on computed values. + &EvalValidateResource{ + Provider: &provider, + Config: &resourceConfig, + ResourceName: n.Config.Name, + ResourceType: n.Config.Type, + ResourceMode: n.Config.Mode, + IgnoreWarnings: true, + }, + &EvalReadState{ + Name: stateId, + Output: &state, + }, + &EvalDiff{ + Info: info, + Config: &resourceConfig, + Resource: n.Config, + Provider: &provider, + State: &state, + OutputDiff: &diff, + OutputState: &state, + }, + &EvalCheckPreventDestroy{ + Resource: n.Config, + Diff: &diff, + }, + &EvalWriteState{ + Name: stateId, + ResourceType: n.Config.Type, + Provider: n.Config.Provider, + Dependencies: stateDeps, + State: &state, + }, + &EvalWriteDiff{ + Name: stateId, + Diff: &diff, + }, + }, + } +}