use Dependencies to connect creator and destroyer

The DestroyEdgeTransformer cannot determine ordering from the graph when
the destroyers are from orphaned resources, because there are no
references to resolve. The new stored Dependencies provides what we need
to connect the instances in this case.

We also add the StateDependencies method directly in the
GraphNodeResourceInstance interface, since all instances already
implement this, and we don't need another optional interface to check.

The old code in DestroyEdgeTransformer may no longer be needed in the
long run, but that can be determined separately, since too many of the
tests start with an incomplete state and rely on the Dependencies being
determined from the configuration alone.
This commit is contained in:
James Bardin 2019-10-30 15:59:34 -04:00
parent 10152da478
commit 46dbb3dde5
6 changed files with 63 additions and 34 deletions

View File

@ -260,8 +260,10 @@ func testState() *states.State {
// The weird whitespace here is reflective of how this would
// get written out in a real state file, due to the indentation
// of all of the containing wrapping objects and arrays.
AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"),
Status: states.ObjectReady,
AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"),
Status: states.ObjectReady,
Dependencies: []addrs.AbsResource{},
DependsOn: []addrs.Referenceable{},
},
addrs.ProviderConfig{
Type: "test",

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/provisioners"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
@ -2984,7 +2985,9 @@ func TestContext2Apply_orphanResource(t *testing.T) {
AttrsJSON: []byte(`{}`),
}, providerAddr)
})
if !cmp.Equal(state, want) {
// compare the marshaled form to easily remove empty and nil slices
if !statefile.StatesMarshalEqual(state, want) {
t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state))
}

View File

@ -572,8 +572,10 @@ test_object.b
`)
instanceGraph := filterInstances(g)
if instanceGraph.String() != expected {
t.Fatalf("expected:\n%s\ngot:\n%s", expected, instanceGraph)
got := strings.TrimSpace(instanceGraph.String())
if got != expected {
t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
}
}

View File

@ -34,6 +34,10 @@ type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Ve
// configuration.
type GraphNodeResourceInstance interface {
ResourceInstanceAddr() addrs.AbsResourceInstance
// StateDependencies returns any inter-resource dependencies that are
// stored in the state.
StateDependencies() []addrs.AbsResource
}
// NodeAbstractResource represents a resource that has no associated

View File

@ -58,30 +58,35 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
destroyers := make(map[string][]GraphNodeDestroyer)
destroyerAddrs := make(map[string]addrs.AbsResourceInstance)
// Record the creators, which will need to depend on the destroyers if they
// are only being updated.
creators := make(map[string]GraphNodeCreator)
// destroyersByResource records each destroyer by the AbsResourceAddress.
// We use this because dependencies are only referenced as resources, but we
// will want to connect all the individual instances for correct ordering.
destroyersByResource := make(map[string][]GraphNodeDestroyer)
for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyer)
if !ok {
continue
switch n := v.(type) {
case GraphNodeDestroyer:
addrP := n.DestroyAddr()
if addrP == nil {
log.Printf("[WARN] DestroyEdgeTransformer: %q (%T) has no destroy address", dag.VertexName(n), v)
continue
}
addr := *addrP
key := addr.String()
log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(n), v, key)
destroyers[key] = append(destroyers[key], n)
destroyerAddrs[key] = addr
resAddr := addr.Resource.Absolute(addr.Module).String()
destroyersByResource[resAddr] = append(destroyersByResource[resAddr], n)
case GraphNodeCreator:
addr := n.CreateAddr()
creators[addr.String()] = n
}
addrP := dn.DestroyAddr()
if addrP == nil {
log.Printf("[WARN] DestroyEdgeTransformer: %q (%T) has no destroy address", dag.VertexName(dn), v)
continue
}
addr := *addrP
key := addr.String()
log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key)
destroyers[key] = append(destroyers[key], dn)
destroyerAddrs[key] = addr
resAddr := addr.Resource.Absolute(addr.Module).String()
destroyersByResource[resAddr] = append(destroyersByResource[resAddr], dn)
}
// If we aren't destroying anything, there will be no edges to make
@ -90,22 +95,17 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
return nil
}
type withDeps interface {
StateDependencies() []addrs.AbsResource
}
// Connect destroy dependencies directly as stored in the state in case
// there's no configuration to create the edges otherwise.
// Connect destroy despendencies as stored in the state
for _, ds := range destroyers {
for _, des := range ds {
ri, ok := des.(withDeps)
ri, ok := des.(GraphNodeResourceInstance)
if !ok {
continue
}
for _, resAddr := range ri.StateDependencies() {
for _, desDep := range destroyersByResource[resAddr.String()] {
log.Printf("[TRACE] DestroyEdgeTransformer: %s depends on %s\n", dag.VertexName(des), dag.VertexName(desDep))
log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(desDep), dag.VertexName(des))
g.Connect(dag.BasicEdge(desDep, des))
}
@ -113,6 +113,22 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
}
}
// connect creators to any destroyers on which they may depend
for _, c := range creators {
ri, ok := c.(GraphNodeResourceInstance)
if !ok {
continue
}
for _, resAddr := range ri.StateDependencies() {
for _, desDep := range destroyersByResource[resAddr.String()] {
log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(c), dag.VertexName(desDep))
g.Connect(dag.BasicEdge(c, desDep))
}
}
}
// Go through and connect creators to destroyers. Going along with
// our example, this makes: A_d => A
for _, v := range g.Vertices() {
@ -149,8 +165,6 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
}
}
// FIXME: connect StateDependencies in here somewhere
// This is strange but is the easiest way to get the dependencies
// of a node that is being destroyed. We use another graph to make sure
// the resource is in the graph and ask for references. We have to do this

View File

@ -3,6 +3,7 @@ package terraform
import (
"fmt"
"log"
"sort"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs"
@ -158,11 +159,14 @@ func (t AttachDependenciesTransformer) Transform(g *Graph) error {
for _, d := range depMap {
deps = append(deps, d)
}
sort.Slice(deps, func(i, j int) bool {
return deps[i].String() < deps[j].String()
})
log.Printf("[TRACE] AttachDependenciesTransformer: %s depends on %s", attacher.ResourceAddr(), deps)
attacher.AttachDependencies(deps)
}
return nil
}