package terraform import ( "bytes" "fmt" "os" "reflect" "sort" "strings" "sync" "testing" "github.com/davecgh/go-spew/spew" "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/config/hcl2shim" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) func TestContext2Plan_basic(t *testing.T) { m := testModule(t, "plan-good") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), ProviderSHA256s: map[string][]byte{ "aws": []byte("placeholder"), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if l := len(plan.Changes.Resources); l < 2 { t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources)) } if !reflect.DeepEqual(plan.ProviderSHA256s, ctx.providerSHA256s) { t.Errorf("wrong ProviderSHA256s %#v; want %#v", plan.ProviderSHA256s, ctx.providerSHA256s) } if !ctx.State().Empty() { t.Fatalf("expected empty state, got %#v\n", ctx.State()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() for _, r := range plan.Changes.Resources { ric, err := r.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": foo := ric.After.GetAttr("foo").AsString() if foo != "2" { t.Fatalf("incorrect plan for 'bar': %#v", ric.After) } case "aws_instance.foo": num, _ := ric.After.GetAttr("num").AsBigFloat().Int64() if num != 2 { t.Fatalf("incorrect plan for 'foo': %#v", ric.After) } default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_createBefore_deposed(t *testing.T) { m := testModule(t, "plan-cbd") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: []string{"root"}, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "baz", Attributes: map[string]string{ "id": "baz", }, }, Deposed: []*InstanceState{ &InstanceState{ ID: "foo", Attributes: map[string]string{ "id": "foo", }, }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } // the state should still show one deposed expectedState := strings.TrimSpace(` aws_instance.foo: (1 deposed) ID = baz provider = provider.aws Deposed ID 1 = foo`) if ctx.State().String() != expectedState { t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, ctx.State().String()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() type InstanceGen struct { Addr string DeposedKey states.DeposedKey } want := map[InstanceGen]bool{ { Addr: "aws_instance.foo", }: true, { Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001"), }: true, } got := make(map[InstanceGen]bool) changes := make(map[InstanceGen]*plans.ResourceInstanceChangeSrc) for _, change := range plan.Changes.Resources { k := InstanceGen{ Addr: change.Addr.String(), DeposedKey: change.DeposedKey, } got[k] = true changes[k] = change } if !reflect.DeepEqual(got, want) { t.Fatalf("wrong resource instance object changes in plan\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(want)) } { ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty) if err != nil { t.Fatal(err) } if got, want := ric.Action, plans.NoOp; got != want { t.Errorf("current object change action is %s; want %s", got, want) } // the existing instance should only have an unchanged id expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{"id": cty.StringVal("baz")})) if err != nil { t.Fatal(err) } checkVals(t, expected, ric.After) } { ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty) if err != nil { t.Fatal(err) } if got, want := ric.Action, plans.Delete; got != want { t.Errorf("deposed object change action is %s; want %s", got, want) } } } func TestContext2Plan_createBefore_maintainRoot(t *testing.T) { m := testModule(t, "plan-cbd-maintain-root") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "in": &InputValue{ Value: cty.StringVal("a,b,c"), SourceType: ValueFromCaller, }, }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !ctx.State().Empty() { t.Fatal("expected empty state, got:", ctx.State()) } if len(plan.Changes.Resources) != 4 { t.Error("expected 4 resource in plan, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { // these should all be creates if res.Action != plans.Create { t.Fatalf("unexpected action %s for %s", res.Action, res.Addr.String()) } } } func TestContext2Plan_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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !ctx.State().Empty() { t.Fatal("expected empty state, got:", ctx.State()) } if len(plan.Changes.Resources) != 2 { t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources)) } actions := map[string]plans.Action{} for _, res := range plan.Changes.Resources { actions[res.Addr.String()] = res.Action } expected := map[string]plans.Action{ "aws_instance.foo": plans.Create, "aws_instance.bar": plans.Create, } if !cmp.Equal(expected, actions) { t.Fatal(cmp.Diff(expected, actions)) } } func TestContext2Plan_escapedVar(t *testing.T) { m := testModule(t, "plan-escaped-var") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) != 1 { t.Error("expected 1 resource in plan, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } expected := objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar-${baz}"), "type": cty.StringVal("aws_instance")}, ) checkVals(t, expected, ric.After) } func TestContext2Plan_minimal(t *testing.T) { m := testModule(t, "plan-empty") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !ctx.State().Empty() { t.Fatal("expected empty state, got:", ctx.State()) } if len(plan.Changes.Resources) != 2 { t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources)) } actions := map[string]plans.Action{} for _, res := range plan.Changes.Resources { actions[res.Addr.String()] = res.Action } expected := map[string]plans.Action{ "aws_instance.foo": plans.Create, "aws_instance.bar": plans.Create, } if !cmp.Equal(expected, actions) { t.Fatal(cmp.Diff(expected, actions)) } } func TestContext2Plan_modules(t *testing.T) { m := testModule(t, "plan-modules") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) != 3 { t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources)) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() expectFoo := objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance")}, ) expectNum := objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance")}, ) for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "aws_instance.bar": expected = expectFoo case "aws_instance.foo": expected = expectNum case "module.child.aws_instance.foo": expected = expectNum default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) } } // GH-1475 func TestContext2Plan_moduleCycle(t *testing.T) { m := testModule(t, "plan-module-cycle") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "some_input": {Type: cty.String, Optional: true}, "type": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "aws_instance.b": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }) case "aws_instance.c": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "some_input": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }) default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) } } func TestContext2Plan_moduleDeadlock(t *testing.T) { testCheckDeadlock(t, func() { m := testModule(t, "plan-module-deadlock") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } expected := objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }) switch i := ric.Addr.String(); i { case "module.child.aws_instance.foo[0]": case "module.child.aws_instance.foo[1]": case "module.child.aws_instance.foo[2]": default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) } }) } func TestContext2Plan_moduleInput(t *testing.T) { m := testModule(t, "plan-module-input") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "aws_instance.bar": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance"), }) case "module.child.aws_instance.foo": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("42"), "type": cty.StringVal("aws_instance"), }) default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) } } func TestContext2Plan_moduleInputComputed(t *testing.T) { m := testModule(t, "plan-module-input-computed") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) case "module.child.aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleInputFromVar(t *testing.T) { m := testModule(t, "plan-module-input-var") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "foo": &InputValue{ Value: cty.StringVal("52"), SourceType: ValueFromCaller, }, }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance"), }), ric.After) case "module.child.aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("52"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleMultiVar(t *testing.T) { m := testModule(t, "plan-module-multi-var") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "foo": {Type: cty.String, Optional: true}, "baz": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 5 { t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.parent[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.parent[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.bar[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "baz": cty.StringVal("baz"), }), ric.After) case "module.child.aws_instance.bar[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "baz": cty.StringVal("baz"), }), ric.After) case "module.child.aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("baz,baz"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleOrphans(t *testing.T) { m := testModule(t, "plan-modules-remove") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: []string{"root", "child"}, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "baz", }, Provider: "provider.aws", }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo": if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.After) case "module.child.aws_instance.foo": if res.Action != plans.Delete { t.Fatalf("expected resource delete, got %s", res.Action) } default: t.Fatal("unknown instance:", i) } } expectedState := ` module.child: aws_instance.foo: ID = baz provider = provider.aws` if ctx.State().String() != expectedState { t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, ctx.State().String()) } } // https://github.com/hashicorp/terraform/issues/3114 func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { m := testModule(t, "plan-modules-remove-provisioners") p := testProvider("aws") pr := testProvisioner() p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: []string{"root"}, Resources: map[string]*ResourceState{ "aws_instance.top": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "top", }, }, }, }, &ModuleState{ Path: []string{"root", "parent", "childone"}, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "baz", }, Provider: "provider.aws", }, }, }, &ModuleState{ Path: []string{"root", "parent", "childtwo"}, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "baz", }, Provider: "provider.aws", }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Provisioners: map[string]ProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 3 { t.Error("expected 3 planned resources, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.parent.module.childone.aws_instance.foo": if res.Action != plans.Delete { t.Fatalf("expected resource Delete, got %s", res.Action) } case "module.parent.module.childtwo.aws_instance.foo": if res.Action != plans.Delete { t.Fatalf("expected resource Delete, got %s", res.Action) } case "aws_instance.top": if res.Action != plans.NoOp { t.Fatal("expected no changes, got", res.Action) } default: t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After)) } } expectedState := `aws_instance.top: ID = top provider = provider.aws module.parent.childone: aws_instance.foo: ID = baz provider = provider.aws module.parent.childtwo: aws_instance.foo: ID = baz provider = provider.aws` if expectedState != ctx.State().String() { t.Fatalf("\nexpect state: %q\ngot state: %q\n", expectedState, ctx.State().String()) } } func TestContext2Plan_moduleProviderInherit(t *testing.T) { var l sync.Mutex var calls []string m := testModule(t, "plan-module-provider-inherit") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": func() (providers.Interface, error) { l.Lock() defer l.Unlock() p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "from": {Type: cty.String, Optional: true}, }, }, ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "from": {Type: cty.String, Optional: true}, }, }, }, } 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") l.Lock() defer l.Unlock() calls = append(calls, v.(string)) return testDiffFn(info, state, c) } return p, nil }, }, ), }) _, err := ctx.Plan() 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) } } // This tests (for GH-11282) that deeply nested modules properly inherit // configuration. func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) { var l sync.Mutex m := testModule(t, "plan-module-provider-inherit-deep") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": func() (providers.Interface, error) { l.Lock() defer l.Unlock() var from string p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "from": {Type: cty.String, Optional: true}, }, }, ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{}, }, }, } p.ConfigureFn = func(c *ResourceConfig) error { v, ok := c.Get("from") if !ok || v.(string) != "root" { return fmt.Errorf("bad") } from = v.(string) return nil } p.DiffFn = func( info *InstanceInfo, state *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { if from != "root" { return nil, fmt.Errorf("bad resource") } return testDiffFn(info, state, c) } return p, nil }, }, ), }) _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } } func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) { var l sync.Mutex var calls []string m := testModule(t, "plan-module-provider-defaults-var") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": func() (providers.Interface, error) { l.Lock() defer l.Unlock() p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "to": {Type: cty.String, Optional: true}, "from": {Type: cty.String, Optional: true}, }, }, ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "from": {Type: cty.String, Optional: true}, }, }, }, } 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") } l.Lock() defer l.Unlock() calls = append(calls, buf.String()) return nil } p.DiffFn = testDiffFn return p, nil }, }, ), Variables: InputValues{ "foo": &InputValue{ Value: cty.StringVal("root"), SourceType: ValueFromCaller, }, }, }) _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } expected := []string{ "child\nchild\n", "root\n", } sort.Strings(calls) if !reflect.DeepEqual(calls, expected) { t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls) } } func TestContext2Plan_moduleProviderVar(t *testing.T) { m := testModule(t, "plan-module-provider-var") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "value": {Type: cty.String, Optional: true}, }, }, ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "value": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.test": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "value": cty.StringVal("hello"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleVar(t *testing.T) { m := testModule(t, "plan-module-var") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "aws_instance.bar": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance"), }) case "module.child.aws_instance.foo": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }) default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) } } func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) { m := testModule(t, "plan-module-wrong-var-type") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if !diags.HasErrors() { t.Fatalf("succeeded; want errors") } } func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) { m := testModule(t, "plan-module-wrong-var-type-nested") p := testProvider("null") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "null": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if !diags.HasErrors() { t.Fatalf("succeeded; want errors") } } func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) { m := testModule(t, "plan-module-var-with-default-value") p := testProvider("null") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "null": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } } func TestContext2Plan_moduleVarComputed(t *testing.T) { m := testModule(t, "plan-module-var-computed") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) case "module.child.aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_preventDestroy_bad(t *testing.T) { m := testModule(t, "plan-prevent-destroy-bad") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, }, }, }, }), }) plan, err := ctx.Plan() expectedErr := "aws_instance.foo has lifecycle.prevent_destroy" if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { if plan != nil { t.Logf(legacyDiffComparisonString(plan.Changes)) } t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err) } } func TestContext2Plan_preventDestroy_good(t *testing.T) { m := testModule(t, "plan-prevent-destroy-good") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, }, }, }, }), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !plan.Changes.Empty() { t.Fatalf("expected no changes, got %#v\n", plan.Changes) } } func TestContext2Plan_preventDestroy_countBad(t *testing.T) { m := testModule(t, "plan-prevent-destroy-count-bad") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, "aws_instance.foo.1": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc345", }, }, }, }, }, }), }) plan, err := ctx.Plan() expectedErr := "aws_instance.foo[1] has lifecycle.prevent_destroy" if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { if plan != nil { t.Logf(legacyDiffComparisonString(plan.Changes)) } t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err) } } func TestContext2Plan_preventDestroy_countGood(t *testing.T) { m := testModule(t, "plan-prevent-destroy-count-good") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "current": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, "aws_instance.foo.1": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc345", }, }, }, }, }, }), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if plan.Changes.Empty() { t.Fatalf("Expected non-empty plan, got %s", legacyDiffComparisonString(plan.Changes)) } } func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) { m := testModule(t, "plan-prevent-destroy-count-good") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "current": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", Attributes: map[string]string{ "current": "0", "type": "aws_instance", }, }, }, }, }, }, }), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !plan.Changes.Empty() { t.Fatalf("Expected empty plan, got %s", legacyDiffComparisonString(plan.Changes)) } } func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) { m := testModule(t, "plan-prevent-destroy-good") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, }, }, }, }), Destroy: true, }) plan, diags := ctx.Plan() expectedErr := "aws_instance.foo has lifecycle.prevent_destroy" if !strings.Contains(fmt.Sprintf("%s", diags.Err()), expectedErr) { if plan != nil { t.Logf(legacyDiffComparisonString(plan.Changes)) } t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, diags.Err()) } } func TestContext2Plan_provisionerCycle(t *testing.T) { m := testModule(t, "plan-provisioner-cycle") p := testProvider("aws") p.DiffFn = testDiffFn pr := testProvisioner() ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Provisioners: map[string]ProvisionerFactory{ "local-exec": testProvisionerFuncFixed(pr), }, }) _, diags := ctx.Plan() if !diags.HasErrors() { t.Fatalf("succeeded; want errors") } } func TestContext2Plan_computed(t *testing.T) { m := testModule(t, "plan-computed") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_computedDataResource(t *testing.T) { m := testModule(t, "plan-computed-data-resource") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "num": {Type: cty.String, Optional: true}, "compute": {Type: cty.String, Optional: true}, "foo": {Type: cty.String, Computed: true}, }, }, }, DataSources: map[string]*configschema.Block{ "aws_vpc": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.DataSources["aws_vpc"] ty := schema.ImpliedType() if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil { t.Fatalf("missing diff for aws_instance.foo") } rcs := plan.Changes.ResourceInstance(addrs.Resource{ Mode: addrs.DataResourceMode, Type: "aws_vpc", Name: "bar", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) if rcs == nil { t.Fatalf("missing diff for data.aws_vpc.bar") } rc, err := rcs.Decode(ty) if err != nil { t.Fatal(err) } checkVals(t, cty.ObjectVal(map[string]cty.Value{ "foo": cty.UnknownVal(cty.String), }), rc.After, ) } func TestContext2Plan_computedInFunction(t *testing.T) { m := testModule(t, "plan-computed-in-function") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "attr": {Type: cty.Number, Optional: true}, }, }, }, DataSources: map[string]*configschema.Block{ "aws_data_source": { Attributes: map[string]*configschema.Attribute{ "computed": {Type: cty.List(cty.String), Computed: true}, }, }, }, } p.DiffFn = testDiffFn p.ReadDataSourceResponse = providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "computed": cty.ListVal([]cty.Value{ cty.StringVal("foo"), }), }), } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) diags := ctx.Validate() assertNoErrors(t, diags) state, diags := ctx.Refresh() // data resource is read in this step assertNoErrors(t, diags) if !p.ReadDataSourceCalled { t.Fatalf("ReadDataSource was not called on provider during refresh; should've been called") } p.ReadDataSourceCalled = false // reset for next call t.Logf("state after refresh:\n%s", state) _, diags = ctx.Plan() // should do nothing with data resource in this step, since it was already read assertNoErrors(t, diags) if p.ReadDataSourceCalled { t.Fatalf("ReadDataSource was called on provider during plan; should not have been called") } } func TestContext2Plan_computedDataCountResource(t *testing.T) { m := testModule(t, "plan-computed-data-count") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "num": {Type: cty.String, Optional: true}, "compute": {Type: cty.String, Optional: true}, "foo": {Type: cty.String, Computed: true}, }, }, }, DataSources: map[string]*configschema.Block{ "aws_vpc": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } // make sure we created 3 "bar"s for i := 0; i < 3; i++ { addr := addrs.Resource{ Mode: addrs.DataResourceMode, Type: "aws_vpc", Name: "bar", }.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance) if rcs := plan.Changes.ResourceInstance(addr); rcs == nil { t.Fatalf("missing changes for %s", addr) } } } func TestContext2Plan_localValueCount(t *testing.T) { m := testModule(t, "plan-local-value-count") p := testProvider("test") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "test": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } // make sure we created 3 "foo"s for i := 0; i < 3; i++ { addr := addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "foo", }.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance) if rcs := plan.Changes.ResourceInstance(addr); rcs == nil { t.Fatalf("missing changes for %s", addr) } } } func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { m := testModule(t, "plan-data-resource-becomes-computed") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, "computed": {Type: cty.String, Computed: true}, }, }, }, DataSources: map[string]*configschema.Block{ "aws_data_source": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, }, }, }, } p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { fooVal := req.ProposedNewState.GetAttr("foo") return providers.PlanResourceChangeResponse{ PlannedState: cty.ObjectVal(map[string]cty.Value{ "foo": fooVal, "computed": cty.UnknownVal(cty.String), }), PlannedPrivate: req.PriorPrivate, } } schema := p.GetSchemaReturn.DataSources["aws_data_source"] ty := schema.ImpliedType() p.ReadDataSourceResponse = providers.ReadDataSourceResponse{ // This should not be called, because the configuration for the // data resource contains an unknown value for "foo". Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")), } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "data.aws_data_source.foo": &ResourceState{ Type: "aws_data_source", Primary: &InstanceState{ ID: "i-abc123", Attributes: map[string]string{ "id": "i-abc123", "foo": "baz", }, }, }, }, }, }, }), }) _, diags := ctx.Refresh() if diags.HasErrors() { t.Fatalf("unexpected errors during refresh: %s", diags.Err()) } plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors during plan: %s", diags.Err()) } rcs := plan.Changes.ResourceInstance(addrs.Resource{ Mode: addrs.DataResourceMode, Type: "aws_data_source", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) if rcs == nil { t.Logf("full changeset: %s", spew.Sdump(plan.Changes)) t.Fatalf("missing diff for data.aws_data_resource.foo") } rc, err := rcs.Decode(ty) if err != nil { t.Fatal(err) } // foo should now be unknown foo := rc.After.GetAttr("foo") if foo.IsKnown() { t.Fatalf("foo should be unknown, got %#v", foo) } } func TestContext2Plan_computedList(t *testing.T) { m := testModule(t, "plan-computed-list") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "compute": {Type: cty.String, Optional: true}, "foo": {Type: cty.String, Optional: true}, "num": {Type: cty.String, Optional: true}, "list": {Type: cty.List(cty.String), Computed: true}, }, }, }, } p.DiffFn = func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { diff := &InstanceDiff{ Attributes: map[string]*ResourceAttrDiff{}, } computedKeys := map[string]bool{} for _, k := range c.ComputedKeys { computedKeys[k] = true } compute, _ := c.Raw["compute"].(string) if compute != "" { diff.Attributes[compute] = &ResourceAttrDiff{ Old: "", New: "", NewComputed: true, } } fooOld := s.Attributes["foo"] fooNew, _ := c.Raw["foo"].(string) if fooOld != fooNew { diff.Attributes["foo"] = &ResourceAttrDiff{ Old: fooOld, New: fooNew, NewComputed: computedKeys["foo"], } } numOld := s.Attributes["num"] numNew, _ := c.Raw["num"].(string) if numOld != numNew { diff.Attributes["num"] = &ResourceAttrDiff{ Old: numOld, New: numNew, NewComputed: computedKeys["num"], } } listOld := s.Attributes["list.#"] if listOld == "" { diff.Attributes["list.#"] = &ResourceAttrDiff{ Old: "", New: "", NewComputed: true, } } return diff, nil } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "list": cty.UnknownVal(cty.List(cty.String)), "foo": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "list": cty.UnknownVal(cty.List(cty.String)), "num": cty.NumberIntVal(2), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // GH-8695. This tests that you can index into a computed list on a // splatted resource. func TestContext2Plan_computedMultiIndex(t *testing.T) { m := testModule(t, "plan-computed-multi-index") p := testProvider("aws") p.DiffFn = testDiffFn p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "compute": {Type: cty.String, Optional: true}, "foo": {Type: cty.List(cty.String), Optional: true}, "ip": {Type: cty.List(cty.String), Computed: true}, }, }, }, } p.DiffFn = func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { diff := &InstanceDiff{ Attributes: map[string]*ResourceAttrDiff{}, } compute, _ := c.Raw["compute"].(string) if compute != "" { diff.Attributes[compute] = &ResourceAttrDiff{ Old: "", New: "", NewComputed: true, } } fooOld := s.Attributes["foo"] fooNew, _ := c.Raw["foo"].(string) fooComputed := false for _, k := range c.ComputedKeys { if k == "foo" { fooComputed = true } } if fooNew != "" { diff.Attributes["foo"] = &ResourceAttrDiff{ Old: fooOld, New: fooNew, NewComputed: fooComputed, } } ipOld := s.Attributes["ip"] ipComputed := ipOld == "" diff.Attributes["ip"] = &ResourceAttrDiff{ Old: ipOld, New: "", NewComputed: ipComputed, } return diff, nil } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "ip": cty.UnknownVal(cty.List(cty.String)), "foo": cty.NullVal(cty.List(cty.String)), }), ric.After) case "aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "ip": cty.UnknownVal(cty.List(cty.String)), "foo": cty.NullVal(cty.List(cty.String)), }), ric.After) case "aws_instance.bar[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "ip": cty.UnknownVal(cty.List(cty.String)), "foo": cty.UnknownVal(cty.List(cty.String)), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_count(t *testing.T) { m := testModule(t, "plan-count") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 6 { t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo,foo,foo,foo,foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[2]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[3]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[4]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countComputed(t *testing.T) { m := testModule(t, "plan-count-computed") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, err := ctx.Plan() if err == nil { t.Fatal("should error") } } func TestContext2Plan_countComputedModule(t *testing.T) { m := testModule(t, "plan-count-computed-module") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, err := ctx.Plan() expectedErr := `The "count" value depends on resource attributes` if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { t.Fatalf("expected err would contain %q\nerr: %s\n", expectedErr, err) } } func TestContext2Plan_countModuleStatic(t *testing.T) { m := testModule(t, "plan-count-module-static") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo[2]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) { m := testModule(t, "plan-count-module-static-grandchild") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.module.child.aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.module.child.aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.module.child.aws_instance.foo[2]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countIndex(t *testing.T) { m := testModule(t, "plan-count-index") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("0"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("1"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countVar(t *testing.T) { m := testModule(t, "plan-count-var") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "instance_count": &InputValue{ Value: cty.StringVal("3"), SourceType: ValueFromCaller, }, }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo,foo,foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[2]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countZero(t *testing.T) { m := testModule(t, "plan-count-zero") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.DynamicPseudoType, Optional: true}, }, }, }, } // This schema contains a DynamicPseudoType, and therefore can't go through any shim functions p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { resp.PlannedState = req.ProposedNewState resp.PlannedPrivate = req.PriorPrivate return resp } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } expected := cty.TupleVal(nil) foo := ric.After.GetAttr("foo") if !cmp.Equal(expected, foo, valueComparer) { t.Fatal(cmp.Diff(expected, foo, valueComparer)) } } func TestContext2Plan_countOneIndex(t *testing.T) { m := testModule(t, "plan-count-one-index") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[0]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countDecreaseToOne(t *testing.T) { m := testModule(t, "plan-count-dec") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": if res.Action != plans.NoOp { t.Fatalf("resource %s should be unchanged", i) } case "aws_instance.foo[1]": if res.Action != plans.Delete { t.Fatalf("expected resource delete, got %s", res.Action) } case "aws_instance.foo[2]": if res.Action != plans.Delete { t.Fatalf("expected resource delete, got %s", res.Action) } default: t.Fatal("unknown instance:", i) } } expectedState := `aws_instance.foo.0: ID = bar provider = provider.aws foo = foo type = aws_instance aws_instance.foo.1: ID = bar provider = provider.aws aws_instance.foo.2: ID = bar provider = provider.aws` if ctx.State().String() != expectedState { t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, ctx.State().String()) } } func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { m := testModule(t, "plan-count-inc") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[0]": if res.Action != plans.NoOp { t.Fatalf("resource %s should be unchanged", i) } case "aws_instance.foo[1]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[2]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_countIncreaseFromOne(t *testing.T) { m := testModule(t, "plan-count-inc") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[0]": if res.Action != plans.NoOp { t.Fatalf("resource %s should be unchanged", i) } case "aws_instance.foo[1]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[2]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // https://github.com/PeoplePerHour/terraform/pull/11 // // This tests a case where both a "resource" and "resource.0" are in // the state file, which apparently is a reasonable backwards compatibility // concern found in the above 3rd party repo. func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { m := testModule(t, "plan-count-inc") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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", }, }, }, "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ "foo": "foo", "type": "aws_instance", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 5 { t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } case "aws_instance.foo[0]": if res.Action != plans.NoOp { t.Fatalf("resource %s should be unchanged", i) } case "aws_instance.foo[1]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo[2]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // A common pattern in TF configs is to have a set of resources with the same // count and to use count.index to create correspondences between them: // // foo_id = "${foo.bar.*.id[count.index]}" // // This test is for the situation where some instances already exist and the // count is increased. In that case, we should see only the create diffs // for the new instances and not any update diffs for the existing ones. func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { m := testModule(t, "plan-count-splat-reference") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "name": {Type: cty.String, Optional: true}, "foo_name": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn s := MustShimLegacyState(&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{ "name": "foo 0", }, }, }, "aws_instance.foo.1": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ "name": "foo 1", }, }, }, "aws_instance.bar.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ "foo_name": "foo 0", }, }, }, "aws_instance.bar.1": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ "foo_name": "foo 1", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 6 { t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar[0]", "aws_instance.bar[1]", "aws_instance.foo[0]", "aws_instance.foo[1]": if res.Action != plans.NoOp { t.Fatalf("resource %s should be unchanged", i) } case "aws_instance.bar[2]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "foo_name": cty.StringVal("foo 2"), }), ric.After) case "aws_instance.foo[2]": if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "name": cty.StringVal("foo 2"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_destroy(t *testing.T) { m := testModule(t, "plan-destroy") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, Destroy: true, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.one", "aws_instance.two": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleDestroy(t *testing.T) { m := testModule(t, "plan-module-destroy") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, Destroy: true, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo", "module.child.aws_instance.foo": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } default: t.Fatal("unknown instance:", i) } } } // GH-1835 func TestContext2Plan_moduleDestroyCycle(t *testing.T) { m := testModule(t, "plan-module-destroy-gh-1835") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: []string{"root", "a_module"}, Resources: map[string]*ResourceState{ "aws_instance.a": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "a", }, }, }, }, &ModuleState{ Path: []string{"root", "b_module"}, Resources: map[string]*ResourceState{ "aws_instance.b": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "b", }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, Destroy: true, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.a_module.aws_instance.a", "module.b_module.aws_instance.b": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { m := testModule(t, "plan-module-destroy-multivar") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, Destroy: true, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.foo[0]", "module.child.aws_instance.foo[1]": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_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.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "cwd": {Type: cty.String, Optional: true}, "module": {Type: cty.String, Optional: true}, "root": {Type: cty.String, Optional: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("err: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo": if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "cwd": cty.StringVal(cwd + "/barpath"), "module": cty.StringVal(m.Module.SourceDir + "/foopath"), "root": cty.StringVal(m.Module.SourceDir + "/barpath"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_diffVar(t *testing.T) { m := testModule(t, "plan-diffvar") p := testProvider("aws") s := MustShimLegacyState(&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", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "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, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(3), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": if res.Action != plans.Update { t.Fatalf("resource %s should be updated", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(2), }), ric.Before) checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(3), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_hook(t *testing.T) { m := testModule(t, "plan-good") h := new(MockHook) p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, Hooks: []Hook{h}, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !h.PreDiffCalled { t.Fatal("should be called") } if !h.PostDiffCalled { t.Fatal("should be called") } } func TestContext2Plan_closeProvider(t *testing.T) { // this fixture only has an aliased provider located in the module, to make // sure that the provier name contains a path more complex than // "provider.aws". m := testModule(t, "plan-close-module-provider") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if !p.CloseCalled { t.Fatal("provider not closed") } } func TestContext2Plan_orphan(t *testing.T) { m := testModule(t, "plan-orphan") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.baz": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.baz": if res.Action != plans.Delete { t.Fatalf("resource %s should be removed", i) } case "aws_instance.foo": if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // This tests that configurations with UUIDs don't produce errors. // For shadows, this would produce errors since a UUID changes every time. func TestContext2Plan_shadowUuid(t *testing.T) { m := testModule(t, "plan-shadow-uuid") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } } func TestContext2Plan_state(t *testing.T) { m := testModule(t, "plan-good") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) < 2 { t.Fatalf("bad: %#v", plan.Changes.Resources) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": if res.Action != plans.Update { t.Fatalf("resource %s should be updated", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NullVal(cty.Number), "type": cty.NullVal(cty.String), }), ric.Before) checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_taint(t *testing.T) { m := testModule(t, "plan-taint") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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", Primary: &InstanceState{ ID: "baz", Tainted: true, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.StringVal("aws_instance"), }), ric.After) case "aws_instance.foo": if res.Action != plans.NoOp { t.Fatalf("resource %s should not be changed", i) } default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_taintIgnoreChanges(t *testing.T) { m := testModule(t, "plan-taint-ignore-changes") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "vars": {Type: cty.String, Optional: true}, "type": {Type: cty.String, Computed: true}, }, }, }, } p.ApplyFn = testApplyFn p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "foo", Attributes: map[string]string{ "vars": "foo", "type": "aws_instance", }, Tainted: true, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo": if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("foo"), "vars": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.Before) checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "vars": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // Fails about 50% of the time before the fix for GH-4982, covers the fix. func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { m := testModule(t, "plan-taint-interpolated-count") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Tainted: true, }, }, "aws_instance.foo.1": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ID: "bar"}, }, "aws_instance.foo.2": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ID: "bar"}, }, }, }, }, }) for i := 0; i < 100; i++ { ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), }), ric.Before) checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[1]", "aws_instance.foo[2]": if res.Action != plans.NoOp { t.Fatalf("resource %s should not be changed", i) } default: t.Fatal("unknown instance:", i) } } } } func TestContext2Plan_targeted(t *testing.T) { m := testModule(t, "plan-targeted") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Targets: []addrs.Targetable{ addrs.RootModuleInstance.Resource( addrs.ManagedResourceMode, "aws_instance", "foo", ), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo": if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // Test that targeting a module properly plans any inputs that depend // on another module. func TestContext2Plan_targetedCrossModule(t *testing.T) { m := testModule(t, "plan-targeted-cross-module") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Targets: []addrs.Targetable{ addrs.RootModuleInstance.Child("B", addrs.NoKey), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if res.Action != plans.Create { t.Fatalf("resource %s should be created", ric.Addr) } switch i := ric.Addr.String(); i { case "module.A.aws_instance.foo": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.After) case "module.B.aws_instance.bar": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_targetedModuleWithProvider(t *testing.T) { m := testModule(t, "plan-targeted-module-with-provider") p := testProvider("null") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "key": {Type: cty.String, Optional: true}, }, }, ResourceTypes: map[string]*configschema.Block{ "null_resource": { Attributes: map[string]*configschema.Attribute{}, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "null": testProviderFuncFixed(p), }, ), Targets: []addrs.Targetable{ addrs.RootModuleInstance.Child("child2", addrs.NoKey), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["null_resource"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if ric.Addr.String() != "module.child2.null_resource.foo" { t.Fatalf("unexpcetd resource: %s", ric.Addr) } } func TestContext2Plan_targetedOrphan(t *testing.T) { m := testModule(t, "plan-targeted-orphan") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.orphan": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-789xyz", }, }, "aws_instance.nottargeted": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, }, }, }, }, }), Destroy: true, Targets: []addrs.Targetable{ addrs.RootModuleInstance.Resource( addrs.ManagedResourceMode, "aws_instance", "orphan", ), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.orphan": if res.Action != plans.Delete { t.Fatalf("resource %s should be destroyed", ric.Addr) } default: t.Fatal("unknown instance:", i) } } } // https://github.com/hashicorp/terraform/issues/2538 func TestContext2Plan_targetedModuleOrphan(t *testing.T) { m := testModule(t, "plan-targeted-module-orphan") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: []string{"root", "child"}, Resources: map[string]*ResourceState{ "aws_instance.orphan": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-789xyz", }, Provider: "provider.aws", }, "aws_instance.nottargeted": &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", }, Provider: "provider.aws", }, }, }, }, }), Destroy: true, Targets: []addrs.Targetable{ addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( addrs.ManagedResourceMode, "aws_instance", "orphan", ), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if ric.Addr.String() != "module.child.aws_instance.orphan" { t.Fatalf("unexpected resource :%s", ric.Addr) } if res.Action != plans.Delete { t.Fatalf("resource %s should be deleted", ric.Addr) } } func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) { m := testModule(t, "plan-targeted-module-untargeted-variable") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Targets: []addrs.Targetable{ addrs.RootModuleInstance.Resource( addrs.ManagedResourceMode, "aws_instance", "blue", ), addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if res.Action != plans.Create { t.Fatalf("resource %s should be created", ric.Addr) } switch i := ric.Addr.String(); i { case "aws_instance.blue": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.blue_mod.aws_instance.mod": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "value": cty.UnknownVal(cty.String), "type": cty.StringVal("aws_instance"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } // ensure that outputs missing references due to targetting are removed from // the graph. func TestContext2Plan_outputContainsTargetedResource(t *testing.T) { m := testModule(t, "plan-untargeted-resource-output") p := testProvider("aws") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Targets: []addrs.Targetable{ addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource( addrs.ManagedResourceMode, "aws_instance", "a", ), }, }) _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } } // https://github.com/hashicorp/terraform/issues/4515 func TestContext2Plan_targetedOverTen(t *testing.T) { m := testModule(t, "plan-targeted-over-ten") p := testProvider("aws") p.DiffFn = testDiffFn resources := make(map[string]*ResourceState) var expectedState []string for i := 0; i < 13; i++ { key := fmt.Sprintf("aws_instance.foo.%d", i) id := fmt.Sprintf("i-abc%d", i) resources[key] = &ResourceState{ Type: "aws_instance", Primary: &InstanceState{ID: id}, } expectedState = append(expectedState, fmt.Sprintf("%s:\n ID = %s\n", key, id)) } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: resources, }, }, }), Targets: []addrs.Targetable{ addrs.RootModuleInstance.ResourceInstance( addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), ), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() for _, res := range plan.Changes.Resources { ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if res.Action != plans.NoOp { t.Fatalf("unexpected action %s for %s", res.Action, ric.Addr) } } } func TestContext2Plan_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 := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "foo": &InputValue{ Value: cty.StringVal("bar"), SourceType: ValueFromCaller, }, }, }) if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } if value != "bar" { t.Fatalf("bad: %#v", value) } } func TestContext2Plan_varListErr(t *testing.T) { m := testModule(t, "plan-var-list-err") p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, err := ctx.Plan() if err == nil { t.Fatal("should error") } } func TestContext2Plan_ignoreChanges(t *testing.T) { m := testModule(t, "plan-ignore-changes") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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{"ami": "ami-abcd1234"}, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "foo": &InputValue{ Value: cty.StringVal("ami-1234abcd"), SourceType: ValueFromCaller, }, }, State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } if res.Action != plans.Update { t.Fatalf("resource %s should be updated, got %s", ric.Addr, res.Action) } if ric.Addr.String() != "aws_instance.foo" { t.Fatalf("unexpected resource: %s", ric.Addr) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.StringVal("bar"), "ami": cty.StringVal("ami-abcd1234"), "type": cty.StringVal("aws_instance"), }), ric.After) } func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { m := testModule(t, "plan-ignore-changes-wildcard") p := testProvider("aws") p.DiffFn = testDiffFn s := MustShimLegacyState(&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{ "ami": "ami-abcd1234", "instance": "t2.micro", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), Variables: InputValues{ "foo": &InputValue{ Value: cty.StringVal("ami-1234abcd"), SourceType: ValueFromCaller, }, "bar": &InputValue{ Value: cty.StringVal("t2.small"), SourceType: ValueFromCaller, }, }, State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } for _, res := range plan.Changes.Resources { if res.Action != plans.NoOp { t.Fatalf("unexpected resource diffs in root module: %s", spew.Sdump(plan.Changes.Resources)) } } } func TestContext2Plan_moduleMapLiteral(t *testing.T) { m := testModule(t, "plan-module-map-literal") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "meta": {Type: cty.Map(cty.String), Optional: true}, "tags": {Type: cty.Map(cty.String), Optional: true}, }, }, }, } p.ApplyFn = testApplyFn p.DiffFn = func(i *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { // Here we verify that both the populated and empty map literals made it // through to the resource attributes val, _ := c.Get("tags") m, ok := val.(map[string]interface{}) if !ok { t.Fatalf("Tags attr not map: %#v", val) } if m["foo"] != "bar" { t.Fatalf("Bad value in tags attr: %#v", m) } { val, _ := c.Get("meta") m, ok := val.(map[string]interface{}) if !ok { t.Fatalf("Meta attr not map: %#v", val) } if len(m) != 0 { t.Fatalf("Meta attr not empty: %#v", val) } } return nil, nil } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } } func TestContext2Plan_computedValueInMap(t *testing.T) { m := testModule(t, "plan-computed-value-in-map") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "looked_up": {Type: cty.String, Optional: true}, }, }, "aws_computed_source": { Attributes: map[string]*configschema.Attribute{ "computed_read_only": {Type: cty.String, Computed: true}, }, }, }, } p.DiffFn = func(info *InstanceInfo, state *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { switch info.Type { case "aws_computed_source": return &InstanceDiff{ Attributes: map[string]*ResourceAttrDiff{ "computed_read_only": &ResourceAttrDiff{ NewComputed: true, }, }, }, nil } return testDiffFn(info, state, c) } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type] ric, err := res.Decode(schema.ImpliedType()) if err != nil { t.Fatal(err) } if res.Action != plans.Create { t.Fatalf("resource %s should be created", ric.Addr) } switch i := ric.Addr.String(); i { case "aws_computed_source.intermediates": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "computed_read_only": cty.UnknownVal(cty.String), }), ric.After) case "module.test_mod.aws_instance.inner2": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "looked_up": cty.UnknownVal(cty.String), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_moduleVariableFromSplat(t *testing.T) { m := testModule(t, "plan-module-variable-from-splat") p := testProvider("aws") p.DiffFn = testDiffFn p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "thing": {Type: cty.String, Optional: true}, }, }, }, } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type] ric, err := res.Decode(schema.ImpliedType()) if err != nil { t.Fatal(err) } if res.Action != plans.Create { t.Fatalf("resource %s should be created", ric.Addr) } switch i := ric.Addr.String(); i { case "module.mod1.aws_instance.test[0]", "module.mod1.aws_instance.test[1]", "module.mod2.aws_instance.test[0]", "module.mod2.aws_instance.test[1]": checkVals(t, objectVal(t, schema, map[string]cty.Value{ "thing": cty.StringVal("doesnt"), }), ric.After) default: t.Fatal("unknown instance:", i) } } } func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { m := testModule(t, "plan-cbd-depends-datasource") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "num": {Type: cty.String, Optional: true}, "computed": {Type: cty.String, Optional: true, Computed: true}, }, }, }, DataSources: map[string]*configschema.Block{ "aws_vpc": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "foo": {Type: cty.Number, Optional: true}, }, }, }, } p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { return providers.PlanResourceChangeResponse{ PlannedState: req.ProposedNewState, } } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } for _, res := range plan.Changes.Resources { var schema *configschema.Block switch res.Addr.Resource.Resource.Mode { case addrs.DataResourceMode: schema = p.GetSchemaReturn.DataSources[res.Addr.Resource.Resource.Type] case addrs.ManagedResourceMode: schema = p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type] } ric, err := res.Decode(schema.ImpliedType()) if err != nil { t.Fatal(err) } t.Run(ric.Addr.String(), func(t *testing.T) { switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": if res.Action != plans.Create { t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "num": cty.StringVal("2"), "computed": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[1]": if res.Action != plans.Create { t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "num": cty.StringVal("2"), "computed": cty.UnknownVal(cty.String), }), ric.After) case "data.aws_vpc.bar[0]": if res.Action != plans.Read { t.Fatalf("resource %s should be read, got %s", ric.Addr, ric.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("0"), }), ric.After) case "data.aws_vpc.bar[1]": if res.Action != plans.Read { t.Fatalf("resource %s should be read, got %s", ric.Addr, ric.Action) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("1"), }), ric.After) default: t.Fatal("unknown instance:", i) } }) } } // interpolated lists need to be stored in the original order. func TestContext2Plan_listOrder(t *testing.T) { m := testModule(t, "plan-list-order") p := testProvider("aws") p.ApplyFn = testApplyFn p.DiffFn = testDiffFn p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.List(cty.String), Optional: true}, }, }, }, } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } changes := plan.Changes rDiffA := changes.ResourceInstance(addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "a", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) rDiffB := changes.ResourceInstance(addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "b", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) if !cmp.Equal(rDiffA.After, rDiffB.After, valueComparer) { t.Fatal(cmp.Diff(rDiffA.After, rDiffB.After, valueComparer)) } } // Make sure ignore-changes doesn't interfere with set/list/map diffs. // If a resource was being replaced by a RequiresNew attribute that gets // ignored, we need to filter the diff properly to properly update rather than // replace. func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { m := testModule(t, "plan-ignore-changes-with-flatmaps") p := testProvider("aws") p.DiffFn = testDiffFn p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "user_data": {Type: cty.String, Optional: true}, "require_new": {Type: cty.String, Optional: true}, // This test predates the 0.12 work to integrate cty and // HCL, and so it was ported as-is where its expected // test output was clearly expecting a list of maps here // even though it is named "set". "set": {Type: cty.List(cty.Map(cty.String)), Optional: true}, "lst": {Type: cty.List(cty.String), Optional: true}, }, }, }, } s := MustShimLegacyState(&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{ "user_data": "x", "require_new": "", "set.#": "1", "set.0.%": "1", "set.0.a": "1", "lst.#": "1", "lst.0": "j", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type] ric, err := res.Decode(schema.ImpliedType()) if err != nil { t.Fatal(err) } if res.Action != plans.Update { t.Fatalf("resource %s should be updated, got %s", ric.Addr, ric.Action) } if ric.Addr.String() != "aws_instance.foo" { t.Fatalf("unknown resource: %s", ric.Addr) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ "lst": cty.ListVal([]cty.Value{ cty.StringVal("j"), cty.StringVal("k"), }), "require_new": cty.StringVal(""), "user_data": cty.StringVal("x"), "set": cty.ListVal([]cty.Value{cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("1"), "b": cty.StringVal("2"), })}), }), ric.After) } // TestContext2Plan_resourceNestedCount ensures resource sets that depend on // the count of another resource set (ie: count of a data source that depends // on another data source's instance count - data.x.foo.*.id) get properly // normalized to the indexes they should be. This case comes up when there is // an existing state (after an initial apply). func TestContext2Plan_resourceNestedCount(t *testing.T) { m := testModule(t, "nested-resource-count-plan") p := testProvider("aws") p.DiffFn = testDiffFn p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { return providers.ReadResourceResponse{ NewState: req.PriorState, } } s := MustShimLegacyState(&State{ Modules: []*ModuleState{ &ModuleState{ Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Primary: &InstanceState{ ID: "foo0", Attributes: map[string]string{ "id": "foo0", }, }, }, "aws_instance.foo.1": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Primary: &InstanceState{ ID: "foo1", Attributes: map[string]string{ "id": "foo1", }, }, }, "aws_instance.bar.0": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Dependencies: []string{"aws_instance.foo"}, Primary: &InstanceState{ ID: "bar0", Attributes: map[string]string{ "id": "bar0", }, }, }, "aws_instance.bar.1": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Dependencies: []string{"aws_instance.foo"}, Primary: &InstanceState{ ID: "bar1", Attributes: map[string]string{ "id": "bar1", }, }, }, "aws_instance.baz.0": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Dependencies: []string{"aws_instance.bar"}, Primary: &InstanceState{ ID: "baz0", Attributes: map[string]string{ "id": "baz0", }, }, }, "aws_instance.baz.1": &ResourceState{ Type: "aws_instance", Provider: "provider.aws", Dependencies: []string{"aws_instance.bar"}, Primary: &InstanceState{ ID: "baz1", Attributes: map[string]string{ "id": "baz1", }, }, }, }, }, }, }) ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), State: s, }) diags := ctx.Validate() if diags.HasErrors() { t.Fatalf("validate errors: %s", diags.Err()) } _, diags = ctx.Refresh() if diags.HasErrors() { t.Fatalf("refresh errors: %s", diags.Err()) } plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("plan errors: %s", diags.Err()) } for _, res := range plan.Changes.Resources { if res.Action != plans.NoOp { t.Fatalf("resource %s should now change, plan returned %s", res.Addr, res.Action) } } } // Higher level test at TestResource_dataSourceListApplyPanic func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) { m := testModule(t, "plan-computed-attr-ref-type-mismatch") p := testProvider("aws") p.DiffFn = testDiffFn p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { var diags tfdiags.Diagnostics if req.TypeName == "aws_instance" { amiVal := req.Config.GetAttr("ami") if amiVal.Type() != cty.String { diags = diags.Append(fmt.Errorf("Expected ami to be cty.String, got %#v", amiVal)) } } return providers.ValidateResourceTypeConfigResponse{ Diagnostics: diags, } } p.DiffFn = func( info *InstanceInfo, state *InstanceState, c *ResourceConfig) (*InstanceDiff, error) { switch info.Type { case "aws_ami_list": // Emulate a diff that says "we'll create this list and ids will be populated" return &InstanceDiff{ Attributes: map[string]*ResourceAttrDiff{ "ids.#": &ResourceAttrDiff{NewComputed: true}, }, }, nil case "aws_instance": // If we get to the diff for instance, we should be able to assume types ami, _ := c.Get("ami") _ = ami.(string) } return nil, nil } p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { if info.Type != "aws_ami_list" { t.Fatalf("Reached apply for unexpected resource type! %s", info.Type) } // Pretend like we make a thing and the computed list "ids" is populated return &InstanceState{ ID: "someid", Attributes: map[string]string{ "ids.#": "2", "ids.0": "ami-abc123", "ids.1": "ami-bcd345", }, }, nil } ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if !diags.HasErrors() { t.Fatalf("Succeeded; want type mismatch error for 'ami' argument") } expected := `Inappropriate value for attribute "ami"` if errStr := diags.Err().Error(); !strings.Contains(errStr, expected) { t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected) } } func TestContext2Plan_selfRef(t *testing.T) { p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, }, }, }, } m := testModule(t, "plan-self-ref") c := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) diags := c.Validate() if diags.HasErrors() { t.Fatalf("unexpected validation failure: %s", diags.Err()) } _, diags = c.Plan() if !diags.HasErrors() { t.Fatalf("plan succeeded; want error") } gotErrStr := diags.Err().Error() wantErrStr := "Self-referential block" if !strings.Contains(gotErrStr, wantErrStr) { t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) } } func TestContext2Plan_selfRefMulti(t *testing.T) { p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, }, }, }, } m := testModule(t, "plan-self-ref-multi") c := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) diags := c.Validate() if diags.HasErrors() { t.Fatalf("unexpected validation failure: %s", diags.Err()) } _, diags = c.Plan() if !diags.HasErrors() { t.Fatalf("plan succeeded; want error") } gotErrStr := diags.Err().Error() wantErrStr := "Self-referential block" if !strings.Contains(gotErrStr, wantErrStr) { t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) } } func TestContext2Plan_selfRefMultiAll(t *testing.T) { p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "aws_instance": { Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.List(cty.String), Optional: true}, }, }, }, } m := testModule(t, "plan-self-ref-multi-all") c := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) diags := c.Validate() if diags.HasErrors() { t.Fatalf("unexpected validation failure: %s", diags.Err()) } _, diags = c.Plan() if !diags.HasErrors() { t.Fatalf("plan succeeded; want error") } gotErrStr := diags.Err().Error() // The graph is checked for cycles before we can walk it, so we don't // encounter the self-reference check. //wantErrStr := "Self-referential block" wantErrStr := "Cycle" if !strings.Contains(gotErrStr, wantErrStr) { t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) } } func TestContext2Plan_invalidOutput(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` data "aws_data_source" "name" {} output "out" { value = "${data.aws_data_source.name.missing}" }`, }) p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if !diags.HasErrors() { // Should get this error: // Unsupported attribute: This object does not have an attribute named "missing" t.Fatal("succeeded; want errors") } gotErrStr := diags.Err().Error() wantErrStr := "Unsupported attribute" if !strings.Contains(gotErrStr, wantErrStr) { t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) } } func TestContext2Plan_invalidModuleOutput(t *testing.T) { m := testModuleInline(t, map[string]string{ "child/main.tf": ` data "aws_data_source" "name" {} output "out" { value = "${data.aws_data_source.name.missing}" }`, "main.tf": ` module "child" { source = "./child" } resource "aws_instance" "foo" { foo = "${module.child.out}" }`, }) p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "aws": testProviderFuncFixed(p), }, ), }) _, diags := ctx.Plan() if !diags.HasErrors() { // Should get this error: // Unsupported attribute: This object does not have an attribute named "missing" t.Fatal("succeeded; want errors") } gotErrStr := diags.Err().Error() wantErrStr := "Unsupported attribute" if !strings.Contains(gotErrStr, wantErrStr) { t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) } } func checkVals(t *testing.T, expected, got cty.Value) { t.Helper() if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty)) } } func objectVal(t *testing.T, schema *configschema.Block, m map[string]cty.Value) cty.Value { t.Helper() v, err := schema.CoerceValue( cty.ObjectVal(m), ) if err != nil { t.Fatal(err) } return v } func TestContext2Plan_requiredModuleOutput(t *testing.T) { m := testModule(t, "plan-required-output") p := testProvider("test") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "test_resource": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "required": {Type: cty.String, Required: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "test": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["test_resource"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "test_resource.root": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.UnknownVal(cty.String), }) case "module.mod.test_resource.for_output": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.StringVal("val"), }) default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) }) } } func TestContext2Plan_requiredModuleObject(t *testing.T) { m := testModule(t, "plan-required-whole-mod") p := testProvider("test") p.GetSchemaReturn = &ProviderSchema{ ResourceTypes: map[string]*configschema.Block{ "test_resource": { Attributes: map[string]*configschema.Attribute{ "id": {Type: cty.String, Computed: true}, "required": {Type: cty.String, Required: true}, }, }, }, } p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Config: m, ProviderResolver: providers.ResolverFixed( map[string]providers.Factory{ "test": testProviderFuncFixed(p), }, ), }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } schema := p.GetSchemaReturn.ResourceTypes["test_resource"] ty := schema.ImpliedType() if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } ric, err := res.Decode(ty) if err != nil { t.Fatal(err) } var expected cty.Value switch i := ric.Addr.String(); i { case "test_resource.root": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.UnknownVal(cty.String), }) case "module.mod.test_resource.for_output": expected = objectVal(t, schema, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.StringVal("val"), }) default: t.Fatal("unknown instance:", i) } checkVals(t, expected, ric.After) }) } }