Skip to content

Commit 93c2c68

Browse files
committed
stacks: pre-destroy refresh should use a normal plan
1 parent 53172a5 commit 93c2c68

File tree

7 files changed

+247
-33
lines changed

7 files changed

+247
-33
lines changed

internal/stacks/stackruntime/apply_destroy_test.go

+127-4
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ func TestApplyDestroy(t *testing.T) {
11211121
PlanApplyable: false,
11221122
PlannedInputValues: make(map[string]plans.DynamicValue),
11231123
PlannedOutputValues: map[string]cty.Value{
1124-
"value": cty.DynamicVal,
1124+
"value": cty.UnknownVal(cty.String),
11251125
},
11261126
PlannedCheckResults: &states.CheckResults{},
11271127
PlanTimestamp: fakePlanTimestamp,
@@ -1256,7 +1256,7 @@ func TestApplyDestroy(t *testing.T) {
12561256
PlanApplyable: true,
12571257
RequiredComponents: collections.NewSet(mustAbsComponent("component.two")),
12581258
PlannedOutputValues: map[string]cty.Value{
1259-
"value": cty.DynamicVal,
1259+
"value": cty.StringVal("foo"),
12601260
},
12611261
PlanTimestamp: fakePlanTimestamp,
12621262
},
@@ -1268,7 +1268,7 @@ func TestApplyDestroy(t *testing.T) {
12681268
PlanApplyable: true,
12691269
RequiredComponents: collections.NewSet(mustAbsComponent("component.one")),
12701270
PlannedOutputValues: map[string]cty.Value{
1271-
"value": cty.DynamicVal,
1271+
"value": cty.StringVal("foo"),
12721272
},
12731273
PlanTimestamp: fakePlanTimestamp,
12741274
},
@@ -1319,6 +1319,129 @@ func TestApplyDestroy(t *testing.T) {
13191319
},
13201320
},
13211321
},
1322+
"destroy-partial-state-with-module": {
1323+
path: "with-module",
1324+
state: stackstate.NewStateBuilder().
1325+
AddComponentInstance(stackstate.NewComponentInstanceBuilder(mustAbsComponentInstance("component.self")).
1326+
AddInputVariable("id", cty.StringVal("self")).
1327+
AddInputVariable("input", cty.StringVal("self"))).
1328+
AddResourceInstance(stackstate.NewResourceInstanceBuilder().
1329+
SetAddr(mustAbsResourceInstanceObject("component.self.testing_resource.outside")).
1330+
SetProviderAddr(mustDefaultRootProvider("testing")).
1331+
SetResourceInstanceObjectSrc(states.ResourceInstanceObjectSrc{
1332+
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
1333+
"id": "self",
1334+
"value": "self",
1335+
}),
1336+
Status: states.ObjectReady,
1337+
})).
1338+
Build(),
1339+
store: stacks_testing_provider.NewResourceStoreBuilder().
1340+
AddResource("self", cty.ObjectVal(map[string]cty.Value{
1341+
"id": cty.StringVal("self"),
1342+
"value": cty.StringVal("self"),
1343+
})).
1344+
Build(),
1345+
cycles: []TestCycle{
1346+
{
1347+
planMode: plans.DestroyMode,
1348+
planInputs: map[string]cty.Value{
1349+
"id": cty.StringVal("self"),
1350+
"input": cty.StringVal("self"),
1351+
},
1352+
wantPlannedChanges: []stackplan.PlannedChange{
1353+
&stackplan.PlannedChangeApplyable{
1354+
Applyable: true,
1355+
},
1356+
&stackplan.PlannedChangeComponentInstance{
1357+
Addr: mustAbsComponentInstance("component.self"),
1358+
Action: plans.Delete,
1359+
Mode: plans.DestroyMode,
1360+
PlanApplyable: true,
1361+
PlanComplete: true,
1362+
PlannedInputValues: map[string]plans.DynamicValue{
1363+
"create": mustPlanDynamicValueDynamicType(cty.True),
1364+
"id": mustPlanDynamicValueDynamicType(cty.StringVal("self")),
1365+
"input": mustPlanDynamicValueDynamicType(cty.StringVal("self")),
1366+
},
1367+
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
1368+
"create": nil,
1369+
"id": nil,
1370+
"input": nil,
1371+
},
1372+
PlannedOutputValues: make(map[string]cty.Value),
1373+
PlannedCheckResults: new(states.CheckResults),
1374+
PlanTimestamp: fakePlanTimestamp,
1375+
},
1376+
&stackplan.PlannedChangeResourceInstancePlanned{
1377+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"),
1378+
ChangeSrc: &plans.ResourceInstanceChangeSrc{
1379+
Addr: mustAbsResourceInstance("testing_resource.outside"),
1380+
PrevRunAddr: mustAbsResourceInstance("testing_resource.outside"),
1381+
ProviderAddr: mustDefaultRootProvider("testing"),
1382+
ChangeSrc: plans.ChangeSrc{
1383+
Action: plans.Delete,
1384+
Before: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{
1385+
"id": cty.StringVal("self"),
1386+
"value": cty.StringVal("self"),
1387+
})),
1388+
After: mustPlanDynamicValue(cty.NullVal(cty.Object(map[string]cty.Type{
1389+
"id": cty.String,
1390+
"value": cty.String,
1391+
}))),
1392+
},
1393+
},
1394+
PriorStateSrc: &states.ResourceInstanceObjectSrc{
1395+
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
1396+
"id": "self",
1397+
"value": "self",
1398+
}),
1399+
Status: states.ObjectReady,
1400+
Dependencies: make([]addrs.ConfigResource, 0),
1401+
},
1402+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1403+
Schema: stacks_testing_provider.TestingResourceSchema,
1404+
},
1405+
&stackplan.PlannedChangeHeader{
1406+
TerraformVersion: version.SemVer,
1407+
},
1408+
&stackplan.PlannedChangePlannedTimestamp{
1409+
PlannedTimestamp: fakePlanTimestamp,
1410+
},
1411+
&stackplan.PlannedChangeRootInputValue{
1412+
Addr: mustStackInputVariable("id"),
1413+
Action: plans.Create,
1414+
Before: cty.NullVal(cty.DynamicPseudoType),
1415+
After: cty.StringVal("self"),
1416+
DeleteOnApply: true,
1417+
},
1418+
&stackplan.PlannedChangeRootInputValue{
1419+
Addr: mustStackInputVariable("input"),
1420+
Action: plans.Create,
1421+
Before: cty.NullVal(cty.DynamicPseudoType),
1422+
After: cty.StringVal("self"),
1423+
DeleteOnApply: true,
1424+
},
1425+
},
1426+
wantAppliedChanges: []stackstate.AppliedChange{
1427+
&stackstate.AppliedChangeComponentInstanceRemoved{
1428+
ComponentAddr: mustAbsComponent("component.self"),
1429+
ComponentInstanceAddr: mustAbsComponentInstance("component.self"),
1430+
},
1431+
&stackstate.AppliedChangeResourceInstanceObject{
1432+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.outside"),
1433+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1434+
},
1435+
&stackstate.AppliedChangeInputVariable{
1436+
Addr: mustStackInputVariable("id"),
1437+
},
1438+
&stackstate.AppliedChangeInputVariable{
1439+
Addr: mustStackInputVariable("input"),
1440+
},
1441+
},
1442+
},
1443+
},
1444+
},
13221445
"destroy-partial-state": {
13231446
path: "destroy-partial-state",
13241447
state: stackstate.NewStateBuilder().
@@ -1379,7 +1502,7 @@ func TestApplyDestroy(t *testing.T) {
13791502
PlanComplete: true,
13801503
PlannedInputValues: make(map[string]plans.DynamicValue),
13811504
PlannedOutputValues: map[string]cty.Value{
1382-
"deleted_id": cty.DynamicVal,
1505+
"deleted_id": cty.UnknownVal(cty.String),
13831506
},
13841507
PlannedCheckResults: &states.CheckResults{},
13851508
PlanTimestamp: fakePlanTimestamp,

internal/stacks/stackruntime/internal/stackeval/component_instance.go

+20-26
Original file line numberDiff line numberDiff line change
@@ -249,32 +249,26 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
249249
return nil, diags
250250
}
251251

252-
if !refresh.Complete {
253-
// If the refresh was deferred, then we'll defer the destroy
254-
// plan as well.
255-
opts.ExternalDependencyDeferred = true
256-
} else {
257-
// If we're destroying this instance, then the dependencies
258-
// should be reversed. Unfortunately, we can't compute that
259-
// easily so instead we'll use the dependents computed at the
260-
// last apply operation.
261-
for depAddr := range c.PlanPrevDependents(ctx).All() {
262-
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
263-
if depStack == nil {
264-
// something weird has happened, but this means that
265-
// whatever thing we're depending on being deleted first
266-
// doesn't exist so it's fine.
267-
continue
268-
}
269-
depComponent, depRemoved := depStack.ApplyableComponents(ctx, depAddr.Item)
270-
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
271-
opts.ExternalDependencyDeferred = true
272-
break
273-
}
274-
if depRemoved != nil && !depRemoved.PlanIsComplete(ctx) {
275-
opts.ExternalDependencyDeferred = true
276-
break
277-
}
252+
// If we're destroying this instance, then the dependencies
253+
// should be reversed. Unfortunately, we can't compute that
254+
// easily so instead we'll use the dependents computed at the
255+
// last apply operation.
256+
for depAddr := range c.PlanPrevDependents(ctx).All() {
257+
depStack := c.main.Stack(ctx, depAddr.Stack, PlanPhase)
258+
if depStack == nil {
259+
// something weird has happened, but this means that
260+
// whatever thing we're depending on being deleted first
261+
// doesn't exist so it's fine.
262+
continue
263+
}
264+
depComponent, depRemoved := depStack.ApplyableComponents(ctx, depAddr.Item)
265+
if depComponent != nil && !depComponent.PlanIsComplete(ctx) {
266+
opts.ExternalDependencyDeferred = true
267+
break
268+
}
269+
if depRemoved != nil && !depRemoved.PlanIsComplete(ctx) {
270+
opts.ExternalDependencyDeferred = true
271+
break
278272
}
279273
}
280274

internal/stacks/stackruntime/internal/stackeval/refresh_instance.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (r *RefreshInstance) Result(ctx context.Context) map[string]cty.Value {
6969

7070
func (r *RefreshInstance) Plan(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
7171
return doOnceWithDiags(ctx, &r.moduleTreePlan, r, func(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
72-
opts, diags := r.component.PlanOpts(ctx, plans.RefreshOnlyMode, false)
72+
opts, diags := r.component.PlanOpts(ctx, plans.NormalMode, false)
7373
if opts == nil {
7474
return nil, diags
7575
}

internal/stacks/stackruntime/plan_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func TestPlan(t *testing.T) {
222222
Action: plans.Delete,
223223
Mode: plans.DestroyMode,
224224
PlannedOutputValues: map[string]cty.Value{
225-
"id": cty.NullVal(cty.DynamicPseudoType),
225+
"id": cty.StringVal("foo"),
226226
},
227227
PlanTimestamp: fakePlanTimestamp,
228228
},
@@ -1186,7 +1186,7 @@ func TestPlanWithEphemeralInputVariables(t *testing.T) {
11861186
t.Fatal(err)
11871187
}
11881188
req := PlanRequest{
1189-
Config: cfg,
1189+
Config: cfg,
11901190
InputValues: map[stackaddrs.InputVariable]stackeval.ExternalInputValue{
11911191
// Intentionally not set for this subtest.
11921192
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
terraform {
2+
required_providers {
3+
testing = {
4+
source = "hashicorp/testing"
5+
version = "0.1.0"
6+
}
7+
}
8+
}
9+
10+
variable "id" {
11+
type = string
12+
default = null
13+
nullable = true # We'll generate an ID if none provided.
14+
}
15+
16+
variable "input" {
17+
type = string
18+
}
19+
20+
resource "testing_resource" "data" {
21+
id = var.id
22+
value = var.input
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
terraform {
2+
required_providers {
3+
testing = {
4+
source = "hashicorp/testing"
5+
version = "0.1.0"
6+
}
7+
}
8+
}
9+
10+
variable "create" {
11+
type = bool
12+
default = true
13+
}
14+
15+
variable "id" {
16+
type = string
17+
default = null
18+
nullable = true # We'll generate an ID if none provided.
19+
}
20+
21+
variable "input" {
22+
type = string
23+
}
24+
25+
resource "testing_resource" "resource" {
26+
count = var.create ? 1 : 0
27+
}
28+
29+
30+
module "module" {
31+
source = "./module"
32+
33+
providers = {
34+
testing = testing
35+
}
36+
37+
id = testing_resource.resource[0].id
38+
input = var.input
39+
}
40+
41+
resource "testing_resource" "outside" {
42+
id = var.id
43+
value = var.input
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
required_providers {
2+
testing = {
3+
source = "hashicorp/testing"
4+
version = "0.1.0"
5+
}
6+
}
7+
8+
provider "testing" "default" {}
9+
10+
variable "input" {
11+
type = string
12+
}
13+
14+
variable "id" {
15+
type = string
16+
default = null
17+
}
18+
19+
component "self" {
20+
source = "./"
21+
22+
providers = {
23+
testing = provider.testing.default
24+
}
25+
26+
inputs = {
27+
id = var.id
28+
input = var.input
29+
}
30+
}

0 commit comments

Comments
 (0)