mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
e0ecd2ebb3
Signed-off-by: RLRabinowitz <rlrabinowitz2@gmail.com>
6935 lines
199 KiB
Go
6935 lines
199 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
|
|
"github.com/opentofu/opentofu/internal/lang/marks"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/provisioners"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
func TestContext2Plan_basic(t *testing.T) {
|
|
m := testModule(t, "plan-good")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if l := len(plan.Changes.Resources); l < 2 {
|
|
t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources))
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
for _, r := range plan.Changes.Resources {
|
|
ric, err := r.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
foo := ric.After.GetAttr("foo").AsString()
|
|
if foo != "2" {
|
|
t.Fatalf("incorrect plan for 'bar': %#v", ric.After)
|
|
}
|
|
case "aws_instance.foo":
|
|
num, _ := ric.After.GetAttr("num").AsBigFloat().Int64()
|
|
if num != 2 {
|
|
t.Fatalf("incorrect plan for 'foo': %#v", ric.After)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
|
|
if !p.ValidateProviderConfigCalled {
|
|
t.Fatal("provider config was not checked before Configure")
|
|
}
|
|
|
|
}
|
|
|
|
func TestContext2Plan_createBefore_deposed(t *testing.T) {
|
|
m := testModule(t, "plan-cbd")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
states.DeposedKey("00000001"),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
// the state should still show one deposed
|
|
expectedState := strings.TrimSpace(`
|
|
aws_instance.foo: (1 deposed)
|
|
ID = baz
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
type = aws_instance
|
|
Deposed ID 1 = foo`)
|
|
|
|
if plan.PriorState.String() != expectedState {
|
|
t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, plan.PriorState.String())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
type InstanceGen struct {
|
|
Addr string
|
|
DeposedKey states.DeposedKey
|
|
}
|
|
want := map[InstanceGen]bool{
|
|
{
|
|
Addr: "aws_instance.foo",
|
|
}: true,
|
|
{
|
|
Addr: "aws_instance.foo",
|
|
DeposedKey: states.DeposedKey("00000001"),
|
|
}: true,
|
|
}
|
|
got := make(map[InstanceGen]bool)
|
|
changes := make(map[InstanceGen]*plans.ResourceInstanceChangeSrc)
|
|
|
|
for _, change := range plan.Changes.Resources {
|
|
k := InstanceGen{
|
|
Addr: change.Addr.String(),
|
|
DeposedKey: change.DeposedKey,
|
|
}
|
|
got[k] = true
|
|
changes[k] = change
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("wrong resource instance object changes in plan\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(want))
|
|
}
|
|
|
|
{
|
|
ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got, want := ric.Action, plans.NoOp; got != want {
|
|
t.Errorf("current object change action is %s; want %s", got, want)
|
|
}
|
|
|
|
// the existing instance should only have an unchanged id
|
|
expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("baz"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
|
|
{
|
|
ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got, want := ric.Action, plans.Delete; got != want {
|
|
t.Errorf("deposed object change action is %s; want %s", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_createBefore_maintainRoot(t *testing.T) {
|
|
m := testModule(t, "plan-cbd-maintain-root")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !plan.PriorState.Empty() {
|
|
t.Fatal("expected empty prior state, got:", plan.PriorState)
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Error("expected 4 resource in plan, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
// these should all be creates
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("unexpected action %s for %s", res.Action, res.Addr.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_emptyDiff(t *testing.T) {
|
|
m := testModule(t, "plan-empty")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !plan.PriorState.Empty() {
|
|
t.Fatal("expected empty state, got:", plan.PriorState)
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
actions := map[string]plans.Action{}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
actions[res.Addr.String()] = res.Action
|
|
}
|
|
|
|
expected := map[string]plans.Action{
|
|
"aws_instance.foo": plans.Create,
|
|
"aws_instance.bar": plans.Create,
|
|
}
|
|
if !cmp.Equal(expected, actions) {
|
|
t.Fatal(cmp.Diff(expected, actions))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_escapedVar(t *testing.T) {
|
|
m := testModule(t, "plan-escaped-var")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Error("expected 1 resource in plan, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar-${baz}"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
|
|
func TestContext2Plan_minimal(t *testing.T) {
|
|
m := testModule(t, "plan-empty")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !plan.PriorState.Empty() {
|
|
t.Fatal("expected empty state, got:", plan.PriorState)
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
actions := map[string]plans.Action{}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
actions[res.Addr.String()] = res.Action
|
|
}
|
|
|
|
expected := map[string]plans.Action{
|
|
"aws_instance.foo": plans.Create,
|
|
"aws_instance.bar": plans.Create,
|
|
}
|
|
if !cmp.Equal(expected, actions) {
|
|
t.Fatal(cmp.Diff(expected, actions))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_modules(t *testing.T) {
|
|
m := testModule(t, "plan-modules")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
expectFoo := objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
|
|
expectNum := objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
expected = expectFoo
|
|
case "aws_instance.foo":
|
|
expected = expectNum
|
|
case "module.child.aws_instance.foo":
|
|
expected = expectNum
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
}
|
|
func TestContext2Plan_moduleExpand(t *testing.T) {
|
|
// Test a smattering of plan expansion behavior
|
|
m := testModule(t, "plan-modules-expand")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
expected := map[string]struct{}{
|
|
`aws_instance.foo["a"]`: {},
|
|
`module.count_child[1].aws_instance.foo[0]`: {},
|
|
`module.count_child[1].aws_instance.foo[1]`: {},
|
|
`module.count_child[0].aws_instance.foo[0]`: {},
|
|
`module.count_child[0].aws_instance.foo[1]`: {},
|
|
`module.for_each_child["a"].aws_instance.foo[1]`: {},
|
|
`module.for_each_child["a"].aws_instance.foo[0]`: {},
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, ok := expected[ric.Addr.String()]
|
|
if !ok {
|
|
t.Fatal("unexpected resource:", ric.Addr.String())
|
|
}
|
|
delete(expected, ric.Addr.String())
|
|
}
|
|
for addr := range expected {
|
|
t.Error("missing resource", addr)
|
|
}
|
|
}
|
|
|
|
// GH-1475
|
|
func TestContext2Plan_moduleCycle(t *testing.T) {
|
|
m := testModule(t, "plan-module-cycle")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"some_input": {Type: cty.String, Optional: true},
|
|
"type": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.b":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
case "aws_instance.c":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"some_input": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleDeadlock(t *testing.T) {
|
|
testCheckDeadlock(t, func() {
|
|
m := testModule(t, "plan-module-deadlock")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.aws_instance.foo[0]":
|
|
case "module.child.aws_instance.foo[1]":
|
|
case "module.child.aws_instance.foo[2]":
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestContext2Plan_moduleInput(t *testing.T) {
|
|
m := testModule(t, "plan-module-input")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
case "module.child.aws_instance.foo":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("42"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleInputComputed(t *testing.T) {
|
|
m := testModule(t, "plan-module-input-computed")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
"compute": cty.StringVal("foo"),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleInputFromVar(t *testing.T) {
|
|
m := testModule(t, "plan-module-input-var")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("52"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("52"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleMultiVar(t *testing.T) {
|
|
m := testModule(t, "plan-module-multi-var")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
"baz": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 5 {
|
|
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.parent[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.parent[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.bar[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"baz": cty.StringVal("baz"),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.bar[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"baz": cty.StringVal("baz"),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("baz,baz"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleOrphans(t *testing.T) {
|
|
m := testModule(t, "plan-modules-remove")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
|
|
expectedState := `<no state>
|
|
module.child:
|
|
aws_instance.foo:
|
|
ID = baz
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]`
|
|
|
|
if plan.PriorState.String() != expectedState {
|
|
t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, plan.PriorState.String())
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/3114
|
|
func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) {
|
|
m := testModule(t, "plan-modules-remove-provisioners")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
pr := testProvisioner()
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.top").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"top","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
child1 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child1", addrs.NoKey))
|
|
child1.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
child2 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child2", addrs.NoKey))
|
|
child2.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Error("expected 3 planned resources, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.parent.module.child1.aws_instance.foo":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("expected resource Delete, got %s", res.Action)
|
|
}
|
|
case "module.parent.module.child2.aws_instance.foo":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("expected resource Delete, got %s", res.Action)
|
|
}
|
|
case "aws_instance.top":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("expected no changes, got %s", res.Action)
|
|
}
|
|
default:
|
|
t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After))
|
|
}
|
|
}
|
|
|
|
expectedState := `aws_instance.top:
|
|
ID = top
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
type = aws_instance
|
|
|
|
module.parent.child1:
|
|
aws_instance.foo:
|
|
ID = baz
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
type = aws_instance
|
|
module.parent.child2:
|
|
aws_instance.foo:
|
|
ID = baz
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
type = aws_instance`
|
|
|
|
if expectedState != plan.PriorState.String() {
|
|
t.Fatalf("\nexpect state:\n%s\n\ngot state:\n%s\n", expectedState, plan.PriorState.String())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleProviderInherit(t *testing.T) {
|
|
var l sync.Mutex
|
|
var calls []string
|
|
|
|
m := testModule(t, "plan-module-provider-inherit")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"from": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"from": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
from := req.Config.GetAttr("from")
|
|
if from.IsNull() || from.AsString() != "root" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root"))
|
|
}
|
|
|
|
return
|
|
}
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
from := req.Config.GetAttr("from").AsString()
|
|
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
calls = append(calls, from)
|
|
return testDiffFn(req)
|
|
}
|
|
return p, nil
|
|
},
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := calls
|
|
sort.Strings(actual)
|
|
expected := []string{"child", "root"}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
// This tests (for GH-11282) that deeply nested modules properly inherit
|
|
// configuration.
|
|
func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) {
|
|
var l sync.Mutex
|
|
|
|
m := testModule(t, "plan-module-provider-inherit-deep")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
|
|
var from string
|
|
p := testProvider("aws")
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"from": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{},
|
|
},
|
|
},
|
|
})
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
v := req.Config.GetAttr("from")
|
|
if v.IsNull() || v.AsString() != "root" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root"))
|
|
}
|
|
from = v.AsString()
|
|
|
|
return
|
|
}
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
if from != "root" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("bad resource"))
|
|
return
|
|
}
|
|
|
|
return testDiffFn(req)
|
|
}
|
|
return p, nil
|
|
},
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) {
|
|
var l sync.Mutex
|
|
var calls []string
|
|
|
|
m := testModule(t, "plan-module-provider-defaults-var")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"to": {Type: cty.String, Optional: true},
|
|
"from": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"from": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
var buf bytes.Buffer
|
|
from := req.Config.GetAttr("from")
|
|
if !from.IsNull() {
|
|
buf.WriteString(from.AsString() + "\n")
|
|
}
|
|
to := req.Config.GetAttr("to")
|
|
if !to.IsNull() {
|
|
buf.WriteString(to.AsString() + "\n")
|
|
}
|
|
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
calls = append(calls, buf.String())
|
|
return
|
|
}
|
|
|
|
return p, nil
|
|
},
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("root"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := []string{
|
|
"child\nchild\n",
|
|
"root\n",
|
|
}
|
|
sort.Strings(calls)
|
|
if !reflect.DeepEqual(calls, expected) {
|
|
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleProviderVar(t *testing.T) {
|
|
m := testModule(t, "plan-module-provider-var")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.aws_instance.test":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"value": cty.StringVal("hello"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVar(t *testing.T) {
|
|
m := testModule(t, "plan-module-var")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
case "module.child.aws_instance.foo":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
})
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) {
|
|
m := testModule(t, "plan-module-wrong-var-type")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("succeeded; want errors")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) {
|
|
m := testModule(t, "plan-module-wrong-var-type-nested")
|
|
p := testProvider("null")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("succeeded; want errors")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) {
|
|
m := testModule(t, "plan-module-var-with-default-value")
|
|
p := testProvider("null")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVarComputed(t *testing.T) {
|
|
m := testModule(t, "plan-module-var-computed")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
"compute": cty.StringVal("foo"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_bad(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-bad")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, err := ctx.Plan(m, state, DefaultPlanOpts)
|
|
|
|
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
|
if plan != nil {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_good(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-good")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !plan.Changes.Empty() {
|
|
t.Fatalf("expected no changes, got %#v\n", plan.Changes)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_countBad(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-count-bad")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, err := ctx.Plan(m, state, DefaultPlanOpts)
|
|
|
|
expectedErr := "aws_instance.foo[1] has lifecycle.prevent_destroy"
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
|
if plan != nil {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_countGood(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-count-good")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"current": {Type: cty.String, Optional: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if plan.Changes.Empty() {
|
|
t.Fatalf("Expected non-empty plan, got %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-count-good")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"current": {Type: cty.String, Optional: true},
|
|
"type": {Type: cty.String, Optional: true, Computed: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123","current":"0","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !plan.Changes.Empty() {
|
|
t.Fatalf("Expected empty plan, got %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) {
|
|
m := testModule(t, "plan-prevent-destroy-good")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
|
|
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
|
|
if !strings.Contains(fmt.Sprintf("%s", diags.Err()), expectedErr) {
|
|
if plan != nil {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
t.Fatalf("expected diagnostics would contain %q\nactual diags: %s", expectedErr, diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_provisionerCycle(t *testing.T) {
|
|
m := testModule(t, "plan-provisioner-cycle")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"local-exec": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("succeeded; want errors")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computed(t *testing.T) {
|
|
m := testModule(t, "plan-computed")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
"compute": cty.StringVal("foo"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_blockNestingGroup(t *testing.T) {
|
|
m := testModule(t, "plan-block-nesting-group")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test": {
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"blah": {
|
|
Nesting: configschema.NestingGroup,
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {Type: cty.String, Required: true},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if got, want := 1, len(plan.Changes.Resources); got != want {
|
|
t.Fatalf("wrong number of planned resource changes %d; want %d\n%s", got, want, spew.Sdump(plan.Changes.Resources))
|
|
}
|
|
|
|
if !p.PlanResourceChangeCalled {
|
|
t.Fatalf("PlanResourceChange was not called at all")
|
|
}
|
|
|
|
got := p.PlanResourceChangeRequest
|
|
want := providers.PlanResourceChangeRequest{
|
|
TypeName: "test",
|
|
|
|
// Because block type "blah" is defined as NestingGroup, we get a non-null
|
|
// value for it with null nested attributes, rather than the "blah" object
|
|
// itself being null, when there's no "blah" block in the config at all.
|
|
//
|
|
// This represents the situation where the remote service _always_ creates
|
|
// a single "blah", regardless of whether the block is present, but when
|
|
// the block _is_ present the user can override some aspects of it. The
|
|
// absense of the block means "use the defaults", in that case.
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"blah": cty.ObjectVal(map[string]cty.Value{
|
|
"baz": cty.NullVal(cty.String),
|
|
}),
|
|
}),
|
|
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
|
|
"blah": cty.ObjectVal(map[string]cty.Value{
|
|
"baz": cty.NullVal(cty.String),
|
|
}),
|
|
}),
|
|
}
|
|
if !cmp.Equal(got, want, valueTrans) {
|
|
t.Errorf("wrong PlanResourceChange request\n%s", cmp.Diff(got, want, valueTrans))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computedDataResource(t *testing.T) {
|
|
m := testModule(t, "plan-computed-data-resource")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"num": {Type: cty.String, Optional: true},
|
|
"compute": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil {
|
|
t.Fatalf("missing diff for aws_instance.foo")
|
|
}
|
|
rcs := plan.Changes.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "aws_vpc",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
if rcs == nil {
|
|
t.Fatalf("missing diff for data.aws_vpc.bar")
|
|
}
|
|
|
|
rc, err := rcs.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkVals(t,
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.String),
|
|
}),
|
|
rc.After,
|
|
)
|
|
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want {
|
|
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computedInFunction(t *testing.T) {
|
|
m := testModule(t, "plan-computed-in-function")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"attr": {Type: cty.Number, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"computed": {Type: cty.List(cty.String), Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"computed": cty.ListVal([]cty.Value{
|
|
cty.StringVal("foo"),
|
|
}),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := ctx.Validate(m)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ReadDataSourceCalled {
|
|
t.Fatalf("ReadDataSource was not called on provider during plan; should've been called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computedDataCountResource(t *testing.T) {
|
|
m := testModule(t, "plan-computed-data-count")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"num": {Type: cty.String, Optional: true},
|
|
"compute": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
// make sure we created 3 "bar"s
|
|
for i := 0; i < 3; i++ {
|
|
addr := addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "aws_vpc",
|
|
Name: "bar",
|
|
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
|
|
|
|
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
|
|
t.Fatalf("missing changes for %s", addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_localValueCount(t *testing.T) {
|
|
m := testModule(t, "plan-local-value-count")
|
|
p := testProvider("test")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
// make sure we created 3 "foo"s
|
|
for i := 0; i < 3; i++ {
|
|
addr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
|
|
|
|
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
|
|
t.Fatalf("missing changes for %s", addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) {
|
|
m := testModule(t, "plan-data-resource-becomes-computed")
|
|
p := testProvider("aws")
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
"computed": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
fooVal := req.ProposedNewState.GetAttr("foo")
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": fooVal,
|
|
"computed": cty.UnknownVal(cty.String),
|
|
}),
|
|
PlannedPrivate: req.PriorPrivate,
|
|
}
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.DataSources["aws_data_source"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
// This should not be called, because the configuration for the
|
|
// data resource contains an unknown value for "foo".
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("data.aws_data_source.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123","foo":"baz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors during plan: %s", diags.Err())
|
|
}
|
|
|
|
rcs := plan.Changes.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "aws_data_source",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
if rcs == nil {
|
|
t.Logf("full changeset: %s", spew.Sdump(plan.Changes))
|
|
t.Fatalf("missing diff for data.aws_data_resource.foo")
|
|
}
|
|
|
|
rc, err := rcs.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseConfigUnknown; got != want {
|
|
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
|
|
// foo should now be unknown
|
|
foo := rc.After.GetAttr("foo")
|
|
if foo.IsKnown() {
|
|
t.Fatalf("foo should be unknown, got %#v", foo)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computedList(t *testing.T) {
|
|
m := testModule(t, "plan-computed-list")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"compute": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
"num": {Type: cty.String, Optional: true},
|
|
"list": {Type: cty.List(cty.String), Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"list": cty.UnknownVal(cty.List(cty.String)),
|
|
"num": cty.NumberIntVal(2),
|
|
"compute": cty.StringVal("list.#"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GH-8695. This tests that you can index into a computed list on a
|
|
// splatted resource.
|
|
func TestContext2Plan_computedMultiIndex(t *testing.T) {
|
|
m := testModule(t, "plan-computed-multi-index")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"compute": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
"ip": {Type: cty.List(cty.String), Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"ip": cty.UnknownVal(cty.List(cty.String)),
|
|
"foo": cty.NullVal(cty.List(cty.String)),
|
|
"compute": cty.StringVal("ip.#"),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"ip": cty.UnknownVal(cty.List(cty.String)),
|
|
"foo": cty.NullVal(cty.List(cty.String)),
|
|
"compute": cty.StringVal("ip.#"),
|
|
}), ric.After)
|
|
case "aws_instance.bar[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.List(cty.String)),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_count(t *testing.T) {
|
|
m := testModule(t, "plan-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 6 {
|
|
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo,foo,foo,foo,foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[2]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[3]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[4]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countComputed(t *testing.T) {
|
|
m := testModule(t, "plan-count-computed")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countComputedModule(t *testing.T) {
|
|
m := testModule(t, "plan-count-computed-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
|
|
expectedErr := `The "count" value depends on resource attributes`
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
|
t.Fatalf("expected err would contain %q\nerr: %s\n",
|
|
expectedErr, err)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countModuleStatic(t *testing.T) {
|
|
m := testModule(t, "plan-count-module-static")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.aws_instance.foo[2]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) {
|
|
m := testModule(t, "plan-count-module-static-grandchild")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.module.child.aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.module.child.aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.child.module.child.aws_instance.foo[2]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countIndex(t *testing.T) {
|
|
m := testModule(t, "plan-count-index")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("0"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("1"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countVar(t *testing.T) {
|
|
m := testModule(t, "plan-count-var")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"instance_count": &InputValue{
|
|
Value: cty.StringVal("3"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo,foo,foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[2]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countZero(t *testing.T) {
|
|
m := testModule(t, "plan-count-zero")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.DynamicPseudoType, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// This schema contains a DynamicPseudoType, and therefore can't go through any shim functions
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
resp.PlannedPrivate = req.PriorPrivate
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := cty.TupleVal(nil)
|
|
|
|
foo := ric.After.GetAttr("foo")
|
|
|
|
if !cmp.Equal(expected, foo, valueComparer) {
|
|
t.Fatal(cmp.Diff(expected, foo, valueComparer))
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countOneIndex(t *testing.T) {
|
|
m := testModule(t, "plan-count-one-index")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[0]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
|
m := testModule(t, "plan-count-dec")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
}
|
|
case "aws_instance.foo[1]":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
}
|
|
case "aws_instance.foo[2]":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
|
|
expectedState := `aws_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
aws_instance.foo.1:
|
|
ID = bar
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
|
aws_instance.foo.2:
|
|
ID = bar
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"]`
|
|
|
|
if plan.PriorState.String() != expectedState {
|
|
t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, plan.PriorState.String())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) {
|
|
m := testModule(t, "plan-count-inc")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance","foo":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[0]":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
}
|
|
case "aws_instance.foo[1]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[2]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_countIncreaseFromOne(t *testing.T) {
|
|
m := testModule(t, "plan-count-inc")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[0]":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
}
|
|
case "aws_instance.foo[1]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[2]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://github.com/PeoplePerHour/terraform/pull/11
|
|
//
|
|
// This tests a case where both a "resource" and "resource.0" are in
|
|
// the state file, which apparently is a reasonable backwards compatibility
|
|
// concern found in the above 3rd party repo.
|
|
func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) {
|
|
m := testModule(t, "plan-count-inc")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/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.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 5 {
|
|
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
case "aws_instance.foo[0]":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
}
|
|
case "aws_instance.foo[1]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[2]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A common pattern in TF configs is to have a set of resources with the same
|
|
// count and to use count.index to create correspondences between them:
|
|
//
|
|
// foo_id = "${foo.bar.*.id[count.index]}"
|
|
//
|
|
// This test is for the situation where some instances already exist and the
|
|
// count is increased. In that case, we should see only the create diffs
|
|
// for the new instances and not any update diffs for the existing ones.
|
|
func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) {
|
|
m := testModule(t, "plan-count-splat-reference")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"name": {Type: cty.String, Optional: true},
|
|
"foo_name": {Type: cty.String, Optional: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","name":"foo 0"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/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.opentofu.org/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.opentofu.org/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.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 6 {
|
|
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar[0]", "aws_instance.bar[1]", "aws_instance.foo[0]", "aws_instance.foo[1]":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
}
|
|
case "aws_instance.bar[2]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
// The instance ID changed, so just check that the name updated
|
|
if ric.After.GetAttr("foo_name") != cty.StringVal("foo 2") {
|
|
t.Fatalf("resource %s attr \"foo_name\" should be changed", i)
|
|
}
|
|
case "aws_instance.foo[2]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
}
|
|
// The instance ID changed, so just check that the name updated
|
|
if ric.After.GetAttr("name") != cty.StringVal("foo 2") {
|
|
t.Fatalf("resource %s attr \"name\" should be changed", i)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_forEach(t *testing.T) {
|
|
m := testModule(t, "plan-for-each")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 8 {
|
|
t.Fatal("expected 8 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
_, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_forEachUnknownValue(t *testing.T) {
|
|
// This module has a variable defined, but it's value is unknown. We
|
|
// expect this to produce an error, but not to panic.
|
|
m := testModule(t, "plan-for-each-unknown-value")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": {
|
|
Value: cty.UnknownVal(cty.String),
|
|
SourceType: ValueFromCLIArg,
|
|
},
|
|
},
|
|
})
|
|
if !diags.HasErrors() {
|
|
// Should get this error:
|
|
// Invalid for_each argument: The "for_each" value depends on resource attributes that cannot be determined until apply...
|
|
t.Fatal("succeeded; want errors")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
wantErrStr := "Invalid for_each argument"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
|
|
// We should have a diagnostic that is marked as being caused by unknown
|
|
// values.
|
|
for _, diag := range diags {
|
|
if tfdiags.DiagnosticCausedByUnknown(diag) {
|
|
return // don't fall through to the error below
|
|
}
|
|
}
|
|
t.Fatalf("no diagnostic is marked as being caused by unknown\n%s", diags.Err().Error())
|
|
}
|
|
|
|
func TestContext2Plan_destroy(t *testing.T) {
|
|
m := testModule(t, "plan-destroy")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.one").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.two").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.one", "aws_instance.two":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleDestroy(t *testing.T) {
|
|
m := testModule(t, "plan-module-destroy")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/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.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo", "module.child.aws_instance.foo":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GH-1835
|
|
func TestContext2Plan_moduleDestroyCycle(t *testing.T) {
|
|
m := testModule(t, "plan-module-destroy-gh-1835")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
aModule := state.EnsureModule(addrs.RootModuleInstance.Child("a_module", addrs.NoKey))
|
|
aModule.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/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.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.a_module.aws_instance.a", "module.b_module.aws_instance.b":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleDestroyMultivar(t *testing.T) {
|
|
m := testModule(t, "plan-module-destroy-multivar")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar0"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar1"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.aws_instance.foo[0]", "module.child.aws_instance.foo[1]":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_pathVar(t *testing.T) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
m := testModule(t, "plan-path-var")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"cwd": {Type: cty.String, Optional: true},
|
|
"module": {Type: cty.String, Optional: true},
|
|
"root": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"cwd": cty.StringVal(cwd + "/barpath"),
|
|
"module": cty.StringVal(m.Module.SourceDir + "/foopath"),
|
|
"root": cty.StringVal(m.Module.SourceDir + "/barpath"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_diffVar(t *testing.T) {
|
|
m := testModule(t, "plan-diffvar")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(3),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Update {
|
|
t.Fatalf("resource %s should be updated", i)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.Before)
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"num": cty.NumberIntVal(3),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_hook(t *testing.T) {
|
|
m := testModule(t, "plan-good")
|
|
h := new(MockHook)
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !h.PreDiffCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostDiffCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_closeProvider(t *testing.T) {
|
|
// this fixture only has an aliased provider located in the module, to make
|
|
// sure that the provier name contains a path more complex than
|
|
// "provider.aws".
|
|
m := testModule(t, "plan-close-module-provider")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if !p.CloseCalled {
|
|
t.Fatal("provider not closed")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_orphan(t *testing.T) {
|
|
m := testModule(t, "plan-orphan")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.baz").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.baz":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be removed", i)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// This tests that configurations with UUIDs don't produce errors.
|
|
// For shadows, this would produce errors since a UUID changes every time.
|
|
func TestContext2Plan_shadowUuid(t *testing.T) {
|
|
m := testModule(t, "plan-shadow-uuid")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_state(t *testing.T) {
|
|
m := testModule(t, "plan-good")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) < 2 {
|
|
t.Fatalf("bad: %#v", plan.Changes.Resources)
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Update {
|
|
t.Fatalf("resource %s should be updated", i)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"num": cty.NullVal(cty.Number),
|
|
"type": cty.NullVal(cty.String),
|
|
}), ric.Before)
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_requiresReplace(t *testing.T) {
|
|
m := testModule(t, "plan-requires-replace")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
|
Provider: providers.Schema{
|
|
Block: &configschema.Block{},
|
|
},
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_thing": {
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"v": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
RequiresReplace: []cty.Path{
|
|
cty.GetAttrPath("v"),
|
|
},
|
|
}
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_thing.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"v":"hello"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if got, want := len(plan.Changes.Resources), 1; got != want {
|
|
t.Fatalf("got %d changes; want %d", got, want)
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
t.Run(res.Addr.String(), func(t *testing.T) {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "test_thing.foo":
|
|
if got, want := ric.Action, plans.DeleteThenCreate; got != want {
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseCannotUpdate; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"v": cty.StringVal("goodbye"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatalf("unexpected resource instance %s", i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_taint(t *testing.T) {
|
|
m := testModule(t, "plan-taint")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
t.Run(res.Addr.String(), func(t *testing.T) {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.bar":
|
|
if got, want := res.Action, plans.DeleteThenCreate; got != want {
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("2"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo":
|
|
if got, want := res.Action, plans.NoOp; got != want {
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := res.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_taintIgnoreChanges(t *testing.T) {
|
|
m := testModule(t, "plan-taint-ignore-changes")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"vars": {Type: cty.String, Optional: true},
|
|
"type": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"foo","vars":"foo","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo":
|
|
if got, want := res.Action, plans.DeleteThenCreate; got != want {
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
"vars": cty.StringVal("foo"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.Before)
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"vars": cty.StringVal("foo"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fails about 50% of the time before the fix for GH-4982, covers the fix.
|
|
func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
|
|
m := testModule(t, "plan-taint-interpolated-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state.DeepCopy(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo[0]":
|
|
if got, want := ric.Action, plans.DeleteThenCreate; got != want {
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.Before)
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]", "aws_instance.foo[2]":
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should not be changed", i)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targeted(t *testing.T) {
|
|
m := testModule(t, "plan-targeted")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that targeting a module properly plans any inputs that depend
|
|
// on another module.
|
|
func TestContext2Plan_targetedCrossModule(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-cross-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("B", addrs.NoKey),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
}
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.A.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.StringVal("bar"),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.B.aws_instance.bar":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"foo": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targetedModuleWithProvider(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-module-with-provider")
|
|
p := testProvider("null")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"key": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"null_resource": {
|
|
Attributes: map[string]*configschema.Attribute{},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ric.Addr.String() != "module.child2.null_resource.foo" {
|
|
t.Fatalf("unexpcetd resource: %s", ric.Addr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targetedOrphan(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-orphan")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.orphan").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-789xyz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "orphan",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.orphan":
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be destroyed", ric.Addr)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/2538
|
|
func TestContext2Plan_targetedModuleOrphan(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-module-orphan")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.orphan").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-789xyz"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "orphan",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ric.Addr.String() != "module.child.aws_instance.orphan" {
|
|
t.Fatalf("unexpected resource :%s", ric.Addr)
|
|
}
|
|
if res.Action != plans.Delete {
|
|
t.Fatalf("resource %s should be deleted", ric.Addr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-module-untargeted-variable")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "blue",
|
|
),
|
|
addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
}
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.blue":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.blue_mod.aws_instance.mod":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"value": cty.UnknownVal(cty.String),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure that outputs missing references due to targetting are removed from
|
|
// the graph.
|
|
func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
|
m := testModule(t, "plan-untargeted-resource-output")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "a",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags)
|
|
}
|
|
if len(diags) != 1 {
|
|
t.Fatalf("got %d diagnostics; want 1", diags)
|
|
}
|
|
if got, want := diags[0].Severity(), tfdiags.Warning; got != want {
|
|
t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
|
|
}
|
|
if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want {
|
|
t.Errorf("wrong diagnostic summary %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/4515
|
|
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-over-ten")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
for i := 0; i < 13; i++ {
|
|
key := fmt.Sprintf("aws_instance.foo[%d]", i)
|
|
id := fmt.Sprintf("i-abc%d", i)
|
|
attrs := fmt.Sprintf(`{"id":"%s","type":"aws_instance"}`, id)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(key).Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(attrs),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1),
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("unexpected action %s for %s", res.Action, ric.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_provider(t *testing.T) {
|
|
m := testModule(t, "plan-provider")
|
|
p := testProvider("aws")
|
|
|
|
var value interface{}
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
value = req.Config.GetAttr("foo").AsString()
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
opts := &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("bar"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
}
|
|
|
|
if _, err := ctx.Plan(m, states.NewState(), opts); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value != "bar" {
|
|
t.Fatalf("bad: %#v", value)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_varListErr(t *testing.T) {
|
|
m := testModule(t, "plan-var-list-err")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
|
|
if err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_ignoreChanges(t *testing.T) {
|
|
m := testModule(t, "plan-ignore-changes")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
t.Fatalf("unexpected resource: %s", ric.Addr)
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"ami": cty.StringVal("ami-abcd1234"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.After)
|
|
}
|
|
|
|
func TestContext2Plan_ignoreChangesWildcard(t *testing.T) {
|
|
m := testModule(t, "plan-ignore-changes-wildcard")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
// computed attributes should not be set in config
|
|
id := req.Config.GetAttr("id")
|
|
if !id.IsNull() {
|
|
t.Error("computed id set in plan config")
|
|
}
|
|
|
|
foo := req.Config.GetAttr("foo")
|
|
if foo.IsNull() {
|
|
t.Error(`missing "foo" during plan, was set to "bar" in state and config`)
|
|
}
|
|
|
|
return testDiffFn(req)
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","instance":"t2.micro","type":"aws_instance","foo":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
"bar": &InputValue{
|
|
Value: cty.StringVal("t2.small"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("unexpected resource diffs in root module: %s", spew.Sdump(plan.Changes.Resources))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_ignoreChangesInMap(t *testing.T) {
|
|
p := testProvider("test")
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_ignore_changes_map": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"tags": {Type: cty.Map(cty.String), Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
|
|
s := states.BuildState(func(ss *states.SyncState) {
|
|
ss.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_ignore_changes_map",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo","tags":{"ignored":"from state","other":"from state"},"type":"aws_instance"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
m := testModule(t, "plan-ignore-changes-in-map")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, s, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if got, want := len(plan.Changes.Resources), 1; got != want {
|
|
t.Fatalf("wrong number of changes %d; want %d", got, want)
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Action != plans.Update {
|
|
t.Fatalf("resource %s should be updated, got %s", ric.Addr, res.Action)
|
|
}
|
|
|
|
if got, want := ric.Addr.String(), "test_ignore_changes_map.foo"; got != want {
|
|
t.Fatalf("unexpected resource address %s; want %s", got, want)
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"tags": cty.MapVal(map[string]cty.Value{
|
|
"ignored": cty.StringVal("from state"),
|
|
"other": cty.StringVal("from config"),
|
|
}),
|
|
}), ric.After)
|
|
}
|
|
|
|
func TestContext2Plan_ignoreChangesSensitive(t *testing.T) {
|
|
m := testModule(t, "plan-ignore-changes-sensitive")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
t.Fatalf("unexpected resource: %s", ric.Addr)
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"ami": cty.StringVal("ami-abcd1234"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}), ric.After)
|
|
}
|
|
|
|
func TestContext2Plan_moduleMapLiteral(t *testing.T) {
|
|
m := testModule(t, "plan-module-map-literal")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"meta": {Type: cty.Map(cty.String), Optional: true},
|
|
"tags": {Type: cty.Map(cty.String), Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
s := req.ProposedNewState.AsValueMap()
|
|
m := s["tags"].AsValueMap()
|
|
|
|
if m["foo"].AsString() != "bar" {
|
|
t.Fatalf("Bad value in tags attr: %#v", m)
|
|
}
|
|
|
|
meta := s["meta"].AsValueMap()
|
|
if len(meta) != 0 {
|
|
t.Fatalf("Meta attr not empty: %#v", meta)
|
|
}
|
|
return testDiffFn(req)
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_computedValueInMap(t *testing.T) {
|
|
m := testModule(t, "plan-computed-value-in-map")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"looked_up": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
"aws_computed_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"computed_read_only": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp = testDiffFn(req)
|
|
|
|
if req.TypeName != "aws_computed_source" {
|
|
return
|
|
}
|
|
|
|
planned := resp.PlannedState.AsValueMap()
|
|
planned["computed_read_only"] = cty.UnknownVal(cty.String)
|
|
resp.PlannedState = cty.ObjectVal(planned)
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_computed_source.intermediates":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"computed_read_only": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
case "module.test_mod.aws_instance.inner2":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"looked_up": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleVariableFromSplat(t *testing.T) {
|
|
m := testModule(t, "plan-module-variable-from-splat")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"thing": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.mod1.aws_instance.test[0]",
|
|
"module.mod1.aws_instance.test[1]",
|
|
"module.mod2.aws_instance.test[0]",
|
|
"module.mod2.aws_instance.test[1]":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"thing": cty.StringVal("doesnt"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
|
|
m := testModule(t, "plan-cbd-depends-datasource")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"num": {Type: cty.String, Optional: true},
|
|
"computed": {Type: cty.String, Optional: true, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.Number, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
computedVal := req.ProposedNewState.GetAttr("computed")
|
|
if computedVal.IsNull() {
|
|
computedVal = cty.UnknownVal(cty.String)
|
|
}
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"num": req.ProposedNewState.GetAttr("num"),
|
|
"computed": computedVal,
|
|
}),
|
|
}
|
|
}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
cfg := req.Config.AsValueMap()
|
|
cfg["id"] = cty.StringVal("data_id")
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(cfg),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
seenAddrs := make(map[string]struct{})
|
|
for _, res := range plan.Changes.Resources {
|
|
var schema *configschema.Block
|
|
switch res.Addr.Resource.Resource.Mode {
|
|
case addrs.DataResourceMode:
|
|
schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type].Block
|
|
case addrs.ManagedResourceMode:
|
|
schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
|
}
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
seenAddrs[ric.Addr.String()] = struct{}{}
|
|
|
|
t.Run(ric.Addr.String(), func(t *testing.T) {
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo[0]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"num": cty.StringVal("2"),
|
|
"computed": cty.StringVal("data_id"),
|
|
}), ric.After)
|
|
case "aws_instance.foo[1]":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"num": cty.StringVal("2"),
|
|
"computed": cty.StringVal("data_id"),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
})
|
|
}
|
|
|
|
wantAddrs := map[string]struct{}{
|
|
"aws_instance.foo[0]": {},
|
|
"aws_instance.foo[1]": {},
|
|
}
|
|
if !cmp.Equal(seenAddrs, wantAddrs) {
|
|
t.Errorf("incorrect addresses in changeset:\n%s", cmp.Diff(wantAddrs, seenAddrs))
|
|
}
|
|
}
|
|
|
|
// interpolated lists need to be stored in the original order.
|
|
func TestContext2Plan_listOrder(t *testing.T) {
|
|
m := testModule(t, "plan-list-order")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
changes := plan.Changes
|
|
rDiffA := changes.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
rDiffB := changes.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
|
|
if !cmp.Equal(rDiffA.After, rDiffB.After, valueComparer) {
|
|
t.Fatal(cmp.Diff(rDiffA.After, rDiffB.After, valueComparer))
|
|
}
|
|
}
|
|
|
|
// Make sure ignore-changes doesn't interfere with set/list/map diffs.
|
|
// If a resource was being replaced by a RequiresNew attribute that gets
|
|
// ignored, we need to filter the diff properly to properly update rather than
|
|
// replace.
|
|
func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) {
|
|
m := testModule(t, "plan-ignore-changes-with-flatmaps")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"user_data": {Type: cty.String, Optional: true},
|
|
"require_new": {Type: cty.String, Optional: true},
|
|
|
|
// This test predates the 0.12 work to integrate cty and
|
|
// HCL, and so it was ported as-is where its expected
|
|
// test output was clearly expecting a list of maps here
|
|
// even though it is named "set".
|
|
"set": {Type: cty.List(cty.Map(cty.String)), Optional: true},
|
|
"lst": {Type: cty.List(cty.String), Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{
|
|
"user_data":"x","require_new":"",
|
|
"set":[{"a":"1"}],
|
|
"lst":["j"]
|
|
}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
res := plan.Changes.Resources[0]
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if res.Action != plans.Update {
|
|
t.Fatalf("resource %s should be updated, got %s", ric.Addr, ric.Action)
|
|
}
|
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
t.Fatalf("unknown resource: %s", ric.Addr)
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"lst": cty.ListVal([]cty.Value{
|
|
cty.StringVal("j"),
|
|
cty.StringVal("k"),
|
|
}),
|
|
"require_new": cty.StringVal(""),
|
|
"user_data": cty.StringVal("x"),
|
|
"set": cty.ListVal([]cty.Value{cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("1"),
|
|
"b": cty.StringVal("2"),
|
|
})}),
|
|
}), ric.After)
|
|
}
|
|
|
|
// TestContext2Plan_resourceNestedCount ensures resource sets that depend on
|
|
// the count of another resource set (ie: count of a data source that depends
|
|
// on another data source's instance count - data.x.foo.*.id) get properly
|
|
// normalized to the indexes they should be. This case comes up when there is
|
|
// an existing state (after an initial apply).
|
|
func TestContext2Plan_resourceNestedCount(t *testing.T) {
|
|
m := testModule(t, "nested-resource-count-plan")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo0","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo1","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar0","type":"aws_instance"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar1","type":"aws_instance"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.baz[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz0","type":"aws_instance"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.baz[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz1","type":"aws_instance"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := ctx.Validate(m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("validate errors: %s", diags.Err())
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan errors: %s", diags.Err())
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("resource %s should not change, plan returned %s", res.Addr, res.Action)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Higher level test at TestResource_dataSourceListApplyPanic
|
|
func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) {
|
|
m := testModule(t, "plan-computed-attr-ref-type-mismatch")
|
|
p := testProvider("aws")
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
|
|
var diags tfdiags.Diagnostics
|
|
if req.TypeName == "aws_instance" {
|
|
amiVal := req.Config.GetAttr("ami")
|
|
if amiVal.Type() != cty.String {
|
|
diags = diags.Append(fmt.Errorf("Expected ami to be cty.String, got %#v", amiVal))
|
|
}
|
|
}
|
|
return providers.ValidateResourceConfigResponse{
|
|
Diagnostics: diags,
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
if req.TypeName != "aws_ami_list" {
|
|
t.Fatalf("Reached apply for unexpected resource type! %s", req.TypeName)
|
|
}
|
|
// Pretend like we make a thing and the computed list "ids" is populated
|
|
s := req.PlannedState.AsValueMap()
|
|
s["id"] = cty.StringVal("someid")
|
|
s["ids"] = cty.ListVal([]cty.Value{
|
|
cty.StringVal("ami-abc123"),
|
|
cty.StringVal("ami-bcd345"),
|
|
})
|
|
|
|
resp.NewState = cty.ObjectVal(s)
|
|
return
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("Succeeded; want type mismatch error for 'ami' argument")
|
|
}
|
|
|
|
expected := `Inappropriate value for attribute "ami"`
|
|
if errStr := diags.Err().Error(); !strings.Contains(errStr, expected) {
|
|
t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_selfRef(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "plan-self-ref")
|
|
c := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := c.Validate(m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan succeeded; want error")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
wantErrStr := "Self-referential block"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_selfRefMulti(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "plan-self-ref-multi")
|
|
c := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := c.Validate(m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan succeeded; want error")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
wantErrStr := "Self-referential block"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_selfRefMultiAll(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "plan-self-ref-multi-all")
|
|
c := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := c.Validate(m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan succeeded; want error")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
// The graph is checked for cycles before we can walk it, so we don't
|
|
// encounter the self-reference check.
|
|
// wantErrStr := "Self-referential block"
|
|
wantErrStr := "Cycle"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_invalidOutput(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
data "aws_data_source" "name" {}
|
|
|
|
output "out" {
|
|
value = data.aws_data_source.name.missing
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data_id"),
|
|
"foo": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
// Should get this error:
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
t.Fatal("succeeded; want errors")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
wantErrStr := "Unsupported attribute"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_invalidModuleOutput(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"child/main.tf": `
|
|
data "aws_data_source" "name" {}
|
|
|
|
output "out" {
|
|
value = "${data.aws_data_source.name.missing}"
|
|
}`,
|
|
"main.tf": `
|
|
module "child" {
|
|
source = "./child"
|
|
}
|
|
|
|
resource "aws_instance" "foo" {
|
|
foo = "${module.child.out}"
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data_id"),
|
|
"foo": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
// Should get this error:
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
t.Fatal("succeeded; want errors")
|
|
}
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
wantErrStr := "Unsupported attribute"
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_variableValidation(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "x" {
|
|
default = "bar"
|
|
}
|
|
|
|
resource "aws_instance" "foo" {
|
|
foo = var.x
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
|
|
foo := req.Config.GetAttr("foo").AsString()
|
|
if foo == "bar" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar"))
|
|
}
|
|
return
|
|
}
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
// Should get this error:
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
t.Fatal("succeeded; want errors")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_variableSensitivity(t *testing.T) {
|
|
m := testModule(t, "plan-variable-sensitivity")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"foo": cty.StringVal("foo").Mark(marks.Sensitive),
|
|
}), ric.After)
|
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
|
}
|
|
if len(res.ChangeSrc.AfterValMarks) != 1 {
|
|
t.Errorf("unexpected AfterValMarks: %#v", res.ChangeSrc.AfterValMarks)
|
|
continue
|
|
}
|
|
pvm := res.ChangeSrc.AfterValMarks[0]
|
|
if got, want := pvm.Path, cty.GetAttrPath("foo"); !got.Equals(want) {
|
|
t.Errorf("unexpected path for mark\n got: %#v\nwant: %#v", got, want)
|
|
}
|
|
if got, want := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !got.Equal(want) {
|
|
t.Errorf("unexpected value for mark\n got: %#v\nwant: %#v", got, want)
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_variableSensitivityModule(t *testing.T) {
|
|
m := testModule(t, "plan-variable-sensitivity-module")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"sensitive_var": {Value: cty.NilVal},
|
|
"another_var": &InputValue{
|
|
Value: cty.StringVal("boop"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.child.aws_instance.foo":
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"foo": cty.StringVal("foo").Mark(marks.Sensitive),
|
|
"value": cty.StringVal("boop").Mark(marks.Sensitive),
|
|
}), ric.After)
|
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
|
}
|
|
if len(res.ChangeSrc.AfterValMarks) != 2 {
|
|
t.Errorf("expected AfterValMarks to contain two elements: %#v", res.ChangeSrc.AfterValMarks)
|
|
continue
|
|
}
|
|
// validate that the after marks have "foo" and "value"
|
|
contains := func(pvmSlice []cty.PathValueMarks, stepName string) bool {
|
|
for _, pvm := range pvmSlice {
|
|
if pvm.Path.Equals(cty.GetAttrPath(stepName)) {
|
|
if pvm.Marks.Equal(cty.NewValueMarks(marks.Sensitive)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if !contains(res.ChangeSrc.AfterValMarks, "foo") {
|
|
t.Error("unexpected AfterValMarks to contain \"foo\" with sensitive mark")
|
|
}
|
|
if !contains(res.ChangeSrc.AfterValMarks, "value") {
|
|
t.Error("unexpected AfterValMarks to contain \"value\" with sensitive mark")
|
|
}
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkVals(t *testing.T, expected, got cty.Value) {
|
|
t.Helper()
|
|
// The GoStringer format seems to result in the closest thing to a useful
|
|
// diff for values with marks.
|
|
// TODO: if we want to continue using cmp.Diff on cty.Values, we should
|
|
// make a transformer that creates a more comparable structure.
|
|
valueTrans := cmp.Transformer("gostring", func(v cty.Value) string {
|
|
return fmt.Sprintf("%#v\n", v)
|
|
})
|
|
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func objectVal(t *testing.T, schema *configschema.Block, m map[string]cty.Value) cty.Value {
|
|
t.Helper()
|
|
v, err := schema.CoerceValue(
|
|
cty.ObjectVal(m),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestContext2Plan_requiredModuleOutput(t *testing.T) {
|
|
m := testModule(t, "plan-required-output")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_resource": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"required": {Type: cty.String, Required: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
switch i := ric.Addr.String(); i {
|
|
case "test_resource.root":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"required": cty.UnknownVal(cty.String),
|
|
})
|
|
case "module.mod.test_resource.for_output":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"required": cty.StringVal("val"),
|
|
})
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_requiredModuleObject(t *testing.T) {
|
|
m := testModule(t, "plan-required-whole-mod")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_resource": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"required": {Type: cty.String, Required: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
}
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var expected cty.Value
|
|
switch i := ric.Addr.String(); i {
|
|
case "test_resource.root":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"required": cty.UnknownVal(cty.String),
|
|
})
|
|
case "module.mod.test_resource.for_output":
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"required": cty.StringVal("val"),
|
|
})
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
|
|
checkVals(t, expected, ric.After)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_expandOrphan(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
count = 1
|
|
source = "./mod"
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
state := states.NewState()
|
|
state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(0))).SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
expected := map[string]plans.Action{
|
|
`module.mod[1].aws_instance.foo`: plans.Delete,
|
|
`module.mod[0].aws_instance.foo`: plans.NoOp,
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
want := expected[res.Addr.String()]
|
|
if res.Action != want {
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
|
}
|
|
delete(expected, res.Addr.String())
|
|
}
|
|
|
|
for res, action := range expected {
|
|
t.Errorf("missing %s change for %s", action, res)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_indexInVar(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "a" {
|
|
count = 1
|
|
source = "./mod"
|
|
in = "test"
|
|
}
|
|
|
|
module "b" {
|
|
count = 1
|
|
source = "./mod"
|
|
in = length(module.a)
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
foo = var.in
|
|
}
|
|
|
|
variable "in" {
|
|
}
|
|
|
|
output"out" {
|
|
value = aws_instance.foo.id
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targetExpandedAddress(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
count = 3
|
|
source = "./mod"
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
count = 2
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
|
|
targets := []addrs.Targetable{}
|
|
target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]")
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
targets = append(targets, target.Subject)
|
|
|
|
target, diags = addrs.ParseTargetStr("module.mod[2]")
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
targets = append(targets, target.Subject)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: targets,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
expected := map[string]plans.Action{
|
|
// the single targeted mod[1] instances
|
|
`module.mod[1].aws_instance.foo[0]`: plans.Create,
|
|
// the whole mode[2]
|
|
`module.mod[2].aws_instance.foo[0]`: plans.Create,
|
|
`module.mod[2].aws_instance.foo[1]`: plans.Create,
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
want := expected[res.Addr.String()]
|
|
if res.Action != want {
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
|
}
|
|
delete(expected, res.Addr.String())
|
|
}
|
|
|
|
for res, action := range expected {
|
|
t.Errorf("missing %s change for %s", action, res)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
count = 3
|
|
source = "./mod"
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
|
|
target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo")
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
targets := []addrs.Targetable{target.Subject}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: targets,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
expected := map[string]plans.Action{
|
|
// the single targeted mod[1] instance
|
|
`module.mod[1].aws_instance.foo`: plans.Create,
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
want := expected[res.Addr.String()]
|
|
if res.Action != want {
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
|
}
|
|
delete(expected, res.Addr.String())
|
|
}
|
|
|
|
for res, action := range expected {
|
|
t.Errorf("missing %s change for %s", action, res)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_moduleRefIndex(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
for_each = {
|
|
a = "thing"
|
|
}
|
|
in = null
|
|
source = "./mod"
|
|
}
|
|
|
|
module "single" {
|
|
source = "./mod"
|
|
in = module.mod["a"]
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
variable "in" {
|
|
}
|
|
|
|
output "out" {
|
|
value = "foo"
|
|
}
|
|
|
|
resource "aws_instance" "foo" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_noChangeDataPlan(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
data "test_data_source" "foo" {}
|
|
`,
|
|
})
|
|
|
|
p := new(MockProvider)
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
DataSources: map[string]*configschema.Block{
|
|
"test_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data_id"),
|
|
"foo": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("data.test_data_source.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"data_id", "foo":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action)
|
|
}
|
|
}
|
|
}
|
|
|
|
// for_each can reference a resource with 0 instances
|
|
func TestContext2Plan_scaleInForEach(t *testing.T) {
|
|
p := testProvider("test")
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
locals {
|
|
m = {}
|
|
}
|
|
|
|
resource "test_instance" "a" {
|
|
for_each = local.m
|
|
}
|
|
|
|
resource "test_instance" "b" {
|
|
for_each = test_instance.a
|
|
}
|
|
`})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a0"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/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.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
t.Run("test_instance.a[0]", func(t *testing.T) {
|
|
instAddr := mustResourceInstanceAddr("test_instance.a[0]")
|
|
change := plan.Changes.ResourceInstance(instAddr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", instAddr)
|
|
}
|
|
if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
})
|
|
t.Run("test_instance.b", func(t *testing.T) {
|
|
instAddr := mustResourceInstanceAddr("test_instance.b")
|
|
change := plan.Changes.ResourceInstance(instAddr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", instAddr)
|
|
}
|
|
if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestContext2Plan_targetedModuleInstance(t *testing.T) {
|
|
m := testModule(t, "plan-targeted")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
ric, err := res.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
case "module.mod[0].aws_instance.foo":
|
|
if res.Action != plans.Create {
|
|
t.Fatalf("resource %s should be created", i)
|
|
}
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"num": cty.NumberIntVal(2),
|
|
"type": cty.UnknownVal(cty.String),
|
|
}), ric.After)
|
|
default:
|
|
t.Fatal("unknown instance:", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataRefreshedInPlan(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
data "test_data_source" "d" {
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("this"),
|
|
"foo": cty.NullVal(cty.String),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
d := plan.PriorState.ResourceInstance(mustResourceInstanceAddr("data.test_data_source.d"))
|
|
if d == nil || d.Current == nil {
|
|
t.Fatal("data.test_data_source.d not found in state:", plan.PriorState)
|
|
}
|
|
|
|
if d.Current.Status != states.ObjectReady {
|
|
t.Fatal("expected data.test_data_source.d to be fully read in refreshed state, got status", d.Current.Status)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataReferencesResourceDirectly(t *testing.T) {
|
|
// When a data resource refers to a managed resource _directly_, any
|
|
// pending change for the managed resource will cause the data resource
|
|
// to be deferred to the apply step.
|
|
// See also TestContext2Plan_dataReferencesResourceIndirectly for the
|
|
// other case, where the reference is indirect.
|
|
|
|
p := testProvider("test")
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source should not be read"))
|
|
return resp
|
|
}
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
locals {
|
|
x = "value"
|
|
}
|
|
|
|
resource "test_resource" "a" {
|
|
value = local.x
|
|
}
|
|
|
|
// test_resource.a.value can be resolved during plan, but the reference implies
|
|
// that the data source should wait until the resource is created.
|
|
data "test_data_source" "d" {
|
|
foo = test_resource.a.value
|
|
}
|
|
|
|
// ensure referencing an indexed instance that has not yet created will also
|
|
// delay reading the data source
|
|
resource "test_resource" "b" {
|
|
count = 2
|
|
value = local.x
|
|
}
|
|
|
|
data "test_data_source" "e" {
|
|
foo = test_resource.b[0].value
|
|
}
|
|
`})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
rc := plan.Changes.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "test_data_source",
|
|
Name: "d",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
if rc != nil {
|
|
if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseDependencyPending; got != want {
|
|
t.Errorf("wrong ActionReason\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
} else {
|
|
t.Error("no change for test_data_source.e")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataReferencesResourceIndirectly(t *testing.T) {
|
|
// When a data resource refers to a managed resource indirectly, pending
|
|
// changes for the managed resource _do not_ cause the data resource to
|
|
// be deferred to apply. This is a pragmatic special case added for
|
|
// backward compatibility with the old situation where we would _always_
|
|
// eagerly read data resources with known configurations, regardless of
|
|
// the plans for their dependencies.
|
|
// This test creates an indirection through a local value, but the same
|
|
// principle would apply for both input variable and output value
|
|
// indirection.
|
|
//
|
|
// See also TestContext2Plan_dataReferencesResourceDirectly for the
|
|
// other case, where the reference is direct.
|
|
// This special exception doesn't apply for a data resource that has
|
|
// custom conditions; see
|
|
// TestContext2Plan_dataResourceChecksManagedResourceChange for that
|
|
// situation.
|
|
|
|
p := testProvider("test")
|
|
var applyCount int64
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
atomic.AddInt64(&applyCount, 1)
|
|
resp.NewState = req.PlannedState
|
|
return resp
|
|
}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
if atomic.LoadInt64(&applyCount) == 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source read before managed resource apply"))
|
|
} else {
|
|
resp.State = req.Config
|
|
}
|
|
return resp
|
|
}
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
locals {
|
|
x = "value"
|
|
}
|
|
|
|
resource "test_resource" "a" {
|
|
value = local.x
|
|
}
|
|
|
|
locals {
|
|
y = test_resource.a.value
|
|
}
|
|
|
|
// test_resource.a.value would ideally cause a pending change for
|
|
// test_resource.a to defer this to the apply step, but we intentionally don't
|
|
// do that when it's indirect (through a local value, here) as a concession
|
|
// to backward compatibility.
|
|
data "test_data_source" "d" {
|
|
foo = local.y
|
|
}
|
|
`})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("successful plan; want an error")
|
|
}
|
|
|
|
if got, want := diags.Err().Error(), "data source read before managed resource apply"; !strings.Contains(got, want) {
|
|
t.Errorf("Missing expected error message\ngot: %s\nwant substring: %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_skipRefresh(t *testing.T) {
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
}
|
|
`})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","type":"test_instance"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SkipRefresh: true,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if p.ReadResourceCalled {
|
|
t.Fatal("Resource should not have been refreshed")
|
|
}
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataInModuleDependsOn(t *testing.T) {
|
|
p := testProvider("test")
|
|
|
|
readDataSourceB := false
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
cfg := req.Config.AsValueMap()
|
|
foo := cfg["foo"].AsString()
|
|
|
|
cfg["id"] = cty.StringVal("ID")
|
|
cfg["foo"] = cty.StringVal("new")
|
|
|
|
if foo == "b" {
|
|
readDataSourceB = true
|
|
}
|
|
|
|
resp.State = cty.ObjectVal(cfg)
|
|
return resp
|
|
}
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "a" {
|
|
source = "./mod_a"
|
|
}
|
|
|
|
module "b" {
|
|
source = "./mod_b"
|
|
depends_on = [module.a]
|
|
}`,
|
|
"mod_a/main.tf": `
|
|
data "test_data_source" "a" {
|
|
foo = "a"
|
|
}`,
|
|
"mod_b/main.tf": `
|
|
data "test_data_source" "b" {
|
|
foo = "b"
|
|
}`,
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// The change to data source a should not prevent data source b from being
|
|
// read.
|
|
if !readDataSourceB {
|
|
t.Fatal("data source b was not read during plan")
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_rpcDiagnostics(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
resp := testDiffFn(req)
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble"))
|
|
return resp
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if len(diags) == 0 {
|
|
t.Fatal("expected warnings")
|
|
}
|
|
|
|
for _, d := range diags {
|
|
des := d.Description().Summary
|
|
if !strings.Contains(des, "frobble") {
|
|
t.Fatalf(`expected frobble, got %q`, des)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ignore_changes needs to be re-applied to the planned value for provider
|
|
// using the LegacyTypeSystem
|
|
func TestContext2Plan_legacyProviderIgnoreChanges(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
lifecycle {
|
|
ignore_changes = [data]
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
m := req.ProposedNewState.AsValueMap()
|
|
// this provider "hashes" the data attribute as bar
|
|
m["data"] = cty.StringVal("bar")
|
|
|
|
resp.PlannedState = cty.ObjectVal(m)
|
|
resp.LegacyTypeSystem = true
|
|
return resp
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"data": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_validateIgnoreAll(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
lifecycle {
|
|
ignore_changes = all
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"data": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
|
|
var diags tfdiags.Diagnostics
|
|
if req.TypeName == "test_instance" {
|
|
if !req.Config.GetAttr("id").IsNull() {
|
|
diags = diags.Append(errors.New("id cannot be set in config"))
|
|
}
|
|
}
|
|
return providers.ValidateResourceConfigResponse{
|
|
Diagnostics: diags,
|
|
}
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
_, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_legacyProviderIgnoreAll(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
lifecycle {
|
|
ignore_changes = all
|
|
}
|
|
data = "foo"
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"data": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
plan := req.ProposedNewState.AsValueMap()
|
|
// Update both the computed id and the configured data.
|
|
// Legacy providers expect tofu to be able to ignore these.
|
|
|
|
plan["id"] = cty.StringVal("updated")
|
|
plan["data"] = cty.StringVal("updated")
|
|
resp.PlannedState = cty.ObjectVal(plan)
|
|
resp.LegacyTypeSystem = true
|
|
return resp
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"orig","data":"orig"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("expected NoOp plan, got %s\n", c.Action)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_dataRemovalNoProvider(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
)
|
|
|
|
// the provider for this data source is no longer in the config, but that
|
|
// should not matter for state removal.
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("data.test_data_source.d").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"d"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/local/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
// We still need to be able to locate the provider to decode the
|
|
// state, since we do not know during init that this provider is
|
|
// only used for an orphaned data source.
|
|
addrs.NewProvider("registry.opentofu.org", "local", "test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
_, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_noSensitivityChange(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "sensitive_var" {
|
|
default = "hello"
|
|
sensitive = true
|
|
}
|
|
|
|
resource "test_resource" "foo" {
|
|
value = var.sensitive_var
|
|
sensitive_value = var.sensitive_var
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo", "value":"hello", "sensitive_value":"hello"}`),
|
|
AttrSensitivePaths: []cty.PathValueMarks{
|
|
{Path: cty.Path{cty.GetAttrStep{Name: "value"}}, Marks: cty.NewValueMarks(marks.Sensitive)},
|
|
{Path: cty.Path{cty.GetAttrStep{Name: "sensitive_value"}}, Marks: cty.NewValueMarks(marks.Sensitive)},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) {
|
|
m := testModule(t, "validate-variable-custom-validations-child-sensitive")
|
|
|
|
p := testProvider("test")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("succeeded; want errors")
|
|
}
|
|
if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) {
|
|
t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_nullOutputNoOp(t *testing.T) {
|
|
// this should always plan a NoOp change for the output
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
output "planned" {
|
|
value = false ? 1 : null
|
|
}
|
|
`,
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
r := s.Module(addrs.RootModuleInstance)
|
|
r.SetOutputValue("planned", cty.NullVal(cty.DynamicPseudoType), false)
|
|
})
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
for _, c := range plan.Changes.Outputs {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Plan_createOutput(t *testing.T) {
|
|
// this should always plan a NoOp change for the output
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
output "planned" {
|
|
value = 1
|
|
}
|
|
`,
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
for _, c := range plan.Changes.Outputs {
|
|
if c.Action != plans.Create {
|
|
t.Fatalf("expected Create change, got %s for %q", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// NOTE: Due to the size of this file, new tests should be added to
|
|
// context_plan2_test.go.
|
|
////////////////////////////////////////////////////////////////////////////////
|