make use of the new state Dependencies

Make use of the new Dependencies field in the instance state.

The inter-instance dependencies will be determined from the complete
reference graph, so that absolute addresses can be stored, rather than
just references within a module. The Dependencies are added to the node
in the same manner as state, i.e. via an "attacher" interface and
transformer.  This is because dependencies are calculated from the graph
itself, and not from the config.
This commit is contained in:
James Bardin 2019-10-17 16:05:27 -04:00
parent 5a0a0020a0
commit 42bb4a644c
15 changed files with 406 additions and 166 deletions

View File

@ -1328,19 +1328,7 @@ func testContext2Apply_destroyDependsOn(t *testing.T) {
// Test that destroy ordering is correct with dependencies only // Test that destroy ordering is correct with dependencies only
// in the state. // in the state.
func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
// It is possible for this to be racy, so we loop a number of times legacyState := MustShimLegacyState(&State{
// just to check.
for i := 0; i < 10; i++ {
testContext2Apply_destroyDependsOnStateOnly(t)
}
}
func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
m := testModule(t, "empty")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
state := MustShimLegacyState(&State{
Modules: []*ModuleState{ Modules: []*ModuleState{
&ModuleState{ &ModuleState{
Path: rootModulePath, Path: rootModulePath,
@ -1368,6 +1356,65 @@ func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
}, },
}) })
newState := states.NewState()
root := newState.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo"}`),
Dependencies: []addrs.AbsResource{},
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
root.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar"}`),
Dependencies: []addrs.AbsResource{
addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
},
Module: root.Addr,
},
},
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
// It is possible for this to be racy, so we loop a number of times
// just to check.
for i := 0; i < 10; i++ {
t.Run("legacy", func(t *testing.T) {
testContext2Apply_destroyDependsOnStateOnly(t, legacyState)
})
t.Run("new", func(t *testing.T) {
testContext2Apply_destroyDependsOnStateOnly(t, newState)
})
}
}
func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) {
m := testModule(t, "empty")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
// Record the order we see Apply // Record the order we see Apply
var actual []string var actual []string
var actualLock sync.Mutex var actualLock sync.Mutex
@ -1408,19 +1455,7 @@ func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
// Test that destroy ordering is correct with dependencies only // Test that destroy ordering is correct with dependencies only
// in the state within a module (GH-11749) // in the state within a module (GH-11749)
func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
// It is possible for this to be racy, so we loop a number of times legacyState := MustShimLegacyState(&State{
// just to check.
for i := 0; i < 10; i++ {
testContext2Apply_destroyDependsOnStateOnlyModule(t)
}
}
func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
m := testModule(t, "empty")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
state := MustShimLegacyState(&State{
Modules: []*ModuleState{ Modules: []*ModuleState{
&ModuleState{ &ModuleState{
Path: []string{"root", "child"}, Path: []string{"root", "child"},
@ -1448,6 +1483,66 @@ func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
}, },
}) })
newState := states.NewState()
child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
child.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo"}`),
Dependencies: []addrs.AbsResource{},
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
child.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar"}`),
Dependencies: []addrs.AbsResource{
addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
},
Module: child.Addr,
},
},
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
// It is possible for this to be racy, so we loop a number of times
// just to check.
for i := 0; i < 10; i++ {
t.Run("legacy", func(t *testing.T) {
testContext2Apply_destroyDependsOnStateOnlyModule(t, legacyState)
})
t.Run("new", func(t *testing.T) {
testContext2Apply_destroyDependsOnStateOnlyModule(t, newState)
})
}
}
func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) {
m := testModule(t, "empty")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
// Record the order we see Apply // Record the order we see Apply
var actual []string var actual []string
var actualLock sync.Mutex var actualLock sync.Mutex
@ -3466,6 +3561,9 @@ module.B:
provider = provider.aws provider = provider.aws
foo = foo foo = foo
type = aws_instance type = aws_instance
Dependencies:
module.A.aws_instance.foo
`) `)
} }
@ -8706,7 +8804,7 @@ aws_instance.foo:
type = aws_instance type = aws_instance
Dependencies: Dependencies:
module.child module.child.aws_instance.mod
module.child: module.child:
aws_instance.mod: aws_instance.mod:

View File

@ -1964,3 +1964,81 @@ func TestContext2Refresh_dataResourceDependsOn(t *testing.T) {
t.Fatalf("unexpected errors: %s", diags.Err()) t.Fatalf("unexpected errors: %s", diags.Err())
} }
} }
// verify that dependencies are updated in the state during refresh
func TestRefresh_updateDependencies(t *testing.T) {
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo"}`),
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
root.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`),
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
m := testModuleInline(t, map[string]string{
"main.tf": `
resource "aws_instance" "bar" {
foo = aws_instance.foo.id
}
resource "aws_instance" "foo" {
}`,
})
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[string]providers.Factory{
"aws": testProviderFuncFixed(p),
},
),
State: state,
})
result, diags := ctx.Refresh()
if diags.HasErrors() {
t.Fatalf("plan errors: %s", diags.Err())
}
expect := strings.TrimSpace(`
aws_instance.bar:
ID = bar
provider = provider.aws
foo = foo
Dependencies:
aws_instance.foo
aws_instance.foo:
ID = foo
provider = provider.aws
`)
checkStateString(t, result, expect)
}

View File

@ -24,7 +24,6 @@ import (
type EvalApply struct { type EvalApply struct {
Addr addrs.ResourceInstance Addr addrs.ResourceInstance
Config *configs.Resource Config *configs.Resource
Dependencies []addrs.Referenceable
State **states.ResourceInstanceObject State **states.ResourceInstanceObject
Change **plans.ResourceInstanceChange Change **plans.ResourceInstanceChange
ProviderAddr addrs.AbsProviderConfig ProviderAddr addrs.AbsProviderConfig
@ -271,10 +270,9 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
var newState *states.ResourceInstanceObject var newState *states.ResourceInstanceObject
if !newVal.IsNull() { // null value indicates that the object is deleted, so we won't set a new state in that case if !newVal.IsNull() { // null value indicates that the object is deleted, so we won't set a new state in that case
newState = &states.ResourceInstanceObject{ newState = &states.ResourceInstanceObject{
Status: states.ObjectReady, Status: states.ObjectReady,
Value: newVal, Value: newVal,
Private: resp.Private, Private: resp.Private,
Dependencies: n.Dependencies, // Should be populated by the caller from the StateDependencies method on the resource instance node
} }
} }

View File

@ -21,7 +21,6 @@ import (
type EvalReadData struct { type EvalReadData struct {
Addr addrs.ResourceInstance Addr addrs.ResourceInstance
Config *configs.Resource Config *configs.Resource
Dependencies []addrs.Referenceable
Provider *providers.Interface Provider *providers.Interface
ProviderAddr addrs.AbsProviderConfig ProviderAddr addrs.AbsProviderConfig
ProviderSchema **ProviderSchema ProviderSchema **ProviderSchema
@ -161,9 +160,8 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
} }
if n.OutputState != nil { if n.OutputState != nil {
state := &states.ResourceInstanceObject{ state := &states.ResourceInstanceObject{
Value: change.After, Value: change.After,
Status: states.ObjectPlanned, // because the partial value in the plan must be used for now Status: states.ObjectPlanned, // because the partial value in the plan must be used for now
Dependencies: n.Dependencies,
} }
*n.OutputState = state *n.OutputState = state
} }
@ -275,9 +273,8 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
}, },
} }
state := &states.ResourceInstanceObject{ state := &states.ResourceInstanceObject{
Value: change.After, Value: change.After,
Status: states.ObjectReady, // because we completed the read from the provider Status: states.ObjectReady, // because we completed the read from the provider
Dependencies: n.Dependencies,
} }
err = ctx.Hook(func(h Hook) (HookAction, error) { err = ctx.Hook(func(h Hook) (HookAction, error) {
@ -306,14 +303,13 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
// EvalReadDataApply is an EvalNode implementation that executes a data // EvalReadDataApply is an EvalNode implementation that executes a data
// resource's ReadDataApply method to read data from the data source. // resource's ReadDataApply method to read data from the data source.
type EvalReadDataApply struct { type EvalReadDataApply struct {
Addr addrs.ResourceInstance Addr addrs.ResourceInstance
Provider *providers.Interface Provider *providers.Interface
ProviderAddr addrs.AbsProviderConfig ProviderAddr addrs.AbsProviderConfig
ProviderSchema **ProviderSchema ProviderSchema **ProviderSchema
Output **states.ResourceInstanceObject Output **states.ResourceInstanceObject
Config *configs.Resource Config *configs.Resource
Change **plans.ResourceInstanceChange Change **plans.ResourceInstanceChange
StateReferences []addrs.Referenceable
} }
func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
@ -385,9 +381,8 @@ func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
if n.Output != nil { if n.Output != nil {
*n.Output = &states.ResourceInstanceObject{ *n.Output = &states.ResourceInstanceObject{
Value: newVal, Value: newVal,
Status: states.ObjectReady, Status: states.ObjectReady,
Dependencies: n.StateReferences,
} }
} }

View File

@ -200,6 +200,10 @@ type EvalWriteState struct {
// ProviderAddr is the address of the provider configuration that // ProviderAddr is the address of the provider configuration that
// produced the given object. // produced the given object.
ProviderAddr addrs.AbsProviderConfig ProviderAddr addrs.AbsProviderConfig
// Dependencies are the inter-resource dependencies to be stored in the
// state.
Dependencies []addrs.AbsResource
} }
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
@ -215,7 +219,6 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
if n.ProviderAddr.ProviderConfig.Type == "" { if n.ProviderAddr.ProviderConfig.Type == "" {
return nil, fmt.Errorf("failed to write state for %s, missing provider type", absAddr) return nil, fmt.Errorf("failed to write state for %s, missing provider type", absAddr)
} }
obj := *n.State obj := *n.State
if obj == nil || obj.Value.IsNull() { if obj == nil || obj.Value.IsNull() {
// No need to encode anything: we'll just write it directly. // No need to encode anything: we'll just write it directly.
@ -223,6 +226,10 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
log.Printf("[TRACE] EvalWriteState: removing state object for %s", absAddr) log.Printf("[TRACE] EvalWriteState: removing state object for %s", absAddr)
return nil, nil return nil, nil
} }
// store the new deps in the state
obj.Dependencies = n.Dependencies
if n.ProviderSchema == nil || *n.ProviderSchema == nil { if n.ProviderSchema == nil || *n.ProviderSchema == nil {
// Should never happen, unless our state object is nil // Should never happen, unless our state object is nil
panic("EvalWriteState used with pointer to nil ProviderSchema object") panic("EvalWriteState used with pointer to nil ProviderSchema object")

View File

@ -155,6 +155,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
// Connect references so ordering is correct // Connect references so ordering is correct
&ReferenceTransformer{}, &ReferenceTransformer{},
&AttachDependenciesTransformer{},
// Destruction ordering // Destruction ordering
&DestroyEdgeTransformer{ &DestroyEdgeTransformer{

View File

@ -165,6 +165,7 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
// Connect so that the references are ready for targeting. We'll // Connect so that the references are ready for targeting. We'll
// have to connect again later for providers and so on. // have to connect again later for providers and so on.
&ReferenceTransformer{}, &ReferenceTransformer{},
&AttachDependenciesTransformer{},
// Target // Target
&TargetsTransformer{ &TargetsTransformer{

View File

@ -169,7 +169,6 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
&EvalReadData{ &EvalReadData{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Dependencies: n.StateReferences(),
Provider: &provider, Provider: &provider,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,

View File

@ -3,7 +3,6 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"sort"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
@ -93,8 +92,8 @@ type NodeAbstractResourceInstance struct {
// The fields below will be automatically set using the Attach // The fields below will be automatically set using the Attach
// interfaces if you're running those transforms, but also be explicitly // interfaces if you're running those transforms, but also be explicitly
// set if you already have that information. // set if you already have that information.
ResourceState *states.Resource ResourceState *states.Resource
Dependencies []addrs.AbsResource
} }
var ( var (
@ -220,7 +219,8 @@ func (n *NodeAbstractResource) References() []*addrs.Reference {
func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { func (n *NodeAbstractResourceInstance) References() []*addrs.Reference {
// If we have a configuration attached then we'll delegate to our // If we have a configuration attached then we'll delegate to our
// embedded abstract resource, which knows how to extract dependencies // embedded abstract resource, which knows how to extract dependencies
// from configuration. // from configuration. If there is no config, then the dependencies will
// be connected during destroy from those stored in the state.
if n.Config != nil { if n.Config != nil {
if n.Schema == nil { if n.Schema == nil {
// We'll produce a log message about this out here so that // We'll produce a log message about this out here so that
@ -232,8 +232,10 @@ func (n *NodeAbstractResourceInstance) References() []*addrs.Reference {
return n.NodeAbstractResource.References() return n.NodeAbstractResource.References()
} }
// Otherwise, if we have state then we'll use the values stored in state // FIXME: remove once the deprecated DependsOn values have been removed from state
// as a fallback. // The state dependencies are now connected in a separate transformation as
// absolute addresses, but we need to keep this here until we can be sure
// that no state will need to use the old depends_on references.
if rs := n.ResourceState; rs != nil { if rs := n.ResourceState; rs != nil {
if s := rs.Instance(n.InstanceKey); s != nil { if s := rs.Instance(n.InstanceKey); s != nil {
// State is still storing dependencies as old-style strings, so we'll // State is still storing dependencies as old-style strings, so we'll
@ -248,23 +250,23 @@ func (n *NodeAbstractResourceInstance) References() []*addrs.Reference {
// https://github.com/hashicorp/terraform/issues/21407 // https://github.com/hashicorp/terraform/issues/21407
if s.Current == nil { if s.Current == nil {
log.Printf("[WARN] no current state found for %s", n.Name()) log.Printf("[WARN] no current state found for %s", n.Name())
} else { return nil
for _, addr := range s.Current.Dependencies { }
if addr == nil { for _, addr := range s.Current.DependsOn {
// Should never happen; indicates a bug in the state loader if addr == nil {
panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr())) // Should never happen; indicates a bug in the state loader
} panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr()))
// This is a little weird: we need to manufacture an addrs.Reference
// with a fake range here because the state isn't something we can
// make source references into.
result = append(result, &addrs.Reference{
Subject: addr,
SourceRange: tfdiags.SourceRange{
Filename: "(state file)",
},
})
} }
// This is a little weird: we need to manufacture an addrs.Reference
// with a fake range here because the state isn't something we can
// make source references into.
result = append(result, &addrs.Reference{
Subject: addr,
SourceRange: tfdiags.SourceRange{
Filename: "(state file)",
},
})
} }
return result return result
} }
@ -288,67 +290,17 @@ func dottedInstanceAddr(tr addrs.ResourceInstance) string {
return tr.Resource.String() + suffix return tr.Resource.String() + suffix
} }
// StateReferences returns the dependencies to put into the state for // StateDependencies returns the dependencies saved in the state.
// this resource. func (n *NodeAbstractResourceInstance) StateDependencies() []addrs.AbsResource {
func (n *NodeAbstractResourceInstance) StateReferences() []addrs.Referenceable { if rs := n.ResourceState; rs != nil {
selfAddrs := n.ReferenceableAddrs() if s := rs.Instance(n.InstanceKey); s != nil {
if s.Current != nil {
// Since we don't include the source location references in our return s.Current.Dependencies
// results from this method, we'll also filter out duplicates: }
// there's no point in listing the same object twice without
// that additional context.
seen := map[string]struct{}{}
// Pretend that we've already "seen" all of our own addresses so that we
// won't record self-references in the state. This can arise if, for
// example, a provisioner for a resource refers to the resource itself,
// which is valid (since provisioners always run after apply) but should
// not create an explicit dependency edge.
for _, selfAddr := range selfAddrs {
seen[selfAddr.String()] = struct{}{}
if riAddr, ok := selfAddr.(addrs.ResourceInstance); ok {
seen[riAddr.ContainingResource().String()] = struct{}{}
} }
} }
depsRaw := n.References() return nil
deps := make([]addrs.Referenceable, 0, len(depsRaw))
for _, d := range depsRaw {
subj := d.Subject
if mco, isOutput := subj.(addrs.ModuleCallOutput); isOutput {
// For state dependencies, we simplify outputs to just refer
// to the module as a whole. It's not really clear why we do this,
// but this logic is preserved from before the 0.12 rewrite of
// this function.
subj = mco.Call
}
k := subj.String()
if _, exists := seen[k]; exists {
continue
}
seen[k] = struct{}{}
switch tr := subj.(type) {
case addrs.ResourceInstance:
deps = append(deps, tr)
case addrs.Resource:
deps = append(deps, tr)
case addrs.ModuleCallInstance:
deps = append(deps, tr)
default:
// No other reference types are recorded in the state.
}
}
// We'll also sort them, since that'll avoid creating changes in the
// serialized state that make no semantic difference.
sort.Slice(deps, func(i, j int) bool {
// Simple string-based sort because we just care about consistency,
// not user-friendliness.
return deps[i].String() < deps[j].String()
})
return deps
} }
func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) {

View File

@ -28,12 +28,13 @@ type NodeApplyableResourceInstance struct {
} }
var ( var (
_ GraphNodeResource = (*NodeApplyableResourceInstance)(nil) _ GraphNodeResource = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeResourceInstance = (*NodeApplyableResourceInstance)(nil) _ GraphNodeResourceInstance = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil) _ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil) _ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil) _ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil) _ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil)
_ GraphNodeAttachDependencies = (*NodeApplyableResourceInstance)(nil)
) )
// GraphNodeAttachDestroyer // GraphNodeAttachDestroyer
@ -97,6 +98,11 @@ func (n *NodeApplyableResourceInstance) References() []*addrs.Reference {
return ret return ret
} }
// GraphNodeAttachDependencies
func (n *NodeApplyableResourceInstance) AttachDependencies(deps []addrs.AbsResource) {
n.Dependencies = deps
}
// GraphNodeEvalable // GraphNodeEvalable
func (n *NodeApplyableResourceInstance) EvalTree() EvalNode { func (n *NodeApplyableResourceInstance) EvalTree() EvalNode {
addr := n.ResourceInstanceAddr() addr := n.ResourceInstanceAddr()
@ -171,7 +177,6 @@ func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
&EvalReadData{ &EvalReadData{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Dependencies: n.StateReferences(),
Planned: &change, // setting this indicates that the result must be complete Planned: &change, // setting this indicates that the result must be complete
Provider: &provider, Provider: &provider,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
@ -341,7 +346,6 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
&EvalApply{ &EvalApply{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Dependencies: n.StateReferences(),
State: &state, State: &state,
Change: &diffApply, Change: &diffApply,
Provider: &provider, Provider: &provider,
@ -363,6 +367,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
State: &state, State: &state,
Dependencies: n.Dependencies,
}, },
&EvalApplyProvisioners{ &EvalApplyProvisioners{
Addr: addr.Resource, Addr: addr.Resource,
@ -384,6 +389,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
State: &state, State: &state,
Dependencies: n.Dependencies,
}, },
&EvalIf{ &EvalIf{
If: func(ctx EvalContext) (bool, error) { If: func(ctx EvalContext) (bool, error) {

View File

@ -78,8 +78,8 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
// Check and see if any of our dependencies have changes. // Check and see if any of our dependencies have changes.
changes := ctx.Changes() changes := ctx.Changes()
for _, d := range n.StateReferences() { for _, d := range n.References() {
ri, ok := d.(addrs.ResourceInstance) ri, ok := d.Subject.(addrs.ResourceInstance)
if !ok { if !ok {
continue continue
} }
@ -114,7 +114,6 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
&EvalReadData{ &EvalReadData{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Dependencies: n.StateReferences(),
Provider: &provider, Provider: &provider,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,

View File

@ -18,6 +18,10 @@ import (
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added. // NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
type NodeRefreshableManagedResource struct { type NodeRefreshableManagedResource struct {
*NodeAbstractResource *NodeAbstractResource
// We attach dependencies to the Resource during refresh, since the
// instances are instantiated during DynamicExpand.
Dependencies []addrs.AbsResource
} }
var ( var (
@ -27,8 +31,14 @@ var (
_ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil) _ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeResource = (*NodeRefreshableManagedResource)(nil) _ GraphNodeResource = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil) _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeAttachDependencies = (*NodeRefreshableManagedResource)(nil)
) )
// GraphNodeAttachDependencies
func (n *NodeRefreshableManagedResource) AttachDependencies(deps []addrs.AbsResource) {
n.Dependencies = deps
}
// GraphNodeDynamicExpandable // GraphNodeDynamicExpandable
func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
@ -58,6 +68,7 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph,
// Add the config and state since we don't do that via transforms // Add the config and state since we don't do that via transforms
a.Config = n.Config a.Config = n.Config
a.ResolvedProvider = n.ResolvedProvider a.ResolvedProvider = n.ResolvedProvider
a.Dependencies = n.Dependencies
return &NodeRefreshableManagedResourceInstance{ return &NodeRefreshableManagedResourceInstance{
NodeAbstractResourceInstance: a, NodeAbstractResourceInstance: a,

View File

@ -608,9 +608,6 @@ aws_instance.bar:
foo = true foo = true
type = aws_instance type = aws_instance
Dependencies:
module.child
module.child: module.child:
<no state> <no state>
Outputs: Outputs:
@ -666,6 +663,9 @@ module.child:
provider = provider.aws provider = provider.aws
type = aws_instance type = aws_instance
value = bar value = bar
Dependencies:
aws_instance.foo
` `
const testTerraformApplyOutputOrphanStr = ` const testTerraformApplyOutputOrphanStr = `
@ -784,17 +784,11 @@ aws_instance.foo.1:
provider = provider.aws provider = provider.aws
foo = number 1 foo = number 1
type = aws_instance type = aws_instance
Dependencies:
aws_instance.foo[0]
aws_instance.foo.2: aws_instance.foo.2:
ID = foo ID = foo
provider = provider.aws provider = provider.aws
foo = number 2 foo = number 2
type = aws_instance type = aws_instance
Dependencies:
aws_instance.foo[0]
` `
const testTerraformApplyProvisionerDiffStr = ` const testTerraformApplyProvisionerDiffStr = `
@ -859,7 +853,7 @@ aws_instance.a:
type = aws_instance type = aws_instance
Dependencies: Dependencies:
module.child module.child.aws_instance.child
module.child: module.child:
aws_instance.child: aws_instance.child:
@ -877,7 +871,7 @@ aws_instance.a:
type = aws_instance type = aws_instance
Dependencies: Dependencies:
module.child module.child.module.grandchild.aws_instance.c
module.child.grandchild: module.child.grandchild:
aws_instance.c: aws_instance.c:
@ -897,7 +891,7 @@ module.child:
type = aws_instance type = aws_instance
Dependencies: Dependencies:
module.grandchild module.child.module.grandchild.aws_instance.c
module.child.grandchild: module.child.grandchild:
aws_instance.c: aws_instance.c:
ID = foo ID = foo
@ -1305,9 +1299,6 @@ data.null_data_source.bar:
ID = foo ID = foo
provider = provider.null provider = provider.null
bar = yes bar = yes
Dependencies:
data.null_data_source.foo
data.null_data_source.foo: data.null_data_source.foo:
ID = foo ID = foo
provider = provider.null provider = provider.null

View File

@ -54,10 +54,14 @@ type DestroyEdgeTransformer struct {
func (t *DestroyEdgeTransformer) Transform(g *Graph) error { func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
// Build a map of what is being destroyed (by address string) to // Build a map of what is being destroyed (by address string) to
// the list of destroyers. Usually there will be at most one destroyer // the list of destroyers.
// per node, but we allow multiple if present for completeness.
destroyers := make(map[string][]GraphNodeDestroyer) destroyers := make(map[string][]GraphNodeDestroyer)
destroyerAddrs := make(map[string]addrs.AbsResourceInstance) destroyerAddrs := make(map[string]addrs.AbsResourceInstance)
// 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() { for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyer) dn, ok := v.(GraphNodeDestroyer)
if !ok { if !ok {
@ -66,6 +70,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
addrP := dn.DestroyAddr() addrP := dn.DestroyAddr()
if addrP == nil { if addrP == nil {
log.Printf("[WARN] DestroyEdgeTransformer: %q (%T) has no destroy address", dag.VertexName(dn), v)
continue continue
} }
addr := *addrP addr := *addrP
@ -74,6 +79,9 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key) log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key)
destroyers[key] = append(destroyers[key], dn) destroyers[key] = append(destroyers[key], dn)
destroyerAddrs[key] = addr 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 // If we aren't destroying anything, there will be no edges to make
@ -82,6 +90,29 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
return nil 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.
for _, ds := range destroyers {
for _, des := range ds {
ri, ok := des.(withDeps)
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))
g.Connect(dag.BasicEdge(desDep, des))
}
}
}
}
// Go through and connect creators to destroyers. Going along with // Go through and connect creators to destroyers. Going along with
// our example, this makes: A_d => A // our example, this makes: A_d => A
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
@ -95,13 +126,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
continue continue
} }
key := addr.String() for _, d := range destroyers[addr.String()] {
ds := destroyers[key]
if len(ds) == 0 {
continue
}
for _, d := range ds {
// For illustrating our example // For illustrating our example
a_d := d.(dag.Vertex) a_d := d.(dag.Vertex)
a := v a := v
@ -124,6 +149,8 @@ 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 // 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 // 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 // the resource is in the graph and ask for references. We have to do this

View File

@ -6,9 +6,11 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/lang" "github.com/hashicorp/terraform/lang"
"github.com/hashicorp/terraform/states"
) )
// GraphNodeReferenceable must be implemented by any node that represents // GraphNodeReferenceable must be implemented by any node that represents
@ -37,6 +39,11 @@ type GraphNodeReferencer interface {
References() []*addrs.Reference References() []*addrs.Reference
} }
type GraphNodeAttachDependencies interface {
GraphNodeResource
AttachDependencies([]addrs.AbsResource)
}
// GraphNodeReferenceOutside is an interface that can optionally be implemented. // GraphNodeReferenceOutside is an interface that can optionally be implemented.
// A node that implements it can specify that its own referenceable addresses // A node that implements it can specify that its own referenceable addresses
// and/or the addresses it references are in a different module than the // and/or the addresses it references are in a different module than the
@ -84,11 +91,81 @@ func (t *ReferenceTransformer) Transform(g *Graph) error {
for _, parent := range parents { for _, parent := range parents {
g.Connect(dag.BasicEdge(v, parent)) g.Connect(dag.BasicEdge(v, parent))
} }
if len(parents) > 0 {
continue
}
} }
return nil return nil
} }
// AttachDependenciesTransformer records all resource dependencies for each
// instance, and attaches the addresses to the node itself. Managed resource
// will record these in the state for proper ordering of destroy operations.
type AttachDependenciesTransformer struct {
Config *configs.Config
State *states.State
Schemas *Schemas
}
func (t AttachDependenciesTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
attacher, ok := v.(GraphNodeAttachDependencies)
if !ok {
continue
}
selfAddr := attacher.ResourceAddr()
// Data sources don't need to track destroy dependencies
if selfAddr.Resource.Mode == addrs.DataResourceMode {
continue
}
ans, err := g.Ancestors(v)
if err != nil {
return err
}
// dedupe addrs when there's multiple instances involved, or
// multiple paths in the un-reduced graph
depMap := map[string]addrs.AbsResource{}
for _, d := range ans.List() {
var addr addrs.AbsResource
switch d := d.(type) {
case GraphNodeResourceInstance:
instAddr := d.ResourceInstanceAddr()
addr = instAddr.Resource.Resource.Absolute(instAddr.Module)
case GraphNodeResource:
addr = d.ResourceAddr()
default:
continue
}
// Data sources don't need to track destroy dependencies
if addr.Resource.Mode == addrs.DataResourceMode {
continue
}
if addr.Equal(selfAddr) {
continue
}
depMap[addr.String()] = addr
}
deps := make([]addrs.AbsResource, 0, len(depMap))
for _, d := range depMap {
deps = append(deps, d)
}
log.Printf("[TRACE] AttachDependenciesTransformer: %s depends on %s", attacher.ResourceAddr(), deps)
attacher.AttachDependencies(deps)
}
return nil
}
// DestroyReferenceTransformer is a GraphTransformer that reverses the edges // DestroyReferenceTransformer is a GraphTransformer that reverses the edges
// for locals and outputs that depend on other nodes which will be // for locals and outputs that depend on other nodes which will be
// removed during destroy. If a destroy node is evaluated before the local or // removed during destroy. If a destroy node is evaluated before the local or