diff --git a/terraform/context.go b/terraform/context.go new file mode 100644 index 000000000..0e36e83fd --- /dev/null +++ b/terraform/context.go @@ -0,0 +1,114 @@ +package terraform + +import ( + "sync" + + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" +) + +// ContextOpts are the user-configurable options to create a context with +// NewContext. +type ContextOpts struct { + Diff *Diff + Hooks []Hook + Module *module.Tree + Parallelism int + State *State + Providers map[string]ResourceProviderFactory + Provisioners map[string]ResourceProvisionerFactory + Variables map[string]string + + UIInput UIInput +} + +// Context represents all the context that Terraform needs in order to +// perform operations on infrastructure. This structure is built using +// NewContext. See the documentation for that. +type Context2 struct { + module *module.Tree + providers map[string]ResourceProviderFactory + state *State +} + +// NewContext creates a new Context structure. +// +// Once a Context is creator, the pointer values within ContextOpts +// should not be mutated in any way, since the pointers are copied, not +// the values themselves. +func NewContext2(opts *ContextOpts) *Context2 { + return &Context2{ + module: opts.Module, + providers: opts.Providers, + state: opts.State, + } +} + +// GraphBuilder returns the GraphBuilder that will be used to create +// the graphs for this context. +func (c *Context2) GraphBuilder() GraphBuilder { + // TODO test + providers := make([]string, 0, len(c.providers)) + for k, _ := range c.providers { + providers = append(providers, k) + } + + return &BuiltinGraphBuilder{ + Root: c.module, + Providers: providers, + State: c.state, + } +} + +// Validate validates the configuration and returns any warnings or errors. +func (c *Context2) Validate() ([]string, []error) { + evalCtx := c.evalContext() + evalCtx.ComputeMissing = true + + // Build the graph + graph, err := c.GraphBuilder().Build(RootModulePath) + if err != nil { + return nil, []error{err} + } + + // Valiate the graph + if err := graph.Validate(); err != nil { + return nil, []error{err} + } + + // Walk the graph + var warns []string + var errs []error + var lock sync.Mutex + graph.Walk(func(v dag.Vertex) { + ev, ok := v.(GraphNodeEvalable) + if !ok { + return + } + + _, err := Eval(ev.EvalTree(), evalCtx) + if err == nil { + return + } + + lock.Lock() + defer lock.Unlock() + + verr, ok := err.(*EvalValidateError) + if !ok { + errs = append(errs, err) + return + } + + warns = append(warns, verr.Warnings...) + errs = append(errs, verr.Errors...) + }) + + return warns, errs +} + +func (c *Context2) evalContext() *BuiltinEvalContext { + return &BuiltinEvalContext{ + Providers: c.providers, + } +} diff --git a/terraform/context_old.go b/terraform/context_old.go index 3ad2a15a2..e1de3d209 100644 --- a/terraform/context_old.go +++ b/terraform/context_old.go @@ -44,21 +44,6 @@ type Context struct { sh *stopHook } -// ContextOpts are the user-creatable configuration structure to create -// a context with NewContext. -type ContextOpts struct { - Diff *Diff - Hooks []Hook - Module *module.Tree - Parallelism int - State *State - Providers map[string]ResourceProviderFactory - Provisioners map[string]ResourceProvisionerFactory - Variables map[string]string - - UIInput UIInput -} - // InputMode defines what sort of input will be asked for when Input // is called on Context. type InputMode byte diff --git a/terraform/context_test.go b/terraform/context_test.go new file mode 100644 index 000000000..938db75b1 --- /dev/null +++ b/terraform/context_test.go @@ -0,0 +1,4789 @@ +package terraform + +import ( + "testing" +) + +func TestContext2Validate(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-good") + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContext2Validate_badVar(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-bad-var") + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +/* +func TestContextValidate_goodModule(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-good-module") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_badModuleOutput(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-bad-module-output") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_countNegative(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-count-negative") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_countVariable(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "apply-count-variable") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + for _, err := range e { + t.Errorf("bad: %s", err) + } + t.FailNow() + } +} + +func TestContextValidate_countVariableNoDefault(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-count-variable") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 1 { + for _, err := range e { + t.Errorf("bad: %s", err) + } + t.FailNow() + } +} + +func TestContextValidate_moduleBadResource(t *testing.T) { + m := testModule(t, "validate-module-bad-rc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_moduleProviderInherit(t *testing.T) { + m := testModule(t, "validate-module-pc-inherit") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"set"}) + } + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_orphans(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-good") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.ValidateResourceFn = func( + t string, c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_tainted(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-good") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + }, + } + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.ValidateResourceFn = func( + t string, c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_providerConfig_bad(t *testing.T) { + m := testModule(t, "validate-bad-pc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_providerConfig_badEmpty(t *testing.T) { + m := testModule(t, "validate-bad-pc-empty") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_providerConfig_good(t *testing.T) { + m := testModule(t, "validate-bad-pc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_resourceConfig_bad(t *testing.T) { + m := testModule(t, "validate-bad-rc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_resourceConfig_good(t *testing.T) { + m := testModule(t, "validate-bad-rc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_resourceNameSymbol(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-resource-name-symbol") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) == 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_requiredVar(t *testing.T) { + m := testModule(t, "validate-required-var") + c := testContext(t, &ContextOpts{ + Module: m, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_provisionerConfig_bad(t *testing.T) { + m := testModule(t, "validate-bad-prov-conf") + p := testProvider("aws") + pr := testProvisioner() + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + pr.ValidateReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_provisionerConfig_good(t *testing.T) { + m := testModule(t, "validate-bad-prov-conf") + p := testProvider("aws") + pr := testProvisioner() + pr.ValidateFn = func(c *ResourceConfig) ([]string, []error) { + if c == nil { + t.Fatalf("missing resource config for provisioner") + } + return nil, nil + } + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_selfRef(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-self-ref") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_selfRefMulti(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-self-ref-multi") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_selfRefMultiAll(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-self-ref-multi-all") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_varRef(t *testing.T) { + m := testModule(t, "validate-variable-ref") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + computed := false + p.ValidateResourceFn = func(t string, c *ResourceConfig) ([]string, []error) { + computed = c.IsComputed("foo") + return nil, nil + } + + c.Validate() + if !computed { + t.Fatal("should be computed") + } +} + +func TestContextValidate_varRefFilled(t *testing.T) { + m := testModule(t, "validate-variable-ref") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "bar", + }, + }) + + var value interface{} + p.ValidateResourceFn = func(t string, c *ResourceConfig) ([]string, []error) { + value, _ = c.Get("foo") + return nil, nil + } + + c.Validate() + if value != "bar" { + t.Fatalf("bad: %#v", value) + } +} + +func TestContextInput(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + "amis.us-east-1": "override", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "us-east-1", + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformInputVarsStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextInput_provider(t *testing.T) { + m := testModule(t, "input-provider") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Config["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Config["foo"] + return nil + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } +} + +func TestContextInput_providerId(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + UIInput: input, + }) + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + v, err := i.Input(&InputOpts{Id: "foo"}) + if err != nil { + return nil, err + } + + c.Config["foo"] = v + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Config["foo"] + return nil + } + + input.InputReturnMap = map[string]string{ + "provider.aws.foo": "bar", + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } +} + +func TestContextInput_providerOnly(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "us-east-1", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Config["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Config["foo"] + return nil + } + + if err := ctx.Input(InputModeProvider); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testTerraformInputProviderOnlyStr) + if actualStr != expectedStr { + t.Fatalf("bad: \n%s", actualStr) + } +} + +func TestContextInput_providerVars(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-with-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "bar", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "bar", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Config["bar"] = "baz" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual, _ = c.Get("foo") + return nil + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } +} + +func TestContextInput_varOnly(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "us-east-1", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Raw["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Raw["foo"] + return nil + } + + if err := ctx.Input(InputModeVar); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testTerraformInputVarOnlyStr) + if actualStr != expectedStr { + t.Fatalf("bad: \n%s", actualStr) + } +} + +func TestContextApply(t *testing.T) { + m := testModule(t, "apply-good") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) < 2 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_emptyModule(t *testing.T) { + m := testModule(t, "apply-empty-module") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + actual = strings.Replace(actual, " ", "", -1) + expected := strings.TrimSpace(testTerraformApplyEmptyModuleStr) + if actual != expected { + t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected) + } +} + +func TestContextApply_createBeforeDestroy(t *testing.T) { + m := testModule(t, "apply-good-create-before") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "abc", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) != 1 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_Minimal(t *testing.T) { + m := testModule(t, "apply-minimal") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyMinimalStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_badDiff(t *testing.T) { + m := testModule(t, "apply-good") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "newp": nil, + }, + }, nil + } + + if _, err := ctx.Apply(); err == nil { + t.Fatal("should error") + } +} + +func TestContextApply_cancel(t *testing.T) { + stopped := false + + m := testModule(t, "apply-cancel") + p := testProvider("aws") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ApplyFn = func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error) { + if !stopped { + stopped = true + go ctx.Stop() + + for { + if ctx.sh.Stopped() { + break + } + } + } + + return &InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "num": "2", + }, + }, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + New: "bar", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + // Start the Apply in a goroutine + stateCh := make(chan *State) + go func() { + state, err := ctx.Apply() + if err != nil { + panic(err) + } + + stateCh <- state + }() + + state := <-stateCh + + mod := state.RootModule() + if len(mod.Resources) != 1 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCancelStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_compute(t *testing.T) { + m := testModule(t, "apply-compute") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + ctx.variables = map[string]string{"value": "1"} + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyComputeStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_countDecrease(t *testing.T) { + m := testModule(t, "apply-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", + Attributes: map[string]string{ + "foo": "foo", + "type": "aws_instance", + }, + }, + }, + "aws_instance.foo.2": &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, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCountDecStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_countDecreaseToOne(t *testing.T) { + m := testModule(t, "apply-count-dec-one") + 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, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCountDecToOneStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_countTainted(t *testing.T) { + m := testModule(t, "apply-count-tainted") + 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", + Tainted: []*InstanceState{ + &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, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCountTaintedStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_countVariable(t *testing.T) { + m := testModule(t, "apply-count-variable") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCountVariableStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_module(t *testing.T) { + m := testModule(t, "apply-module") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyModuleStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_nilDiff(t *testing.T) { + m := testModule(t, "apply-good") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return nil, nil + } + + if _, err := ctx.Apply(); err == nil { + t.Fatal("should error") + } +} + +func TestContextApply_Provisioner_compute(t *testing.T) { + m := testModule(t, "apply-provisioner-compute") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + val, ok := c.Config["foo"] + if !ok || val != "computed_dynamical" { + t.Fatalf("bad value for foo: %v %#v", val, c) + } + + return nil + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + Variables: map[string]string{ + "value": "1", + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was invoked + if !pr.ApplyCalled { + t.Fatalf("provisioner not invoked") + } +} + +func TestContextApply_provisionerCreateFail(t *testing.T) { + m := testModule(t, "apply-provisioner-fail-create") + p := testProvider("aws") + pr := testProvisioner() + p.DiffFn = testDiffFn + + p.ApplyFn = func( + info *InstanceInfo, + is *InstanceState, + id *InstanceDiff) (*InstanceState, error) { + is.ID = "foo" + return is, fmt.Errorf("error") + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_provisionerCreateFailNoId(t *testing.T) { + m := testModule(t, "apply-provisioner-fail-create") + p := testProvider("aws") + pr := testProvisioner() + p.DiffFn = testDiffFn + + p.ApplyFn = func( + info *InstanceInfo, + is *InstanceState, + id *InstanceDiff) (*InstanceState, error) { + return nil, fmt.Errorf("error") + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateNoIdStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_provisionerFail(t *testing.T) { + m := testModule(t, "apply-provisioner-fail") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + pr.ApplyFn = func(*InstanceState, *ResourceConfig) error { + return fmt.Errorf("EXPLOSION") + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + Variables: map[string]string{ + "value": "1", + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerFailStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_provisionerFail_createBeforeDestroy(t *testing.T) { + m := testModule(t, "apply-provisioner-fail-create-before") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + pr.ApplyFn = func(*InstanceState, *ResourceConfig) error { + return fmt.Errorf("EXPLOSION") + } + + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "abc", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateBeforeDestroyStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_error_createBeforeDestroy(t *testing.T) { + m := testModule(t, "apply-error-create-before") + p := testProvider("aws") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "abc", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) { + return nil, fmt.Errorf("error") + } + p.DiffFn = testDiffFn + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should have error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr) + if actual != expected { + t.Fatalf("bad: \n%s\n\n\n%s", actual, expected) + } +} + +func TestContextApply_errorDestroy_createBeforeDestroy(t *testing.T) { + m := testModule(t, "apply-error-create-before") + p := testProvider("aws") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "abc", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) { + // Fail the destroy! + if id.Destroy { + return is, fmt.Errorf("error") + } + + // Create should work + is = &InstanceState{ + ID: "foo", + } + return is, nil + } + p.DiffFn = testDiffFn + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should have error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyErrorDestroyCreateBeforeDestroyStr) + if actual != expected { + t.Fatalf("bad: actual:\n%s\n\nexpected:\n%s", actual, expected) + } +} + +func TestContextApply_provisionerResourceRef(t *testing.T) { + m := testModule(t, "apply-provisioner-resource-ref") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + val, ok := c.Config["foo"] + if !ok || val != "2" { + t.Fatalf("bad value for foo: %v %#v", val, c) + } + + return nil + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerResourceRefStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was invoked + if !pr.ApplyCalled { + t.Fatalf("provisioner not invoked") + } +} + +// Provisioner should NOT run on a diff, only create +func TestContextApply_Provisioner_Diff(t *testing.T) { + m := testModule(t, "apply-provisioner-diff") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + return nil + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerDiffStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was invoked + if !pr.ApplyCalled { + t.Fatalf("provisioner not invoked") + } + pr.ApplyCalled = false + + // Change the state to force a diff + mod := state.RootModule() + mod.Resources["aws_instance.bar"].Primary.Attributes["foo"] = "baz" + + // Re-create context with state + ctx = testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state2, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual = strings.TrimSpace(state2.String()) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was NOT invoked + if pr.ApplyCalled { + t.Fatalf("provisioner invoked") + } +} + +func TestContextApply_outputDiffVars(t *testing.T) { + m := testModule(t, "apply-good") + p := testProvider("aws") + 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, + }) + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + for k, ad := range d.Attributes { + if ad.NewComputed { + return nil, fmt.Errorf("%s: computed", k) + } + } + + result := s.MergeDiff(d) + result.ID = "foo" + return result, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + NewComputed: true, + Type: DiffAttrOutput, + }, + "bar": &ResourceAttrDiff{ + New: "baz", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestContextApply_Provisioner_ConnInfo(t *testing.T) { + m := testModule(t, "apply-provisioner-conninfo") + p := testProvider("aws") + pr := testProvisioner() + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + if s.Ephemeral.ConnInfo == nil { + t.Fatalf("ConnInfo not initialized") + } + + result, _ := testApplyFn(info, s, d) + result.Ephemeral.ConnInfo = map[string]string{ + "type": "ssh", + "host": "127.0.0.1", + "port": "22", + } + return result, nil + } + p.DiffFn = testDiffFn + + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + conn := rs.Ephemeral.ConnInfo + if conn["type"] != "telnet" { + t.Fatalf("Bad: %#v", conn) + } + if conn["host"] != "127.0.0.1" { + t.Fatalf("Bad: %#v", conn) + } + if conn["port"] != "2222" { + t.Fatalf("Bad: %#v", conn) + } + if conn["user"] != "superuser" { + t.Fatalf("Bad: %#v", conn) + } + if conn["pass"] != "test" { + t.Fatalf("Bad: %#v", conn) + } + + return nil + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + Variables: map[string]string{ + "value": "1", + "pass": "test", + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProvisionerStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was invoked + if !pr.ApplyCalled { + t.Fatalf("provisioner not invoked") + } +} + +func TestContextApply_destroy(t *testing.T) { + m := testModule(t, "apply-destroy") + h := new(HookRecordApplyOrder) + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + // First plan and apply a create operation + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + // Next, plan and apply a destroy operation + if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + t.Fatalf("err: %s", err) + } + + h.Active = true + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test that things were destroyed + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyDestroyStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Test that things were destroyed _in the right order_ + expected2 := []string{"aws_instance.bar", "aws_instance.foo"} + actual2 := h.IDs + if !reflect.DeepEqual(actual2, expected2) { + t.Fatalf("bad: %#v", actual2) + } +} + +func TestContextApply_destroyOutputs(t *testing.T) { + m := testModule(t, "apply-destroy-outputs") + h := new(HookRecordApplyOrder) + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + // First plan and apply a create operation + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + // Next, plan and apply a destroy operation + if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + t.Fatalf("err: %s", err) + } + + h.Active = true + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) > 0 { + t.Fatalf("bad: %#v", mod) + } +} + +func TestContextApply_destroyOrphan(t *testing.T) { + m := testModule(t, "apply-error") + p := testProvider("aws") + 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, + }) + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + if d.Destroy { + return nil, nil + } + + result := s.MergeDiff(d) + result.ID = "foo" + return result, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + New: "bar", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if _, ok := mod.Resources["aws_instance.baz"]; ok { + t.Fatalf("bad: %#v", mod.Resources) + } +} + +func TestContextApply_destroyTaintedProvisioner(t *testing.T) { + m := testModule(t, "apply-destroy-provisioner") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + called := false + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + called = true + return nil + } + + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "id": "bar", + }, + }, + }, + }, + }, + }, + }, + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: s, + }) + + if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if called { + t.Fatal("provisioner should not be called") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace("") + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_error(t *testing.T) { + errored := false + + m := testModule(t, "apply-error") + p := testProvider("aws") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ApplyFn = func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error) { + if errored { + state := &InstanceState{ + ID: "bar", + } + return state, fmt.Errorf("error") + } + errored = true + + return &InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "num": "2", + }, + }, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + New: "bar", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should have error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyErrorStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_errorPartial(t *testing.T) { + errored := false + + m := testModule(t, "apply-error") + p := testProvider("aws") + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + if errored { + return s, fmt.Errorf("error") + } + errored = true + + return &InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "num": "2", + }, + }, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + New: "bar", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should have error") + } + + mod := state.RootModule() + if len(mod.Resources) != 2 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyErrorPartialStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_hook(t *testing.T) { + m := testModule(t, "apply-good") + h := new(MockHook) + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + if !h.PreApplyCalled { + t.Fatal("should be called") + } + if !h.PostApplyCalled { + t.Fatal("should be called") + } +} + +func TestContextApply_idAttr(t *testing.T) { + m := testModule(t, "apply-idattr") + p := testProvider("aws") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + result := s.MergeDiff(d) + result.ID = "foo" + result.Attributes = map[string]string{ + "id": "bar", + } + + return result, nil + } + p.DiffFn = func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) { + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + New: "bar", + }, + }, + }, nil + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + rs, ok := mod.Resources["aws_instance.foo"] + if !ok { + t.Fatal("not in state") + } + if rs.Primary.ID != "foo" { + t.Fatalf("bad: %#v", rs.Primary.ID) + } + if rs.Primary.Attributes["id"] != "foo" { + t.Fatalf("bad: %#v", rs.Primary.Attributes) + } +} + +func TestContextApply_output(t *testing.T) { + m := testModule(t, "apply-output") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_outputInvalid(t *testing.T) { + m := testModule(t, "apply-output-invalid") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + _, err := ctx.Plan(nil) + if err == nil { + t.Fatalf("err: %s", err) + } + if !strings.Contains(err.Error(), "is not a string") { + t.Fatalf("err: %s", err) + } +} + +func TestContextApply_outputList(t *testing.T) { + m := testModule(t, "apply-output-list") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputListStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_outputMulti(t *testing.T) { + m := testModule(t, "apply-output-multi") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputMultiStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_outputMultiIndex(t *testing.T) { + m := testModule(t, "apply-output-multi-index") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputMultiIndexStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_taint(t *testing.T) { + m := testModule(t, "apply-taint") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "baz", + Attributes: map[string]string{ + "num": "2", + "type": "aws_instance", + }, + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyTaintStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestContextApply_unknownAttribute(t *testing.T) { + m := testModule(t, "apply-unknown") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err == nil { + t.Fatal("should error") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_vars(t *testing.T) { + m := testModule(t, "apply-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + "amis.us-east-1": "override", + }, + }) + + w, e := ctx.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %s", e) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyVarsStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContextApply_createBefore_depends(t *testing.T) { + m := testModule(t, "apply-depends-create-before") + h := new(HookRecordApplyOrder) + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "ami-old", + }, + }, + }, + "aws_instance.lb": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "baz", + Attributes: map[string]string{ + "instance": "bar", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + h.Active = true + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) < 2 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr) + if actual != expected { + t.Fatalf("bad: \n%s\n%s", actual, expected) + } + + // Test that things were managed _in the right order_ + order := h.States + diffs := h.Diffs + if order[0].ID != "" || diffs[0].Destroy { + t.Fatalf("should create new instance first: %#v", order) + } + + if order[1].ID != "baz" { + t.Fatalf("update must happen after create: %#v", order) + } + + if order[2].ID != "bar" || !diffs[2].Destroy { + t.Fatalf("destroy must happen after update: %#v", order) + } +} + +func TestContextApply_singleDestroy(t *testing.T) { + m := testModule(t, "apply-depends-create-before") + h := new(HookRecordApplyOrder) + p := testProvider("aws") + + invokeCount := 0 + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + invokeCount++ + switch invokeCount { + case 1: + if d.Destroy { + t.Fatalf("should not destroy") + } + if s.ID != "" { + t.Fatalf("should not have ID") + } + case 2: + if d.Destroy { + t.Fatalf("should not destroy") + } + if s.ID != "baz" { + t.Fatalf("should have id") + } + case 3: + if !d.Destroy { + t.Fatalf("should destroy") + } + if s.ID == "" { + t.Fatalf("should have ID") + } + default: + t.Fatalf("bad invoke count %d", invokeCount) + } + return testApplyFn(info, s, d) + } + p.DiffFn = testDiffFn + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "ami-old", + }, + }, + }, + "aws_instance.lb": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "baz", + Attributes: map[string]string{ + "instance": "bar", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + h.Active = true + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if invokeCount != 3 { + t.Fatalf("bad: %d", invokeCount) + } +} + +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 TestContextRefresh(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + 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.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + s, err := ctx.Refresh() + mod := s.RootModule() + if err != nil { + t.Fatalf("err: %s", err) + } + if !p.RefreshCalled { + t.Fatal("refresh should be called") + } + if p.RefreshState.ID != "foo" { + t.Fatalf("bad: %#v", p.RefreshState) + } + if !reflect.DeepEqual(mod.Resources["aws_instance.web"].Primary, p.RefreshReturn) { + t.Fatalf("bad: %#v %#v", mod.Resources["aws_instance.web"], p.RefreshReturn) + } + + for _, r := range mod.Resources { + if r.Type == "" { + t.Fatalf("no type: %#v", r) + } + } +} + +func TestContextRefresh_delete(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + 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.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = nil + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := s.RootModule() + if len(mod.Resources) > 0 { + t.Fatal("resources should be empty") + } +} + +func TestContextRefresh_ignoreUncreated(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: nil, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + if p.RefreshCalled { + t.Fatal("refresh should not be called") + } +} + +func TestContextRefresh_hook(t *testing.T) { + h := new(MockHook) + p := testProvider("aws") + m := testModule(t, "refresh-basic") + ctx := testContext(t, &ContextOpts{ + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }) + + if _, err := ctx.Refresh(); err != nil { + t.Fatalf("err: %s", err) + } + if !h.PreRefreshCalled { + t.Fatal("should be called") + } + if !h.PostRefreshCalled { + t.Fatal("should be called") + } +} + +func TestContextRefresh_modules(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-modules") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + + &ModuleState{ + Path: []string{"root", "child"}, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "baz", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { + if s.ID != "baz" { + return s, nil + } + + s.ID = "new" + return s, nil + } + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(s.String()) + expected := strings.TrimSpace(testContextRefreshModuleStr) + if actual != expected { + t.Fatalf("bad:\n\n%s\n\n%s", actual, expected) + } +} + +func TestContextRefresh_moduleInputComputedOutput(t *testing.T) { + m := testModule(t, "refresh-module-input-computed-output") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Refresh(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestContextRefresh_moduleVarModule(t *testing.T) { + m := testModule(t, "refresh-module-var-module") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Refresh(); err != nil { + t.Fatalf("err: %s", err) + } +} + +// GH-70 +func TestContextRefresh_noState(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-no-state") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + if _, err := ctx.Refresh(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestContextRefresh_outputPartial(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-output-partial") + 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: "foo", + }, + }, + }, + }, + }, + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = nil + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(s.String()) + expected := strings.TrimSpace(testContextRefreshOutputPartialStr) + if actual != expected { + t.Fatalf("bad:\n\n%s\n\n%s", actual, expected) + } +} + +func TestContextRefresh_state(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + originalMod := state.RootModule() + mod := s.RootModule() + if !p.RefreshCalled { + t.Fatal("refresh should be called") + } + if !reflect.DeepEqual(p.RefreshState, originalMod.Resources["aws_instance.web"].Primary) { + t.Fatalf("bad: %#v %#v", p.RefreshState, originalMod.Resources["aws_instance.web"].Primary) + } + if !reflect.DeepEqual(mod.Resources["aws_instance.web"].Primary, p.RefreshReturn) { + t.Fatalf("bad: %#v", mod.Resources) + } +} + +func TestContextRefresh_tainted(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + }, + } + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + if !p.RefreshCalled { + t.Fatal("refresh should be called") + } + + actual := strings.TrimSpace(s.String()) + expected := strings.TrimSpace(testContextRefreshTaintedStr) + if actual != expected { + t.Fatalf("bad:\n\n%s\n\n%s", actual, expected) + } +} + +func TestContextRefresh_vars(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-vars") + 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.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = &InstanceState{ + ID: "foo", + } + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + mod := s.RootModule() + if !p.RefreshCalled { + t.Fatal("refresh should be called") + } + if p.RefreshState.ID != "foo" { + t.Fatalf("bad: %#v", p.RefreshState) + } + if !reflect.DeepEqual(mod.Resources["aws_instance.web"].Primary, p.RefreshReturn) { + t.Fatalf("bad: %#v", mod.Resources["aws_instance.web"]) + } + + for _, r := range mod.Resources { + if r.Type == "" { + t.Fatalf("no type: %#v", r) + } + } +} + +func testContext(t *testing.T, opts *ContextOpts) *Context { + return NewContext(opts) +} + +func testApplyFn( + info *InstanceInfo, + s *InstanceState, + d *InstanceDiff) (*InstanceState, error) { + if d.Destroy { + return nil, nil + } + + id := "foo" + if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed { + id = idAttr.New + } + + result := &InstanceState{ + ID: id, + } + + if d != nil { + result = result.MergeDiff(d) + } + return result, nil +} + +func testDiffFn( + info *InstanceInfo, + s *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + var diff InstanceDiff + diff.Attributes = make(map[string]*ResourceAttrDiff) + + for k, v := range c.Raw { + if _, ok := v.(string); !ok { + continue + } + + if k == "nil" { + return nil, nil + } + + // This key is used for other purposes + if k == "compute_value" { + continue + } + + if k == "compute" { + attrDiff := &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + } + + if cv, ok := c.Config["compute_value"]; ok { + if cv.(string) == "1" { + attrDiff.NewComputed = false + attrDiff.New = fmt.Sprintf("computed_%s", v.(string)) + } + } + + diff.Attributes[v.(string)] = attrDiff + continue + } + + // If this key is not computed, then look it up in the + // cleaned config. + found := false + for _, ck := range c.ComputedKeys { + if ck == k { + found = true + break + } + } + if !found { + v = c.Config[k] + } + + attrDiff := &ResourceAttrDiff{ + Old: "", + New: v.(string), + } + + if k == "require_new" { + attrDiff.RequiresNew = true + } + diff.Attributes[k] = attrDiff + } + + for _, k := range c.ComputedKeys { + diff.Attributes[k] = &ResourceAttrDiff{ + Old: "", + NewComputed: true, + } + } + + for k, v := range diff.Attributes { + if v.NewComputed { + continue + } + + old, ok := s.Attributes[k] + if !ok { + continue + } + if old == v.New { + delete(diff.Attributes, k) + } + } + + if !diff.Empty() { + diff.Attributes["type"] = &ResourceAttrDiff{ + Old: "", + New: info.Type, + } + } + + return &diff, nil +} + +func testProvider(prefix string) *MockResourceProvider { + p := new(MockResourceProvider) + p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { + return s, nil + } + p.ResourcesReturn = []ResourceType{ + ResourceType{ + Name: fmt.Sprintf("%s_instance", prefix), + }, + } + + return p +} + +func testProvisioner() *MockResourceProvisioner { + p := new(MockResourceProvisioner) + return p +} + +const testContextGraph = ` +root: root +aws_instance.bar + aws_instance.bar -> provider.aws +aws_instance.foo + aws_instance.foo -> provider.aws +provider.aws +root + root -> aws_instance.bar + root -> aws_instance.foo +` + +const testContextRefreshModuleStr = ` +aws_instance.web: (1 tainted) + ID = + Tainted ID 1 = bar + +module.child: + aws_instance.web: + ID = new +` + +const testContextRefreshOutputPartialStr = ` + +` + +const testContextRefreshTaintedStr = ` +aws_instance.web: (1 tainted) + ID = + Tainted ID 1 = foo +` +*/ + +func testContext2(t *testing.T, opts *ContextOpts) *Context2 { + return NewContext2(opts) +} diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go new file mode 100644 index 000000000..819dd8431 --- /dev/null +++ b/terraform/eval_validate.go @@ -0,0 +1,40 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/config" +) + +// EvalValidateError is the error structure returned if there were +// validation errors. +type EvalValidateError struct { + Warnings []string + Errors []error +} + +func (e *EvalValidateError) Error() string { + return "" +} + +// EvalValidateResource is an EvalNode implementation that validates +// the configuration of a resource. +type EvalValidateResource struct { + Provider EvalNode + Config *config.RawConfig +} + +func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) { + return []EvalNode{n.Provider}, + []EvalType{EvalTypeResourceProvider} +} + +func (n *EvalValidateResource) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + //provider := args[0].(ResourceProvider) + return nil, fmt.Errorf("WHAT") +} + +func (n *EvalValidateResource) Type() EvalType { + return EvalTypeNull +} diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index edea52287..833f82171 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -119,6 +119,14 @@ func (n *GraphNodeConfigResource) Name() string { return n.Resource.Id() } +// GraphNodeEvalable impl. +func (n *GraphNodeConfigResource) EvalTree() EvalNode { + return &EvalValidateResource{ + Provider: &EvalGetProvider{Name: n.ProvidedBy()}, + Config: n.Resource.RawConfig, + } +} + // GraphNodeProviderConsumer func (n *GraphNodeConfigResource) ProvidedBy() string { return resourceProvider(n.Resource.Type)