From c4dd31e62bfec81c26c2d97f424072d91f261cf5 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 26 Sep 2017 18:58:43 -0400 Subject: [PATCH 01/10] "destroy" outputs too Ouptuts don't need to be re-evaluated during destroy, since everything is already in the state, so we can simply remove them. --- terraform/node_output.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/terraform/node_output.go b/terraform/node_output.go index 9017a63c4..d3281c346 100644 --- a/terraform/node_output.go +++ b/terraform/node_output.go @@ -69,17 +69,23 @@ func (n *NodeApplyableOutput) References() []string { // GraphNodeEvalable func (n *NodeApplyableOutput) EvalTree() EvalNode { - return &EvalOpFilter{ - Ops: []walkOperation{walkRefresh, walkPlan, walkApply, - walkDestroy, walkInput, walkValidate}, - Node: &EvalSequence{ - Nodes: []EvalNode{ - &EvalWriteOutput{ + return &EvalSequence{ + Nodes: []EvalNode{ + &EvalOpFilter{ + Ops: []walkOperation{walkRefresh, walkPlan, walkApply, + walkInput, walkValidate}, + Node: &EvalWriteOutput{ Name: n.Config.Name, Sensitive: n.Config.Sensitive, Value: n.Config.RawConfig, }, }, + &EvalOpFilter{ + Ops: []walkOperation{walkDestroy, walkPlanDestroy}, + Node: &EvalDeleteOutput{ + Name: n.Config.Name, + }, + }, }, } } From 1ad97f6be8b8654ee152366f5259d784d8947ae7 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 26 Sep 2017 19:00:57 -0400 Subject: [PATCH 02/10] use an EvalOpFilter for module variables Remove the Input flag threaded through the input graph creation process to prevent interpolation failures on module variables. Use an EvalOpFilter instead to inset the correct EvalNode during walkInput. Remove the EvalTryInterpolate type, and use the same ContinueOnErr flag as the output node for consistency and to try and keep the number possible eval node types down. --- terraform/eval_interpolate.go | 36 ++++++-------------------- terraform/graph_builder_input.go | 3 --- terraform/graph_builder_plan.go | 4 --- terraform/node_module_variable.go | 34 ++++++++++++------------ terraform/transform_module_variable.go | 2 -- 5 files changed, 24 insertions(+), 55 deletions(-) diff --git a/terraform/eval_interpolate.go b/terraform/eval_interpolate.go index df3bcb98e..78c881360 100644 --- a/terraform/eval_interpolate.go +++ b/terraform/eval_interpolate.go @@ -9,14 +9,19 @@ import ( // EvalInterpolate is an EvalNode implementation that takes a raw // configuration and interpolates it. type EvalInterpolate struct { - Config *config.RawConfig - Resource *Resource - Output **ResourceConfig + Config *config.RawConfig + Resource *Resource + Output **ResourceConfig + ContinueOnErr bool } func (n *EvalInterpolate) Eval(ctx EvalContext) (interface{}, error) { rc, err := ctx.Interpolate(n.Config, n.Resource) if err != nil { + if n.ContinueOnErr { + log.Printf("[WARN] Interpolation %q failed: %s", n.Config.Key, err) + return nil, EvalEarlyExitError{} + } return nil, err } @@ -26,28 +31,3 @@ func (n *EvalInterpolate) Eval(ctx EvalContext) (interface{}, error) { return nil, nil } - -// EvalTryInterpolate is an EvalNode implementation that takes a raw -// configuration and interpolates it, but only logs a warning on an -// interpolation error, and stops further Eval steps. -// This is used during Input where a value may not be known before Refresh, but -// we don't want to block Input. -type EvalTryInterpolate struct { - Config *config.RawConfig - Resource *Resource - Output **ResourceConfig -} - -func (n *EvalTryInterpolate) Eval(ctx EvalContext) (interface{}, error) { - rc, err := ctx.Interpolate(n.Config, n.Resource) - if err != nil { - log.Printf("[WARN] Interpolation %q failed: %s", n.Config.Key, err) - return nil, EvalEarlyExitError{} - } - - if n.Output != nil { - *n.Output = rc - } - - return nil, nil -} diff --git a/terraform/graph_builder_input.go b/terraform/graph_builder_input.go index 10fd8b1e9..0df48cdb8 100644 --- a/terraform/graph_builder_input.go +++ b/terraform/graph_builder_input.go @@ -10,9 +10,6 @@ import ( // and is based on the PlanGraphBuilder. The PlanGraphBuilder passed in will be // modified and should not be used for any other operations. func InputGraphBuilder(p *PlanGraphBuilder) GraphBuilder { - // convert this to an InputPlan - p.Input = true - // We're going to customize the concrete functions p.CustomConcrete = true diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 9d05d4a43..429e42481 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -40,9 +40,6 @@ type PlanGraphBuilder struct { // Validate will do structural validation of the graph. Validate bool - // Input represents that this builder is for an Input operation. - Input bool - // CustomConcrete can be set to customize the node types created // for various parts of the plan. This is useful in order to customize // the plan behavior. @@ -115,7 +112,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { // Add module variables &ModuleVariableTransformer{ Module: b.Module, - Input: b.Input, }, // Connect so that the references are ready for targeting. We'll diff --git a/terraform/node_module_variable.go b/terraform/node_module_variable.go index 63b84a9c5..66ff7d5e3 100644 --- a/terraform/node_module_variable.go +++ b/terraform/node_module_variable.go @@ -15,9 +15,6 @@ type NodeApplyableModuleVariable struct { Value *config.RawConfig // Value is the value that is set Module *module.Tree // Antiquated, want to remove - - // Input is set if this graph was created for the Input operation. - Input bool } func (n *NodeApplyableModuleVariable) Name() string { @@ -96,23 +93,24 @@ func (n *NodeApplyableModuleVariable) EvalTree() EvalNode { var config *ResourceConfig variables := make(map[string]interface{}) - var interpolate EvalNode - - if n.Input { - interpolate = &EvalTryInterpolate{ - Config: n.Value, - Output: &config, - } - } else { - interpolate = &EvalInterpolate{ - Config: n.Value, - Output: &config, - } - } - return &EvalSequence{ Nodes: []EvalNode{ - interpolate, + &EvalOpFilter{ + Ops: []walkOperation{walkInput}, + Node: &EvalInterpolate{ + Config: n.Value, + Output: &config, + ContinueOnErr: true, + }, + }, + &EvalOpFilter{ + Ops: []walkOperation{walkRefresh, walkPlan, walkApply, + walkDestroy, walkValidate}, + Node: &EvalInterpolate{ + Config: n.Value, + Output: &config, + }, + }, &EvalVariableBlock{ Config: &config, diff --git a/terraform/transform_module_variable.go b/terraform/transform_module_variable.go index dbfd16871..467950bdc 100644 --- a/terraform/transform_module_variable.go +++ b/terraform/transform_module_variable.go @@ -17,7 +17,6 @@ type ModuleVariableTransformer struct { Module *module.Tree DisablePrune bool // True if pruning unreferenced should be disabled - Input bool // True if this is from an Input operation. } func (t *ModuleVariableTransformer) Transform(g *Graph) error { @@ -100,7 +99,6 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module. Config: v, Value: value, Module: t.Module, - Input: t.Input, } if !t.DisablePrune { From a048bcffa0c6d45e1b56279691de74d3338fa1b5 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 27 Sep 2017 11:41:14 -0400 Subject: [PATCH 03/10] continue on output errors during Input Module outputs may not have complete information during Input, because it happens before refresh. Continue process on output interpolation errors during the Input walk. --- terraform/eval_output.go | 26 +++++++++++++++++++++----- terraform/node_output.go | 14 ++++++++++++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/terraform/eval_output.go b/terraform/eval_output.go index cf61781e5..a454efe94 100644 --- a/terraform/eval_output.go +++ b/terraform/eval_output.go @@ -41,15 +41,16 @@ type EvalWriteOutput struct { Name string Sensitive bool Value *config.RawConfig + // ContinueOnErr allows interpolation to fail during Input + ContinueOnErr bool } // TODO: test func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) { + // This has to run before we have a state lock, since interpolation also + // reads the state cfg, err := ctx.Interpolate(n.Value, nil) - if err != nil { - // Log error but continue anyway - log.Printf("[WARN] Output interpolation %q failed: %s", n.Name, err) - } + // handle the error after we have the module from the state state, lock := ctx.State() if state == nil { @@ -59,13 +60,28 @@ func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) { // Get a write lock so we can access this instance lock.Lock() defer lock.Unlock() - // Look for the module state. If we don't have one, create it. mod := state.ModuleByPath(ctx.Path()) if mod == nil { mod = state.AddModule(ctx.Path()) } + // handling the interpolation error + if err != nil { + if n.ContinueOnErr { + log.Printf("[ERROR] Output interpolation %q failed: %s", n.Name, err) + // if we're continueing, make sure the output is included, and + // marked as unknown + mod.Outputs[n.Name] = &OutputState{ + Type: "string", + Value: config.UnknownVariableValue, + } + return nil, EvalEarlyExitError{} + } + + return nil, err + } + // Get the value from the config var valueRaw interface{} = config.UnknownVariableValue if cfg != nil { diff --git a/terraform/node_output.go b/terraform/node_output.go index d3281c346..4fd246a10 100644 --- a/terraform/node_output.go +++ b/terraform/node_output.go @@ -72,8 +72,18 @@ func (n *NodeApplyableOutput) EvalTree() EvalNode { return &EvalSequence{ Nodes: []EvalNode{ &EvalOpFilter{ - Ops: []walkOperation{walkRefresh, walkPlan, walkApply, - walkInput, walkValidate}, + // Don't let interpolation errors stop Input, since it happens + // before Refresh. + Ops: []walkOperation{walkInput}, + Node: &EvalWriteOutput{ + Name: n.Config.Name, + Sensitive: n.Config.Sensitive, + Value: n.Config.RawConfig, + ContinueOnErr: true, + }, + }, + &EvalOpFilter{ + Ops: []walkOperation{walkRefresh, walkPlan, walkApply, walkValidate}, Node: &EvalWriteOutput{ Name: n.Config.Name, Sensitive: n.Config.Sensitive, From 78964d305c19192497d5d5e0429a77a303cdd66f Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 27 Sep 2017 18:59:48 -0400 Subject: [PATCH 04/10] remove broken outputs from targeted graph A Targeted graph may include outputs that were transitively included, but if they are missing any dependencies they will fail to interpolate later on. Prune any outputs in the TargetsTransformer that have missing dependencies, and are not depended on by any resource. This will maintain the existing behavior of outputs failing silently ni most cases, but allow errors to be surfaced where the output value is required. --- terraform/transform_targets.go | 50 ++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index 4f117b4f7..0cfcb0ac0 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -73,9 +73,11 @@ func (t *TargetsTransformer) Transform(g *Graph) error { if _, ok := v.(GraphNodeResource); ok { removable = true } + if vr, ok := v.(RemovableIfNotTargeted); ok { removable = vr.RemoveIfNotTargeted() } + if removable && !targetedNodes.Include(v) { log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) g.Remove(v) @@ -135,7 +137,10 @@ func (t *TargetsTransformer) selectTargetedNodes( } } } + return t.addDependencies(targetedNodes, g) +} +func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (*dag.Set, error) { // Handle nodes that need to be included if their dependencies are included. // This requires multiple passes since we need to catch transitive // dependencies if and only if they are via other nodes that also @@ -157,11 +162,6 @@ func (t *TargetsTransformer) selectTargetedNodes( } dependers = dependers.Filter(func(dv interface{}) bool { - // Can ignore nodes that are already targeted - /*if targetedNodes.Include(dv) { - return false - }*/ - _, ok := dv.(GraphNodeTargetDownstream) return ok }) @@ -180,6 +180,7 @@ func (t *TargetsTransformer) selectTargetedNodes( // depending on in case that informs its decision about whether // it is safe to be targeted. deps := g.DownEdges(v) + depsTargeted := deps.Intersection(targetedNodes) depsUntargeted := deps.Difference(depsTargeted) @@ -193,7 +194,44 @@ func (t *TargetsTransformer) selectTargetedNodes( } } - return targetedNodes, nil + return targetedNodes.Filter(func(dv interface{}) bool { + return filterPartialOutputs(dv, targetedNodes, g) + }), nil +} + +// Outputs may have been included transitively, but if any of their +// dependencies have been pruned they won't be resolvable. +// If nothing depends on the output, and the output is missing any +// dependencies, remove it from the graph. +// This essentially maintains the previous behavior where interpolation in +// outputs would fail silently, but can now surface errors where the output +// is required. +func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool { + // should this just be done with TargetDownstream? + if _, ok := v.(*NodeApplyableOutput); !ok { + return true + } + + dependers := g.UpEdges(v) + for _, d := range dependers.List() { + if _, ok := d.(*NodeCountBoundary); ok { + continue + } + // as soon as we see a real dependency, we mark this as + // non-removable + return true + } + + depends := g.DownEdges(v) + + for _, d := range depends.List() { + if !targetedNodes.Include(d) { + log.Printf("[WARN] %s missing targeted dependency %s, removing from the graph", + dag.VertexName(v), dag.VertexName(d)) + return false + } + } + return true } func (t *TargetsTransformer) nodeIsTarget( From 7fde7d9f20df101dddb0af8030111871ba99572f Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 27 Sep 2017 19:26:14 -0400 Subject: [PATCH 05/10] make a destroy plan work the same as destroy When working on an existing plan, the context always used walkApply, even if the plan was for a full destroy. Mark in the plan if it was icreated for a destroy, and transfer that to the context when reading the plan. --- terraform/context.go | 1 + terraform/plan.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/terraform/context.go b/terraform/context.go index 28ca14cbf..724ce39ed 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -539,6 +539,7 @@ func (c *Context) Plan() (*Plan, error) { var operation walkOperation if c.destroy { operation = walkPlanDestroy + p.Destroy = true } else { // Set our state to be something temporary. We do this so that // the plan can update a fake state so that variables work, then diff --git a/terraform/plan.go b/terraform/plan.go index 7b9ae1b52..8611628e3 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -71,6 +71,9 @@ type Plan struct { // Backend is the backend that this plan should use and store data with. Backend *BackendState + // Destroy indicates that this plan was created for a full destroy operation + Destroy bool + once sync.Once } @@ -100,6 +103,7 @@ func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { opts.Module = p.Module opts.Targets = p.Targets opts.ProviderSHA256s = p.ProviderSHA256s + opts.Destroy = p.Destroy if opts.State == nil { opts.State = p.State From 3414e1b996460f86eeabb03efbfb423af2f2e8d0 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 28 Sep 2017 09:41:05 -0400 Subject: [PATCH 06/10] remove dead code --- terraform/node_module_destroy.go | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 terraform/node_module_destroy.go diff --git a/terraform/node_module_destroy.go b/terraform/node_module_destroy.go deleted file mode 100644 index 319df1e3a..000000000 --- a/terraform/node_module_destroy.go +++ /dev/null @@ -1,29 +0,0 @@ -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} -} From 35c6a4e89dd90b5490ea6426b4300200961ab90a Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 29 Sep 2017 11:12:57 -0400 Subject: [PATCH 07/10] add DestroyValueReferenceTransformer DestroyValueReferenceTransformer is used during destroy to reverse the edges for output and local values. Because destruction is going to remove these from the state, nodes that depend on their value need to be visited first. --- terraform/graph_builder_apply.go | 7 +++++++ terraform/transform_destroy_edge.go | 3 +++ terraform/transform_reference.go | 31 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 4d7832772..430195c7f 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -120,6 +120,13 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Connect references so ordering is correct &ReferenceTransformer{}, + // Reverse the edges to outputs and locals, so that + // interpolations don't fail during destroy. + GraphTransformIf( + func() bool { return b.Destroy }, + &DestroyValueReferenceTransformer{}, + ), + // Add the node to fix the state count boundaries &CountBoundaryTransformer{}, diff --git a/terraform/transform_destroy_edge.go b/terraform/transform_destroy_edge.go index 22be1ab62..d9bd362f2 100644 --- a/terraform/transform_destroy_edge.go +++ b/terraform/transform_destroy_edge.go @@ -119,6 +119,9 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error { return &NodeApplyableProvider{NodeAbstractProvider: a} } steps := []GraphTransformer{ + // Add the local values + &LocalTransformer{Module: t.Module}, + // Add outputs and metadata &OutputTransformer{Module: t.Module}, &AttachResourceConfigTransformer{Module: t.Module}, diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index ce55e1d77..24ddc7a0a 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -76,6 +76,37 @@ func (t *ReferenceTransformer) Transform(g *Graph) error { return nil } +// DestroyReferenceTransformer is a GraphTransformer that reverses the edges +// for nodes that depend on an Output or Local value. Output and local nodes are +// removed during destroy, so anything which depends on them must be evaluated +// first. These can't be interpolated during destroy, so the stored value must +// be used anyway hence they don't need to be re-evaluated. +type DestroyValueReferenceTransformer struct{} + +func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error { + vs := g.Vertices() + + for _, v := range vs { + switch v.(type) { + case *NodeApplyableOutput, *NodeLocal: + // OK + default: + continue + } + + // reverse any incoming edges so that the value is removed last + for _, e := range g.EdgesTo(v) { + source := e.Source() + log.Printf("[TRACE] output dep: %s", dag.VertexName(source)) + + g.RemoveEdge(e) + g.Connect(&DestroyEdge{S: v, T: source}) + } + } + + return nil +} + // ReferenceMap is a structure that can be used to efficiently check // for references on a graph. type ReferenceMap struct { From 715036d209b82bd5d71ccba103779e2568203732 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 29 Sep 2017 14:45:55 -0400 Subject: [PATCH 08/10] put output errors behind a feature flag We're going to start merging breaking functgionality behind feature flags, to reduce the need for long-lived feature branches. --- terraform/eval_output.go | 24 ++++++++++++++---------- terraform/features.go | 9 +++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 terraform/features.go diff --git a/terraform/eval_output.go b/terraform/eval_output.go index a454efe94..657c70304 100644 --- a/terraform/eval_output.go +++ b/terraform/eval_output.go @@ -68,18 +68,22 @@ func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) { // handling the interpolation error if err != nil { - if n.ContinueOnErr { - log.Printf("[ERROR] Output interpolation %q failed: %s", n.Name, err) - // if we're continueing, make sure the output is included, and - // marked as unknown - mod.Outputs[n.Name] = &OutputState{ - Type: "string", - Value: config.UnknownVariableValue, + switch { + case featureOutputErrors: + if n.ContinueOnErr { + log.Printf("[ERROR] Output interpolation %q failed: %s", n.Name, err) + // if we're continueing, make sure the output is included, and + // marked as unknown + mod.Outputs[n.Name] = &OutputState{ + Type: "string", + Value: config.UnknownVariableValue, + } + return nil, EvalEarlyExitError{} } - return nil, EvalEarlyExitError{} + return nil, err + default: + log.Printf("[WARN] Output interpolation %q failed: %s", n.Name, err) } - - return nil, err } // Get the value from the config diff --git a/terraform/features.go b/terraform/features.go new file mode 100644 index 000000000..9bcf19d9c --- /dev/null +++ b/terraform/features.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "os" +) + +// This file holds feature flags for the next release + +var featureOutputErrors = os.Getenv("TF_OUTPUT_ERRORS") != "" From 3c2b3df5a9bdc46215eb79abaa488c82298fb8a4 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 29 Sep 2017 15:14:32 -0400 Subject: [PATCH 09/10] add some output error tests Add some tests for output errors and catch the errors behind the output errors feature flag. --- terraform/context_plan_test.go | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 0f6fdf284..bf9f7a65e 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -3614,3 +3614,106 @@ aws_instance.foo.1: t.Fatalf("bad:\n%s\n\nexpected\n\n%s", actual, expected) } } + +func TestContext2Plan_invalidOutput(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +data "aws_instance" "name" {} + +output "out" { + value = "${data.aws_instance.name.missing}" +}`, + }) + + p := testProvider("aws") + ctx := testContext2(t, &ContextOpts{ + Module: m, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), + }) + + // if this ever fails to pass validate, add a resource to reference in the config + w, e := ctx.Validate() + if len(w) > 0 { + t.Fatalf("warnings generated on validate: %#v", w) + } + if len(e) > 0 { + t.Fatalf("errors generated on validate: %v", e) + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("refresh err: %s", err) + } + + _, err = ctx.Plan() + switch { + case featureOutputErrors: + if err == nil { + t.Fatal("expected error") + } + default: + if err != nil { + t.Fatalf("plan err: %s", err) + } + } +} + +func TestContext2Plan_invalidModuleOutput(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "child/main.tf": ` +data "aws_instance" "name" {} + +output "out" { + value = "${data.aws_instance.name.missing}" +}`, + "main.tf": ` +module "child" { + source = "./child" +} + +resource "aws_instance" "foo" { + foo = "${module.child.out}" +}`, + }) + + p := testProvider("aws") + ctx := testContext2(t, &ContextOpts{ + Module: m, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), + }) + + // if this ever fails to pass validate, add a resource to reference in the config + w, e := ctx.Validate() + if len(w) > 0 { + t.Fatalf("warnings generated on validate: %#v", w) + } + if len(e) > 0 { + t.Fatalf("errors generated on validate: %v", e) + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("refresh err: %s", err) + } + + _, err = ctx.Plan() + switch { + case featureOutputErrors: + if err == nil { + t.Fatal("expected error") + } + default: + if err != nil { + t.Fatalf("plan err: %s", err) + } + } + +} From 3ed7b1abecf30eff695af7535068ce3fc97dfeef Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 2 Oct 2017 16:21:37 -0400 Subject: [PATCH 10/10] fix the providerWithLocals test This test was set to fail once this issue was fixed, and now it's fixed. --- terraform/context_apply_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 2ab26e885..36f1140e7 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -8928,8 +8928,7 @@ func TestContext2Apply_providerWithLocals(t *testing.T) { // Destroy won't work because the local value is removed before the // provider. Once this is fixed this test will start to fail, and we // can remove the invalid interpolation string; - // if providerRegion != "bar" { - if providerRegion != "${local.foo}" { + if providerRegion != "bar" { t.Fatalf("expected region %q, got: %q", "bar", providerRegion) } }