Skip to content

Commit c9e581e

Browse files
committed
don't connect module closers to destroy nodes
One of the tenants of the graph transformations is that resource destroy nodes can only be ordered relative to other resources, and can't be referenced directly. This was broken by the module close node which naively connected to all module nodes, creating cycles in some cases when edges are reversed from CreateBeforeDestroy.
1 parent 883e448 commit c9e581e

File tree

2 files changed

+98
-12
lines changed

2 files changed

+98
-12
lines changed

terraform/context_apply_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11766,3 +11766,84 @@ output "outputs" {
1176611766
// Destroying again from the empty state should not cause any errors either
1176711767
destroy()
1176811768
}
11769+
11770+
func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) {
11771+
m := testModuleInline(t, map[string]string{
11772+
"main.tf": `
11773+
variable "v" {}
11774+
11775+
module "mod" {
11776+
source = "./mod"
11777+
in = var.v
11778+
}
11779+
11780+
resource "test_resource" "a" {
11781+
value = var.v
11782+
depends_on = [module.mod]
11783+
lifecycle {
11784+
create_before_destroy = true
11785+
}
11786+
}
11787+
`,
11788+
"mod/main.tf": `
11789+
variable "in" {}
11790+
11791+
resource "test_resource" "a" {
11792+
value = var.in
11793+
}
11794+
`})
11795+
11796+
p := testProvider("test")
11797+
p.ApplyFn = testApplyFn
11798+
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
11799+
proposed := req.ProposedNewState.AsValueMap()
11800+
proposed["id"] = cty.UnknownVal(cty.String)
11801+
return providers.PlanResourceChangeResponse{
11802+
PlannedState: cty.ObjectVal(proposed),
11803+
RequiresReplace: []cty.Path{cty.Path{cty.GetAttrStep{Name: "value"}}},
11804+
}
11805+
}
11806+
11807+
ctx := testContext2(t, &ContextOpts{
11808+
Config: m,
11809+
Providers: map[addrs.Provider]providers.Factory{
11810+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
11811+
},
11812+
Variables: InputValues{
11813+
"v": &InputValue{
11814+
Value: cty.StringVal("A"),
11815+
},
11816+
},
11817+
})
11818+
11819+
if _, diags := ctx.Plan(); diags.HasErrors() {
11820+
t.Fatalf("plan errors: %s", diags.Err())
11821+
}
11822+
11823+
state, diags := ctx.Apply()
11824+
if diags.HasErrors() {
11825+
t.Fatalf("apply errors: %s", diags.Err())
11826+
}
11827+
11828+
ctx = testContext2(t, &ContextOpts{
11829+
Config: m,
11830+
Providers: map[addrs.Provider]providers.Factory{
11831+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
11832+
},
11833+
Variables: InputValues{
11834+
"v": &InputValue{
11835+
Value: cty.StringVal("B"),
11836+
},
11837+
},
11838+
State: state,
11839+
})
11840+
11841+
if _, diags := ctx.Plan(); diags.HasErrors() {
11842+
t.Fatalf("plan errors: %s", diags.Err())
11843+
}
11844+
11845+
_, diags = ctx.Apply()
11846+
if diags.HasErrors() {
11847+
t.Fatalf("apply errors: %s", diags.Err())
11848+
}
11849+
}

terraform/transform_module_expansion.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ func (t *ModuleExpansionTransformer) Transform(g *Graph) error {
4343
// handled by the RemovedModuleTransformer, and those module closers are in
4444
// the graph already, and need to be connected to their parent closers.
4545
for _, v := range g.Vertices() {
46-
// skip closers so they don't attach to themselves
47-
if _, ok := v.(*nodeCloseModule); ok {
46+
switch v.(type) {
47+
case GraphNodeDestroyer:
48+
// Destroy nodes can only be ordered relative to other resource
49+
// instances.
50+
continue
51+
case *nodeCloseModule:
52+
// a module closer cannot connect to itself
4853
continue
4954
}
5055

@@ -84,17 +89,17 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
8489
Config: c.Module,
8590
ModuleCall: modCall,
8691
}
87-
var v dag.Vertex = n
92+
var expander dag.Vertex = n
8893
if t.Concrete != nil {
89-
v = t.Concrete(n)
94+
expander = t.Concrete(n)
9095
}
9196

92-
g.Add(v)
93-
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, v)
97+
g.Add(expander)
98+
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, expander)
9499

95100
if parentNode != nil {
96-
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(v), dag.VertexName(parentNode))
97-
g.Connect(dag.BasicEdge(v, parentNode))
101+
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(expander), dag.VertexName(parentNode))
102+
g.Connect(dag.BasicEdge(expander, parentNode))
98103
}
99104

100105
// Add the closer (which acts as the root module node) to provide a
@@ -103,12 +108,12 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
103108
Addr: c.Path,
104109
}
105110
g.Add(closer)
106-
g.Connect(dag.BasicEdge(closer, v))
111+
g.Connect(dag.BasicEdge(closer, expander))
107112
t.closers[c.Path.String()] = closer
108113

109114
for _, childV := range g.Vertices() {
110115
// don't connect a node to itself
111-
if childV == v {
116+
if childV == expander {
112117
continue
113118
}
114119

@@ -126,13 +131,13 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
126131

127132
if path.Equal(c.Path) {
128133
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(childV), c.Path)
129-
g.Connect(dag.BasicEdge(childV, v))
134+
g.Connect(dag.BasicEdge(childV, expander))
130135
}
131136
}
132137

133138
// Also visit child modules, recursively.
134139
for _, cc := range c.Children {
135-
if err := t.transform(g, cc, v); err != nil {
140+
if err := t.transform(g, cc, expander); err != nil {
136141
return err
137142
}
138143
}

0 commit comments

Comments
 (0)