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.
This commit is contained in:
James Bardin 2020-09-09 12:23:23 -04:00
parent 883e4487a2
commit c9e581e58a
2 changed files with 98 additions and 12 deletions

View File

@ -11766,3 +11766,84 @@ output "outputs" {
// Destroying again from the empty state should not cause any errors either
destroy()
}
func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
variable "v" {}
module "mod" {
source = "./mod"
in = var.v
}
resource "test_resource" "a" {
value = var.v
depends_on = [module.mod]
lifecycle {
create_before_destroy = true
}
}
`,
"mod/main.tf": `
variable "in" {}
resource "test_resource" "a" {
value = var.in
}
`})
p := testProvider("test")
p.ApplyFn = testApplyFn
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
proposed := req.ProposedNewState.AsValueMap()
proposed["id"] = cty.UnknownVal(cty.String)
return providers.PlanResourceChangeResponse{
PlannedState: cty.ObjectVal(proposed),
RequiresReplace: []cty.Path{cty.Path{cty.GetAttrStep{Name: "value"}}},
}
}
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
Variables: InputValues{
"v": &InputValue{
Value: cty.StringVal("A"),
},
},
})
if _, diags := ctx.Plan(); diags.HasErrors() {
t.Fatalf("plan errors: %s", diags.Err())
}
state, diags := ctx.Apply()
if diags.HasErrors() {
t.Fatalf("apply errors: %s", diags.Err())
}
ctx = testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
Variables: InputValues{
"v": &InputValue{
Value: cty.StringVal("B"),
},
},
State: state,
})
if _, diags := ctx.Plan(); diags.HasErrors() {
t.Fatalf("plan errors: %s", diags.Err())
}
_, diags = ctx.Apply()
if diags.HasErrors() {
t.Fatalf("apply errors: %s", diags.Err())
}
}

View File

@ -43,8 +43,13 @@ func (t *ModuleExpansionTransformer) Transform(g *Graph) error {
// handled by the RemovedModuleTransformer, and those module closers are in
// the graph already, and need to be connected to their parent closers.
for _, v := range g.Vertices() {
// skip closers so they don't attach to themselves
if _, ok := v.(*nodeCloseModule); ok {
switch v.(type) {
case GraphNodeDestroyer:
// Destroy nodes can only be ordered relative to other resource
// instances.
continue
case *nodeCloseModule:
// a module closer cannot connect to itself
continue
}
@ -84,17 +89,17 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
Config: c.Module,
ModuleCall: modCall,
}
var v dag.Vertex = n
var expander dag.Vertex = n
if t.Concrete != nil {
v = t.Concrete(n)
expander = t.Concrete(n)
}
g.Add(v)
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, v)
g.Add(expander)
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, expander)
if parentNode != nil {
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(v), dag.VertexName(parentNode))
g.Connect(dag.BasicEdge(v, parentNode))
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(expander), dag.VertexName(parentNode))
g.Connect(dag.BasicEdge(expander, parentNode))
}
// 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
Addr: c.Path,
}
g.Add(closer)
g.Connect(dag.BasicEdge(closer, v))
g.Connect(dag.BasicEdge(closer, expander))
t.closers[c.Path.String()] = closer
for _, childV := range g.Vertices() {
// don't connect a node to itself
if childV == v {
if childV == expander {
continue
}
@ -126,13 +131,13 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
if path.Equal(c.Path) {
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(childV), c.Path)
g.Connect(dag.BasicEdge(childV, v))
g.Connect(dag.BasicEdge(childV, expander))
}
}
// Also visit child modules, recursively.
for _, cc := range c.Children {
if err := t.transform(g, cc, v); err != nil {
if err := t.transform(g, cc, expander); err != nil {
return err
}
}