diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 397334bbb0..4451db330f 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -508,6 +508,38 @@ func TestContext2Apply_destroyComputed(t *testing.T) { } } +func TestContext2Apply_dataBasic(t *testing.T) { + m := testModule(t, "apply-data-basic") + p := testProvider("null") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + p.ReadDataApplyReturn = &InstanceState{ID: "yo"} + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + }) + + if p, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } else { + t.Logf(p.String()) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyDataBasicStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContext2Apply_destroyData(t *testing.T) { m := testModule(t, "apply-destroy-data-resource") p := testProvider("null") diff --git a/terraform/node_resource_apply.go b/terraform/node_resource_apply.go index f663cd8933..0219e349b2 100644 --- a/terraform/node_resource_apply.go +++ b/terraform/node_resource_apply.go @@ -2,6 +2,8 @@ package terraform import ( "fmt" + + "github.com/hashicorp/terraform/config" ) // NodeApplyableResource represents a resource that is "applyable": @@ -49,6 +51,106 @@ func (n *NodeApplyableResource) EvalTree() EvalNode { 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 *NodeApplyableResource) 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 *NodeApplyableResource) 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 diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index ceab1d7faf..f58e3b116d 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -25,13 +25,13 @@ func TestMain(m *testing.M) { // Experimental features xNewApply := flag.Bool("Xnew-apply", false, "Experiment: new apply graph") + // Normal features + shadow := flag.Bool("shadow", true, "Enable shadow graph") + flag.Parse() // Setup experimental features X_newApply = *xNewApply - if X_newApply { - println("Xnew-apply enabled") - } if testing.Verbose() { // if we're verbose, use the logging requested by TF_LOG @@ -48,7 +48,7 @@ func TestMain(m *testing.M) { contextTestDeepCopyOnPlan = true // Shadow the new graphs - contextTestShadow = true + contextTestShadow = *shadow os.Exit(m.Run()) } @@ -257,6 +257,11 @@ aws_instance.foo: type = aws_instance ` +const testTerraformApplyDataBasicStr = ` +data.null_data_source.testing: + ID = yo +` + const testTerraformApplyRefCountStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/apply-data-basic/main.tf b/terraform/test-fixtures/apply-data-basic/main.tf new file mode 100644 index 0000000000..0c3bd8817e --- /dev/null +++ b/terraform/test-fixtures/apply-data-basic/main.tf @@ -0,0 +1 @@ +data "null_data_source" "testing" {}