|
|
|
|
@ -19,6 +19,7 @@ import (
|
|
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
|
|
|
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type deferredActionsTest struct {
|
|
|
|
|
@ -929,7 +930,7 @@ resource "test" "c" {
|
|
|
|
|
wantActions: map[string]plans.Action{
|
|
|
|
|
"test.b": plans.Create,
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]providers.DeferredReason{},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{},
|
|
|
|
|
allowWarnings: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
@ -991,9 +992,9 @@ resource "test" "b" {
|
|
|
|
|
wantActions: map[string]plans.Action{
|
|
|
|
|
"test.c": plans.Create,
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]providers.DeferredReason{
|
|
|
|
|
"test.a[\"*\"]": providers.DeferredReasonInstanceCountUnknown,
|
|
|
|
|
"test.b": providers.DeferredReasonDeferredPrereq,
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a[\"*\"]": {Reason: providers.DeferredReasonInstanceCountUnknown, Action: plans.Create},
|
|
|
|
|
"test.b": {Reason: providers.DeferredReasonDeferredPrereq, Action: plans.Create},
|
|
|
|
|
},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
"c": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
@ -1047,7 +1048,7 @@ resource "test" "b" {
|
|
|
|
|
"test.b": plans.Create,
|
|
|
|
|
"test.c": plans.NoOp,
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]providers.DeferredReason{},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
"a:0": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("a:0"),
|
|
|
|
|
@ -1125,8 +1126,8 @@ resource "test" "b" {
|
|
|
|
|
"test.a[0]": plans.Create,
|
|
|
|
|
"test.a[1]": plans.Create,
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]providers.DeferredReason{
|
|
|
|
|
"test.b[\"*\"]": providers.DeferredReasonInstanceCountUnknown,
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.b[\"*\"]": {Reason: providers.DeferredReasonInstanceCountUnknown, Action: plans.Create},
|
|
|
|
|
},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
"a:0": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
@ -1175,7 +1176,7 @@ resource "test" "b" {
|
|
|
|
|
"test.b[\"a:0\"]": plans.Create,
|
|
|
|
|
"test.b[\"a:1\"]": plans.Create,
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: make(map[string]providers.DeferredReason),
|
|
|
|
|
wantDeferred: make(map[string]ExpectedDeferred),
|
|
|
|
|
allowWarnings: true,
|
|
|
|
|
complete: false, // because we still did targeting
|
|
|
|
|
},
|
|
|
|
|
@ -1605,7 +1606,7 @@ output "a" {
|
|
|
|
|
output "b" {
|
|
|
|
|
value = test.b
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
@ -1630,10 +1631,393 @@ output "b" {
|
|
|
|
|
})),
|
|
|
|
|
"b": cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]providers.DeferredReason{
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
// data.test.a is not part of the plan so we can only
|
|
|
|
|
// observe the indirect consequence on the resource.
|
|
|
|
|
"test.b": providers.DeferredReasonDeferredPrereq,
|
|
|
|
|
"test.b": {Reason: providers.DeferredReasonDeferredPrereq, Action: plans.Create},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planCreateResourceChange is a test that covers the behavior of planning a resource that is being created.
|
|
|
|
|
planCreateResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.UnknownVal(cty.String),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{
|
|
|
|
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.Create},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planUpdateResourceChange is a test that covers the behavior of planning a resource that is being updated
|
|
|
|
|
planUpdateResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "old_value",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.UnknownVal(cty.String),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{
|
|
|
|
|
"a": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("old_value"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.NullVal(cty.String),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.Update},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planNoOpResourceChange is a test that covers the behavior of planning a resource that is the same as the current state.
|
|
|
|
|
planNoOpResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "deferred_resource_change",
|
|
|
|
|
"output": "computed_output",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("computed_output"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{
|
|
|
|
|
"a": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("computed_output"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.NoOp},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planReplaceResourceChange is a test that covers the behavior of planning a resource that the provider
|
|
|
|
|
// marks as needing replacement.
|
|
|
|
|
planReplaceResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "old_value",
|
|
|
|
|
"output": "mark_for_replacement", // tells the mock provider to replace the resource
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("mark_for_replacement"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{
|
|
|
|
|
"a": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("old_value"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("mark_for_replacement"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.DeleteThenCreate},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planForceReplaceResourceChange is a test that covers the behavior of planning a resource that is marked for replacement
|
|
|
|
|
planForceReplaceResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "old_value",
|
|
|
|
|
"output": "computed_output",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
buildOpts: func(opts *PlanOpts) {
|
|
|
|
|
opts.ForceReplace = []addrs.AbsResourceInstance{
|
|
|
|
|
{
|
|
|
|
|
Module: addrs.RootModuleInstance,
|
|
|
|
|
Resource: addrs.ResourceInstance{
|
|
|
|
|
Resource: addrs.Resource{
|
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
|
Type: "test",
|
|
|
|
|
Name: "a",
|
|
|
|
|
},
|
|
|
|
|
Key: addrs.NoKey,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("computed_output"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{
|
|
|
|
|
"a": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("old_value"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.StringVal("computed_output"),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.DeleteThenCreate},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planDeleteResourceChange is a test that covers the behavior of planning a resource that is removed from the config.
|
|
|
|
|
planDeleteResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
// Empty config, expect to delete everything
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "deferred_resource_change",
|
|
|
|
|
"output": "computed_output",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.Delete},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// planDestroyResourceChange is a test that covers the behavior of planning a resource
|
|
|
|
|
planDestroyResourceChange = deferredActionsTest{
|
|
|
|
|
configs: map[string]string{
|
|
|
|
|
"main.tf": `
|
|
|
|
|
resource "test" "a" {
|
|
|
|
|
name = "deferred_resource_change"
|
|
|
|
|
}
|
|
|
|
|
output "a" {
|
|
|
|
|
value = test.a
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
state: states.BuildState(func(state *states.SyncState) {
|
|
|
|
|
state.SetResourceInstanceCurrent(
|
|
|
|
|
mustResourceInstanceAddr("test.a"),
|
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
|
AttrsJSON: mustParseJson(map[string]interface{}{
|
|
|
|
|
"name": "deferred_resource_change",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
stages: []deferredActionsTestStage{
|
|
|
|
|
{
|
|
|
|
|
buildOpts: func(opts *PlanOpts) {
|
|
|
|
|
opts.Mode = plans.DestroyMode
|
|
|
|
|
},
|
|
|
|
|
inputs: map[string]cty.Value{},
|
|
|
|
|
wantPlanned: map[string]cty.Value{
|
|
|
|
|
"deferred_resource_change": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
|
"name": cty.StringVal("deferred_resource_change"),
|
|
|
|
|
"upstream_names": cty.NullVal(cty.Set(cty.String)),
|
|
|
|
|
"output": cty.UnknownVal(cty.String),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
wantActions: map[string]plans.Action{},
|
|
|
|
|
wantApplied: map[string]cty.Value{
|
|
|
|
|
// The all resources will be deferred, so shouldn't
|
|
|
|
|
// have any action at this stage.
|
|
|
|
|
},
|
|
|
|
|
wantOutputs: map[string]cty.Value{},
|
|
|
|
|
wantDeferred: map[string]ExpectedDeferred{
|
|
|
|
|
"test.a": {Reason: providers.DeferredReasonProviderConfigUnknown, Action: plans.Delete},
|
|
|
|
|
},
|
|
|
|
|
complete: false,
|
|
|
|
|
},
|
|
|
|
|
@ -1657,7 +2041,15 @@ func TestContextApply_deferredActions(t *testing.T) {
|
|
|
|
|
"custom_conditions_with_orphans": customConditionsWithOrphansTest,
|
|
|
|
|
"resource_read": resourceReadTest,
|
|
|
|
|
"data_read": readDataSourceTest,
|
|
|
|
|
"plan_create_resource_change": planCreateResourceChange,
|
|
|
|
|
"plan_update_resource_change": planUpdateResourceChange,
|
|
|
|
|
"plan_noop_resource_change": planNoOpResourceChange,
|
|
|
|
|
"plan_replace_resource_change": planReplaceResourceChange,
|
|
|
|
|
"plan_force_replace_resource_change": planForceReplaceResourceChange,
|
|
|
|
|
"plan_delete_resource_change": planDeleteResourceChange,
|
|
|
|
|
"plan_destroy_resource_change": planDestroyResourceChange,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, test := range tests {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
if test.skip {
|
|
|
|
|
@ -1712,37 +2104,43 @@ func TestContextApply_deferredActions(t *testing.T) {
|
|
|
|
|
stage.buildOpts(opts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(cfg, state, opts)
|
|
|
|
|
|
|
|
|
|
// We expect the correct planned changes and no diagnostics.
|
|
|
|
|
if stage.allowWarnings {
|
|
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
} else {
|
|
|
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if plan.Complete != stage.complete {
|
|
|
|
|
t.Errorf("wrong completion status in plan: got %v, want %v", plan.Complete, stage.complete)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
provider.plannedChanges.Test(t, stage.wantPlanned)
|
|
|
|
|
|
|
|
|
|
// We expect the correct actions.
|
|
|
|
|
gotActions := make(map[string]plans.Action)
|
|
|
|
|
for _, cs := range plan.Changes.Resources {
|
|
|
|
|
gotActions[cs.Addr.String()] = cs.Action
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantActions, gotActions); diff != "" {
|
|
|
|
|
t.Errorf("wrong actions in plan\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
var plan *plans.Plan
|
|
|
|
|
t.Run("plan", func(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
plan, diags = ctx.Plan(cfg, state, opts)
|
|
|
|
|
|
|
|
|
|
// We expect the correct planned changes and no diagnostics.
|
|
|
|
|
if stage.allowWarnings {
|
|
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
} else {
|
|
|
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if plan.Complete != stage.complete {
|
|
|
|
|
t.Errorf("wrong completion status in plan: got %v, want %v", plan.Complete, stage.complete)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
provider.plannedChanges.Test(t, stage.wantPlanned)
|
|
|
|
|
|
|
|
|
|
// We expect the correct actions.
|
|
|
|
|
gotActions := make(map[string]plans.Action)
|
|
|
|
|
for _, cs := range plan.Changes.Resources {
|
|
|
|
|
gotActions[cs.Addr.String()] = cs.Action
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantActions, gotActions); diff != "" {
|
|
|
|
|
t.Errorf("wrong actions in plan\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gotDeferred := make(map[string]ExpectedDeferred)
|
|
|
|
|
for _, dc := range plan.DeferredResources {
|
|
|
|
|
gotDeferred[dc.ChangeSrc.Addr.String()] = ExpectedDeferred{Reason: dc.DeferredReason, Action: dc.ChangeSrc.Action}
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantDeferred, gotDeferred); diff != "" {
|
|
|
|
|
t.Errorf("wrong deferred reasons or actions in plan\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gotDeferred := make(map[string]ExpectedDeferred)
|
|
|
|
|
for _, dc := range plan.DeferredResources {
|
|
|
|
|
gotDeferred[dc.ChangeSrc.Addr.String()] = ExpectedDeferred{Reason: dc.DeferredReason, Action: dc.ChangeSrc.Action}
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantDeferred, gotDeferred); diff != "" {
|
|
|
|
|
t.Errorf("wrong deferred reasons or actions in plan\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if stage.wantApplied == nil {
|
|
|
|
|
// Don't execute the apply stage if wantApplied is nil.
|
|
|
|
|
@ -1750,31 +2148,34 @@ func TestContextApply_deferredActions(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.Mode == plans.RefreshOnlyMode {
|
|
|
|
|
// Don't execute the apply stage if wantApplied is nil.
|
|
|
|
|
// Don't execute the apply stage if mode is refresh-only.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updatedState, diags := ctx.Apply(plan, cfg, nil)
|
|
|
|
|
|
|
|
|
|
// We expect the correct applied changes and no diagnostics.
|
|
|
|
|
if stage.allowWarnings {
|
|
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
} else {
|
|
|
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
|
}
|
|
|
|
|
provider.appliedChanges.Test(t, stage.wantApplied)
|
|
|
|
|
|
|
|
|
|
// We also want the correct output values.
|
|
|
|
|
gotOutputs := make(map[string]cty.Value)
|
|
|
|
|
for name, output := range updatedState.RootOutputValues {
|
|
|
|
|
gotOutputs[name] = output.Value
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantOutputs, gotOutputs, ctydebug.CmpOptions); diff != "" {
|
|
|
|
|
t.Errorf("wrong output values\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the state for the next stage.
|
|
|
|
|
state = updatedState
|
|
|
|
|
t.Run("apply", func(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
updatedState, diags := ctx.Apply(plan, cfg, nil)
|
|
|
|
|
|
|
|
|
|
// We expect the correct applied changes and no diagnostics.
|
|
|
|
|
if stage.allowWarnings {
|
|
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
} else {
|
|
|
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
|
}
|
|
|
|
|
provider.appliedChanges.Test(t, stage.wantApplied)
|
|
|
|
|
|
|
|
|
|
// We also want the correct output values.
|
|
|
|
|
gotOutputs := make(map[string]cty.Value)
|
|
|
|
|
for name, output := range updatedState.RootOutputValues {
|
|
|
|
|
gotOutputs[name] = output.Value
|
|
|
|
|
}
|
|
|
|
|
if diff := cmp.Diff(stage.wantOutputs, gotOutputs, ctydebug.CmpOptions); diff != "" {
|
|
|
|
|
t.Errorf("wrong output values\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the state for the next stage.
|
|
|
|
|
state = updatedState
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
@ -1883,10 +2284,19 @@ func (provider *deferredActionsProvider) Provider() providers.Interface {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
PlanResourceChangeFn: func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
|
|
|
var deferred *providers.Deferred
|
|
|
|
|
var requiresReplace []cty.Path
|
|
|
|
|
if req.ProposedNewState.IsNull() {
|
|
|
|
|
// Then we're deleting a concrete instance.
|
|
|
|
|
if key := req.PriorState.GetAttr("name"); key.IsKnown() && key.AsString() == "deferred_resource_change" {
|
|
|
|
|
deferred = &providers.Deferred{
|
|
|
|
|
Reason: providers.DeferredReasonProviderConfigUnknown,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
|
|
|
PlannedState: req.ProposedNewState,
|
|
|
|
|
Deferred: deferred,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1896,22 +2306,38 @@ func (provider *deferredActionsProvider) Provider() providers.Interface {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plannedState := req.ProposedNewState
|
|
|
|
|
if key == "deferred_resource_change" {
|
|
|
|
|
deferred = &providers.Deferred{
|
|
|
|
|
Reason: providers.DeferredReasonProviderConfigUnknown,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if plannedState.GetAttr("output").IsNull() {
|
|
|
|
|
plannedStateValues := req.ProposedNewState.AsValueMap()
|
|
|
|
|
plannedStateValues["output"] = cty.UnknownVal(cty.String)
|
|
|
|
|
plannedState = cty.ObjectVal(plannedStateValues)
|
|
|
|
|
} else if plannedState.GetAttr("output").AsString() == "mark_for_replacement" {
|
|
|
|
|
requiresReplace = append(requiresReplace, cty.GetAttrPath("name"), cty.GetAttrPath("output"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
provider.plannedChanges.Set(key, plannedState)
|
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
|
|
|
PlannedState: plannedState,
|
|
|
|
|
PlannedState: plannedState,
|
|
|
|
|
Deferred: deferred,
|
|
|
|
|
RequiresReplace: requiresReplace,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
ApplyResourceChangeFn: func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
|
|
|
key := req.Config.GetAttr("name").AsString()
|
|
|
|
|
var key string
|
|
|
|
|
if v := req.Config.GetAttr("name"); v.IsKnown() {
|
|
|
|
|
key = req.Config.GetAttr("name").AsString()
|
|
|
|
|
} else {
|
|
|
|
|
key = "<unknown>"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newState := req.PlannedState
|
|
|
|
|
if !newState.GetAttr("output").IsKnown() {
|
|
|
|
|
|
|
|
|
|
if req.PlannedState.IsKnown() && !req.PlannedState.IsNull() && !newState.GetAttr("output").IsKnown() {
|
|
|
|
|
newStateValues := req.PlannedState.AsValueMap()
|
|
|
|
|
newStateValues["output"] = cty.StringVal(key)
|
|
|
|
|
newState = cty.ObjectVal(newStateValues)
|
|
|
|
|
|