diff --git a/terraform/context.go b/terraform/context.go index b2cd56bf0..68e7f0ef8 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -26,6 +26,7 @@ type ContextOpts struct { // perform operations on infrastructure. This structure is built using // NewContext. See the documentation for that. type Context2 struct { + diff *Diff hooks []Hook module *module.Tree providers map[string]ResourceProviderFactory @@ -41,12 +42,19 @@ type Context2 struct { // should not be mutated in any way, since the pointers are copied, not // the values themselves. func NewContext2(opts *ContextOpts) *Context2 { + state := opts.State + if state == nil { + state = new(State) + state.init() + } + return &Context2{ + diff: opts.Diff, hooks: opts.Hooks, module: opts.Module, providers: opts.Providers, provisioners: opts.Provisioners, - state: opts.State, + state: state, variables: opts.Variables, } } @@ -73,6 +81,51 @@ func (c *Context2) GraphBuilder() GraphBuilder { } } +// Plan generates an execution plan for the given context. +// +// The execution plan encapsulates the context and can be stored +// in order to reinstantiate a context later for Apply. +// +// Plan also updates the diff of this context to be the diff generated +// by the plan, so Apply can be called after. +func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) { + p := &Plan{ + Module: c.module, + Vars: c.variables, + State: c.state, + } + + var operation walkOperation + if opts != nil && opts.Destroy { + operation = walkPlanDestroy + } 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 + // we replace it back with our old state. + old := c.state + if old == nil { + c.state = &State{} + c.state.init() + } else { + c.state = old.deepcopy() + } + defer func() { + c.state = old + }() + + operation = walkPlan + } + + // Do the walk + walker, err := c.walk(operation) + p.Diff = walker.Diff + + // Update the diff so that our context is up-to-date + c.diff = p.Diff + + return p, err +} + // Refresh goes through all the resources in the state and refreshes them // to their latest state. This will update the state that this context // works with, along with returning it. diff --git a/terraform/context_test.go b/terraform/context_test.go index d25868cef..6ee6fbd24 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -7,6 +7,1357 @@ import ( "testing" ) +func TestContext2Plan(t *testing.T) { + m := testModule(t, "plan-good") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(plan.Diff.RootModule().Resources) < 2 { + t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +/* +func TestContextPlan_emptyDiff(t *testing.T) { + m := testModule(t, "plan-empty") + p := testProvider("aws") + p.DiffFn = func( + info *InstanceInfo, + s *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + return nil, nil + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanEmptyStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_minimal(t *testing.T) { + m := testModule(t, "plan-empty") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanEmptyStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_modules(t *testing.T) { + m := testModule(t, "plan-modules") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModulesStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleInput(t *testing.T) { + m := testModule(t, "plan-module-input") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleInputStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleInputComputed(t *testing.T) { + m := testModule(t, "plan-module-input-computed") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleInputComputedStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleInputFromVar(t *testing.T) { + m := testModule(t, "plan-module-input-var") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "52", + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleInputVarStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} +func TestContextPlan_moduleMultiVar(t *testing.T) { + m := testModule(t, "plan-module-multi-var") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleMultiVarStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} +func TestContextPlan_moduleOrphans(t *testing.T) { + m := testModule(t, "plan-modules-remove") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root", "child"}, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "baz", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleOrphansStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleProviderInherit(t *testing.T) { + var l sync.Mutex + var calls []string + + m := testModule(t, "plan-module-provider-inherit") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { + l.Lock() + defer l.Unlock() + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + if v, ok := c.Get("from"); !ok || v.(string) != "root" { + return fmt.Errorf("bad") + } + + return nil + } + p.DiffFn = func( + info *InstanceInfo, + state *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + v, _ := c.Get("from") + calls = append(calls, v.(string)) + return testDiffFn(info, state, c) + } + return p, nil + }, + }, + }) + + _, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := calls + sort.Strings(actual) + expected := []string{"child", "root"} + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestContextPlan_moduleProviderDefaults(t *testing.T) { + var l sync.Mutex + var calls []string + toCount := 0 + + m := testModule(t, "plan-module-provider-defaults") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { + l.Lock() + defer l.Unlock() + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + if v, ok := c.Get("from"); !ok || v.(string) != "root" { + return fmt.Errorf("bad") + } + if v, ok := c.Get("to"); ok && v.(string) == "child" { + toCount++ + } + + return nil + } + p.DiffFn = func( + info *InstanceInfo, + state *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + v, _ := c.Get("from") + calls = append(calls, v.(string)) + return testDiffFn(info, state, c) + } + return p, nil + }, + }, + }) + + _, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if toCount != 1 { + t.Fatal("provider in child didn't set proper config") + } + + actual := calls + sort.Strings(actual) + expected := []string{"child", "root"} + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestContextPlan_moduleProviderDefaultsVar(t *testing.T) { + var l sync.Mutex + var calls []string + + m := testModule(t, "plan-module-provider-defaults-var") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { + l.Lock() + defer l.Unlock() + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + var buf bytes.Buffer + if v, ok := c.Get("from"); ok { + buf.WriteString(v.(string) + "\n") + } + if v, ok := c.Get("to"); ok { + buf.WriteString(v.(string) + "\n") + } + + calls = append(calls, buf.String()) + return nil + } + p.DiffFn = testDiffFn + return p, nil + }, + }, + Variables: map[string]string{ + "foo": "root", + }, + }) + + _, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{ + "root\n", + "root\nchild\n", + } + if !reflect.DeepEqual(calls, expected) { + t.Fatalf("BAD: %#v", calls) + } +} + +func TestContextPlan_moduleVar(t *testing.T) { + m := testModule(t, "plan-module-var") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleVarStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleVarComputed(t *testing.T) { + m := testModule(t, "plan-module-var-computed") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleVarComputedStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_nil(t *testing.T) { + m := testModule(t, "plan-nil") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(plan.Diff.RootModule().Resources) != 0 { + t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) + } +} + +func TestContextPlan_computed(t *testing.T) { + m := testModule(t, "plan-computed") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanComputedStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_computedList(t *testing.T) { + m := testModule(t, "plan-computed-list") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanComputedListStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_count(t *testing.T) { + m := testModule(t, "plan-count") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(plan.Diff.RootModule().Resources) < 6 { + t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countComputed(t *testing.T) { + m := testModule(t, "plan-count-computed") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + _, err := ctx.Plan(nil) + if err == nil { + t.Fatal("should error") + } +} + +func TestContextPlan_countIndex(t *testing.T) { + m := testModule(t, "plan-count-index") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountIndexStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countIndexZero(t *testing.T) { + m := testModule(t, "plan-count-index-zero") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountIndexZeroStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countVar(t *testing.T) { + m := testModule(t, "plan-count-var") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "count": "3", + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountVarStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countZero(t *testing.T) { + m := testModule(t, "plan-count-zero") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountZeroStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countOneIndex(t *testing.T) { + m := testModule(t, "plan-count-one-index") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountOneIndexStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countDecreaseToOne(t *testing.T) { + m := testModule(t, "plan-count-dec") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "foo", + "type": "aws_instance", + }, + }, + }, + "aws_instance.foo.1": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + "aws_instance.foo.2": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountDecreaseStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countIncreaseFromNotSet(t *testing.T) { + m := testModule(t, "plan-count-inc") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "foo", + "type": "aws_instance", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountIncreaseStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_countIncreaseFromOne(t *testing.T) { + m := testModule(t, "plan-count-inc") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "foo", + "type": "aws_instance", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountIncreaseFromOneStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_destroy(t *testing.T) { + m := testModule(t, "plan-destroy") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.one": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + "aws_instance.two": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "baz", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(plan.Diff.RootModule().Resources) != 2 { + t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanDestroyStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleDestroy(t *testing.T) { + m := testModule(t, "plan-module-destroy") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + &ModuleState{ + Path: []string{"root", "child"}, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleDestroyStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_moduleDestroyMultivar(t *testing.T) { + m := testModule(t, "plan-module-destroy-multivar") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{}, + }, + &ModuleState{ + Path: []string{"root", "child"}, + Resources: map[string]*ResourceState{ + "aws_instance.foo.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar0", + }, + }, + "aws_instance.foo.1": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar1", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanModuleDestroyMultivarStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_pathVar(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + + m := testModule(t, "plan-path-var") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanPathVarStr) + + // Warning: this ordering REALLY matters for this test. The + // order is: cwd, module, root. + expected = fmt.Sprintf( + expected, + cwd, + m.Config().Dir, + m.Config().Dir) + + if actual != expected { + t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) + } +} + +func TestContextPlan_diffVar(t *testing.T) { + m := testModule(t, "plan-diffvar") + p := testProvider("aws") + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "num": "2", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + p.DiffFn = func( + info *InstanceInfo, + s *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + if s.ID != "bar" { + return testDiffFn(info, s, c) + } + + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + Old: "2", + New: "3", + }, + }, + }, nil + } + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanDiffVarStr) + if actual != expected { + t.Fatalf("actual:\n%s\n\nexpected:\n%s", actual, expected) + } +} + +func TestContextPlan_hook(t *testing.T) { + m := testModule(t, "plan-good") + h := new(MockHook) + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + _, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !h.PreDiffCalled { + t.Fatal("should be called") + } + if !h.PostDiffCalled { + t.Fatal("should be called") + } +} + +func TestContextPlan_orphan(t *testing.T) { + m := testModule(t, "plan-orphan") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.baz": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanOrphanStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_state(t *testing.T) { + m := testModule(t, "plan-good") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(plan.Diff.RootModule().Resources) < 2 { + t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanStateStr) + if actual != expected { + t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) + } +} + +func TestContextPlan_taint(t *testing.T) { + m := testModule(t, "plan-taint") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{"num": "2"}, + }, + }, + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "baz", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanTaintStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +// Doing a Refresh (or any operation really, but Refresh usually +// happens first) with a config with an unknown provider should result in +// an error. The key bug this found was that this wasn't happening if +// Providers was _empty_. +func TestContextRefresh_unknownProvider(t *testing.T) { + m := testModule(t, "refresh-unknown-provider") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{}, + }) + + if _, err := ctx.Refresh(); err == nil { + t.Fatal("should error") + } +} + +func TestContextPlan_multiple_taint(t *testing.T) { + m := testModule(t, "plan-taint") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{"num": "2"}, + }, + }, + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "baz", + }, + &InstanceState{ + ID: "zip", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanMultipleTaintStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_provider(t *testing.T) { + m := testModule(t, "plan-provider") + p := testProvider("aws") + p.DiffFn = testDiffFn + + var value interface{} + p.ConfigureFn = func(c *ResourceConfig) error { + value, _ = c.Get("foo") + return nil + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "bar", + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if value != "bar" { + t.Fatalf("bad: %#v", value) + } +} + +func TestContextPlan_varMultiCountOne(t *testing.T) { + m := testModule(t, "plan-var-multi-count-one") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanVarMultiCountOneStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextPlan_varListErr(t *testing.T) { + m := testModule(t, "plan-var-list-err") + p := testProvider("aws") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + _, err := ctx.Plan(nil) + if err == nil { + t.Fatal("should error") + } +} +*/ + func TestContext2Refresh(t *testing.T) { p := testProvider("aws") m := testModule(t, "refresh-basic") @@ -3269,1357 +4620,6 @@ func TestContextApply_singleDestroy(t *testing.T) { } } -func TestContextPlan(t *testing.T) { - m := testModule(t, "plan-good") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(plan.Diff.RootModule().Resources) < 2 { - t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_emptyDiff(t *testing.T) { - m := testModule(t, "plan-empty") - p := testProvider("aws") - p.DiffFn = func( - info *InstanceInfo, - s *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - return nil, nil - } - - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanEmptyStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_minimal(t *testing.T) { - m := testModule(t, "plan-empty") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanEmptyStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_modules(t *testing.T) { - m := testModule(t, "plan-modules") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModulesStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleInput(t *testing.T) { - m := testModule(t, "plan-module-input") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleInputStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleInputComputed(t *testing.T) { - m := testModule(t, "plan-module-input-computed") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleInputComputedStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleInputFromVar(t *testing.T) { - m := testModule(t, "plan-module-input-var") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - Variables: map[string]string{ - "foo": "52", - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleInputVarStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} -func TestContextPlan_moduleMultiVar(t *testing.T) { - m := testModule(t, "plan-module-multi-var") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleMultiVarStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} -func TestContextPlan_moduleOrphans(t *testing.T) { - m := testModule(t, "plan-modules-remove") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "child"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "baz", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleOrphansStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleProviderInherit(t *testing.T) { - var l sync.Mutex - var calls []string - - m := testModule(t, "plan-module-provider-inherit") - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - if v, ok := c.Get("from"); !ok || v.(string) != "root" { - return fmt.Errorf("bad") - } - - return nil - } - p.DiffFn = func( - info *InstanceInfo, - state *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - v, _ := c.Get("from") - calls = append(calls, v.(string)) - return testDiffFn(info, state, c) - } - return p, nil - }, - }, - }) - - _, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := calls - sort.Strings(actual) - expected := []string{"child", "root"} - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestContextPlan_moduleProviderDefaults(t *testing.T) { - var l sync.Mutex - var calls []string - toCount := 0 - - m := testModule(t, "plan-module-provider-defaults") - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - if v, ok := c.Get("from"); !ok || v.(string) != "root" { - return fmt.Errorf("bad") - } - if v, ok := c.Get("to"); ok && v.(string) == "child" { - toCount++ - } - - return nil - } - p.DiffFn = func( - info *InstanceInfo, - state *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - v, _ := c.Get("from") - calls = append(calls, v.(string)) - return testDiffFn(info, state, c) - } - return p, nil - }, - }, - }) - - _, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if toCount != 1 { - t.Fatal("provider in child didn't set proper config") - } - - actual := calls - sort.Strings(actual) - expected := []string{"child", "root"} - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestContextPlan_moduleProviderDefaultsVar(t *testing.T) { - var l sync.Mutex - var calls []string - - m := testModule(t, "plan-module-provider-defaults-var") - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - var buf bytes.Buffer - if v, ok := c.Get("from"); ok { - buf.WriteString(v.(string) + "\n") - } - if v, ok := c.Get("to"); ok { - buf.WriteString(v.(string) + "\n") - } - - calls = append(calls, buf.String()) - return nil - } - p.DiffFn = testDiffFn - return p, nil - }, - }, - Variables: map[string]string{ - "foo": "root", - }, - }) - - _, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := []string{ - "root\n", - "root\nchild\n", - } - if !reflect.DeepEqual(calls, expected) { - t.Fatalf("BAD: %#v", calls) - } -} - -func TestContextPlan_moduleVar(t *testing.T) { - m := testModule(t, "plan-module-var") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleVarStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleVarComputed(t *testing.T) { - m := testModule(t, "plan-module-var-computed") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleVarComputedStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_nil(t *testing.T) { - m := testModule(t, "plan-nil") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if len(plan.Diff.RootModule().Resources) != 0 { - t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) - } -} - -func TestContextPlan_computed(t *testing.T) { - m := testModule(t, "plan-computed") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanComputedStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_computedList(t *testing.T) { - m := testModule(t, "plan-computed-list") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanComputedListStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_count(t *testing.T) { - m := testModule(t, "plan-count") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(plan.Diff.RootModule().Resources) < 6 { - t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countComputed(t *testing.T) { - m := testModule(t, "plan-count-computed") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - _, err := ctx.Plan(nil) - if err == nil { - t.Fatal("should error") - } -} - -func TestContextPlan_countIndex(t *testing.T) { - m := testModule(t, "plan-count-index") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountIndexStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countIndexZero(t *testing.T) { - m := testModule(t, "plan-count-index-zero") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountIndexZeroStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countVar(t *testing.T) { - m := testModule(t, "plan-count-var") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - Variables: map[string]string{ - "count": "3", - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountVarStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countZero(t *testing.T) { - m := testModule(t, "plan-count-zero") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountZeroStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countOneIndex(t *testing.T) { - m := testModule(t, "plan-count-one-index") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountOneIndexStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countDecreaseToOne(t *testing.T) { - m := testModule(t, "plan-count-dec") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "foo": "foo", - "type": "aws_instance", - }, - }, - }, - "aws_instance.foo.1": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - "aws_instance.foo.2": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountDecreaseStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countIncreaseFromNotSet(t *testing.T) { - m := testModule(t, "plan-count-inc") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "foo": "foo", - "type": "aws_instance", - }, - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountIncreaseStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_countIncreaseFromOne(t *testing.T) { - m := testModule(t, "plan-count-inc") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "foo": "foo", - "type": "aws_instance", - }, - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanCountIncreaseFromOneStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_destroy(t *testing.T) { - m := testModule(t, "plan-destroy") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.one": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - "aws_instance.two": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "baz", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(plan.Diff.RootModule().Resources) != 2 { - t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanDestroyStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleDestroy(t *testing.T) { - m := testModule(t, "plan-module-destroy") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - &ModuleState{ - Path: []string{"root", "child"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleDestroyStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_moduleDestroyMultivar(t *testing.T) { - m := testModule(t, "plan-module-destroy-multivar") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{}, - }, - &ModuleState{ - Path: []string{"root", "child"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar0", - }, - }, - "aws_instance.foo.1": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar1", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanModuleDestroyMultivarStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_pathVar(t *testing.T) { - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - - m := testModule(t, "plan-path-var") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanPathVarStr) - - // Warning: this ordering REALLY matters for this test. The - // order is: cwd, module, root. - expected = fmt.Sprintf( - expected, - cwd, - m.Config().Dir, - m.Config().Dir) - - if actual != expected { - t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) - } -} - -func TestContextPlan_diffVar(t *testing.T) { - m := testModule(t, "plan-diffvar") - p := testProvider("aws") - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "num": "2", - }, - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - p.DiffFn = func( - info *InstanceInfo, - s *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - if s.ID != "bar" { - return testDiffFn(info, s, c) - } - - return &InstanceDiff{ - Attributes: map[string]*ResourceAttrDiff{ - "num": &ResourceAttrDiff{ - Old: "2", - New: "3", - }, - }, - }, nil - } - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanDiffVarStr) - if actual != expected { - t.Fatalf("actual:\n%s\n\nexpected:\n%s", actual, expected) - } -} - -func TestContextPlan_hook(t *testing.T) { - m := testModule(t, "plan-good") - h := new(MockHook) - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - _, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !h.PreDiffCalled { - t.Fatal("should be called") - } - if !h.PostDiffCalled { - t.Fatal("should be called") - } -} - -func TestContextPlan_orphan(t *testing.T) { - m := testModule(t, "plan-orphan") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.baz": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanOrphanStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_state(t *testing.T) { - m := testModule(t, "plan-good") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(plan.Diff.RootModule().Resources) < 2 { - t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanStateStr) - if actual != expected { - t.Fatalf("bad:\n%s\n\nexpected:\n\n%s", actual, expected) - } -} - -func TestContextPlan_taint(t *testing.T) { - m := testModule(t, "plan-taint") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{"num": "2"}, - }, - }, - "aws_instance.bar": &ResourceState{ - Type: "aws_instance", - Tainted: []*InstanceState{ - &InstanceState{ - ID: "baz", - }, - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanTaintStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -// Doing a Refresh (or any operation really, but Refresh usually -// happens first) with a config with an unknown provider should result in -// an error. The key bug this found was that this wasn't happening if -// Providers was _empty_. -func TestContextRefresh_unknownProvider(t *testing.T) { - m := testModule(t, "refresh-unknown-provider") - p := testProvider("aws") - p.ApplyFn = testApplyFn - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{}, - }) - - if _, err := ctx.Refresh(); err == nil { - t.Fatal("should error") - } -} - -func TestContextPlan_multiple_taint(t *testing.T) { - m := testModule(t, "plan-taint") - p := testProvider("aws") - p.DiffFn = testDiffFn - s := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - Attributes: map[string]string{"num": "2"}, - }, - }, - "aws_instance.bar": &ResourceState{ - Type: "aws_instance", - Tainted: []*InstanceState{ - &InstanceState{ - ID: "baz", - }, - &InstanceState{ - ID: "zip", - }, - }, - }, - }, - }, - }, - } - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: s, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanMultipleTaintStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_provider(t *testing.T) { - m := testModule(t, "plan-provider") - p := testProvider("aws") - p.DiffFn = testDiffFn - - var value interface{} - p.ConfigureFn = func(c *ResourceConfig) error { - value, _ = c.Get("foo") - return nil - } - - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - Variables: map[string]string{ - "foo": "bar", - }, - }) - - if _, err := ctx.Plan(nil); err != nil { - t.Fatalf("err: %s", err) - } - - if value != "bar" { - t.Fatalf("bad: %#v", value) - } -} - -func TestContextPlan_varMultiCountOne(t *testing.T) { - m := testModule(t, "plan-var-multi-count-one") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanVarMultiCountOneStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestContextPlan_varListErr(t *testing.T) { - m := testModule(t, "plan-var-list-err") - p := testProvider("aws") - ctx := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - _, err := ctx.Plan(nil) - if err == nil { - t.Fatal("should error") - } -} - - - func testContext(t *testing.T, opts *ContextOpts) *Context { return NewContext(opts) } diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 558993e7f..50c35ef2d 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -49,6 +49,10 @@ type EvalContext interface { // that is currently being acted upon. Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error) + // Diff returns the global diff as well as the lock that should + // be used to modify that diff. + Diff() (*Diff, *sync.RWMutex) + // State returns the global state as well as the lock that should // be used to modify that state. State() (*State, *sync.RWMutex) @@ -96,6 +100,10 @@ type MockEvalContext struct { PathCalled bool PathPath []string + DiffCalled bool + DiffDiff *Diff + DiffLock *sync.RWMutex + StateCalled bool StateState *State StateLock *sync.RWMutex @@ -156,6 +164,11 @@ func (c *MockEvalContext) Path() []string { return c.PathPath } +func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) { + c.DiffCalled = true + return c.DiffDiff, c.DiffLock +} + func (c *MockEvalContext) State() (*State, *sync.RWMutex) { c.StateCalled = true return c.StateState, c.StateLock diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index d0a343d09..e2fbd121f 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -22,6 +22,8 @@ type BuiltinEvalContext struct { Provisioners map[string]ResourceProvisionerFactory ProvisionerCache map[string]ResourceProvisioner ProvisionerLock *sync.Mutex + DiffValue *Diff + DiffLock *sync.RWMutex StateValue *State StateLock *sync.RWMutex @@ -177,6 +179,10 @@ func (ctx *BuiltinEvalContext) Path() []string { return ctx.PathValue } +func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) { + return ctx.DiffValue, ctx.DiffLock +} + func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) { return ctx.StateValue, ctx.StateLock } diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go new file mode 100644 index 000000000..18ced69e0 --- /dev/null +++ b/terraform/eval_diff.go @@ -0,0 +1,131 @@ +package terraform + +// EvalDiff is an EvalNode implementation that does a refresh for +// a resource. +type EvalDiff struct { + Info *InstanceInfo + Config EvalNode + Provider EvalNode + State EvalNode + Output *InstanceDiff +} + +func (n *EvalDiff) Args() ([]EvalNode, []EvalType) { + return []EvalNode{n.Config, n.Provider, n.State}, + []EvalType{EvalTypeConfig, EvalTypeResourceProvider, + EvalTypeInstanceState} +} + +// TODO: test +func (n *EvalDiff) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + // Extract our arguments + var state *InstanceState + config := args[0].(*ResourceConfig) + provider := args[1].(ResourceProvider) + if args[2] != nil { + state = args[2].(*InstanceState) + } + + // Call pre-diff hook + err := ctx.Hook(func(h Hook) (HookAction, error) { + return h.PreDiff(n.Info, state) + }) + if err != nil { + return nil, err + } + + // The state for the diff must never be nil + diffState := state + if diffState == nil { + diffState = new(InstanceState) + } + diffState.init() + + // Diff! + diff, err := provider.Diff(n.Info, diffState, config) + if err != nil { + return nil, err + } + if diff == nil { + diff = new(InstanceDiff) + } + + // Require a destroy if there is no ID and it requires new. + if diff.RequiresNew() && state != nil && state.ID != "" { + diff.Destroy = true + } + + // If we're creating a new resource, compute its ID + if diff.RequiresNew() || state == nil || state.ID == "" { + var oldID string + if state != nil { + oldID = state.Attributes["id"] + } + + // Add diff to compute new ID + diff.init() + diff.Attributes["id"] = &ResourceAttrDiff{ + Old: oldID, + NewComputed: true, + RequiresNew: true, + Type: DiffAttrOutput, + } + } + + // Call post-refresh hook + err = ctx.Hook(func(h Hook) (HookAction, error) { + return h.PostDiff(n.Info, diff) + }) + if err != nil { + return nil, err + } + + // Update our output + *n.Output = *diff + + // Merge our state so that the state is updated with our plan + if !diff.Empty() { + state = state.MergeDiff(diff) + } + + return state, nil +} + +func (n *EvalDiff) Type() EvalType { + return EvalTypeInstanceState +} + +// EvalWriteDiff is an EvalNode implementation that writes the diff to +// the full diff. +type EvalWriteDiff struct { + Name string + Diff *InstanceDiff +} + +func (n *EvalWriteDiff) Args() ([]EvalNode, []EvalType) { + return nil, nil +} + +// TODO: test +func (n *EvalWriteDiff) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + diff, lock := ctx.Diff() + + // Acquire the lock so that we can do this safely concurrently + lock.Lock() + defer lock.Unlock() + + // Write the diff + modDiff := diff.ModuleByPath(ctx.Path()) + if modDiff == nil { + modDiff = diff.AddModule(ctx.Path()) + } + modDiff.Resources[n.Name] = n.Diff + + return nil, nil +} + +func (n *EvalWriteDiff) Type() EvalType { + return EvalTypeNull +} diff --git a/terraform/eval_type.go b/terraform/eval_type.go index 9a951fdca..52dfa5bd5 100644 --- a/terraform/eval_type.go +++ b/terraform/eval_type.go @@ -17,5 +17,6 @@ const ( EvalTypeConfig // *ResourceConfig EvalTypeResourceProvider // ResourceProvider EvalTypeResourceProvisioner // ResourceProvisioner + EvalTypeInstanceDiff // *InstanceDiff EvalTypeInstanceState // *InstanceState ) diff --git a/terraform/evaltype_string.go b/terraform/evaltype_string.go index c6126790d..f37d69806 100644 --- a/terraform/evaltype_string.go +++ b/terraform/evaltype_string.go @@ -10,7 +10,8 @@ const ( _EvalType_name_2 = "EvalTypeConfig" _EvalType_name_3 = "EvalTypeResourceProvider" _EvalType_name_4 = "EvalTypeResourceProvisioner" - _EvalType_name_5 = "EvalTypeInstanceState" + _EvalType_name_5 = "EvalTypeInstanceDiff" + _EvalType_name_6 = "EvalTypeInstanceState" ) var ( @@ -19,7 +20,8 @@ var ( _EvalType_index_2 = [...]uint8{0, 14} _EvalType_index_3 = [...]uint8{0, 24} _EvalType_index_4 = [...]uint8{0, 27} - _EvalType_index_5 = [...]uint8{0, 21} + _EvalType_index_5 = [...]uint8{0, 20} + _EvalType_index_6 = [...]uint8{0, 21} ) func (i EvalType) String() string { @@ -36,6 +38,8 @@ func (i EvalType) String() string { return _EvalType_name_4 case i == 32: return _EvalType_name_5 + case i == 64: + return _EvalType_name_6 default: return fmt.Sprintf("EvalType(%d)", i) } diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index 0bd23aa81..9e31df5fe 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -19,11 +19,13 @@ type ContextGraphWalker struct { // Outputs, do not set these. Do not read these while the graph // is being walked. EvalError error + Diff *Diff ValidationWarnings []string ValidationErrors []error errorLock sync.Mutex once sync.Once + diffLock sync.RWMutex providerCache map[string]ResourceProvider providerConfigCache map[string]*ResourceConfig providerLock sync.Mutex @@ -44,6 +46,8 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext { Provisioners: w.Context.provisioners, ProvisionerCache: w.provisionerCache, ProvisionerLock: &w.provisionerLock, + DiffValue: w.Diff, + DiffLock: &w.diffLock, StateValue: w.Context.state, StateLock: &w.Context.stateLock, Interpolater: &Interpolater{ @@ -87,6 +91,9 @@ func (w *ContextGraphWalker) ExitEvalTree( } func (w *ContextGraphWalker) init() { + w.Diff = new(Diff) + w.Diff.init() + w.providerCache = make(map[string]ResourceProvider, 5) w.providerConfigCache = make(map[string]*ResourceConfig, 5) w.provisionerCache = make(map[string]ResourceProvisioner, 5) diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 3ab9afc69..c205e17c1 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -115,6 +115,32 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { }, }) + // Diff the resource + var diff InstanceDiff + seq.Nodes = append(seq.Nodes, &EvalOpFilter{ + Ops: []walkOperation{walkPlan}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalWriteState{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &EvalDiff{ + Info: info, + Config: &EvalInterpolate{Config: n.Resource.RawConfig}, + Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, + State: &EvalReadState{Name: n.stateId()}, + Output: &diff, + }, + }, + &EvalWriteDiff{ + Name: n.stateId(), + Diff: &diff, + }, + }, + }, + }) + return seq }