mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Most of our transformers are pure compute and so don't really have a strong need to generate trace spans under our current focus of only exposing user-facing concepts and external requests in our traces, but unfortunately some of them indirectly depend on provider schema, which in turn means that they can potentially be unlucky enough to be the trigger for making all of the provider requests needed to fill the schema cache and therefore would end up with provider request spans being reported beneath them. As usual with these interface updates, this initial change focuses only on changing the interface and updating its direct callers and implementers to match, without any further refactoring or attempts to plumb contexts to or from other functions that don't have them yet. That means there are a few new context.TODO() calls here that we'll tidy up in a later commit that hopefully won't involve all of the noise that is caused by changing an interface API. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
886 lines
23 KiB
Go
886 lines
23 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
)
|
|
|
|
func TestApplyGraphBuilder_impl(t *testing.T) {
|
|
var _ GraphBuilder = new(ApplyGraphBuilder)
|
|
}
|
|
|
|
func TestApplyGraphBuilder(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.create"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.other"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child.test_object.create"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child.test_object.other"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-basic"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong path %q", g.Path.String())
|
|
}
|
|
|
|
got := strings.TrimSpace(g.String())
|
|
want := strings.TrimSpace(testApplyGraphBuilderStr)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Fatalf("wrong result\n%s", diff)
|
|
}
|
|
}
|
|
|
|
// This tests the ordering of two resources where a non-CBD depends
|
|
// on a CBD. GH-11349.
|
|
func TestApplyGraphBuilder_depCbd(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.A"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.CreateThenDelete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.B"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.A").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"A"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.B").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-dep-cbd"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong path %q", g.Path.String())
|
|
}
|
|
|
|
// We're going to go hunting for our deposed instance node here, so we
|
|
// can find out its key to use in the assertions below.
|
|
var dk states.DeposedKey
|
|
for _, v := range g.Vertices() {
|
|
tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if dk != states.NotDeposed {
|
|
t.Fatalf("more than one deposed instance node in the graph; want only one")
|
|
}
|
|
dk = tv.DeposedKey
|
|
}
|
|
if dk == states.NotDeposed {
|
|
t.Fatalf("no deposed instance node in the graph; want one")
|
|
}
|
|
|
|
destroyName := fmt.Sprintf("test_object.A (destroy deposed %s)", dk)
|
|
|
|
// Create A, Modify B, Destroy A
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.A",
|
|
destroyName,
|
|
)
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.A",
|
|
"test_object.B",
|
|
)
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.B",
|
|
destroyName,
|
|
)
|
|
}
|
|
|
|
// This tests the ordering of two resources that are both CBD that
|
|
// require destroy/create.
|
|
func TestApplyGraphBuilder_doubleCBD(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.A"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.CreateThenDelete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.B"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.CreateThenDelete,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-double-cbd"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong path %q", g.Path.String())
|
|
}
|
|
|
|
// We're going to go hunting for our deposed instance node here, so we
|
|
// can find out its key to use in the assertions below.
|
|
var destroyA, destroyB string
|
|
for _, v := range g.Vertices() {
|
|
tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
switch tv.Addr.Resource.Resource.Name {
|
|
case "A":
|
|
destroyA = fmt.Sprintf("test_object.A (destroy deposed %s)", tv.DeposedKey)
|
|
case "B":
|
|
destroyB = fmt.Sprintf("test_object.B (destroy deposed %s)", tv.DeposedKey)
|
|
default:
|
|
t.Fatalf("unknown instance: %s", tv.Addr)
|
|
}
|
|
}
|
|
|
|
// Create A, Modify B, Destroy A
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.A",
|
|
destroyA,
|
|
)
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.A",
|
|
"test_object.B",
|
|
)
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"test_object.B",
|
|
destroyB,
|
|
)
|
|
}
|
|
|
|
// This tests the ordering of two resources being destroyed that depend
|
|
// on each other from only state. GH-11749
|
|
func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child.test_object.A"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child.test_object.B"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.A").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.B").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.A")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "empty"),
|
|
Changes: changes,
|
|
State: state,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
}
|
|
|
|
g, diags := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong path %q", g.Path.String())
|
|
}
|
|
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"module.child.test_object.B (destroy)",
|
|
"module.child.test_object.A (destroy)")
|
|
}
|
|
|
|
// This tests the ordering of destroying a single count of a resource.
|
|
func TestApplyGraphBuilder_destroyCount(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.A[1]"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.B"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.RootModule()
|
|
addrA := mustResourceInstanceAddr("test_object.A[1]")
|
|
root.SetResourceInstanceCurrent(
|
|
addrA.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"B"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.B").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"B"}`),
|
|
Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-count"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong module path %q", g.Path)
|
|
}
|
|
|
|
got := strings.TrimSpace(g.String())
|
|
want := strings.TrimSpace(testApplyGraphBuilderDestroyCountStr)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Fatalf("wrong result\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.A.test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.B.test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
modA := state.EnsureModule(addrs.RootModuleInstance.Child("A", addrs.NoKey))
|
|
modA.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
modB := state.EnsureModule(addrs.RootModuleInstance.Child("B", addrs.NoKey))
|
|
modB.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo","value":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.A.test_object.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-module-destroy"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
testGraphHappensBefore(
|
|
t, g,
|
|
"module.B.test_object.foo (destroy)",
|
|
"module.A.test_object.foo (destroy)",
|
|
)
|
|
}
|
|
|
|
func TestApplyGraphBuilder_targetModule(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child2.test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-target-module"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
|
},
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
testGraphNotContains(t, g, "test_object.foo")
|
|
}
|
|
|
|
func TestApplyGraphBuilder_excludeModule(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("module.child2.test_object.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-target-module"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
Excludes: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
|
},
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
testGraphNotContains(t, g, "mod.child2.test_object.foo")
|
|
}
|
|
|
|
// Ensure that an update resulting from the removal of a resource happens after
|
|
// that resource is destroyed.
|
|
func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) {
|
|
schemas := simpleTestSchemas()
|
|
instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
|
|
|
|
bBefore, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("a_id"),
|
|
}), instanceSchema.Block.ImpliedType())
|
|
bAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("changed"),
|
|
}), instanceSchema.Block.ImpliedType())
|
|
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.a"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.b"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
Before: bBefore,
|
|
After: bAfter,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a_id"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
addrs.NoKey,
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "a",
|
|
},
|
|
Module: root.Addr.Module(),
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-orphan-update"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := strings.TrimSpace(`
|
|
test_object.a (destroy)
|
|
test_object.b
|
|
test_object.a (destroy)
|
|
`)
|
|
|
|
instanceGraph := filterInstances(g)
|
|
got := strings.TrimSpace(instanceGraph.String())
|
|
|
|
if got != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
|
|
}
|
|
}
|
|
|
|
// Ensure that an update resulting from the removal of a resource happens before
|
|
// a CBD resource is destroyed.
|
|
func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) {
|
|
schemas := simpleTestSchemas()
|
|
instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
|
|
|
|
bBefore, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("a_id"),
|
|
}), instanceSchema.Block.ImpliedType())
|
|
bAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("changed"),
|
|
}), instanceSchema.Block.ImpliedType())
|
|
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.a"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.b"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
Before: bBefore,
|
|
After: bAfter,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a_id"}`),
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_object",
|
|
Name: "a",
|
|
},
|
|
Module: root.Addr.Module(),
|
|
},
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-orphan-update"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := strings.TrimSpace(`
|
|
test_object.a (destroy)
|
|
test_object.b
|
|
test_object.b
|
|
`)
|
|
|
|
instanceGraph := filterInstances(g)
|
|
got := strings.TrimSpace(instanceGraph.String())
|
|
|
|
if got != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
|
|
}
|
|
}
|
|
|
|
// The orphan clean up node should not be connected to a provider
|
|
func TestApplyGraphBuilder_orphanedWithProvider(t *testing.T) {
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("test_object.A"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_object.A").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"A"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"].foo`),
|
|
addrs.NoKey,
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-orphan-alias"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The cleanup node has no state or config of its own, so would create a
|
|
// default provider which we don't want.
|
|
testGraphNotContains(t, g, "provider.test")
|
|
}
|
|
|
|
func TestApplyGraphBuilder_withChecks(t *testing.T) {
|
|
awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
|
|
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: mustResourceInstanceAddr("aws_instance.foo"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("aws_instance.baz"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
},
|
|
{
|
|
Addr: mustResourceInstanceAddr("data.aws_data_source.bar"),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Read,
|
|
},
|
|
ActionReason: plans.ResourceInstanceReadBecauseCheckNested,
|
|
},
|
|
},
|
|
}
|
|
|
|
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
|
}, nil)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "apply-with-checks"),
|
|
Changes: changes,
|
|
Plugins: plugins,
|
|
State: states.NewState(),
|
|
Operation: walkApply,
|
|
}
|
|
|
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if g.Path.String() != addrs.RootModuleInstance.String() {
|
|
t.Fatalf("wrong path %q", g.Path.String())
|
|
}
|
|
|
|
got := strings.TrimSpace(g.String())
|
|
// We're especially looking for the edge here, where aws_instance.bat
|
|
// has a dependency on aws_instance.boo
|
|
want := strings.TrimSpace(testPlanWithCheckGraphBuilderStr)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Fatalf("\ngot:\n%s\n\nwant:\n%s\n\ndiff:\n%s", got, want, diff)
|
|
}
|
|
|
|
}
|
|
|
|
const testPlanWithCheckGraphBuilderStr = `
|
|
(execute checks)
|
|
aws_instance.baz
|
|
aws_instance.baz
|
|
aws_instance.baz (expand)
|
|
aws_instance.baz (expand)
|
|
aws_instance.foo
|
|
aws_instance.foo
|
|
aws_instance.foo (expand)
|
|
aws_instance.foo (expand)
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
check.my_check (expand)
|
|
data.aws_data_source.bar
|
|
data.aws_data_source.bar
|
|
(execute checks)
|
|
data.aws_data_source.bar (expand)
|
|
data.aws_data_source.bar (expand)
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"]
|
|
provider["registry.opentofu.org/hashicorp/aws"] (close)
|
|
data.aws_data_source.bar
|
|
root
|
|
check.my_check (expand)
|
|
provider["registry.opentofu.org/hashicorp/aws"] (close)
|
|
`
|
|
|
|
const testApplyGraphBuilderStr = `
|
|
module.child (close)
|
|
module.child.test_object.other
|
|
module.child (expand)
|
|
module.child.test_object.create
|
|
module.child.test_object.create (expand)
|
|
module.child.test_object.create (expand)
|
|
module.child (expand)
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
module.child.test_object.other
|
|
module.child.test_object.other (expand)
|
|
module.child.test_object.other (expand)
|
|
module.child.test_object.create
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
provider["registry.opentofu.org/hashicorp/test"] (close)
|
|
module.child.test_object.other
|
|
test_object.other
|
|
root
|
|
module.child (close)
|
|
provider["registry.opentofu.org/hashicorp/test"] (close)
|
|
test_object.create
|
|
test_object.create (expand)
|
|
test_object.create (expand)
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
test_object.other
|
|
test_object.other (expand)
|
|
test_object.other (expand)
|
|
test_object.create
|
|
`
|
|
|
|
const testApplyGraphBuilderDestroyCountStr = `
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
provider["registry.opentofu.org/hashicorp/test"] (close)
|
|
test_object.B
|
|
root
|
|
provider["registry.opentofu.org/hashicorp/test"] (close)
|
|
test_object.A (expand)
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
test_object.A[1] (destroy)
|
|
provider["registry.opentofu.org/hashicorp/test"]
|
|
test_object.B
|
|
test_object.A[1] (destroy)
|
|
test_object.B (expand)
|
|
test_object.B (expand)
|
|
test_object.A (expand)
|
|
`
|