mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
0e4a6e3e89
Fixes #10440 This updates the behavior of "apply" resources to depend on the destroy versions of their dependencies. We make an exception to this behavior when the "apply" resource is CBD. This is odd and not 100% correct, but it mimics the behavior of the legacy graphs and avoids us having to do major core work to support the 100% correct solution. I'll explain this in examples... Given the following configuration: resource "null_resource" "a" { count = "${var.count}" } resource "null_resource" "b" { triggers { key = "${join(",", null_resource.a.*.id)}" } } Assume we've successfully created this configuration with count = 2. When going from count = 2 to count = 1, `null_resource.b` should wait for `null_resource.a.1` to destroy. If it doesn't, then it is a race: depending when we interpolate the `triggers.key` attribute of `null_resource.b`, we may get 1 value or 2. If `null_resource.a.1` is destroyed, we'll get 1. Otherwise, we'll get 2. This was the root cause of #10440 In the legacy graphs, `null_resource.b` would depend on the destruction of any `null_resource.a` (orphans, tainted, anything!). This would ensure proper ordering. We mimic that behavior here. The difference is CBD. If `null_resource.b` has CBD enabled, then the ordering **in the legacy graph** becomes: 1. null_resource.b (create) 2. null_resource.b (destroy) 3. null_resource.a (destroy) In this case, the update would always have 2 values for `triggers.key`, even though we were destroying a resource later! This scenario required two `terraform apply` operations. This is what the CBD check is for in this PR. We do this to mimic the behavior of the legacy graph. The correct solution to do one day is to allow splat references (`null_resource.a.*.id`) to happen in parallel and only read up to to the `count` amount in the state. This requires some fairly significant work close to the 0.8 release date, so we can defer this to later and adopt the 0.7.x behavior for now.
179 lines
4.3 KiB
Go
179 lines
4.3 KiB
Go
package terraform
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDestroyEdgeTransformer_basic(t *testing.T) {
|
|
g := Graph{Path: RootModulePath}
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
|
tf := &DestroyEdgeTransformer{
|
|
Module: testModule(t, "transform-destroy-edge-basic"),
|
|
}
|
|
if err := tf.Transform(&g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestDestroyEdgeTransformer_create(t *testing.T) {
|
|
g := Graph{Path: RootModulePath}
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
|
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
|
|
tf := &DestroyEdgeTransformer{
|
|
Module: testModule(t, "transform-destroy-edge-basic"),
|
|
}
|
|
if err := tf.Transform(&g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformDestroyEdgeCreatorStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestDestroyEdgeTransformer_multi(t *testing.T) {
|
|
g := Graph{Path: RootModulePath}
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.C"})
|
|
tf := &DestroyEdgeTransformer{
|
|
Module: testModule(t, "transform-destroy-edge-multi"),
|
|
}
|
|
if err := tf.Transform(&g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformDestroyEdgeMultiStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestDestroyEdgeTransformer_selfRef(t *testing.T) {
|
|
g := Graph{Path: RootModulePath}
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
|
|
tf := &DestroyEdgeTransformer{
|
|
Module: testModule(t, "transform-destroy-edge-self-ref"),
|
|
}
|
|
if err := tf.Transform(&g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformDestroyEdgeSelfRefStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestDestroyEdgeTransformer_module(t *testing.T) {
|
|
g := Graph{Path: RootModulePath}
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "module.child.aws_instance.b"})
|
|
g.Add(&graphNodeDestroyerTest{AddrString: "aws_instance.a"})
|
|
tf := &DestroyEdgeTransformer{
|
|
Module: testModule(t, "transform-destroy-edge-module"),
|
|
}
|
|
if err := tf.Transform(&g); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(g.String())
|
|
expected := strings.TrimSpace(testTransformDestroyEdgeModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
type graphNodeCreatorTest struct {
|
|
AddrString string
|
|
Refs []string
|
|
}
|
|
|
|
func (n *graphNodeCreatorTest) Name() string { return n.CreateAddr().String() }
|
|
func (n *graphNodeCreatorTest) CreateAddr() *ResourceAddress {
|
|
addr, err := ParseResourceAddress(n.AddrString)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return addr
|
|
}
|
|
|
|
func (n *graphNodeCreatorTest) References() []string { return n.Refs }
|
|
|
|
type graphNodeDestroyerTest struct {
|
|
AddrString string
|
|
CBD bool
|
|
Modified bool
|
|
}
|
|
|
|
func (n *graphNodeDestroyerTest) Name() string {
|
|
result := n.DestroyAddr().String() + " (destroy)"
|
|
if n.Modified {
|
|
result += " (modified)"
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool { return n.CBD }
|
|
|
|
func (n *graphNodeDestroyerTest) ModifyCreateBeforeDestroy(v bool) error {
|
|
n.Modified = true
|
|
return nil
|
|
}
|
|
|
|
func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
|
|
addr, err := ParseResourceAddress(n.AddrString)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return addr
|
|
}
|
|
|
|
const testTransformDestroyEdgeBasicStr = `
|
|
test.A (destroy)
|
|
test.B (destroy)
|
|
test.B (destroy)
|
|
`
|
|
|
|
const testTransformDestroyEdgeCreatorStr = `
|
|
test.A
|
|
test.A (destroy)
|
|
test.A (destroy)
|
|
test.B (destroy)
|
|
test.B (destroy)
|
|
`
|
|
|
|
const testTransformDestroyEdgeMultiStr = `
|
|
test.A (destroy)
|
|
test.B (destroy)
|
|
test.C (destroy)
|
|
test.B (destroy)
|
|
test.C (destroy)
|
|
test.C (destroy)
|
|
`
|
|
|
|
const testTransformDestroyEdgeSelfRefStr = `
|
|
test.A (destroy)
|
|
`
|
|
|
|
const testTransformDestroyEdgeModuleStr = `
|
|
aws_instance.a (destroy)
|
|
module.child.aws_instance.b (destroy)
|
|
aws_instance.a (destroy)
|
|
`
|