stacks: pre-destroy refresh should use a normal plan (#36696)

* stacks: pre-destroy refresh should use a normal plan

* format
pull/36710/head
Liam Cervante 1 year ago committed by GitHub
parent c16d466773
commit 9bbe34daa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1121,7 +1121,7 @@ func TestApplyDestroy(t *testing.T) {
PlanApplyable: false,
PlannedInputValues: make(map[string]plans.DynamicValue),
PlannedOutputValues: map[string]cty.Value{
"value": cty.DynamicVal,
"value": cty.UnknownVal(cty.String),
},
PlannedCheckResults: &states.CheckResults{},
PlanTimestamp: fakePlanTimestamp,
@ -1256,7 +1256,7 @@ func TestApplyDestroy(t *testing.T) {
PlanApplyable: true,
RequiredComponents: collections.NewSet(mustAbsComponent("component.two")),
PlannedOutputValues: map[string]cty.Value{
"value": cty.DynamicVal,
"value": cty.StringVal("foo"),
},
PlanTimestamp: fakePlanTimestamp,
},
@ -1268,7 +1268,7 @@ func TestApplyDestroy(t *testing.T) {
PlanApplyable: true,
RequiredComponents: collections.NewSet(mustAbsComponent("component.one")),
PlannedOutputValues: map[string]cty.Value{
"value": cty.DynamicVal,
"value": cty.StringVal("foo"),
},
PlanTimestamp: fakePlanTimestamp,
},
@ -1319,6 +1319,129 @@ func TestApplyDestroy(t *testing.T) {
},
},
},
"destroy-partial-state-with-module": {
path: "with-module",
state: stackstate.NewStateBuilder().
AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.self")).
AddInputVariable("id", cty.StringVal("self")).
AddInputVariable("input", cty.StringVal("self"))).
AddResourceInstance(stackstate.NewResourceInstanceBuilder().
SetAddr(mustAbsResourceInstanceObject("component.self.testing_resource.outside")).
SetProviderAddr(mustDefaultRootProvider("testing")).
SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
"id": "self",
"value": "self",
}),
Status: states.ObjectReady,
})).
Build(),
store: stacks_testing_provider.NewResourceStoreBuilder().
AddResource("self", cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("self"),
"value": cty.StringVal("self"),
})).
Build(),
cycles: []TestCycle{
{
planMode: plans.DestroyMode,
planInputs: map[string]cty.Value{
"id": cty.StringVal("self"),
"input": cty.StringVal("self"),
},
wantPlannedChanges: []stackplan.PlannedChange{
&stackplan.PlannedChangeApplyable{
Applyable: true,
},
&stackplan.PlannedChangeComponentInstance{
Addr: mustAbsComponentInstance("component.self"),
Action: plans.Delete,
Mode: plans.DestroyMode,
PlanApplyable: true,
PlanComplete: true,
PlannedInputValues: map[string]plans.DynamicValue{
"create": mustPlanDynamicValueDynamicType(cty.True),
"id": mustPlanDynamicValueDynamicType(cty.StringVal("self")),
"input": mustPlanDynamicValueDynamicType(cty.StringVal("self")),
},
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
"create": nil,
"id": nil,
"input": nil,
},
PlannedOutputValues: make(map[string]cty.Value),
PlannedCheckResults: new(states.CheckResults),
PlanTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeResourceInstancePlanned{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"),
ChangeSrc: &plans.ResourceInstanceChangeSrc{
Addr: mustAbsResourceInstance("testing_resource.outside"),
PrevRunAddr: mustAbsResourceInstance("testing_resource.outside"),
ProviderAddr: mustDefaultRootProvider("testing"),
ChangeSrc: plans.ChangeSrc{
Action: plans.Delete,
Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("self"),
"value": cty.StringVal("self"),
})),
After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{
"id": cty.String,
"value": cty.String,
}))),
},
},
PriorStateSrc: &states.ResourceInstanceObjectSrc{
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
"id": "self",
"value": "self",
}),
Status: states.ObjectReady,
Dependencies: make([]addrs.ConfigResource, 0),
},
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: stacks_testing_provider.TestingResourceSchema,
},
&stackplan.PlannedChangeHeader{
TerraformVersion: version.SemVer,
},
&stackplan.PlannedChangePlannedTimestamp{
PlannedTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeRootInputValue{
Addr: mustStackInputVariable("id"),
Action: plans.Create,
Before: cty.NullVal(cty.DynamicPseudoType),
After: cty.StringVal("self"),
DeleteOnApply: true,
},
&stackplan.PlannedChangeRootInputValue{
Addr: mustStackInputVariable("input"),
Action: plans.Create,
Before: cty.NullVal(cty.DynamicPseudoType),
After: cty.StringVal("self"),
DeleteOnApply: true,
},
},
wantAppliedChanges: []stackstate.AppliedChange{
&stackstate.AppliedChangeComponentInstanceRemoved{
ComponentAddr: mustAbsComponent("component.self"),
ComponentInstanceAddr: mustAbsComponentInstance("component.self"),
},
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("input"),
},
},
},
},
},
"destroy-partial-state": {
path: "destroy-partial-state",
state: stackstate.NewStateBuilder().
@ -1379,7 +1502,7 @@ func TestApplyDestroy(t *testing.T) {
PlanComplete: true,
PlannedInputValues: make(map[string]plans.DynamicValue),
PlannedOutputValues: map[string]cty.Value{
"deleted_id": cty.DynamicVal,
"deleted_id": cty.UnknownVal(cty.String),
},
PlannedCheckResults: &states.CheckResults{},
PlanTimestamp: fakePlanTimestamp,

@ -249,34 +249,28 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
return nil, diags
}
if !refresh.Complete {
// If the refresh was deferred, then we'll defer the destroy
// plan as well.
opts.ExternalDependencyDeferred = true
} else {
// If we're destroying this instance, then the dependencies
// should be reversed. Unfortunately, we can't compute that
// easily so instead we'll use the dependents computed at the
// last apply operation.
Dependents:
for depAddr := range c.PlanPrevDependents(ctx).All() {
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
if depStack == nil {
// something weird has happened, but this means that
// whatever thing we're depending on being deleted first
// doesn't exist so it's fine.
continue
}
depComponent, depRemoveds := depStack.ApplyableComponents(ctx, depAddr.Item)
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
// If we're destroying this instance, then the dependencies
// should be reversed. Unfortunately, we can't compute that
// easily so instead we'll use the dependents computed at the
// last apply operation.
Dependents:
for depAddr := range c.PlanPrevDependents(ctx).All() {
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
if depStack == nil {
// something weird has happened, but this means that
// whatever thing we're depending on being deleted first
// doesn't exist so it's fine.
continue
}
depComponent, depRemoveds := depStack.ApplyableComponents(ctx, depAddr.Item)
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
opts.ExternalDependencyDeferred = true
break
}
for _, depRemoved := range depRemoveds {
if !depRemoved.PlanIsComplete(ctx) {
opts.ExternalDependencyDeferred = true
break
}
for _, depRemoved := range depRemoveds {
if !depRemoved.PlanIsComplete(ctx) {
opts.ExternalDependencyDeferred = true
break Dependents
}
break Dependents
}
}
}

@ -69,7 +69,7 @@ func (r *RefreshInstance) Result(ctx context.Context) map[string]cty.Value {
func (r *RefreshInstance) Plan(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
return doOnceWithDiags(ctx, &r.moduleTreePlan, r, func(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
opts, diags := r.component.PlanOpts(ctx, plans.RefreshOnlyMode, false)
opts, diags := r.component.PlanOpts(ctx, plans.NormalMode, false)
if opts == nil {
return nil, diags
}

@ -222,7 +222,7 @@ func TestPlan(t *testing.T) {
Action: plans.Delete,
Mode: plans.DestroyMode,
PlannedOutputValues: map[string]cty.Value{
"id": cty.NullVal(cty.DynamicPseudoType),
"id": cty.StringVal("foo"),
},
PlanTimestamp: fakePlanTimestamp,
},
@ -1186,10 +1186,10 @@ func TestPlanWithEphemeralInputVariables(t *testing.T) {
t.Fatal(err)
}
req := PlanRequest{
Config: cfg,
InputValues: map[stackaddrs.InputVariable]stackeval.ExternalInputValue{
// Intentionally not set for this subtest.
},
Config: cfg,
ForcePlanTimestamp: &fakePlanTimestamp,
}
resp := PlanResponse{

@ -0,0 +1,23 @@
terraform {
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
}
variable "id" {
type = string
default = null
nullable = true # We'll generate an ID if none provided.
}
variable "input" {
type = string
}
resource "testing_resource" "data" {
id = var.id
value = var.input
}

@ -0,0 +1,44 @@
terraform {
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
}
variable "create" {
type = bool
default = true
}
variable "id" {
type = string
default = null
nullable = true # We'll generate an ID if none provided.
}
variable "input" {
type = string
}
resource "testing_resource" "resource" {
count = var.create ? 1 : 0
}
module "module" {
source = "./module"
providers = {
testing = testing
}
id = testing_resource.resource[0].id
input = var.input
}
resource "testing_resource" "outside" {
id = var.id
value = var.input
}

@ -0,0 +1,30 @@
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
provider "testing" "default" {}
variable "input" {
type = string
}
variable "id" {
type = string
default = null
}
component "self" {
source = "./"
providers = {
testing = provider.testing.default
}
inputs = {
id = var.id
input = var.input
}
}
Loading…
Cancel
Save