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:
parent
5a0a0020a0
commit
42bb4a644c
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -274,7 +273,6 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -163,7 +162,6 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -277,7 +275,6 @@ 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) {
|
||||||
|
@ -313,7 +310,6 @@ type EvalReadDataApply struct {
|
||||||
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) {
|
||||||
|
@ -387,7 +383,6 @@ func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
*n.Output = &states.ResourceInstanceObject{
|
*n.Output = &states.ResourceInstanceObject{
|
||||||
Value: newVal,
|
Value: newVal,
|
||||||
Status: states.ObjectReady,
|
Status: states.ObjectReady,
|
||||||
Dependencies: n.StateReferences,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,8 +250,9 @@ 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 {
|
}
|
||||||
|
for _, addr := range s.Current.DependsOn {
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
// Should never happen; indicates a bug in the state loader
|
// Should never happen; indicates a bug in the state loader
|
||||||
panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr()))
|
panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr()))
|
||||||
|
@ -265,7 +268,6 @@ func (n *NodeAbstractResourceInstance) References() []*addrs.Reference {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ var (
|
||||||
_ 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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue