From 4cdaf6f68716ca6889cad8e0a2c710ba851d43c4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Nov 2016 10:07:17 -0800 Subject: [PATCH] terraform: ResourceTransformer to ResourceTransformerOld --- terraform/graph_config_node_resource.go | 2 +- terraform/node_resource_plan.go | 219 ++++------------------- terraform/node_resource_plan_instance.go | 196 ++++++++++++++++++++ terraform/transform_resource.go | 8 +- terraform/transform_resource_test.go | 20 +-- 5 files changed, 241 insertions(+), 204 deletions(-) create mode 100644 terraform/node_resource_plan_instance.go diff --git a/terraform/graph_config_node_resource.go b/terraform/graph_config_node_resource.go index e3decb452..db4e6626c 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -156,7 +156,7 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) steps := make([]GraphTransformer, 0, 5) // Expand counts. - steps = append(steps, &ResourceCountTransformer{ + steps = append(steps, &ResourceCountTransformerOld{ Resource: n.Resource, Destroy: n.Destroy, Targets: n.Targets, diff --git a/terraform/node_resource_plan.go b/terraform/node_resource_plan.go index b708715a4..7950c5595 100644 --- a/terraform/node_resource_plan.go +++ b/terraform/node_resource_plan.go @@ -1,11 +1,5 @@ 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 { @@ -14,194 +8,41 @@ type NodePlannableResource struct { // 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{}, + // The EvalTree for a plannable resource primarily involves + // interpolating the count since it can contain variables + // we only just received access to. + // + // With the interpolated count, we can then DynamicExpand + // into the proper number of instances. + &EvalInterpolate{Config: n.Config.RawCount}, }, } } -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 +/* +// GraphNodeDynamicExpandable +func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { + state, lock := ctx.State() + lock.RLock() + defer lock.RUnlock() - 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, - }, - }, - } + // Start creating the steps + steps := make([]GraphTransformer, 0, 5) + + // Expand counts. + steps = append(steps, &ResourceCountTransformer{ + Resource: n.Resource, + Destroy: n.Destroy, + Targets: n.Targets, + }) + + // Always end with the root being added + steps = append(steps, &RootTransformer{}) + + // Build the graph + b := &BasicGraphBuilder{Steps: steps, Validate: true} + return b.Build(ctx.Path()) } +*/ diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go new file mode 100644 index 000000000..e9e62786e --- /dev/null +++ b/terraform/node_resource_plan_instance.go @@ -0,0 +1,196 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/config" +) + +// NodePlannableResourceInstance represents a _single_ resource +// instance that is plannable. This means this represents a single +// count index, for example. +type NodePlannableResourceInstance struct { + *NodeAbstractResource +} + +// GraphNodeEvalable +func (n *NodePlannableResourceInstance) 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 *NodePlannableResourceInstance) 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{ + &EvalReadState{ + Name: stateId, + Output: &state, + }, + + // We need to re-interpolate the config here because some + // of the attributes may have become computed during + // earlier planning, due to other resources having + // "requires new resource" diffs. + &EvalInterpolate{ + Config: n.Config.RawConfig.Copy(), + Resource: resource, + Output: &config, + }, + + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0 + + // If the configuration is complete and we + // already have a state then we don't need to + // do any further work during apply, because we + // already populated the state during refresh. + if !computed && state != nil { + return true, EvalEarlyExitError{} + } + + return true, nil + }, + Then: EvalNoop{}, + }, + + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + + &EvalReadDataDiff{ + Info: info, + Config: &config, + Provider: &provider, + Output: &diff, + OutputState: &state, + }, + + &EvalWriteState{ + Name: stateId, + ResourceType: n.Config.Type, + Provider: n.Config.Provider, + Dependencies: stateDeps, + State: &state, + }, + + &EvalWriteDiff{ + Name: stateId, + Diff: &diff, + }, + }, + } +} + +func (n *NodePlannableResourceInstance) 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, + }, + }, + } +} diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index d53e9f951..f5e597569 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -8,15 +8,15 @@ import ( "github.com/hashicorp/terraform/dag" ) -// ResourceCountTransformer is a GraphTransformer that expands the count +// ResourceCountTransformerOld is a GraphTransformer that expands the count // out for a specific resource. -type ResourceCountTransformer struct { +type ResourceCountTransformerOld struct { Resource *config.Resource Destroy bool Targets []ResourceAddress } -func (t *ResourceCountTransformer) Transform(g *Graph) error { +func (t *ResourceCountTransformerOld) Transform(g *Graph) error { // Expand the resource count count, err := t.Resource.Count() if err != nil { @@ -72,7 +72,7 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error { return nil } -func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { +func (t *ResourceCountTransformerOld) nodeIsTargeted(node dag.Vertex) bool { // no targets specified, everything stays in the graph if len(t.Targets) == 0 { return true diff --git a/terraform/transform_resource_test.go b/terraform/transform_resource_test.go index 6933c622c..017e7f1c2 100644 --- a/terraform/transform_resource_test.go +++ b/terraform/transform_resource_test.go @@ -5,64 +5,64 @@ import ( "testing" ) -func TestResourceCountTransformer(t *testing.T) { +func TestResourceCountTransformerOld(t *testing.T) { cfg := testModule(t, "transform-resource-count-basic").Config() resource := cfg.Resources[0] g := Graph{Path: RootModulePath} { - tf := &ResourceCountTransformer{Resource: resource} + tf := &ResourceCountTransformerOld{Resource: resource} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testResourceCountTransformStr) + expected := strings.TrimSpace(testResourceCountTransformOldStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } } -func TestResourceCountTransformer_countNegative(t *testing.T) { +func TestResourceCountTransformerOld_countNegative(t *testing.T) { cfg := testModule(t, "transform-resource-count-negative").Config() resource := cfg.Resources[0] g := Graph{Path: RootModulePath} { - tf := &ResourceCountTransformer{Resource: resource} + tf := &ResourceCountTransformerOld{Resource: resource} if err := tf.Transform(&g); err == nil { t.Fatal("should error") } } } -func TestResourceCountTransformer_deps(t *testing.T) { +func TestResourceCountTransformerOld_deps(t *testing.T) { cfg := testModule(t, "transform-resource-count-deps").Config() resource := cfg.Resources[0] g := Graph{Path: RootModulePath} { - tf := &ResourceCountTransformer{Resource: resource} + tf := &ResourceCountTransformerOld{Resource: resource} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testResourceCountTransformDepsStr) + expected := strings.TrimSpace(testResourceCountTransformOldDepsStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } } -const testResourceCountTransformStr = ` +const testResourceCountTransformOldStr = ` aws_instance.foo #0 aws_instance.foo #1 aws_instance.foo #2 ` -const testResourceCountTransformDepsStr = ` +const testResourceCountTransformOldDepsStr = ` aws_instance.foo #0 aws_instance.foo #1 aws_instance.foo #0