mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-12 00:52:35 -06:00
251 lines
6.6 KiB
Go
251 lines
6.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/providers"
|
|
"github.com/hashicorp/terraform/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{
|
|
Config: m,
|
|
Hooks: []Hook{hook},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
State: state,
|
|
})
|
|
|
|
if p, diags := ctx.Plan(); diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(p.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply()
|
|
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{
|
|
Config: m,
|
|
Providers: ps,
|
|
})
|
|
|
|
_, diags := ctx.Plan()
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply()
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
// now destroy the whole thing
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Config: m,
|
|
Providers: ps,
|
|
Destroy: true,
|
|
})
|
|
|
|
_, diags = ctx.Plan()
|
|
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()
|
|
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{
|
|
Config: m,
|
|
State: state,
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if _, diags := ctx.Plan(); diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags := ctx.Apply()
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if order[0] != "b" {
|
|
t.Fatalf("expected apply order [b, a], got: %v\n", order)
|
|
}
|
|
}
|