terraform/terraform/context_plan_test.go

6488 lines
173 KiB
Go
Raw Normal View History

package terraform
import (
"bytes"
"errors"
"fmt"
"os"
"reflect"
"sort"
"strings"
"sync"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
2019-08-07 01:58:58 +02:00
"github.com/hashicorp/terraform/configs/hcl2shim"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
)
2016-11-06 08:00:05 +01:00
func TestContext2Plan_basic(t *testing.T) {
m := testModule(t, "plan-good")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
ProviderSHA256s: map[string][]byte{
"aws": []byte("placeholder"),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if l := len(plan.Changes.Resources); l < 2 {
t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources))
}
if !reflect.DeepEqual(plan.ProviderSHA256s, ctx.providerSHA256s) {
t.Errorf("wrong ProviderSHA256s %#v; want %#v", plan.ProviderSHA256s, ctx.providerSHA256s)
}
2018-09-15 00:40:09 +02:00
if !ctx.State().Empty() {
t.Fatalf("expected empty state, got %#v\n", ctx.State())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
for _, r := range plan.Changes.Resources {
2018-09-19 00:44:51 +02:00
ric, err := r.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
2018-09-19 00:44:51 +02:00
foo := ric.After.GetAttr("foo").AsString()
2018-09-15 00:40:09 +02:00
if foo != "2" {
2018-09-19 00:44:51 +02:00
t.Fatalf("incorrect plan for 'bar': %#v", ric.After)
2018-09-15 00:40:09 +02:00
}
case "aws_instance.foo":
2018-09-19 00:44:51 +02:00
num, _ := ric.After.GetAttr("num").AsBigFloat().Int64()
2018-09-18 01:25:11 +02:00
if num != 2 {
2018-09-19 00:44:51 +02:00
t.Fatalf("incorrect plan for 'foo': %#v", ric.After)
2018-09-15 00:40:09 +02:00
}
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_createBefore_deposed(t *testing.T) {
m := testModule(t, "plan-cbd")
p := testProvider("aws")
p.DiffFn = 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"}`),
},
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
// the state should still show one deposed
expectedState := strings.TrimSpace(`
aws_instance.foo: (1 deposed)
ID = baz
provider = provider["registry.terraform.io/hashicorp/aws"]
2018-09-18 01:25:11 +02:00
Deposed ID 1 = foo`)
2018-09-15 00:40:09 +02:00
if ctx.State().String() != expectedState {
t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, ctx.State().String())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
type InstanceGen struct {
Addr string
DeposedKey states.DeposedKey
2018-09-15 00:40:09 +02:00
}
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)
2018-09-15 00:40:09 +02:00
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))
2018-09-15 00:40:09 +02:00
}
{
ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty)
if err != nil {
t.Fatal(err)
}
if got, want := ric.Action, plans.NoOp; got != want {
t.Errorf("current object change action is %s; want %s", got, want)
}
// the existing instance should only have an unchanged id
expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{"id": cty.StringVal("baz")}))
if err != nil {
t.Fatal(err)
}
checkVals(t, expected, ric.After)
}
2018-09-15 00:40:09 +02:00
{
ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty)
if err != nil {
t.Fatal(err)
}
if got, want := ric.Action, plans.Delete; got != want {
t.Errorf("deposed object change action is %s; want %s", got, want)
}
}
}
func TestContext2Plan_createBefore_maintainRoot(t *testing.T) {
m := testModule(t, "plan-cbd-maintain-root")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
if !ctx.State().Empty() {
t.Fatal("expected empty state, got:", ctx.State())
}
2018-09-15 00:40:09 +02:00
if len(plan.Changes.Resources) != 4 {
t.Error("expected 4 resource in plan, got", len(plan.Changes.Resources))
}
2018-09-15 00:40:09 +02:00
for _, res := range plan.Changes.Resources {
// these should all be creates
if res.Action != plans.Create {
t.Fatalf("unexpected action %s for %s", res.Action, res.Addr.String())
}
}
}
func TestContext2Plan_emptyDiff(t *testing.T) {
m := testModule(t, "plan-empty")
p := testProvider("aws")
p.DiffFn = func(
info *InstanceInfo,
s *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
return nil, nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
if !ctx.State().Empty() {
t.Fatal("expected empty state, got:", ctx.State())
}
if len(plan.Changes.Resources) != 2 {
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
}
actions := map[string]plans.Action{}
for _, res := range plan.Changes.Resources {
actions[res.Addr.String()] = res.Action
}
expected := map[string]plans.Action{
"aws_instance.foo": plans.Create,
"aws_instance.bar": plans.Create,
}
if !cmp.Equal(expected, actions) {
t.Fatal(cmp.Diff(expected, actions))
}
}
2016-01-19 21:37:55 +01:00
func TestContext2Plan_escapedVar(t *testing.T) {
m := testModule(t, "plan-escaped-var")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
2016-01-19 21:37:55 +01:00
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
2016-01-19 21:37:55 +01:00
}
2018-09-15 00:40:09 +02:00
if len(plan.Changes.Resources) != 1 {
t.Error("expected 1 resource in plan, got", len(plan.Changes.Resources))
}
res := plan.Changes.Resources[0]
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
2016-01-19 21:37:55 +01:00
}
2018-09-15 00:40:09 +02:00
expected := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar-${baz}"),
"type": cty.StringVal("aws_instance")},
)
2018-09-19 00:44:51 +02:00
checkVals(t, expected, ric.After)
2016-01-19 21:37:55 +01:00
}
func TestContext2Plan_minimal(t *testing.T) {
m := testModule(t, "plan-empty")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
if !ctx.State().Empty() {
t.Fatal("expected empty state, got:", ctx.State())
}
if len(plan.Changes.Resources) != 2 {
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
}
actions := map[string]plans.Action{}
for _, res := range plan.Changes.Resources {
actions[res.Addr.String()] = res.Action
}
expected := map[string]plans.Action{
"aws_instance.foo": plans.Create,
"aws_instance.bar": plans.Create,
}
if !cmp.Equal(expected, actions) {
t.Fatal(cmp.Diff(expected, actions))
}
}
func TestContext2Plan_modules(t *testing.T) {
m := testModule(t, "plan-modules")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
if len(plan.Changes.Resources) != 3 {
t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources))
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
expectFoo := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance")},
)
expectNum := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance")},
)
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
var expected cty.Value
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
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)
}
2018-09-19 00:44:51 +02:00
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")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
expected := map[string]struct{}{
`aws_instance.foo["a"]`: struct{}{},
`module.count_child[1].aws_instance.foo[0]`: struct{}{},
`module.count_child[1].aws_instance.foo[1]`: struct{}{},
`module.count_child[0].aws_instance.foo[0]`: struct{}{},
`module.count_child[0].aws_instance.foo[1]`: struct{}{},
`module.for_each_child["a"].aws_instance.foo[1]`: struct{}{},
`module.for_each_child["a"].aws_instance.foo[0]`: struct{}{},
}
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.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"some_input": {Type: cty.String, Optional: true},
"type": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
var expected cty.Value
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.b":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
})
case "aws_instance.c":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"some_input": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
})
default:
t.Fatal("unknown instance:", i)
}
2018-09-19 00:44:51 +02:00
checkVals(t, expected, ric.After)
}
}
func TestContext2Plan_moduleDeadlock(t *testing.T) {
testCheckDeadlock(t, func() {
m := testModule(t, "plan-module-deadlock")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-15 00:40:09 +02:00
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-15 00:40:09 +02:00
expected := objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
})
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
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)
}
2018-09-19 00:44:51 +02:00
checkVals(t, expected, ric.After)
}
})
}
func TestContext2Plan_moduleInput(t *testing.T) {
m := testModule(t, "plan-module-input")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
var expected cty.Value
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance"),
})
case "module.child.aws_instance.foo":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("42"),
"type": cty.StringVal("aws_instance"),
})
default:
t.Fatal("unknown instance:", i)
}
2018-09-19 00:44:51 +02:00
checkVals(t, expected, ric.After)
}
}
func TestContext2Plan_moduleInputComputed(t *testing.T) {
m := testModule(t, "plan-module-input-computed")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
"compute": cty.StringVal("foo"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
case "module.child.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleInputFromVar(t *testing.T) {
m := testModule(t, "plan-module-input-var")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"foo": &InputValue{
Value: cty.StringVal("52"),
SourceType: ValueFromCaller,
},
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
case "module.child.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("52"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleMultiVar(t *testing.T) {
m := testModule(t, "plan-module-multi-var")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"foo": {Type: cty.String, Optional: true},
"baz": {Type: cty.String, Optional: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 5 {
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.parent[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
case "aws_instance.parent[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
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"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
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"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
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"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleOrphans(t *testing.T) {
m := testModule(t, "plan-modules-remove")
p := testProvider("aws")
p.DiffFn = 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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-15 00:40:09 +02:00
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.foo":
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
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"]`
2018-09-15 00:40:09 +02:00
if ctx.State().String() != expectedState {
t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, ctx.State().String())
}
}
// https://github.com/hashicorp/terraform/issues/3114
func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) {
m := testModule(t, "plan-modules-remove-provisioners")
p := testProvider("aws")
pr := testProvisioner()
p.DiffFn = testDiffFn
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("aws_instance.top").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"top"}`),
},
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"}`),
},
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"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Provisioners: map[string]ProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 3 {
t.Error("expected 3 planned resources, got", len(plan.Changes.Resources))
}
2018-09-15 00:40:09 +02:00
for _, res := range plan.Changes.Resources {
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "module.parent.module.child1.aws_instance.foo":
2018-09-15 00:40:09 +02:00
if res.Action != plans.Delete {
t.Fatalf("expected resource Delete, got %s", res.Action)
}
case "module.parent.module.child2.aws_instance.foo":
2018-09-15 00:40:09 +02:00
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)
2018-09-15 00:40:09 +02:00
}
default:
2018-09-19 00:44:51 +02:00
t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After))
2018-09-15 00:40:09 +02:00
}
}
2018-09-15 00:40:09 +02:00
expectedState := `aws_instance.top:
ID = top
provider = provider["registry.terraform.io/hashicorp/aws"]
module.parent.child1:
aws_instance.foo:
ID = baz
provider = provider["registry.terraform.io/hashicorp/aws"]
module.parent.child2:
aws_instance.foo:
ID = baz
provider = provider["registry.terraform.io/hashicorp/aws"]`
2018-09-15 00:40:09 +02:00
if expectedState != ctx.State().String() {
t.Fatalf("\nexpect state:\n%s\n\ngot state:\n%s\n", expectedState, ctx.State().String())
}
}
func TestContext2Plan_moduleProviderInherit(t *testing.T) {
var l sync.Mutex
var calls []string
m := testModule(t, "plan-module-provider-inherit")
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
l.Lock()
defer l.Unlock()
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"from": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"from": {Type: cty.String, Optional: true},
},
},
},
}
p.ConfigureFn = func(c *ResourceConfig) error {
if v, ok := c.Get("from"); !ok || v.(string) != "root" {
return fmt.Errorf("bad")
}
return nil
}
p.DiffFn = func(
info *InstanceInfo,
state *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
v, _ := c.Get("from")
l.Lock()
defer l.Unlock()
calls = append(calls, v.(string))
return testDiffFn(info, state, c)
}
return p, nil
},
},
})
_, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := calls
sort.Strings(actual)
expected := []string{"child", "root"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
2017-01-24 21:56:13 +01:00
// This tests (for GH-11282) that deeply nested modules properly inherit
// configuration.
func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) {
var l sync.Mutex
m := testModule(t, "plan-module-provider-inherit-deep")
ctx := testContext2(t, &ContextOpts{
Config: m,
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.GetSchemaReturn = &ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"from": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{},
},
},
}
p.ConfigureFn = func(c *ResourceConfig) error {
v, ok := c.Get("from")
if !ok || v.(string) != "root" {
return fmt.Errorf("bad")
2017-01-24 21:56:13 +01:00
}
from = v.(string)
return nil
}
2017-01-24 21:56:13 +01:00
p.DiffFn = func(
info *InstanceInfo,
state *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
if from != "root" {
return nil, fmt.Errorf("bad resource")
2017-01-24 21:56:13 +01:00
}
return testDiffFn(info, state, c)
}
return p, nil
2017-01-24 21:56:13 +01:00
},
},
2017-01-24 21:56:13 +01:00
})
_, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) {
var l sync.Mutex
var calls []string
m := testModule(t, "plan-module-provider-defaults-var")
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
l.Lock()
defer l.Unlock()
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"to": {Type: cty.String, Optional: true},
"from": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"from": {Type: cty.String, Optional: true},
},
},
},
}
p.ConfigureFn = func(c *ResourceConfig) error {
var buf bytes.Buffer
if v, ok := c.Get("from"); ok {
buf.WriteString(v.(string) + "\n")
}
if v, ok := c.Get("to"); ok {
buf.WriteString(v.(string) + "\n")
}
l.Lock()
defer l.Unlock()
calls = append(calls, buf.String())
return nil
}
p.DiffFn = testDiffFn
return p, nil
},
},
Variables: InputValues{
"foo": &InputValue{
Value: cty.StringVal("root"),
SourceType: ValueFromCaller,
},
},
})
_, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
expected := []string{
"child\nchild\n",
"root\n",
}
sort.Strings(calls)
if !reflect.DeepEqual(calls, expected) {
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls)
}
}
func TestContext2Plan_moduleProviderVar(t *testing.T) {
m := testModule(t, "plan-module-provider-var")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "module.child.aws_instance.test":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"value": cty.StringVal("hello"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleVar(t *testing.T) {
m := testModule(t, "plan-module-var")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
var expected cty.Value
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance"),
})
case "module.child.aws_instance.foo":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
})
default:
t.Fatal("unknown instance:", i)
}
2018-09-19 00:44:51 +02:00
checkVals(t, expected, ric.After)
}
}
2016-11-07 22:55:35 +01:00
func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) {
m := testModule(t, "plan-module-wrong-var-type")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
t.Fatalf("succeeded; want errors")
}
}
func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) {
m := testModule(t, "plan-module-wrong-var-type-nested")
p := testProvider("null")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
t.Fatalf("succeeded; want errors")
}
}
func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) {
m := testModule(t, "plan-module-var-with-default-value")
p := testProvider("null")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
}
func TestContext2Plan_moduleVarComputed(t *testing.T) {
m := testModule(t, "plan-module-var-computed")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-15 00:40:09 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-15 00:40:09 +02:00
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)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-15 00:40:09 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-15 00:40:09 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
case "module.child.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
"compute": cty.StringVal("foo"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-15 00:40:09 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_preventDestroy_bad(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-bad")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, err := ctx.Plan()
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
if plan != nil {
t.Logf(legacyDiffComparisonString(plan.Changes))
}
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
}
}
func TestContext2Plan_preventDestroy_good(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-good")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if !plan.Changes.Empty() {
2018-09-15 00:40:09 +02:00
t.Fatalf("expected no changes, got %#v\n", plan.Changes)
}
}
func TestContext2Plan_preventDestroy_countBad(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-count-bad")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, err := ctx.Plan()
expectedErr := "aws_instance.foo[1] has lifecycle.prevent_destroy"
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
if plan != nil {
t.Logf(legacyDiffComparisonString(plan.Changes))
}
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
}
}
func TestContext2Plan_preventDestroy_countGood(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-count-good")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"current": {Type: cty.String, Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = 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":"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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if plan.Changes.Empty() {
t.Fatalf("Expected non-empty plan, got %s", legacyDiffComparisonString(plan.Changes))
}
}
func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-count-good")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"current": {Type: cty.String, Optional: true},
"type": {Type: cty.String, Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = 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":"i-abc123","current":"0","type":"aws_instance"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if !plan.Changes.Empty() {
t.Fatalf("Expected empty plan, got %s", legacyDiffComparisonString(plan.Changes))
}
}
func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) {
m := testModule(t, "plan-prevent-destroy-good")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})
plan, diags := ctx.Plan()
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
if !strings.Contains(fmt.Sprintf("%s", diags.Err()), expectedErr) {
if plan != nil {
t.Logf(legacyDiffComparisonString(plan.Changes))
}
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, diags.Err())
}
}
func TestContext2Plan_provisionerCycle(t *testing.T) {
m := testModule(t, "plan-provisioner-cycle")
p := testProvider("aws")
p.DiffFn = testDiffFn
pr := testProvisioner()
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Provisioners: map[string]ProvisionerFactory{
"local-exec": testProvisionerFuncFixed(pr),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
t.Fatalf("succeeded; want errors")
}
}
func TestContext2Plan_computed(t *testing.T) {
m := testModule(t, "plan-computed")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
"compute": cty.StringVal("foo"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
configs/configschema: Introduce the NestingGroup mode for blocks In study of existing providers we've found a pattern we werent previously accounting for of using a nested block type to represent a group of arguments that relate to a particular feature that is always enabled but where it improves configuration readability to group all of its settings together in a nested block. The existing NestingSingle was not a good fit for this because it is designed under the assumption that the presence or absence of the block has some significance in enabling or disabling the relevant feature, and so for these always-active cases we'd generate a misleading plan where the settings for the feature appear totally absent, rather than showing the default values that will be selected. NestingGroup is, therefore, a slight variation of NestingSingle where presence vs. absence of the block is not distinguishable (it's never null) and instead its contents are treated as unset when the block is absent. This then in turn causes any default values associated with the nested arguments to be honored and displayed in the plan whenever the block is not explicitly configured. The current SDK cannot activate this mode, but that's okay because its "legacy type system" opt-out flag allows it to force a block to be processed in this way anyway. We're adding this now so that we can introduce the feature in a future SDK without causing a breaking change to the protocol, since the set of possible block nesting modes is not extensible.
2019-04-09 00:32:53 +02:00
func TestContext2Plan_blockNestingGroup(t *testing.T) {
m := testModule(t, "plan-block-nesting-group")
p := testProvider("test")
p.GetSchemaReturn = &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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
configs/configschema: Introduce the NestingGroup mode for blocks In study of existing providers we've found a pattern we werent previously accounting for of using a nested block type to represent a group of arguments that relate to a particular feature that is always enabled but where it improves configuration readability to group all of its settings together in a nested block. The existing NestingSingle was not a good fit for this because it is designed under the assumption that the presence or absence of the block has some significance in enabling or disabling the relevant feature, and so for these always-active cases we'd generate a misleading plan where the settings for the feature appear totally absent, rather than showing the default values that will be selected. NestingGroup is, therefore, a slight variation of NestingSingle where presence vs. absence of the block is not distinguishable (it's never null) and instead its contents are treated as unset when the block is absent. This then in turn causes any default values associated with the nested arguments to be honored and displayed in the plan whenever the block is not explicitly configured. The current SDK cannot activate this mode, but that's okay because its "legacy type system" opt-out flag allows it to force a block to be processed in this way anyway. We're adding this now so that we can introduce the feature in a future SDK without causing a breaking change to the protocol, since the set of possible block nesting modes is not extensible.
2019-04-09 00:32:53 +02:00
})
plan, diags := ctx.Plan()
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.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"num": {Type: cty.String, Optional: true},
"compute": {Type: cty.String, Optional: true},
"foo": {Type: cty.String, Computed: true},
},
},
},
DataSources: map[string]*configschema.Block{
"aws_vpc": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.DataSources["aws_vpc"]
ty := schema.ImpliedType()
if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil {
t.Fatalf("missing diff for aws_instance.foo")
}
rcs := plan.Changes.ResourceInstance(addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "aws_vpc",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
if rcs == nil {
t.Fatalf("missing diff for data.aws_vpc.bar")
}
2018-09-18 01:25:11 +02:00
rc, err := rcs.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-18 01:25:11 +02:00
checkVals(t,
cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String),
}),
rc.After,
)
}
func TestContext2Plan_computedInFunction(t *testing.T) {
m := testModule(t, "plan-computed-in-function")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"attr": {Type: cty.Number, Optional: true},
},
},
},
DataSources: map[string]*configschema.Block{
"aws_data_source": {
Attributes: map[string]*configschema.Attribute{
"computed": {Type: cty.List(cty.String), Computed: true},
},
},
},
}
p.DiffFn = testDiffFn
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
State: cty.ObjectVal(map[string]cty.Value{
"computed": cty.ListVal([]cty.Value{
cty.StringVal("foo"),
}),
}),
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
diags := ctx.Validate()
assertNoErrors(t, diags)
_, diags = ctx.Plan()
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.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"num": {Type: cty.String, Optional: true},
"compute": {Type: cty.String, Optional: true},
"foo": {Type: cty.String, Computed: true},
},
},
},
DataSources: map[string]*configschema.Block{
"aws_vpc": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
// make sure we created 3 "bar"s
for i := 0; i < 3; i++ {
addr := addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "aws_vpc",
Name: "bar",
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
t.Fatalf("missing changes for %s", addr)
}
}
}
func TestContext2Plan_localValueCount(t *testing.T) {
m := testModule(t, "plan-local-value-count")
p := testProvider("test")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
// make sure we created 3 "foo"s
for i := 0; i < 3; i++ {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
t.Fatalf("missing changes for %s", addr)
}
}
}
2018-09-18 01:25:11 +02:00
func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) {
m := testModule(t, "plan-data-resource-becomes-computed")
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
p := testProvider("aws")
2018-09-18 01:25:11 +02:00
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
2018-09-18 01:25:11 +02:00
"foo": {Type: cty.String, Optional: true},
"computed": {Type: cty.String, Computed: true},
},
},
},
DataSources: map[string]*configschema.Block{
2018-09-18 01:25:11 +02:00
"aws_data_source": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
2018-09-18 01:25:11 +02:00
"foo": {Type: cty.String, Optional: true},
},
},
},
}
2018-09-18 01:25:11 +02:00
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
fooVal := req.ProposedNewState.GetAttr("foo")
2018-09-18 01:25:11 +02:00
return providers.PlanResourceChangeResponse{
PlannedState: cty.ObjectVal(map[string]cty.Value{
"foo": fooVal,
"computed": cty.UnknownVal(cty.String),
}),
2018-09-18 01:25:11 +02:00
PlannedPrivate: req.PriorPrivate,
}
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.DataSources["aws_data_source"]
ty := schema.ImpliedType()
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
// This should not be called, because the configuration for the
// data resource contains an unknown value for "foo".
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")),
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
}
2018-09-18 01:25:11 +02:00
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"]`),
)
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
})
_, diags := ctx.Refresh()
if diags.HasErrors() {
t.Fatalf("unexpected errors during refresh: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors during plan: %s", diags.Err())
core: rerun resource validation before plan and apply In #7170 we found two scenarios where the type checking done during the `context.Validate()` graph walk was circumvented, and the subsequent assumption of type safety in the provider's `Diff()` implementation caused panics. Both scenarios have to do with interpolations that reference Computed values. The sentinel we use to indicate that a value is Computed does not carry any type information with it yet. That means that an incorrect reference to a list or a map in a string attribute can "sneak through" validation only to crop up... 1. ...during Plan for Data Source References 2. ...during Apply for Resource references In order to address this, we: * add high-level tests for each of these two scenarios in `provider/test` * add context-level tests for the same two scenarios in `terraform` (these tests proved _really_ tricky to write!) * place an `EvalValidateResource` just before `EvalDiff` and `EvalApply` to catch these errors * add some plumbing to `Plan()` and `Apply()` to return validation errors, which were previously only generated during `Validate()` * wrap unit-tests around `EvalValidateResource` * add an `IgnoreWarnings` option to `EvalValidateResource` to prevent active warnings from halting execution on the second-pass validation Eventually, we might be able to attach type information to Computed values, which would allow for these errors to be caught earlier. For now, this solution keeps us safe from panics and raises the proper errors to the user. Fixes #7170
2016-07-01 01:22:20 +02:00
}
2018-09-18 01:25:11 +02:00
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))
2018-09-18 01:25:11 +02:00
t.Fatalf("missing diff for data.aws_data_resource.foo")
}
2018-09-18 01:25:11 +02:00
rc, err := rcs.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-18 01:25:11 +02:00
// foo should now be unknown
foo := rc.After.GetAttr("foo")
if foo.IsKnown() {
t.Fatalf("foo should be unknown, got %#v", foo)
}
}
func TestContext2Plan_computedList(t *testing.T) {
m := testModule(t, "plan-computed-list")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Optional: true},
"foo": {Type: cty.String, Optional: true},
"num": {Type: cty.String, Optional: true},
"list": {Type: cty.List(cty.String), Computed: true},
},
},
},
}
p.DiffFn = func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) {
diff := &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{},
}
computedKeys := map[string]bool{}
for _, k := range c.ComputedKeys {
computedKeys[k] = true
}
compute, _ := c.Raw["compute"].(string)
if compute != "" {
diff.Attributes[compute] = &ResourceAttrDiff{
Old: "",
New: "",
NewComputed: true,
}
diff.Attributes["compute"] = &ResourceAttrDiff{
Old: "",
New: compute,
}
}
fooOld := s.Attributes["foo"]
fooNew, _ := c.Raw["foo"].(string)
if fooOld != fooNew {
diff.Attributes["foo"] = &ResourceAttrDiff{
Old: fooOld,
New: fooNew,
NewComputed: computedKeys["foo"],
}
}
numOld := s.Attributes["num"]
numNew, _ := c.Raw["num"].(string)
if numOld != numNew {
diff.Attributes["num"] = &ResourceAttrDiff{
Old: numOld,
New: numNew,
NewComputed: computedKeys["num"],
}
}
listOld := s.Attributes["list.#"]
if listOld == "" {
diff.Attributes["list.#"] = &ResourceAttrDiff{
Old: "",
New: "",
NewComputed: true,
}
}
return diff, nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
2018-11-19 23:59:38 +01:00
"list": cty.UnknownVal(cty.List(cty.String)),
"foo": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
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.#"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
// GH-8695. This tests that you can index into a computed list on a
// splatted resource.
func TestContext2Plan_computedMultiIndex(t *testing.T) {
m := testModule(t, "plan-computed-multi-index")
p := testProvider("aws")
p.DiffFn = testDiffFn
2018-09-18 01:25:11 +02:00
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Optional: true},
"foo": {Type: cty.List(cty.String), Optional: true},
"ip": {Type: cty.List(cty.String), Computed: true},
},
},
},
}
p.DiffFn = func(info *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) {
diff := &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{},
}
compute, _ := c.Raw["compute"].(string)
if compute != "" {
diff.Attributes[compute] = &ResourceAttrDiff{
Old: "",
New: "",
NewComputed: true,
}
diff.Attributes["compute"] = &ResourceAttrDiff{
Old: "",
New: compute,
}
}
fooOld := s.Attributes["foo"]
fooNew, _ := c.Raw["foo"].(string)
fooComputed := false
for _, k := range c.ComputedKeys {
if k == "foo" {
fooComputed = true
}
}
if fooNew != "" {
diff.Attributes["foo"] = &ResourceAttrDiff{
Old: fooOld,
New: fooNew,
NewComputed: fooComputed,
}
}
ipOld := s.Attributes["ip"]
ipComputed := ipOld == ""
diff.Attributes["ip"] = &ResourceAttrDiff{
Old: ipOld,
New: "",
NewComputed: ipComputed,
}
return diff, nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 3 {
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
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.#"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
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.#"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.bar[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"ip": cty.UnknownVal(cty.List(cty.String)),
2018-09-18 01:25:11 +02:00
"foo": cty.UnknownVal(cty.List(cty.String)),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_count(t *testing.T) {
m := testModule(t, "plan-count")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 6 {
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo,foo,foo,foo,foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[2]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[3]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[4]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countComputed(t *testing.T) {
m := testModule(t, "plan-count-computed")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, err := ctx.Plan()
if err == nil {
t.Fatal("should error")
}
}
func TestContext2Plan_countComputedModule(t *testing.T) {
m := testModule(t, "plan-count-computed-module")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, err := ctx.Plan()
expectedErr := `The "count" value depends on resource attributes`
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
t.Fatalf("expected err would contain %q\nerr: %s\n",
expectedErr, err)
}
}
func TestContext2Plan_countModuleStatic(t *testing.T) {
m := testModule(t, "plan-count-module-static")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-18 01:25:11 +02:00
if len(plan.Changes.Resources) != 3 {
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
}
2018-09-18 01:25:11 +02:00
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "module.child.aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "module.child.aws_instance.foo[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "module.child.aws_instance.foo[2]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) {
m := testModule(t, "plan-count-module-static-grandchild")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-18 01:25:11 +02:00
if len(plan.Changes.Resources) != 3 {
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
}
2018-09-18 01:25:11 +02:00
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "module.child.module.child.aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "module.child.module.child.aws_instance.foo[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "module.child.module.child.aws_instance.foo[2]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countIndex(t *testing.T) {
m := testModule(t, "plan-count-index")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("0"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.foo[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("1"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countVar(t *testing.T) {
m := testModule(t, "plan-count-var")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"instance_count": &InputValue{
Value: cty.StringVal("3"),
SourceType: ValueFromCaller,
},
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 4 {
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
}
2018-09-18 01:25:11 +02:00
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
2018-09-18 01:25:11 +02:00
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
2018-09-18 01:25:11 +02:00
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo,foo,foo"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.foo[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
case "aws_instance.foo[2]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
2018-09-19 00:44:51 +02:00
}), ric.After)
2018-09-18 01:25:11 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countZero(t *testing.T) {
m := testModule(t, "plan-count-zero")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.DynamicPseudoType, Optional: true},
},
},
},
}
2018-09-19 00:44:51 +02:00
// This schema contains a DynamicPseudoType, and therefore can't go through any shim functions
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
resp.PlannedState = req.ProposedNewState
resp.PlannedPrivate = req.PriorPrivate
return resp
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-18 01:25:11 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-18 01:25:11 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-18 01:25:11 +02:00
2018-09-19 00:44:51 +02:00
res := plan.Changes.Resources[0]
2018-09-18 01:25:11 +02:00
2018-09-19 00:44:51 +02:00
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)
2018-09-18 01:25:11 +02:00
}
expected := cty.TupleVal(nil)
2018-09-18 01:25:11 +02:00
foo := ric.After.GetAttr("foo")
if !cmp.Equal(expected, foo, valueComparer) {
t.Fatal(cmp.Diff(expected, foo, valueComparer))
2018-09-19 00:44:51 +02:00
}
}
func TestContext2Plan_countOneIndex(t *testing.T) {
m := testModule(t, "plan-count-one-index")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[0]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
m := testModule(t, "plan-count-dec")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 4 {
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
2018-09-19 00:44:51 +02:00
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should be unchanged", i)
}
case "aws_instance.foo[1]":
if res.Action != plans.Delete {
t.Fatalf("expected resource delete, got %s", res.Action)
}
case "aws_instance.foo[2]":
if res.Action != plans.Delete {
t.Fatalf("expected resource delete, got %s", res.Action)
}
default:
t.Fatal("unknown instance:", i)
}
}
expectedState := `aws_instance.foo:
2018-09-19 00:44:51 +02:00
ID = bar
provider = provider["registry.terraform.io/hashicorp/aws"]
2018-09-19 00:44:51 +02:00
foo = foo
type = aws_instance
aws_instance.foo.1:
ID = bar
provider = provider["registry.terraform.io/hashicorp/aws"]
2018-09-19 00:44:51 +02:00
aws_instance.foo.2:
ID = bar
provider = provider["registry.terraform.io/hashicorp/aws"]`
2018-09-19 00:44:51 +02:00
if ctx.State().String() != expectedState {
t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, ctx.State().String())
}
}
func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) {
m := testModule(t, "plan-count-inc")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 4 {
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[0]":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should be unchanged", i)
}
case "aws_instance.foo[1]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[2]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_countIncreaseFromOne(t *testing.T) {
m := testModule(t, "plan-count-inc")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 4 {
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[0]":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should be unchanged", i)
}
case "aws_instance.foo[1]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[2]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// https://github.com/PeoplePerHour/terraform/pull/11
//
// This tests a case where both a "resource" and "resource.0" are in
// the state file, which apparently is a reasonable backwards compatibility
// concern found in the above 3rd party repo.
func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) {
m := testModule(t, "plan-count-inc")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 5 {
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
case "aws_instance.foo[0]":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should be unchanged", i)
}
case "aws_instance.foo[1]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo[2]":
if res.Action != plans.Create {
t.Fatalf("expected resource create, got %s", res.Action)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// A common pattern in TF configs is to have a set of resources with the same
// count and to use count.index to create correspondences between them:
//
// foo_id = "${foo.bar.*.id[count.index]}"
//
// This test is for the situation where some instances already exist and the
// count is increased. In that case, we should see only the create diffs
// for the new instances and not any update diffs for the existing ones.
func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) {
m := testModule(t, "plan-count-splat-reference")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Optional: true},
"foo_name": {Type: cty.String, Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = 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","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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 6 {
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
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)
}
2018-09-19 00:44:51 +02:00
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)
}
2018-09-19 00:44:51 +02:00
default:
t.Fatal("unknown instance:", i)
}
}
}
2019-06-12 17:07:32 +02:00
func TestContext2Plan_forEach(t *testing.T) {
m := testModule(t, "plan-for-each")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
2019-06-12 17:07:32 +02:00
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 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")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"foo": {
Value: cty.UnknownVal(cty.String),
SourceType: ValueFromCLIArg,
},
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
2019-08-26 21:30:42 +02:00
// 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)
}
2019-06-12 17:07:32 +02:00
}
func TestContext2Plan_destroy(t *testing.T) {
m := testModule(t, "plan-destroy")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
2018-09-19 00:44:51 +02:00
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.one", "aws_instance.two":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleDestroy(t *testing.T) {
m := testModule(t, "plan-module-destroy")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "aws_instance.foo", "module.child.aws_instance.foo":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
// GH-1835
func TestContext2Plan_moduleDestroyCycle(t *testing.T) {
m := testModule(t, "plan-module-destroy-gh-1835")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "module.a_module.aws_instance.a", "module.b_module.aws_instance.b":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleDestroyMultivar(t *testing.T) {
m := testModule(t, "plan-module-destroy-multivar")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "module.child.aws_instance.foo[0]", "module.child.aws_instance.foo[1]":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_pathVar(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
m := testModule(t, "plan-path-var")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"cwd": {Type: cty.String, Optional: true},
"module": {Type: cty.String, Optional: true},
"root": {Type: cty.String, Optional: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("err: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
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")
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"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
p.DiffFn = func(
info *InstanceInfo,
s *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
if s.ID != "bar" {
return testDiffFn(info, s, c)
}
return &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"num": &ResourceAttrDiff{
Old: "2",
New: "3",
},
},
}, nil
}
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(3),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo":
if res.Action != plans.Update {
t.Fatalf("resource %s should be updated", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"num": cty.NumberIntVal(2),
}), ric.Before)
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"num": cty.NumberIntVal(3),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_hook(t *testing.T) {
m := testModule(t, "plan-good")
h := new(MockHook)
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Hooks: []Hook{h},
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if !h.PreDiffCalled {
t.Fatal("should be called")
}
if !h.PostDiffCalled {
t.Fatal("should be called")
}
}
func TestContext2Plan_closeProvider(t *testing.T) {
// this fixture only has an aliased provider located in the module, to make
// sure that the provier name contains a path more complex than
// "provider.aws".
m := testModule(t, "plan-close-module-provider")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if !p.CloseCalled {
t.Fatal("provider not closed")
}
}
func TestContext2Plan_orphan(t *testing.T) {
m := testModule(t, "plan-orphan")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.baz":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be removed", i)
}
case "aws_instance.foo":
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// This tests that configurations with UUIDs don't produce errors.
// For shadows, this would produce errors since a UUID changes every time.
func TestContext2Plan_shadowUuid(t *testing.T) {
m := testModule(t, "plan-shadow-uuid")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
}
func TestContext2Plan_state(t *testing.T) {
m := testModule(t, "plan-good")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if len(plan.Changes.Resources) < 2 {
t.Fatalf("bad: %#v", plan.Changes.Resources)
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo":
if res.Action != plans.Update {
t.Fatalf("resource %s should be updated", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"num": cty.NullVal(cty.Number),
"type": cty.NullVal(cty.String),
}), ric.Before)
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_taint(t *testing.T) {
m := testModule(t, "plan-taint")
p := testProvider("aws")
p.DiffFn = testDiffFn
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"}`),
},
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.bar":
if res.Action != plans.DeleteThenCreate {
2018-09-19 00:44:51 +02:00
t.Fatalf("resource %s should be replaced", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("2"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "aws_instance.foo":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should not be changed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
2018-05-22 01:20:32 +02:00
func TestContext2Plan_taintIgnoreChanges(t *testing.T) {
m := testModule(t, "plan-taint-ignore-changes")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
2018-09-19 00:44:51 +02:00
"id": {Type: cty.String, Computed: true},
"vars": {Type: cty.String, Optional: true},
2018-09-19 00:44:51 +02:00
"type": {Type: cty.String, Computed: true},
},
},
},
}
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.foo":
if res.Action != plans.DeleteThenCreate {
2018-09-19 00:44:51 +02:00
t.Fatalf("resource %s should be replaced", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("foo"),
"vars": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.Before)
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"vars": cty.StringVal("foo"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// Fails about 50% of the time before the fix for GH-4982, covers the fix.
func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
m := testModule(t, "plan-taint-interpolated-count")
p := testProvider("aws")
p.DiffFn = testDiffFn
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"}`),
},
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"]`),
)
for i := 0; i < 100; i++ {
2018-09-19 00:44:51 +02:00
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state.DeepCopy(),
2018-09-19 00:44:51 +02:00
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 3 {
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "aws_instance.foo[0]":
if res.Action != plans.DeleteThenCreate {
t.Fatalf("resource %s should be replaced, not %s", i, res.Action)
2018-09-19 00:44:51 +02:00
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
}), ric.Before)
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
}), ric.After)
case "aws_instance.foo[1]", "aws_instance.foo[2]":
if res.Action != plans.NoOp {
t.Fatalf("resource %s should not be changed", i)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
}
func TestContext2Plan_targeted(t *testing.T) {
m := testModule(t, "plan-targeted")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Resource(
addrs.ManagedResourceMode, "aws_instance", "foo",
),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "aws_instance.foo":
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", i)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"num": cty.NumberIntVal(2),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// Test that targeting a module properly plans any inputs that depend
// on another module.
func TestContext2Plan_targetedCrossModule(t *testing.T) {
m := testModule(t, "plan-targeted-cross-module")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("B", addrs.NoKey),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", ric.Addr)
}
switch i := ric.Addr.String(); i {
case "module.A.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.StringVal("bar"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
case "module.B.aws_instance.bar":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"foo": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_targetedModuleWithProvider(t *testing.T) {
m := testModule(t, "plan-targeted-module-with-provider")
p := testProvider("null")
p.GetSchemaReturn = &ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"key": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"null_resource": {
Attributes: map[string]*configschema.Attribute{},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["null_resource"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
res := plan.Changes.Resources[0]
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
if ric.Addr.String() != "module.child2.null_resource.foo" {
t.Fatalf("unexpcetd resource: %s", ric.Addr)
}
}
func TestContext2Plan_targetedOrphan(t *testing.T) {
m := testModule(t, "plan-targeted-orphan")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Resource(
addrs.ManagedResourceMode, "aws_instance", "orphan",
),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
switch i := ric.Addr.String(); i {
case "aws_instance.orphan":
if res.Action != plans.Delete {
t.Fatalf("resource %s should be destroyed", ric.Addr)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
// https://github.com/hashicorp/terraform/issues/2538
func TestContext2Plan_targetedModuleOrphan(t *testing.T) {
m := testModule(t, "plan-targeted-module-orphan")
p := testProvider("aws")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Destroy: true,
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
addrs.ManagedResourceMode, "aws_instance", "orphan",
),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
res := plan.Changes.Resources[0]
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
if ric.Addr.String() != "module.child.aws_instance.orphan" {
t.Fatalf("unexpected resource :%s", ric.Addr)
}
if res.Action != plans.Delete {
t.Fatalf("resource %s should be deleted", ric.Addr)
}
}
func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
m := testModule(t, "plan-targeted-module-untargeted-variable")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Resource(
addrs.ManagedResourceMode, "aws_instance", "blue",
),
addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", ric.Addr)
}
switch i := ric.Addr.String(); i {
case "aws_instance.blue":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
}), ric.After)
case "module.blue_mod.aws_instance.mod":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
"type": cty.StringVal("aws_instance"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
// ensure that outputs missing references due to targetting are removed from
// the graph.
func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
m := testModule(t, "plan-untargeted-resource-output")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource(
addrs.ManagedResourceMode, "aws_instance", "a",
),
},
})
_, diags := ctx.Plan()
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.DiffFn = testDiffFn
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
var expectedState []string
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\"}", id)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr(key).Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(attrs),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
expectedState = append(expectedState, fmt.Sprintf("%s:\n ID = %s\n", key, id))
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
Targets: []addrs.Targetable{
addrs.RootModuleInstance.ResourceInstance(
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1),
),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
2018-09-19 00:44:51 +02:00
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
if res.Action != plans.NoOp {
t.Fatalf("unexpected action %s for %s", res.Action, ric.Addr)
}
}
}
func TestContext2Plan_provider(t *testing.T) {
m := testModule(t, "plan-provider")
p := testProvider("aws")
p.DiffFn = testDiffFn
var value interface{}
p.ConfigureFn = func(c *ResourceConfig) error {
value, _ = c.Get("foo")
return nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"foo": &InputValue{
Value: cty.StringVal("bar"),
SourceType: ValueFromCaller,
},
},
})
if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}
if value != "bar" {
t.Fatalf("bad: %#v", value)
}
}
func TestContext2Plan_varListErr(t *testing.T) {
m := testModule(t, "plan-var-list-err")
p := testProvider("aws")
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, err := ctx.Plan()
if err == nil {
t.Fatal("should error")
}
}
func TestContext2Plan_ignoreChanges(t *testing.T) {
m := testModule(t, "plan-ignore-changes")
p := testProvider("aws")
p.DiffFn = testDiffFn
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"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"foo": &InputValue{
Value: cty.StringVal("ami-1234abcd"),
SourceType: ValueFromCaller,
},
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
2018-09-19 00:44:51 +02:00
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
res := plan.Changes.Resources[0]
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
2018-09-19 00:44:51 +02:00
if ric.Addr.String() != "aws_instance.foo" {
t.Fatalf("unexpected resource: %s", ric.Addr)
}
2018-09-19 00:44:51 +02:00
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"ami": cty.StringVal("ami-abcd1234"),
2018-09-19 00:44:51 +02:00
}), ric.After)
}
func TestContext2Plan_ignoreChangesWildcard(t *testing.T) {
m := testModule(t, "plan-ignore-changes-wildcard")
p := testProvider("aws")
p.DiffFn = 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","instance":"t2.micro"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Variables: InputValues{
"foo": &InputValue{
Value: cty.StringVal("ami-1234abcd"),
SourceType: ValueFromCaller,
},
"bar": &InputValue{
Value: cty.StringVal("t2.small"),
SourceType: ValueFromCaller,
},
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.NoOp {
t.Fatalf("unexpected resource diffs in root module: %s", spew.Sdump(plan.Changes.Resources))
}
}
}
func TestContext2Plan_ignoreChangesInMap(t *testing.T) {
p := testProvider("test")
p.GetSchemaReturn = &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,
}
}
p.DiffFn = testDiffFn
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(`{"tags":{"ignored":"from state","other":"from state"}}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
})
m := testModule(t, "plan-ignore-changes-in-map")
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: s,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["test_ignore_changes_map"]
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_moduleMapLiteral(t *testing.T) {
m := testModule(t, "plan-module-map-literal")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"meta": {Type: cty.Map(cty.String), Optional: true},
"tags": {Type: cty.Map(cty.String), Optional: true},
},
},
},
}
p.ApplyFn = testApplyFn
p.DiffFn = func(i *InstanceInfo, s *InstanceState, c *ResourceConfig) (*InstanceDiff, error) {
// Here we verify that both the populated and empty map literals made it
// through to the resource attributes
val, _ := c.Get("tags")
m, ok := val.(map[string]interface{})
if !ok {
t.Fatalf("Tags attr not map: %#v", val)
}
if m["foo"] != "bar" {
t.Fatalf("Bad value in tags attr: %#v", m)
}
{
val, _ := c.Get("meta")
m, ok := val.(map[string]interface{})
if !ok {
t.Fatalf("Meta attr not map: %#v", val)
}
if len(m) != 0 {
t.Fatalf("Meta attr not empty: %#v", val)
}
}
return nil, nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
}
func TestContext2Plan_computedValueInMap(t *testing.T) {
m := testModule(t, "plan-computed-value-in-map")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"looked_up": {Type: cty.String, Optional: true},
},
},
"aws_computed_source": {
Attributes: map[string]*configschema.Attribute{
"computed_read_only": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = func(info *InstanceInfo, state *InstanceState, c *ResourceConfig) (*InstanceDiff, error) {
switch info.Type {
case "aws_computed_source":
return &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"computed_read_only": &ResourceAttrDiff{
NewComputed: true,
},
},
}, nil
}
return testDiffFn(info, state, c)
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type]
ric, err := res.Decode(schema.ImpliedType())
if err != nil {
t.Fatal(err)
}
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", ric.Addr)
}
switch i := ric.Addr.String(); i {
case "aws_computed_source.intermediates":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"computed_read_only": cty.UnknownVal(cty.String),
}), ric.After)
case "module.test_mod.aws_instance.inner2":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"looked_up": cty.UnknownVal(cty.String),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_moduleVariableFromSplat(t *testing.T) {
m := testModule(t, "plan-module-variable-from-splat")
p := testProvider("aws")
p.DiffFn = testDiffFn
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"thing": {Type: cty.String, Optional: true},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if len(plan.Changes.Resources) != 4 {
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type]
ric, err := res.Decode(schema.ImpliedType())
if err != nil {
t.Fatal(err)
}
if res.Action != plans.Create {
t.Fatalf("resource %s should be created", ric.Addr)
}
switch i := ric.Addr.String(); i {
case "module.mod1.aws_instance.test[0]",
"module.mod1.aws_instance.test[1]",
"module.mod2.aws_instance.test[0]",
"module.mod2.aws_instance.test[1]":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"thing": cty.StringVal("doesnt"),
}), ric.After)
default:
t.Fatal("unknown instance:", i)
}
}
}
func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
m := testModule(t, "plan-cbd-depends-datasource")
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"num": {Type: cty.String, Optional: true},
"computed": {Type: cty.String, Optional: true, Computed: true},
},
},
},
DataSources: map[string]*configschema.Block{
"aws_vpc": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"foo": {Type: cty.Number, Optional: true},
},
},
},
}
2018-09-28 23:54:59 +02:00
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
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.GetSchemaReturn.DataSources[res.Addr.Resource.Resource.Type]
case addrs.ManagedResourceMode:
schema = p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type]
}
ric, err := res.Decode(schema.ImpliedType())
if err != nil {
t.Fatal(err)
}
seenAddrs[ric.Addr.String()] = struct{}{}
2018-09-28 23:54:59 +02:00
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]": struct{}{},
"aws_instance.foo[1]": struct{}{},
}
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.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.List(cty.String), Optional: true},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
changes := plan.Changes
rDiffA := changes.ResourceInstance(addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "a",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
rDiffB := changes.ResourceInstance(addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "b",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
if !cmp.Equal(rDiffA.After, rDiffB.After, valueComparer) {
t.Fatal(cmp.Diff(rDiffA.After, rDiffB.After, valueComparer))
}
}
// Make sure ignore-changes doesn't interfere with set/list/map diffs.
// If a resource was being replaced by a RequiresNew attribute that gets
// ignored, we need to filter the diff properly to properly update rather than
// replace.
func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) {
m := testModule(t, "plan-ignore-changes-with-flatmaps")
p := testProvider("aws")
p.DiffFn = testDiffFn
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"user_data": {Type: cty.String, Optional: true},
"require_new": {Type: cty.String, Optional: true},
// This test predates the 0.12 work to integrate cty and
// HCL, and so it was ported as-is where its expected
// test output was clearly expecting a list of maps here
// even though it is named "set".
"set": {Type: cty.List(cty.Map(cty.String)), Optional: true},
"lst": {Type: cty.List(cty.String), Optional: true},
},
},
},
}
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
res := plan.Changes.Resources[0]
schema := p.GetSchemaReturn.ResourceTypes[res.Addr.Resource.Resource.Type]
ric, err := res.Decode(schema.ImpliedType())
if err != nil {
t.Fatal(err)
}
if res.Action != plans.Update {
t.Fatalf("resource %s should be updated, got %s", ric.Addr, ric.Action)
}
if ric.Addr.String() != "aws_instance.foo" {
t.Fatalf("unknown resource: %s", ric.Addr)
}
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"lst": cty.ListVal([]cty.Value{
cty.StringVal("j"),
cty.StringVal("k"),
}),
"require_new": cty.StringVal(""),
"user_data": cty.StringVal("x"),
"set": cty.ListVal([]cty.Value{cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("1"),
"b": cty.StringVal("2"),
})}),
}), ric.After)
}
// TestContext2Plan_resourceNestedCount ensures resource sets that depend on
// the count of another resource set (ie: count of a data source that depends
// on another data source's instance count - data.x.foo.*.id) get properly
// normalized to the indexes they should be. This case comes up when there is
// an existing state (after an initial apply).
func TestContext2Plan_resourceNestedCount(t *testing.T) {
m := testModule(t, "nested-resource-count-plan")
p := testProvider("aws")
p.DiffFn = testDiffFn
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return providers.ReadResourceResponse{
NewState: req.PriorState,
}
}
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"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo1"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar0"}`),
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"}`),
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"}`),
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"}`),
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
State: state,
})
diags := ctx.Validate()
if diags.HasErrors() {
t.Fatalf("validate errors: %s", diags.Err())
}
_, diags = ctx.Refresh()
if diags.HasErrors() {
t.Fatalf("refresh errors: %s", diags.Err())
}
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("plan errors: %s", diags.Err())
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.NoOp {
t.Fatalf("resource %s should now change, plan returned %s", res.Addr, res.Action)
}
}
}
// Higher level test at TestResource_dataSourceListApplyPanic
func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) {
m := testModule(t, "plan-computed-attr-ref-type-mismatch")
p := testProvider("aws")
p.DiffFn = testDiffFn
2018-09-15 00:40:09 +02:00
p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
var diags tfdiags.Diagnostics
if req.TypeName == "aws_instance" {
amiVal := req.Config.GetAttr("ami")
if amiVal.Type() != cty.String {
diags = diags.Append(fmt.Errorf("Expected ami to be cty.String, got %#v", amiVal))
}
}
return providers.ValidateResourceTypeConfigResponse{
Diagnostics: diags,
}
}
p.DiffFn = func(
info *InstanceInfo,
state *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
switch info.Type {
case "aws_ami_list":
// Emulate a diff that says "we'll create this list and ids will be populated"
return &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"ids.#": &ResourceAttrDiff{NewComputed: true},
},
}, nil
case "aws_instance":
// If we get to the diff for instance, we should be able to assume types
ami, _ := c.Get("ami")
_ = ami.(string)
}
return nil, nil
}
p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) {
if info.Type != "aws_ami_list" {
t.Fatalf("Reached apply for unexpected resource type! %s", info.Type)
}
// Pretend like we make a thing and the computed list "ids" is populated
return &InstanceState{
ID: "someid",
Attributes: map[string]string{
"ids.#": "2",
"ids.0": "ami-abc123",
"ids.1": "ami-bcd345",
},
}, nil
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
t.Fatalf("Succeeded; want type mismatch error for 'ami' argument")
}
expected := `Inappropriate value for attribute "ami"`
if errStr := diags.Err().Error(); !strings.Contains(errStr, expected) {
t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected)
}
}
func TestContext2Plan_selfRef(t *testing.T) {
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
}
m := testModule(t, "plan-self-ref")
c := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
diags := c.Validate()
if diags.HasErrors() {
t.Fatalf("unexpected validation failure: %s", diags.Err())
}
_, diags = c.Plan()
if !diags.HasErrors() {
t.Fatalf("plan succeeded; want error")
}
gotErrStr := diags.Err().Error()
wantErrStr := "Self-referential block"
if !strings.Contains(gotErrStr, wantErrStr) {
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
}
}
func TestContext2Plan_selfRefMulti(t *testing.T) {
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
}
m := testModule(t, "plan-self-ref-multi")
c := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
diags := c.Validate()
if diags.HasErrors() {
t.Fatalf("unexpected validation failure: %s", diags.Err())
}
_, diags = c.Plan()
if !diags.HasErrors() {
t.Fatalf("plan succeeded; want error")
}
gotErrStr := diags.Err().Error()
wantErrStr := "Self-referential block"
if !strings.Contains(gotErrStr, wantErrStr) {
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
}
}
func TestContext2Plan_selfRefMultiAll(t *testing.T) {
p := testProvider("aws")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.List(cty.String), Optional: true},
},
},
},
}
m := testModule(t, "plan-self-ref-multi-all")
c := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
diags := c.Validate()
if diags.HasErrors() {
t.Fatalf("unexpected validation failure: %s", diags.Err())
}
_, diags = c.Plan()
if !diags.HasErrors() {
t.Fatalf("plan succeeded; want error")
}
gotErrStr := diags.Err().Error()
// The graph is checked for cycles before we can walk it, so we don't
// encounter the self-reference check.
//wantErrStr := "Self-referential block"
wantErrStr := "Cycle"
if !strings.Contains(gotErrStr, wantErrStr) {
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
}
}
2018-09-15 00:40:09 +02:00
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
// Should get this error:
// Unsupported attribute: This object does not have an attribute named "missing"
t.Fatal("succeeded; want errors")
}
gotErrStr := diags.Err().Error()
wantErrStr := "Unsupported attribute"
if !strings.Contains(gotErrStr, wantErrStr) {
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
}
}
func TestContext2Plan_invalidModuleOutput(t *testing.T) {
m := testModuleInline(t, map[string]string{
"child/main.tf": `
data "aws_data_source" "name" {}
output "out" {
value = "${data.aws_data_source.name.missing}"
}`,
"main.tf": `
module "child" {
source = "./child"
}
resource "aws_instance" "foo" {
foo = "${module.child.out}"
}`,
})
p := testProvider("aws")
p.ReadDataSourceResponse = providers.ReadDataSourceResponse{
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("data_id"),
"foo": cty.StringVal("foo"),
}),
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
// Should get this error:
// Unsupported attribute: This object does not have an attribute named "missing"
t.Fatal("succeeded; want errors")
}
gotErrStr := diags.Err().Error()
wantErrStr := "Unsupported attribute"
if !strings.Contains(gotErrStr, wantErrStr) {
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
}
}
func TestContext2Plan_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.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) {
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if !diags.HasErrors() {
// Should get this error:
// Unsupported attribute: This object does not have an attribute named "missing"
t.Fatal("succeeded; want errors")
}
}
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"foo": cty.StringVal("foo"),
}), 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("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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "module.child.aws_instance.foo":
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"foo": cty.StringVal("foo"),
}), 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("sensitive"); !got.Equal(want) {
t.Errorf("unexpected value for mark\n got: %#v\nwant: %#v", got, want)
}
default:
t.Fatal("unknown instance:", i)
}
}
}
2018-09-15 00:40:09 +02:00
func checkVals(t *testing.T, expected, got cty.Value) {
t.Helper()
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
}
}
func objectVal(t *testing.T, schema *configschema.Block, m map[string]cty.Value) cty.Value {
t.Helper()
v, err := schema.CoerceValue(
cty.ObjectVal(m),
)
if err != nil {
t.Fatal(err)
}
return v
}
func TestContext2Plan_requiredModuleOutput(t *testing.T) {
m := testModule(t, "plan-required-output")
p := testProvider("test")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"required": {Type: cty.String, Required: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["test_resource"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
var expected cty.Value
switch i := ric.Addr.String(); i {
case "test_resource.root":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"required": cty.UnknownVal(cty.String),
})
case "module.mod.test_resource.for_output":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"required": cty.StringVal("val"),
})
default:
t.Fatal("unknown instance:", i)
}
checkVals(t, expected, ric.After)
})
}
}
func TestContext2Plan_requiredModuleObject(t *testing.T) {
m := testModule(t, "plan-required-whole-mod")
p := testProvider("test")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"required": {Type: cty.String, Required: true},
},
},
},
}
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["test_resource"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 2 {
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
if res.Action != plans.Create {
t.Fatalf("expected resource creation, got %s", res.Action)
}
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
var expected cty.Value
switch i := ric.Addr.String(); i {
case "test_resource.root":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"required": cty.UnknownVal(cty.String),
})
case "module.mod.test_resource.for_output":
expected = objectVal(t, schema, map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"required": cty.StringVal("val"),
})
default:
t.Fatal("unknown instance:", i)
}
checkVals(t, expected, ric.After)
})
}
}
2020-03-27 17:42:00 +01:00
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"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
2020-03-27 17:42:00 +01:00
)
state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent(
mustResourceInstanceAddr("aws_instance.foo").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"child"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
2020-03-27 17:42:00 +01:00
)
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
2020-03-27 17:42:00 +01:00
State: state,
})
plan, diags := ctx.Plan()
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,
2020-03-27 17:42:00 +01:00
}
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)
2020-03-27 17:42:00 +01:00
}
delete(expected, res.Addr.String())
}
for res, action := range expected {
t.Errorf("missing %s change for %s", action, res)
2020-03-27 17:42:00 +01:00
}
}
2020-04-10 22:52:47 +02:00
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")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
}
2020-06-10 22:11:05 +02:00
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")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: targets,
})
plan, diags := ctx.Plan()
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")
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: targets,
})
plan, diags := ctx.Plan()
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")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
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.GetSchemaReturn = &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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: state,
})
plan, diags := ctx.Plan()
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")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: state,
})
_, diags := ctx.Plan()
assertNoErrors(t, diags)
}
func TestContext2Plan_targetedModuleInstance(t *testing.T) {
m := testModule(t, "plan-targeted")
p := testProvider("aws")
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
schema := p.GetSchemaReturn.ResourceTypes["aws_instance"]
ty := schema.ImpliedType()
if len(plan.Changes.Resources) != 1 {
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
}
for _, res := range plan.Changes.Resources {
ric, err := res.Decode(ty)
if err != nil {
t.Fatal(err)
}
switch i := ric.Addr.String(); i {
case "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.StringVal("aws_instance"),
}), 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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
d := plan.State.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.State)
}
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_dataReferencesResource(t *testing.T) {
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
assertNoErrors(t, diags)
}
2020-09-23 17:06:56 +02:00
func TestContext2Plan_skipRefresh(t *testing.T) {
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = 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"}`),
Dependencies: []addrs.ConfigResource{},
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: state,
SkipRefresh: true,
})
plan, diags := ctx.Plan()
2020-09-23 17:06:56 +02:00
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)
}
}
2020-09-23 17:06:56 +02:00
}
func TestContext2Plan_dataInModuleDependsOn(t *testing.T) {
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
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{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
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")
}
}