| package terraform |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "os" |
| "reflect" |
| "sort" |
| "strings" |
| "sync" |
| "sync/atomic" |
| "testing" |
| |
| "github.com/davecgh/go-spew/spew" |
| "github.com/google/go-cmp/cmp" |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/configs/hcl2shim" |
| "github.com/hashicorp/terraform/internal/lang/marks" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/provisioners" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| func TestContext2Plan_basic(t *testing.T) { |
| m := testModule(t, "plan-good") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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)) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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) |
| } |
| } |
| |
| if !p.ValidateProviderConfigCalled { |
| t.Fatal("provider config was not checked before Configure") |
| } |
| |
| } |
| |
| func TestContext2Plan_createBefore_deposed(t *testing.T) { |
| m := testModule(t, "plan-cbd") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceDeposed( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| states.DeposedKey("00000001"), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance |
| Deposed ID 1 = foo`) |
| |
| if plan.PriorState.String() != expectedState { |
| t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, plan.PriorState.String()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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"), |
| "type": cty.StringVal("aws_instance"), |
| })) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| if !plan.PriorState.Empty() { |
| t.Fatal("expected empty prior state, got:", plan.PriorState) |
| } |
| |
| 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.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| resp.PlannedState = req.ProposedNewState |
| return resp |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| if !plan.PriorState.Empty() { |
| t.Fatal("expected empty state, got:", plan.PriorState) |
| } |
| |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }) |
| |
| checkVals(t, expected, ric.After) |
| } |
| |
| func TestContext2Plan_minimal(t *testing.T) { |
| m := testModule(t, "plan-empty") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| if !plan.PriorState.Empty() { |
| t.Fatal("expected empty state, got:", plan.PriorState) |
| } |
| |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| ty := schema.ImpliedType() |
| |
| expectFoo := objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "foo": cty.StringVal("2"), |
| "type": cty.UnknownVal(cty.String), |
| }) |
| |
| expectNum := objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "num": cty.NumberIntVal(2), |
| "type": cty.UnknownVal(cty.String), |
| }) |
| |
| 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) |
| } |
| } |
| func TestContext2Plan_moduleExpand(t *testing.T) { |
| // Test a smattering of plan expansion behavior |
| m := testModule(t, "plan-modules-expand") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| ty := schema.ImpliedType() |
| |
| expected := map[string]struct{}{ |
| `aws_instance.foo["a"]`: {}, |
| `module.count_child[1].aws_instance.foo[0]`: {}, |
| `module.count_child[1].aws_instance.foo[1]`: {}, |
| `module.count_child[0].aws_instance.foo[0]`: {}, |
| `module.count_child[0].aws_instance.foo[1]`: {}, |
| `module.for_each_child["a"].aws_instance.foo[1]`: {}, |
| `module.for_each_child["a"].aws_instance.foo[0]`: {}, |
| } |
| |
| 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) |
| } |
| |
| _, ok := expected[ric.Addr.String()] |
| if !ok { |
| t.Fatal("unexpected resource:", ric.Addr.String()) |
| } |
| delete(expected, ric.Addr.String()) |
| } |
| for addr := range expected { |
| t.Error("missing resource", addr) |
| } |
| } |
| |
| // GH-1475 |
| func TestContext2Plan_moduleCycle(t *testing.T) { |
| m := testModule(t, "plan-module-cycle") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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), |
| "type": 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.UnknownVal(cty.String), |
| }) |
| 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.PlanResourceChangeFn = testDiffFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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), |
| "type": 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }) |
| 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.UnknownVal(cty.String), |
| }) |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| "compute": cty.StringVal("foo"), |
| }), 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.UnknownVal(cty.String), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("52"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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 := `<no state> |
| module.child: |
| aws_instance.foo: |
| ID = baz |
| provider = provider["registry.terraform.io/hashicorp/aws"]` |
| |
| if plan.PriorState.String() != expectedState { |
| t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, plan.PriorState.String()) |
| } |
| } |
| |
| // https://github.com/hashicorp/terraform/issues/3114 |
| func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { |
| m := testModule(t, "plan-modules-remove-provisioners") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| pr := testProvisioner() |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.top").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"top","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child1 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child1", addrs.NoKey)) |
| child1.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child2 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child2", addrs.NoKey)) |
| child2.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.child1.aws_instance.foo": |
| if res.Action != plans.Delete { |
| t.Fatalf("expected resource Delete, got %s", res.Action) |
| } |
| case "module.parent.module.child2.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.Fatalf("expected no changes, got %s", res.Action) |
| } |
| default: |
| t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After)) |
| } |
| } |
| |
| expectedState := `aws_instance.top: |
| ID = top |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance |
| |
| module.parent.child1: |
| aws_instance.foo: |
| ID = baz |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance |
| module.parent.child2: |
| aws_instance.foo: |
| ID = baz |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance` |
| |
| if expectedState != plan.PriorState.String() { |
| t.Fatalf("\nexpect state:\n%s\n\ngot state:\n%s\n", expectedState, plan.PriorState.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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { |
| l.Lock() |
| defer l.Unlock() |
| |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| from := req.Config.GetAttr("from") |
| if from.IsNull() || from.AsString() != "root" { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root")) |
| } |
| |
| return |
| } |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| from := req.Config.GetAttr("from").AsString() |
| |
| l.Lock() |
| defer l.Unlock() |
| calls = append(calls, from) |
| return testDiffFn(req) |
| } |
| return p, nil |
| }, |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { |
| l.Lock() |
| defer l.Unlock() |
| |
| var from string |
| p := testProvider("aws") |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| v := req.Config.GetAttr("from") |
| if v.IsNull() || v.AsString() != "root" { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root")) |
| } |
| from = v.AsString() |
| |
| return |
| } |
| |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| if from != "root" { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("bad resource")) |
| return |
| } |
| |
| return testDiffFn(req) |
| } |
| return p, nil |
| }, |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { |
| l.Lock() |
| defer l.Unlock() |
| |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| var buf bytes.Buffer |
| from := req.Config.GetAttr("from") |
| if !from.IsNull() { |
| buf.WriteString(from.AsString() + "\n") |
| } |
| to := req.Config.GetAttr("to") |
| if !to.IsNull() { |
| buf.WriteString(to.AsString() + "\n") |
| } |
| |
| l.Lock() |
| defer l.Unlock() |
| calls = append(calls, buf.String()) |
| return |
| } |
| |
| return p, nil |
| }, |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("root"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }) |
| 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.UnknownVal(cty.String), |
| }) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| "compute": cty.StringVal("foo"), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, err := ctx.Plan(m, state, DefaultPlanOpts) |
| |
| 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.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc345"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, err := ctx.Plan(m, state, DefaultPlanOpts) |
| |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "current": {Type: cty.String, Optional: true}, |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc345"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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.PlanResourceChangeFn = testDiffFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "current": {Type: cty.String, Optional: true}, |
| "type": {Type: cty.String, Optional: true, Computed: true}, |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123","current":"0","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| |
| 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 diagnostics would contain %q\nactual diags: %s", expectedErr, diags.Err()) |
| } |
| } |
| |
| func TestContext2Plan_provisionerCycle(t *testing.T) { |
| m := testModule(t, "plan-provisioner-cycle") |
| p := testProvider("aws") |
| pr := testProvisioner() |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "local-exec": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if !diags.HasErrors() { |
| t.Fatalf("succeeded; want errors") |
| } |
| } |
| |
| func TestContext2Plan_computed(t *testing.T) { |
| m := testModule(t, "plan-computed") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| "compute": cty.StringVal("foo"), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_blockNestingGroup(t *testing.T) { |
| m := testModule(t, "plan-block-nesting-group") |
| p := testProvider("test") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test": { |
| BlockTypes: map[string]*configschema.NestedBlock{ |
| "blah": { |
| Nesting: configschema.NestingGroup, |
| Block: configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "baz": {Type: cty.String, Required: true}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }) |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: req.ProposedNewState, |
| } |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| if got, want := 1, len(plan.Changes.Resources); got != want { |
| t.Fatalf("wrong number of planned resource changes %d; want %d\n%s", got, want, spew.Sdump(plan.Changes.Resources)) |
| } |
| |
| if !p.PlanResourceChangeCalled { |
| t.Fatalf("PlanResourceChange was not called at all") |
| } |
| |
| got := p.PlanResourceChangeRequest |
| want := providers.PlanResourceChangeRequest{ |
| TypeName: "test", |
| |
| // Because block type "blah" is defined as NestingGroup, we get a non-null |
| // value for it with null nested attributes, rather than the "blah" object |
| // itself being null, when there's no "blah" block in the config at all. |
| // |
| // This represents the situation where the remote service _always_ creates |
| // a single "blah", regardless of whether the block is present, but when |
| // the block _is_ present the user can override some aspects of it. The |
| // absense of the block means "use the defaults", in that case. |
| Config: cty.ObjectVal(map[string]cty.Value{ |
| "blah": cty.ObjectVal(map[string]cty.Value{ |
| "baz": cty.NullVal(cty.String), |
| }), |
| }), |
| ProposedNewState: cty.ObjectVal(map[string]cty.Value{ |
| "blah": cty.ObjectVal(map[string]cty.Value{ |
| "baz": cty.NullVal(cty.String), |
| }), |
| }), |
| } |
| if !cmp.Equal(got, want, valueTrans) { |
| t.Errorf("wrong PlanResourceChange request\n%s", cmp.Diff(got, want, valueTrans)) |
| } |
| } |
| |
| func TestContext2Plan_computedDataResource(t *testing.T) { |
| m := testModule(t, "plan-computed-data-resource") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"].Block |
| 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, |
| ) |
| if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want { |
| t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want) |
| } |
| } |
| |
| func TestContext2Plan_computedInFunction(t *testing.T) { |
| m := testModule(t, "plan-computed-in-function") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "computed": cty.ListVal([]cty.Value{ |
| cty.StringVal("foo"), |
| }), |
| }), |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| diags := ctx.Validate(m) |
| assertNoErrors(t, diags) |
| |
| _, diags = ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if !p.ReadDataSourceCalled { |
| t.Fatalf("ReadDataSource was not called on provider during plan; should've been called") |
| } |
| } |
| |
| func TestContext2Plan_computedDataCountResource(t *testing.T) { |
| m := testModule(t, "plan-computed-data-count") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| "id": {Type: cty.String, Computed: true}, |
| "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.GetProviderSchemaResponse.DataSources["aws_data_source"].Block |
| 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")), |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("data.aws_data_source.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123","foo":"baz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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) |
| } |
| |
| if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want { |
| t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want) |
| } |
| |
| // 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.PlanResourceChangeFn = testDiffFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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{ |
| "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), |
| "compute": cty.StringVal("list.#"), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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)), |
| "compute": cty.StringVal("ip.#"), |
| }), 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)), |
| "compute": cty.StringVal("ip.#"), |
| }), ric.After) |
| case "aws_instance.bar[0]": |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_countComputed(t *testing.T) { |
| m := testModule(t, "plan-count-computed") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if err == nil { |
| t.Fatal("should error") |
| } |
| } |
| |
| func TestContext2Plan_countComputedModule(t *testing.T) { |
| m := testModule(t, "plan-count-computed-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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), |
| "type": 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), |
| "type": 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), |
| "type": 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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), |
| "type": 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), |
| "type": 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), |
| "type": 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_countVar(t *testing.T) { |
| m := testModule(t, "plan-count-var") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "instance_count": &InputValue{ |
| Value: cty.StringVal("3"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_countZero(t *testing.T) { |
| m := testModule(t, "plan-count-zero") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_countDecreaseToOne(t *testing.T) { |
| m := testModule(t, "plan-count-dec") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[2]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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: |
| ID = bar |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = foo |
| type = aws_instance |
| aws_instance.foo.1: |
| ID = bar |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| aws_instance.foo.2: |
| ID = bar |
| provider = provider["registry.terraform.io/hashicorp/aws"]` |
| |
| if plan.PriorState.String() != expectedState { |
| t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, plan.PriorState.String()) |
| } |
| } |
| |
| func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { |
| m := testModule(t, "plan-count-inc") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","type":"aws_instance","foo":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_countIncreaseFromOne(t *testing.T) { |
| m := testModule(t, "plan-count-inc") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","name":"foo 0"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","name":"foo 1"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 0"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 1"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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) |
| } |
| // The instance ID changed, so just check that the name updated |
| if ric.After.GetAttr("foo_name") != cty.StringVal("foo 2") { |
| t.Fatalf("resource %s attr \"foo_name\" should be changed", i) |
| } |
| case "aws_instance.foo[2]": |
| if res.Action != plans.Create { |
| t.Fatalf("expected resource create, got %s", res.Action) |
| } |
| // The instance ID changed, so just check that the name updated |
| if ric.After.GetAttr("name") != cty.StringVal("foo 2") { |
| t.Fatalf("resource %s attr \"name\" should be changed", i) |
| } |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_forEach(t *testing.T) { |
| m := testModule(t, "plan-for-each") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| ty := schema.ImpliedType() |
| |
| if len(plan.Changes.Resources) != 8 { |
| t.Fatal("expected 8 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) |
| } |
| _, err := res.Decode(ty) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| func TestContext2Plan_forEachUnknownValue(t *testing.T) { |
| // This module has a variable defined, but it's value is unknown. We |
| // expect this to produce an error, but not to panic. |
| m := testModule(t, "plan-for-each-unknown-value") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": { |
| Value: cty.UnknownVal(cty.String), |
| SourceType: ValueFromCLIArg, |
| }, |
| }, |
| }) |
| if !diags.HasErrors() { |
| // Should get this error: |
| // Invalid for_each argument: The "for_each" value depends on resource attributes that cannot be determined until apply... |
| t.Fatal("succeeded; want errors") |
| } |
| |
| gotErrStr := diags.Err().Error() |
| wantErrStr := "Invalid for_each argument" |
| if !strings.Contains(gotErrStr, wantErrStr) { |
| t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) |
| } |
| |
| // We should have a diagnostic that is marked as being caused by unknown |
| // values. |
| for _, diag := range diags { |
| if tfdiags.DiagnosticCausedByUnknown(diag) { |
| return // don't fall through to the error below |
| } |
| } |
| t.Fatalf("no diagnostic is marked as being caused by unknown\n%s", diags.Err().Error()) |
| } |
| |
| func TestContext2Plan_destroy(t *testing.T) { |
| m := testModule(t, "plan-destroy") |
| p := testProvider("aws") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.one").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.two").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| |
| state := states.NewState() |
| aModule := state.EnsureModule(addrs.RootModuleInstance.Child("a_module", addrs.NoKey)) |
| aModule.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| bModule := state.EnsureModule(addrs.RootModuleInstance.Child("b_module", addrs.NoKey)) |
| bModule.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.b").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"b"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar0"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar1"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("err: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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), |
| "type": cty.StringVal("aws_instance"), |
| }), ric.Before) |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.StringVal("bar"), |
| "num": cty.NumberIntVal(3), |
| "type": cty.StringVal("aws_instance"), |
| }), 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Hooks: []Hook{h}, |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.baz").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| case "aws_instance.foo": |
| if res.Action != plans.Create { |
| t.Fatalf("resource %s should be created", i) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "num": cty.NumberIntVal(2), |
| "type": cty.UnknownVal(cty.String), |
| }), 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "foo": cty.StringVal("2"), |
| "type": cty.UnknownVal(cty.String), |
| }), ric.After) |
| case "aws_instance.foo": |
| if res.Action != plans.Update { |
| t.Fatalf("resource %s should be updated", i) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| 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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_requiresReplace(t *testing.T) { |
| m := testModule(t, "plan-requires-replace") |
| p := testProvider("test") |
| p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ |
| Provider: providers.Schema{ |
| Block: &configschema.Block{}, |
| }, |
| ResourceTypes: map[string]providers.Schema{ |
| "test_thing": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "v": { |
| Type: cty.String, |
| Required: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: req.ProposedNewState, |
| RequiresReplace: []cty.Path{ |
| cty.GetAttrPath("v"), |
| }, |
| } |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_thing.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"v":"hello"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"].Block |
| ty := schema.ImpliedType() |
| |
| if got, want := len(plan.Changes.Resources), 1; got != want { |
| t.Fatalf("got %d changes; want %d", got, want) |
| } |
| |
| for _, res := range plan.Changes.Resources { |
| t.Run(res.Addr.String(), func(t *testing.T) { |
| ric, err := res.Decode(ty) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| switch i := ric.Addr.String(); i { |
| case "test_thing.foo": |
| if got, want := ric.Action, plans.DeleteThenCreate; got != want { |
| t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseCannotUpdate; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "v": cty.StringVal("goodbye"), |
| }), ric.After) |
| default: |
| t.Fatalf("unexpected resource instance %s", i) |
| } |
| }) |
| } |
| } |
| |
| func TestContext2Plan_taint(t *testing.T) { |
| m := testModule(t, "plan-taint") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"baz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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(res.Addr.String(), func(t *testing.T) { |
| ric, err := res.Decode(ty) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| switch i := ric.Addr.String(); i { |
| case "aws_instance.bar": |
| if got, want := res.Action, plans.DeleteThenCreate; got != want { |
| t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "foo": cty.StringVal("2"), |
| "type": cty.UnknownVal(cty.String), |
| }), ric.After) |
| case "aws_instance.foo": |
| if got, want := res.Action, plans.NoOp; got != want { |
| t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := res.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| }) |
| } |
| } |
| |
| func TestContext2Plan_taintIgnoreChanges(t *testing.T) { |
| m := testModule(t, "plan-taint-ignore-changes") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"foo","vars":"foo","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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 got, want := res.Action, plans.DeleteThenCreate; got != want { |
| t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| 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.UnknownVal(cty.String), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[2]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| for i := 0; i < 100; i++ { |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state.DeepCopy(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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 got, want := ric.Action, plans.DeleteThenCreate; got != want { |
| t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { |
| t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) |
| } |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.StringVal("bar"), |
| "type": cty.StringVal("aws_instance"), |
| }), ric.Before) |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| "type": 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Resource( |
| addrs.ManagedResourceMode, "aws_instance", "foo", |
| ), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("B", addrs.NoKey), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.UnknownVal(cty.String), |
| }), 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.UnknownVal(cty.String), |
| }), 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{}, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("child2", addrs.NoKey), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Block |
| 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") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.orphan").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-789xyz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.nottargeted").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Resource( |
| addrs.ManagedResourceMode, "aws_instance", "orphan", |
| ), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.orphan").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-789xyz"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.nottargeted").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"i-abc123"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( |
| addrs.ManagedResourceMode, "aws_instance", "orphan", |
| ), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Resource( |
| addrs.ManagedResourceMode, "aws_instance", "blue", |
| ), |
| addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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), |
| "type": 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.UnknownVal(cty.String), |
| }), 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") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource( |
| addrs.ManagedResourceMode, "aws_instance", "a", |
| ), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("err: %s", diags) |
| } |
| if len(diags) != 1 { |
| t.Fatalf("got %d diagnostics; want 1", diags) |
| } |
| if got, want := diags[0].Severity(), tfdiags.Warning; got != want { |
| t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) |
| } |
| if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want { |
| t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) |
| } |
| } |
| |
| // https://github.com/hashicorp/terraform/issues/4515 |
| func TestContext2Plan_targetedOverTen(t *testing.T) { |
| m := testModule(t, "plan-targeted-over-ten") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| for i := 0; i < 13; i++ { |
| key := fmt.Sprintf("aws_instance.foo[%d]", i) |
| id := fmt.Sprintf("i-abc%d", i) |
| attrs := fmt.Sprintf(`{"id":"%s","type":"aws_instance"}`, id) |
| |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr(key).Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(attrs), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.ResourceInstance( |
| addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), |
| ), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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") |
| |
| var value interface{} |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| value = req.Config.GetAttr("foo").AsString() |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| opts := &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("bar"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| } |
| |
| if _, err := ctx.Plan(m, states.NewState(), opts); 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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| |
| if err == nil { |
| t.Fatal("should error") |
| } |
| } |
| |
| func TestContext2Plan_ignoreChanges(t *testing.T) { |
| m := testModule(t, "plan-ignore-changes") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("ami-1234abcd"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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() != "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.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| // computed attributes should not be set in config |
| id := req.Config.GetAttr("id") |
| if !id.IsNull() { |
| t.Error("computed id set in plan config") |
| } |
| |
| foo := req.Config.GetAttr("foo") |
| if foo.IsNull() { |
| t.Error(`missing "foo" during plan, was set to "bar" in state and config`) |
| } |
| |
| return testDiffFn(req) |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","instance":"t2.micro","type":"aws_instance","foo":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("ami-1234abcd"), |
| SourceType: ValueFromCaller, |
| }, |
| "bar": &InputValue{ |
| Value: cty.StringVal("t2.small"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| 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_ignoreChangesInMap(t *testing.T) { |
| p := testProvider("test") |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_ignore_changes_map": { |
| Attributes: map[string]*configschema.Attribute{ |
| "tags": {Type: cty.Map(cty.String), Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: req.ProposedNewState, |
| } |
| } |
| |
| s := states.BuildState(func(ss *states.SyncState) { |
| ss.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_ignore_changes_map", |
| Name: "foo", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo","tags":{"ignored":"from state","other":"from state"},"type":"aws_instance"}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| }) |
| m := testModule(t, "plan-ignore-changes-in-map") |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, s, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"].Block |
| ty := schema.ImpliedType() |
| |
| if got, want := len(plan.Changes.Resources), 1; got != want { |
| t.Fatalf("wrong number of changes %d; want %d", got, want) |
| } |
| |
| 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 got, want := ric.Addr.String(), "test_ignore_changes_map.foo"; got != want { |
| t.Fatalf("unexpected resource address %s; want %s", got, want) |
| } |
| |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "tags": cty.MapVal(map[string]cty.Value{ |
| "ignored": cty.StringVal("from state"), |
| "other": cty.StringVal("from config"), |
| }), |
| }), ric.After) |
| } |
| |
| func TestContext2Plan_ignoreChangesSensitive(t *testing.T) { |
| m := testModule(t, "plan-ignore-changes-sensitive") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("ami-1234abcd"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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() != "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_moduleMapLiteral(t *testing.T) { |
| m := testModule(t, "plan-module-map-literal") |
| p := testProvider("aws") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| s := req.ProposedNewState.AsValueMap() |
| m := s["tags"].AsValueMap() |
| |
| if m["foo"].AsString() != "bar" { |
| t.Fatalf("Bad value in tags attr: %#v", m) |
| } |
| |
| meta := s["meta"].AsValueMap() |
| if len(meta) != 0 { |
| t.Fatalf("Meta attr not empty: %#v", meta) |
| } |
| return testDiffFn(req) |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| resp = testDiffFn(req) |
| |
| if req.TypeName != "aws_computed_source" { |
| return |
| } |
| |
| planned := resp.PlannedState.AsValueMap() |
| planned["computed_read_only"] = cty.UnknownVal(cty.String) |
| resp.PlannedState = cty.ObjectVal(planned) |
| return resp |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block |
| |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "thing": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block |
| |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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 { |
| computedVal := req.ProposedNewState.GetAttr("computed") |
| if computedVal.IsNull() { |
| computedVal = cty.UnknownVal(cty.String) |
| } |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: cty.ObjectVal(map[string]cty.Value{ |
| "num": req.ProposedNewState.GetAttr("num"), |
| "computed": computedVal, |
| }), |
| } |
| } |
| p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| cfg := req.Config.AsValueMap() |
| cfg["id"] = cty.StringVal("data_id") |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(cfg), |
| } |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| seenAddrs := make(map[string]struct{}) |
| for _, res := range plan.Changes.Resources { |
| var schema *configschema.Block |
| switch res.Addr.Resource.Resource.Mode { |
| case addrs.DataResourceMode: |
| schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type].Block |
| case addrs.ManagedResourceMode: |
| schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block |
| } |
| |
| ric, err := res.Decode(schema.ImpliedType()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| seenAddrs[ric.Addr.String()] = struct{}{} |
| |
| 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.StringVal("data_id"), |
| }), 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.StringVal("data_id"), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| }) |
| } |
| |
| wantAddrs := map[string]struct{}{ |
| "aws_instance.foo[0]": {}, |
| "aws_instance.foo[1]": {}, |
| } |
| if !cmp.Equal(seenAddrs, wantAddrs) { |
| t.Errorf("incorrect addresses in changeset:\n%s", cmp.Diff(wantAddrs, seenAddrs)) |
| } |
| } |
| |
| // 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{ |
| "user_data":"x","require_new":"", |
| "set":[{"a":"1"}], |
| "lst":["j"] |
| }`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block |
| |
| 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.PlanResourceChangeFn = testDiffFn |
| p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { |
| return providers.ReadResourceResponse{ |
| NewState: req.PriorState, |
| } |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo0","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo1","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar0","type":"aws_instance"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar1","type":"aws_instance"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.baz[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz0","type":"aws_instance"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.baz[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz1","type":"aws_instance"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| diags := ctx.Validate(m) |
| if diags.HasErrors() { |
| t.Fatalf("validate errors: %s", diags.Err()) |
| } |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| 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 not 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.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { |
| 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.ValidateResourceConfigResponse{ |
| Diagnostics: diags, |
| } |
| } |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| if req.TypeName != "aws_ami_list" { |
| t.Fatalf("Reached apply for unexpected resource type! %s", req.TypeName) |
| } |
| // Pretend like we make a thing and the computed list "ids" is populated |
| s := req.PlannedState.AsValueMap() |
| s["id"] = cty.StringVal("someid") |
| s["ids"] = cty.ListVal([]cty.Value{ |
| cty.StringVal("ami-abc123"), |
| cty.StringVal("ami-bcd345"), |
| }) |
| |
| resp.NewState = cty.ObjectVal(s) |
| return |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| diags := c.Validate(m) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected validation failure: %s", diags.Err()) |
| } |
| |
| _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| diags := c.Validate(m) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected validation failure: %s", diags.Err()) |
| } |
| |
| _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| diags := c.Validate(m) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected validation failure: %s", diags.Err()) |
| } |
| |
| _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("data_id"), |
| "foo": cty.StringVal("foo"), |
| }), |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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") |
| p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("data_id"), |
| "foo": cty.StringVal("foo"), |
| }), |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| 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_variableValidation(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| variable "x" { |
| default = "bar" |
| } |
| |
| resource "aws_instance" "foo" { |
| foo = var.x |
| }`, |
| }) |
| |
| p := testProvider("aws") |
| p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { |
| foo := req.Config.GetAttr("foo").AsString() |
| if foo == "bar" { |
| resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar")) |
| } |
| return |
| } |
| |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| resp.PlannedState = req.ProposedNewState |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if !diags.HasErrors() { |
| // Should get this error: |
| // Unsupported attribute: This object does not have an attribute named "missing" |
| t.Fatal("succeeded; want errors") |
| } |
| } |
| |
| func TestContext2Plan_variableSensitivity(t *testing.T) { |
| m := testModule(t, "plan-variable-sensitivity") |
| |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| resp.PlannedState = req.ProposedNewState |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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 "aws_instance.foo": |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "foo": cty.StringVal("foo").Mark(marks.Sensitive), |
| }), ric.After) |
| if len(res.ChangeSrc.BeforeValMarks) != 0 { |
| t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks) |
| } |
| if len(res.ChangeSrc.AfterValMarks) != 1 { |
| t.Errorf("unexpected AfterValMarks: %#v", res.ChangeSrc.AfterValMarks) |
| continue |
| } |
| pvm := res.ChangeSrc.AfterValMarks[0] |
| if got, want := pvm.Path, cty.GetAttrPath("foo"); !got.Equals(want) { |
| t.Errorf("unexpected path for mark\n got: %#v\nwant: %#v", got, want) |
| } |
| if got, want := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !got.Equal(want) { |
| t.Errorf("unexpected value for mark\n got: %#v\nwant: %#v", got, want) |
| } |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_variableSensitivityModule(t *testing.T) { |
| m := testModule(t, "plan-variable-sensitivity-module") |
| |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| resp.PlannedState = req.ProposedNewState |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "sensitive_var": {Value: cty.NilVal}, |
| "another_var": &InputValue{ |
| Value: cty.StringVal("boop"), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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.foo": |
| checkVals(t, objectVal(t, schema, map[string]cty.Value{ |
| "foo": cty.StringVal("foo").Mark(marks.Sensitive), |
| "value": cty.StringVal("boop").Mark(marks.Sensitive), |
| }), ric.After) |
| if len(res.ChangeSrc.BeforeValMarks) != 0 { |
| t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks) |
| } |
| if len(res.ChangeSrc.AfterValMarks) != 2 { |
| t.Errorf("expected AfterValMarks to contain two elements: %#v", res.ChangeSrc.AfterValMarks) |
| continue |
| } |
| // validate that the after marks have "foo" and "value" |
| contains := func(pvmSlice []cty.PathValueMarks, stepName string) bool { |
| for _, pvm := range pvmSlice { |
| if pvm.Path.Equals(cty.GetAttrPath(stepName)) { |
| if pvm.Marks.Equal(cty.NewValueMarks(marks.Sensitive)) { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| if !contains(res.ChangeSrc.AfterValMarks, "foo") { |
| t.Error("unexpected AfterValMarks to contain \"foo\" with sensitive mark") |
| } |
| if !contains(res.ChangeSrc.AfterValMarks, "value") { |
| t.Error("unexpected AfterValMarks to contain \"value\" with sensitive mark") |
| } |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func checkVals(t *testing.T, expected, got cty.Value) { |
| t.Helper() |
| // The GoStringer format seems to result in the closest thing to a useful |
| // diff for values with marks. |
| // TODO: if we want to continue using cmp.Diff on cty.Values, we should |
| // make a transformer that creates a more comparable structure. |
| valueTrans := cmp.Transformer("gostring", func(v cty.Value) string { |
| return fmt.Sprintf("%#v\n", v) |
| }) |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block |
| 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.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&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}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block |
| 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_expandOrphan(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "mod" { |
| count = 1 |
| source = "./mod" |
| } |
| `, |
| "mod/main.tf": ` |
| resource "aws_instance" "foo" { |
| } |
| `, |
| }) |
| |
| state := states.NewState() |
| state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(0))).SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| expected := map[string]plans.Action{ |
| `module.mod[1].aws_instance.foo`: plans.Delete, |
| `module.mod[0].aws_instance.foo`: plans.NoOp, |
| } |
| |
| for _, res := range plan.Changes.Resources { |
| want := expected[res.Addr.String()] |
| if res.Action != want { |
| t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) |
| } |
| delete(expected, res.Addr.String()) |
| } |
| |
| for res, action := range expected { |
| t.Errorf("missing %s change for %s", action, res) |
| } |
| } |
| |
| func TestContext2Plan_indexInVar(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "a" { |
| count = 1 |
| source = "./mod" |
| in = "test" |
| } |
| |
| module "b" { |
| count = 1 |
| source = "./mod" |
| in = length(module.a) |
| } |
| `, |
| "mod/main.tf": ` |
| resource "aws_instance" "foo" { |
| foo = var.in |
| } |
| |
| variable "in" { |
| } |
| |
| output"out" { |
| value = aws_instance.foo.id |
| } |
| `, |
| }) |
| |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| } |
| |
| func TestContext2Plan_targetExpandedAddress(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "mod" { |
| count = 3 |
| source = "./mod" |
| } |
| `, |
| "mod/main.tf": ` |
| resource "aws_instance" "foo" { |
| count = 2 |
| } |
| `, |
| }) |
| |
| p := testProvider("aws") |
| |
| targets := []addrs.Targetable{} |
| target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]") |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| targets = append(targets, target.Subject) |
| |
| target, diags = addrs.ParseTargetStr("module.mod[2]") |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| targets = append(targets, target.Subject) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: targets, |
| }) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| expected := map[string]plans.Action{ |
| // the single targeted mod[1] instances |
| `module.mod[1].aws_instance.foo[0]`: plans.Create, |
| // the whole mode[2] |
| `module.mod[2].aws_instance.foo[0]`: plans.Create, |
| `module.mod[2].aws_instance.foo[1]`: plans.Create, |
| } |
| |
| for _, res := range plan.Changes.Resources { |
| want := expected[res.Addr.String()] |
| if res.Action != want { |
| t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) |
| } |
| delete(expected, res.Addr.String()) |
| } |
| |
| for res, action := range expected { |
| t.Errorf("missing %s change for %s", action, res) |
| } |
| } |
| |
| func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "mod" { |
| count = 3 |
| source = "./mod" |
| } |
| `, |
| "mod/main.tf": ` |
| resource "aws_instance" "foo" { |
| } |
| `, |
| }) |
| |
| p := testProvider("aws") |
| |
| target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo") |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| targets := []addrs.Targetable{target.Subject} |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: targets, |
| }) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| expected := map[string]plans.Action{ |
| // the single targeted mod[1] instance |
| `module.mod[1].aws_instance.foo`: plans.Create, |
| } |
| |
| for _, res := range plan.Changes.Resources { |
| want := expected[res.Addr.String()] |
| if res.Action != want { |
| t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) |
| } |
| delete(expected, res.Addr.String()) |
| } |
| |
| for res, action := range expected { |
| t.Errorf("missing %s change for %s", action, res) |
| } |
| } |
| |
| func TestContext2Plan_moduleRefIndex(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "mod" { |
| for_each = { |
| a = "thing" |
| } |
| in = null |
| source = "./mod" |
| } |
| |
| module "single" { |
| source = "./mod" |
| in = module.mod["a"] |
| } |
| `, |
| "mod/main.tf": ` |
| variable "in" { |
| } |
| |
| output "out" { |
| value = "foo" |
| } |
| |
| resource "aws_instance" "foo" { |
| } |
| `, |
| }) |
| |
| p := testProvider("aws") |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| } |
| |
| func TestContext2Plan_noChangeDataPlan(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| data "test_data_source" "foo" {} |
| `, |
| }) |
| |
| p := new(MockProvider) |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| DataSources: map[string]*configschema.Block{ |
| "test_data_source": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| "foo": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("data_id"), |
| "foo": cty.StringVal("foo"), |
| }), |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("data.test_data_source.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"data_id", "foo":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| for _, res := range plan.Changes.Resources { |
| if res.Action != plans.NoOp { |
| t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action) |
| } |
| } |
| } |
| |
| // for_each can reference a resource with 0 instances |
| func TestContext2Plan_scaleInForEach(t *testing.T) { |
| p := testProvider("test") |
| |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| locals { |
| m = {} |
| } |
| |
| resource "test_instance" "a" { |
| for_each = local.m |
| } |
| |
| resource "test_instance" "b" { |
| for_each = test_instance.a |
| } |
| `}) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a0"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.b").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"b"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| t.Run("test_instance.a[0]", func(t *testing.T) { |
| instAddr := mustResourceInstanceAddr("test_instance.a[0]") |
| change := plan.Changes.ResourceInstance(instAddr) |
| if change == nil { |
| t.Fatalf("no planned change for %s", instAddr) |
| } |
| if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) { |
| t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want) |
| } |
| if got, want := change.Action, plans.Delete; got != want { |
| t.Errorf("wrong action for %s %s; want %s", instAddr, got, want) |
| } |
| if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { |
| t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want) |
| } |
| }) |
| t.Run("test_instance.b", func(t *testing.T) { |
| instAddr := mustResourceInstanceAddr("test_instance.b") |
| change := plan.Changes.ResourceInstance(instAddr) |
| if change == nil { |
| t.Fatalf("no planned change for %s", instAddr) |
| } |
| if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) { |
| t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want) |
| } |
| if got, want := change.Action, plans.Delete; got != want { |
| t.Errorf("wrong action for %s %s; want %s", instAddr, got, want) |
| } |
| if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { |
| t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want) |
| } |
| }) |
| } |
| |
| func TestContext2Plan_targetedModuleInstance(t *testing.T) { |
| m := testModule(t, "plan-targeted") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)), |
| }, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block |
| 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 "module.mod[0].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.UnknownVal(cty.String), |
| }), ric.After) |
| default: |
| t.Fatal("unknown instance:", i) |
| } |
| } |
| } |
| |
| func TestContext2Plan_dataRefreshedInPlan(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| data "test_data_source" "d" { |
| } |
| `}) |
| |
| p := testProvider("test") |
| p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("this"), |
| "foo": cty.NullVal(cty.String), |
| }), |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.ErrWithWarnings()) |
| } |
| |
| d := plan.PriorState.ResourceInstance(mustResourceInstanceAddr("data.test_data_source.d")) |
| if d == nil || d.Current == nil { |
| t.Fatal("data.test_data_source.d not found in state:", plan.PriorState) |
| } |
| |
| if d.Current.Status != states.ObjectReady { |
| t.Fatal("expected data.test_data_source.d to be fully read in refreshed state, got status", d.Current.Status) |
| } |
| } |
| |
| func TestContext2Plan_dataReferencesResourceDirectly(t *testing.T) { |
| // When a data resource refers to a managed resource _directly_, any |
| // pending change for the managed resource will cause the data resource |
| // to be deferred to the apply step. |
| // See also TestContext2Plan_dataReferencesResourceIndirectly for the |
| // other case, where the reference is indirect. |
| |
| p := testProvider("test") |
| |
| p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source should not be read")) |
| return resp |
| } |
| |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| locals { |
| x = "value" |
| } |
| |
| resource "test_resource" "a" { |
| value = local.x |
| } |
| |
| // test_resource.a.value can be resolved during plan, but the reference implies |
| // that the data source should wait until the resource is created. |
| data "test_data_source" "d" { |
| foo = test_resource.a.value |
| } |
| |
| // ensure referencing an indexed instance that has not yet created will also |
| // delay reading the data source |
| resource "test_resource" "b" { |
| count = 2 |
| value = local.x |
| } |
| |
| data "test_data_source" "e" { |
| foo = test_resource.b[0].value |
| } |
| `}) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| rc := plan.Changes.ResourceInstance(addrs.Resource{ |
| Mode: addrs.DataResourceMode, |
| Type: "test_data_source", |
| Name: "d", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) |
| if rc != nil { |
| if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseDependencyPending; got != want { |
| t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want) |
| } |
| } else { |
| t.Error("no change for test_data_source.e") |
| } |
| } |
| |
| func TestContext2Plan_dataReferencesResourceIndirectly(t *testing.T) { |
| // When a data resource refers to a managed resource indirectly, pending |
| // changes for the managed resource _do not_ cause the data resource to |
| // be deferred to apply. This is a pragmatic special case added for |
| // backward compatibility with the old situation where we would _always_ |
| // eagerly read data resources with known configurations, regardless of |
| // the plans for their dependencies. |
| // This test creates an indirection through a local value, but the same |
| // principle would apply for both input variable and output value |
| // indirection. |
| // |
| // See also TestContext2Plan_dataReferencesResourceDirectly for the |
| // other case, where the reference is direct. |
| // This special exception doesn't apply for a data resource that has |
| // custom conditions; see |
| // TestContext2Plan_dataResourceChecksManagedResourceChange for that |
| // situation. |
| |
| p := testProvider("test") |
| var applyCount int64 |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| atomic.AddInt64(&applyCount, 1) |
| resp.NewState = req.PlannedState |
| return resp |
| } |
| p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { |
| if atomic.LoadInt64(&applyCount) == 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source read before managed resource apply")) |
| } else { |
| resp.State = req.Config |
| } |
| return resp |
| } |
| |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| locals { |
| x = "value" |
| } |
| |
| resource "test_resource" "a" { |
| value = local.x |
| } |
| |
| locals { |
| y = test_resource.a.value |
| } |
| |
| // test_resource.a.value would ideally cause a pending change for |
| // test_resource.a to defer this to the apply step, but we intentionally don't |
| // do that when it's indirect (through a local value, here) as a concession |
| // to backward compatibility. |
| data "test_data_source" "d" { |
| foo = local.y |
| } |
| `}) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if !diags.HasErrors() { |
| t.Fatalf("successful plan; want an error") |
| } |
| |
| if got, want := diags.Err().Error(), "data source read before managed resource apply"; !strings.Contains(got, want) { |
| t.Errorf("Missing expected error message\ngot: %s\nwant substring: %s", got, want) |
| } |
| } |
| |
| func TestContext2Plan_skipRefresh(t *testing.T) { |
| p := testProvider("test") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| } |
| `}) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a","type":"test_instance"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SkipRefresh: true, |
| }) |
| assertNoErrors(t, diags) |
| |
| if p.ReadResourceCalled { |
| t.Fatal("Resource should not have been refreshed") |
| } |
| |
| for _, c := range plan.Changes.Resources { |
| if c.Action != plans.NoOp { |
| t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) |
| } |
| } |
| } |
| |
| func TestContext2Plan_dataInModuleDependsOn(t *testing.T) { |
| p := testProvider("test") |
| |
| readDataSourceB := false |
| p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { |
| cfg := req.Config.AsValueMap() |
| foo := cfg["foo"].AsString() |
| |
| cfg["id"] = cty.StringVal("ID") |
| cfg["foo"] = cty.StringVal("new") |
| |
| if foo == "b" { |
| readDataSourceB = true |
| } |
| |
| resp.State = cty.ObjectVal(cfg) |
| return resp |
| } |
| |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| module "a" { |
| source = "./mod_a" |
| } |
| |
| module "b" { |
| source = "./mod_b" |
| depends_on = [module.a] |
| }`, |
| "mod_a/main.tf": ` |
| data "test_data_source" "a" { |
| foo = "a" |
| }`, |
| "mod_b/main.tf": ` |
| data "test_data_source" "b" { |
| foo = "b" |
| }`, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| // The change to data source a should not prevent data source b from being |
| // read. |
| if !readDataSourceB { |
| t.Fatal("data source b was not read during plan") |
| } |
| } |
| |
| func TestContext2Plan_rpcDiagnostics(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| } |
| `, |
| }) |
| |
| p := testProvider("test") |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| resp := testDiffFn(req) |
| resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble")) |
| return resp |
| } |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| if len(diags) == 0 { |
| t.Fatal("expected warnings") |
| } |
| |
| for _, d := range diags { |
| des := d.Description().Summary |
| if !strings.Contains(des, "frobble") { |
| t.Fatalf(`expected frobble, got %q`, des) |
| } |
| } |
| } |
| |
| // ignore_changes needs to be re-applied to the planned value for provider |
| // using the LegacyTypeSystem |
| func TestContext2Plan_legacyProviderIgnoreChanges(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| lifecycle { |
| ignore_changes = [data] |
| } |
| } |
| `, |
| }) |
| |
| p := testProvider("test") |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| m := req.ProposedNewState.AsValueMap() |
| // this provider "hashes" the data attribute as bar |
| m["data"] = cty.StringVal("bar") |
| |
| resp.PlannedState = cty.ObjectVal(m) |
| resp.LegacyTypeSystem = true |
| return resp |
| } |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "data": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a","data":"foo"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| for _, c := range plan.Changes.Resources { |
| if c.Action != plans.NoOp { |
| t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) |
| } |
| } |
| } |
| |
| func TestContext2Plan_validateIgnoreAll(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| lifecycle { |
| ignore_changes = all |
| } |
| } |
| `, |
| }) |
| |
| p := testProvider("test") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "data": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { |
| var diags tfdiags.Diagnostics |
| if req.TypeName == "test_instance" { |
| if !req.Config.GetAttr("id").IsNull() { |
| diags = diags.Append(errors.New("id cannot be set in config")) |
| } |
| } |
| return providers.ValidateResourceConfigResponse{ |
| Diagnostics: diags, |
| } |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a","data":"foo"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| _, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| } |
| |
| func TestContext2Plan_legacyProviderIgnoreAll(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| lifecycle { |
| ignore_changes = all |
| } |
| data = "foo" |
| } |
| `, |
| }) |
| |
| p := testProvider("test") |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "data": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { |
| plan := req.ProposedNewState.AsValueMap() |
| // Update both the computed id and the configured data. |
| // Legacy providers expect terraform to be able to ignore these. |
| |
| plan["id"] = cty.StringVal("updated") |
| plan["data"] = cty.StringVal("updated") |
| resp.PlannedState = cty.ObjectVal(plan) |
| resp.LegacyTypeSystem = true |
| return resp |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"orig","data":"orig"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| for _, c := range plan.Changes.Resources { |
| if c.Action != plans.NoOp { |
| t.Fatalf("expected NoOp plan, got %s\n", c.Action) |
| } |
| } |
| } |
| |
| func TestContext2Plan_dataRemovalNoProvider(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| resource "test_instance" "a" { |
| } |
| `, |
| }) |
| |
| p := testProvider("test") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("test_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a","data":"foo"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), |
| ) |
| |
| // the provider for this data source is no longer in the config, but that |
| // should not matter for state removal. |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("data.test_data_source.d").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"d"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/local/test"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| // We still need to be able to locate the provider to decode the |
| // state, since we do not know during init that this provider is |
| // only used for an orphaned data source. |
| addrs.NewProvider("registry.terraform.io", "local", "test"): testProviderFuncFixed(p), |
| }, |
| }) |
| _, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| } |
| |
| func TestContext2Plan_noSensitivityChange(t *testing.T) { |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| variable "sensitive_var" { |
| default = "hello" |
| sensitive = true |
| } |
| |
| resource "test_resource" "foo" { |
| value = var.sensitive_var |
| sensitive_value = var.sensitive_var |
| }`, |
| }) |
| |
| p := testProvider("test") |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| state := states.BuildState(func(s *states.SyncState) { |
| s.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "foo", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo", "value":"hello", "sensitive_value":"hello"}`), |
| AttrSensitivePaths: []cty.PathValueMarks{ |
| {Path: cty.Path{cty.GetAttrStep{Name: "value"}}, Marks: cty.NewValueMarks(marks.Sensitive)}, |
| {Path: cty.Path{cty.GetAttrStep{Name: "sensitive_value"}}, Marks: cty.NewValueMarks(marks.Sensitive)}, |
| }, |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| }) |
| plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| for _, c := range plan.Changes.Resources { |
| if c.Action != plans.NoOp { |
| t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) |
| } |
| } |
| } |
| |
| func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) { |
| m := testModule(t, "validate-variable-custom-validations-child-sensitive") |
| |
| p := testProvider("test") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if !diags.HasErrors() { |
| t.Fatal("succeeded; want errors") |
| } |
| if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) { |
| t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) |
| } |
| } |
| |
| func TestContext2Plan_nullOutputNoOp(t *testing.T) { |
| // this should always plan a NoOp change for the output |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| output "planned" { |
| value = false ? 1 : null |
| } |
| `, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{}) |
| state := states.BuildState(func(s *states.SyncState) { |
| r := s.Module(addrs.RootModuleInstance) |
| r.SetOutputValue("planned", cty.NullVal(cty.DynamicPseudoType), false) |
| }) |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| for _, c := range plan.Changes.Outputs { |
| if c.Action != plans.NoOp { |
| t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) |
| } |
| } |
| } |
| |
| func TestContext2Plan_createOutput(t *testing.T) { |
| // this should always plan a NoOp change for the output |
| m := testModuleInline(t, map[string]string{ |
| "main.tf": ` |
| output "planned" { |
| value = 1 |
| } |
| `, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{}) |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatal(diags.Err()) |
| } |
| |
| for _, c := range plan.Changes.Outputs { |
| if c.Action != plans.Create { |
| t.Fatalf("expected Create change, got %s for %q", c.Action, c.Addr) |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NOTE: Due to the size of this file, new tests should be added to |
| // context_plan2_test.go. |
| //////////////////////////////////////////////////////////////////////////////// |