mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-06 14:13:16 -06:00
38ec730b0e
Previously the graph builders all expected to be given a full manifest of all of the plugin component schemas that they could need during their analysis work. That made sense when terraform.NewContext would always proactively load all of the schemas before doing any other work, but we now have a load-as-needed strategy for schemas. We'll now have the graph builders use the contextPlugins object they each already hold to retrieve individual schemas when needed. This avoids the need to prepare a redundant data structure to pass alongside the contextPlugins object, and leans on the memoization behavior inside contextPlugins to preserve the old behavior of loading each provider's schema only once.
757 lines
20 KiB
Go
757 lines
20 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
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(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())
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
|
|
expected := strings.TrimSpace(testApplyGraphBuilderStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// 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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-dep-cbd"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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(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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "empty"),
|
|
Changes: changes,
|
|
State: state,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
}
|
|
|
|
g, diags := b.Build(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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-count"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testApplyGraphBuilderDestroyCountStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-module-destroy"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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(addrs.RootModuleInstance)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
testGraphNotContains(t, g, "module.child1.output.instance_id")
|
|
}
|
|
|
|
// 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.ImpliedType())
|
|
bAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("changed"),
|
|
}), instanceSchema.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,
|
|
},
|
|
)
|
|
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,
|
|
},
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-orphan-update"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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.ImpliedType())
|
|
bAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b_id"),
|
|
"test_string": cty.StringVal("changed"),
|
|
}), instanceSchema.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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
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.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-apply-orphan-update"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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.terraform.io/hashicorp/test"].foo`),
|
|
)
|
|
|
|
b := &ApplyGraphBuilder{
|
|
Config: testModule(t, "graph-builder-orphan-alias"),
|
|
Changes: changes,
|
|
Plugins: simpleMockPluginLibrary(),
|
|
State: state,
|
|
}
|
|
|
|
g, err := b.Build(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")
|
|
}
|
|
|
|
const testApplyGraphBuilderStr = `
|
|
meta.count-boundary (EachMode fixup)
|
|
module.child (close)
|
|
test_object.other
|
|
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.terraform.io/hashicorp/test"]
|
|
module.child.test_object.other
|
|
module.child.test_object.create
|
|
module.child.test_object.other (expand)
|
|
module.child.test_object.other (expand)
|
|
module.child (expand)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
|
module.child.test_object.other
|
|
test_object.other
|
|
root
|
|
meta.count-boundary (EachMode fixup)
|
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
|
test_object.create
|
|
test_object.create (expand)
|
|
test_object.create (expand)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
test_object.other
|
|
test_object.create
|
|
test_object.other (expand)
|
|
test_object.other (expand)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
`
|
|
|
|
const testApplyGraphBuilderDestroyCountStr = `
|
|
meta.count-boundary (EachMode fixup)
|
|
test_object.B
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
|
test_object.B
|
|
root
|
|
meta.count-boundary (EachMode fixup)
|
|
provider["registry.terraform.io/hashicorp/test"] (close)
|
|
test_object.A (expand)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
test_object.A[1] (destroy)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
test_object.B
|
|
test_object.A (expand)
|
|
test_object.A[1] (destroy)
|
|
test_object.B (expand)
|
|
test_object.B (expand)
|
|
provider["registry.terraform.io/hashicorp/test"]
|
|
`
|