mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 12:42:58 -06:00
b213386a73
instances.Set is only used after all instances have been processes, so it should therefor only handle known instances and not panic when given an address that traverses an unexpanded module.
681 lines
18 KiB
Go
681 lines
18 KiB
Go
package terraform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// Test that the PreApply hook is called with the correct deposed key
|
|
func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) {
|
|
m := testModule(t, "apply-cbd-deposed-only")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
deposedKey := states.NewDeposedKey()
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
deposedKey,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
hook := new(MockHook)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{hook},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Verify PreApply was called correctly
|
|
if !hook.PreApplyCalled {
|
|
t.Fatalf("PreApply hook not called")
|
|
}
|
|
if addr, wantAddr := hook.PreApplyAddr, mustResourceInstanceAddr("aws_instance.bar"); !addr.Equal(wantAddr) {
|
|
t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
|
|
}
|
|
if gen := hook.PreApplyGen; gen != deposedKey {
|
|
t.Errorf("expected gen to be %q, but was %q", deposedKey, gen)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithDataSourceExpansion(t *testing.T) {
|
|
// While managed resources store their destroy-time dependencies, data
|
|
// sources do not. This means that if a provider were only included in a
|
|
// destroy graph because of data sources, it could have dependencies which
|
|
// are not correctly ordered. Here we verify that the provider is not
|
|
// included in the destroy operation, and all dependency evaluations
|
|
// succeed.
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
source = "./mod"
|
|
}
|
|
|
|
provider "other" {
|
|
foo = module.mod.data
|
|
}
|
|
|
|
# this should not require the provider be present during destroy
|
|
data "other_data_source" "a" {
|
|
}
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
data "test_data_source" "a" {
|
|
count = 1
|
|
}
|
|
|
|
data "test_data_source" "b" {
|
|
count = data.test_data_source.a[0].foo == "ok" ? 1 : 0
|
|
}
|
|
|
|
output "data" {
|
|
value = data.test_data_source.a[0].foo == "ok" ? data.test_data_source.b[0].foo : "nope"
|
|
}
|
|
`,
|
|
})
|
|
|
|
testP := testProvider("test")
|
|
otherP := testProvider("other")
|
|
|
|
readData := func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data_source"),
|
|
"foo": cty.StringVal("ok"),
|
|
}),
|
|
}
|
|
}
|
|
|
|
testP.ReadDataSourceFn = readData
|
|
otherP.ReadDataSourceFn = readData
|
|
|
|
ps := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP),
|
|
addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherP),
|
|
}
|
|
|
|
otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
foo := req.Config.GetAttr("foo")
|
|
if foo.IsNull() || foo.AsString() != "ok" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect config val: %#v\n", foo))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
// now destroy the whole thing
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
// should not be used to destroy data sources
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("provider should not be used"))
|
|
return resp
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyThenUpdate(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
value = "udpated"
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var orderMu sync.Mutex
|
|
var order []string
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
if id == "b" {
|
|
// slow down the b destroy, since a should wait for it
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
orderMu.Lock()
|
|
order = append(order, id)
|
|
orderMu.Unlock()
|
|
|
|
resp.NewState = req.PlannedState
|
|
return resp
|
|
}
|
|
|
|
addrA := mustResourceInstanceAddr(`test_instance.a`)
|
|
addrB := mustResourceInstanceAddr(`test_instance.b`)
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`),
|
|
Status: states.ObjectReady,
|
|
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
|
|
|
// test_instance.b depended on test_instance.a, and therefor should be
|
|
// destroyed before any changes to test_instance.a
|
|
s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"b"}`),
|
|
Status: states.ObjectReady,
|
|
Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
|
|
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if order[0] != "b" {
|
|
t.Fatalf("expected apply order [b, a], got: %v\n", order)
|
|
}
|
|
}
|
|
|
|
// verify that dependencies are updated in the state during refresh and apply
|
|
func TestApply_updateDependencies(t *testing.T) {
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
fooAddr := mustResourceInstanceAddr("aws_instance.foo")
|
|
barAddr := mustResourceInstanceAddr("aws_instance.bar")
|
|
bazAddr := mustResourceInstanceAddr("aws_instance.baz")
|
|
bamAddr := mustResourceInstanceAddr("aws_instance.bam")
|
|
binAddr := mustResourceInstanceAddr("aws_instance.bin")
|
|
root.SetResourceInstanceCurrent(
|
|
fooAddr.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
bazAddr.ContainingResource().Config(),
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
binAddr.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bin","type":"aws_instance","unknown":"ok"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
bazAddr.ContainingResource().Config(),
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
bazAddr.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
// Existing dependencies should not be removed from orphaned instances
|
|
bamAddr.ContainingResource().Config(),
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
barAddr.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "aws_instance" "bar" {
|
|
foo = aws_instance.foo.id
|
|
}
|
|
|
|
resource "aws_instance" "foo" {
|
|
}
|
|
|
|
resource "aws_instance" "bin" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
bar := plan.PriorState.ResourceInstance(barAddr)
|
|
if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
|
|
t.Fatalf("bar should depend on foo after refresh, but got %s", bar.Current.Dependencies)
|
|
}
|
|
|
|
foo := plan.PriorState.ResourceInstance(fooAddr)
|
|
if len(foo.Current.Dependencies) == 0 || !foo.Current.Dependencies[0].Equal(bazAddr.ContainingResource().Config()) {
|
|
t.Fatalf("foo should depend on baz after refresh because of the update, but got %s", foo.Current.Dependencies)
|
|
}
|
|
|
|
bin := plan.PriorState.ResourceInstance(binAddr)
|
|
if len(bin.Current.Dependencies) != 0 {
|
|
t.Fatalf("bin should depend on nothing after refresh because there is no change, but got %s", bin.Current.Dependencies)
|
|
}
|
|
|
|
baz := plan.PriorState.ResourceInstance(bazAddr)
|
|
if len(baz.Current.Dependencies) == 0 || !baz.Current.Dependencies[0].Equal(bamAddr.ContainingResource().Config()) {
|
|
t.Fatalf("baz should depend on bam after refresh, but got %s", baz.Current.Dependencies)
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
bar = state.ResourceInstance(barAddr)
|
|
if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
|
|
t.Fatalf("bar should still depend on foo after apply, but got %s", bar.Current.Dependencies)
|
|
}
|
|
|
|
foo = state.ResourceInstance(fooAddr)
|
|
if len(foo.Current.Dependencies) != 0 {
|
|
t.Fatalf("foo should have no deps after apply, but got %s", foo.Current.Dependencies)
|
|
}
|
|
|
|
}
|
|
|
|
func TestContext2Apply_additionalSensitiveFromState(t *testing.T) {
|
|
// Ensure we're not trying to double-mark values decoded from state
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "secret" {
|
|
sensitive = true
|
|
default = ["secret"]
|
|
}
|
|
|
|
resource "test_resource" "a" {
|
|
sensitive_attr = var.secret
|
|
}
|
|
|
|
resource "test_resource" "b" {
|
|
value = test_resource.a.id
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := new(MockProvider)
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_resource": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"value": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"sensitive_attr": {
|
|
Type: cty.List(cty.String),
|
|
Optional: true,
|
|
Sensitive: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(`test_resource.a`),
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"a","sensitive_attr":["secret"]}`),
|
|
AttrSensitivePaths: []cty.PathValueMarks{
|
|
{
|
|
Path: cty.GetAttrPath("sensitive_attr"),
|
|
Marks: cty.NewValueMarks(marks.Sensitive),
|
|
},
|
|
},
|
|
Status: states.ObjectReady,
|
|
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) {
|
|
// Ensure we're not trying to double-mark values decoded from state
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod" {
|
|
source = "./mod"
|
|
}
|
|
|
|
resource "test_object" "a" {
|
|
test_string = module.mod.out
|
|
}
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
variable "in" {
|
|
sensitive = true
|
|
default = "foo"
|
|
}
|
|
output "out" {
|
|
value = var.in
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
|
|
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)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a"))
|
|
if len(obj.Current.AttrSensitivePaths) != 1 {
|
|
t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths)
|
|
}
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// make sure the same marks are compared in the next plan as well
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) {
|
|
// The impure function call should not cause a planned change with
|
|
// ignore_changes
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "pw" {
|
|
sensitive = true
|
|
default = "foo"
|
|
}
|
|
|
|
resource "test_object" "x" {
|
|
test_map = {
|
|
string = "X${bcrypt(var.pw)}"
|
|
}
|
|
lifecycle {
|
|
ignore_changes = [ test_map["string"] ]
|
|
}
|
|
}
|
|
|
|
resource "test_object" "y" {
|
|
test_map = {
|
|
string = "X${bcrypt(var.pw)}"
|
|
}
|
|
lifecycle {
|
|
ignore_changes = [ test_map ]
|
|
}
|
|
}
|
|
|
|
`,
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
|
|
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)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// FINAL PLAN:
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// make sure the same marks are compared in the next plan as well
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Logf("marks before: %#v", c.BeforeValMarks)
|
|
t.Logf("marks after: %#v", c.AfterValMarks)
|
|
t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithDeposed(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_object" "x" {
|
|
test_string = "ok"
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}`,
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
|
|
deposedKey := states.NewDeposedKey()
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("test_object.x").Resource,
|
|
deposedKey,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"test_string":"deposed"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply: %s", diags.Err())
|
|
}
|
|
|
|
}
|
|
|
|
func TestContext2Apply_nullableVariables(t *testing.T) {
|
|
m := testModule(t, "apply-nullable-variables")
|
|
state := states.NewState()
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan: %s", diags.Err())
|
|
}
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply: %s", diags.Err())
|
|
}
|
|
|
|
outputs := state.Module(addrs.RootModuleInstance).OutputValues
|
|
// we check for null outputs be seeing that they don't exists
|
|
if _, ok := outputs["nullable_null_default"]; ok {
|
|
t.Error("nullable_null_default: expected no output value")
|
|
}
|
|
if _, ok := outputs["nullable_non_null_default"]; ok {
|
|
t.Error("nullable_non_null_default: expected no output value")
|
|
}
|
|
if _, ok := outputs["nullable_no_default"]; ok {
|
|
t.Error("nullable_no_default: expected no output value")
|
|
}
|
|
|
|
if v := outputs["non_nullable_default"].Value; v.AsString() != "ok" {
|
|
t.Fatalf("incorrect 'non_nullable_default' output value: %#v\n", v)
|
|
}
|
|
if v := outputs["non_nullable_no_default"].Value; v.AsString() != "ok" {
|
|
t.Fatalf("incorrect 'non_nullable_no_default' output value: %#v\n", v)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_targetedDestroyWithMoved(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "modb" {
|
|
source = "./mod"
|
|
for_each = toset(["a", "b"])
|
|
}
|
|
`,
|
|
"./mod/main.tf": `
|
|
resource "test_object" "a" {
|
|
}
|
|
|
|
module "sub" {
|
|
for_each = toset(["a", "b"])
|
|
source = "./sub"
|
|
}
|
|
|
|
moved {
|
|
from = module.old
|
|
to = module.sub
|
|
}
|
|
`,
|
|
"./mod/sub/main.tf": `
|
|
resource "test_object" "s" {
|
|
}
|
|
`})
|
|
|
|
p := simpleMockProvider()
|
|
|
|
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)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// destroy only a single instance not included in the moved statements
|
|
_, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{mustResourceInstanceAddr(`module.modb["a"].test_object.a`)},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
}
|