Skip to content

Commit b926263

Browse files
stacks: follow component progress cycle for empty destroys
We expect a component instances to report its plan/apply starting and ending as well as reporting the progress / result. This should also be the case for no-ops like an empty component instance.
1 parent 1a36d36 commit b926263

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'stacks: component instances should report no-op plan/apply. This solves a UI inconsistency with convergence destroy plans '
3+
time: 2026-01-15T10:30:00.72402+01:00
4+
custom:
5+
Issue: "38049"

internal/stacks/stackruntime/apply_destroy_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,18 @@ func TestApplyDestroy(t *testing.T) {
17181718
InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")},
17191719
},
17201720
},
1721+
PendingComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](
1722+
mustAbsComponentInstance("component.self"),
1723+
),
1724+
BeginComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](
1725+
mustAbsComponentInstance("component.self"),
1726+
),
1727+
EndComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](
1728+
mustAbsComponentInstance("component.self"),
1729+
),
1730+
ReportComponentInstancePlanned: []*hooks.ComponentInstanceChange{{
1731+
Addr: mustAbsComponentInstance("component.self"),
1732+
}},
17211733
},
17221734
wantAppliedHooks: &ExpectedHooks{
17231735
ComponentExpanded: []*hooks.ComponentInstances{
@@ -1726,6 +1738,18 @@ func TestApplyDestroy(t *testing.T) {
17261738
InstanceAddrs: []stackaddrs.AbsComponentInstance{mustAbsComponentInstance("component.self")},
17271739
},
17281740
},
1741+
PendingComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](
1742+
mustAbsComponentInstance("component.self"),
1743+
),
1744+
BeginComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](
1745+
mustAbsComponentInstance("component.self"),
1746+
),
1747+
EndComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](
1748+
mustAbsComponentInstance("component.self"),
1749+
),
1750+
ReportComponentInstanceApplied: []*hooks.ComponentInstanceChange{{
1751+
Addr: mustAbsComponentInstance("component.self"),
1752+
}},
17291753
},
17301754
},
17311755
},

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/hashicorp/terraform/internal/providers"
2323
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
2424
"github.com/hashicorp/terraform/internal/stacks/stackplan"
25+
"github.com/hashicorp/terraform/internal/stacks/stackruntime/hooks"
2526
"github.com/hashicorp/terraform/internal/stacks/stackstate"
2627
"github.com/hashicorp/terraform/internal/states"
2728
"github.com/hashicorp/terraform/internal/terraform"
@@ -192,6 +193,7 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
192193
ctx, c.tracingName()+" modules", &c.moduleTreePlan,
193194
func(ctx context.Context) (*plans.Plan, tfdiags.Diagnostics) {
194195
var diags tfdiags.Diagnostics
196+
h := hooksFromContext(ctx)
195197

196198
if c.mode == plans.DestroyMode {
197199

@@ -203,7 +205,13 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
203205
// and never applied, or that it was previously destroyed
204206
// via an earlier destroy operation.
205207
//
206-
// Return a dummy plan:
208+
// Return a dummy plan and send dummy events:
209+
hookSingle(ctx, h.PendingComponentInstancePlan, c.Addr())
210+
seq, ctx := hookBegin(ctx, h.BeginComponentInstancePlan, h.ContextAttach, c.Addr())
211+
hookMore(ctx, seq, h.ReportComponentInstancePlanned, &hooks.ComponentInstanceChange{
212+
Addr: c.Addr(),
213+
})
214+
hookMore(ctx, seq, h.EndComponentInstancePlan, c.Addr())
207215
return &plans.Plan{
208216
UIMode: plans.DestroyMode,
209217
Complete: true,
@@ -220,7 +228,6 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
220228
// outputs from this component can read from the refresh result
221229
// without causing a cycle.
222230

223-
h := hooksFromContext(ctx)
224231
hookSingle(ctx, h.PendingComponentInstancePlan, c.Addr())
225232
seq, planCtx := hookBegin(ctx, h.BeginComponentInstancePlan, h.ContextAttach, c.Addr())
226233

@@ -336,7 +343,6 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla
336343
}
337344
}
338345

339-
h := hooksFromContext(ctx)
340346
hookSingle(ctx, h.PendingComponentInstancePlan, c.Addr())
341347
seq, ctx := hookBegin(ctx, h.BeginComponentInstancePlan, h.ContextAttach, c.Addr())
342348
plan, moreDiags := PlanComponentInstance(ctx, c.main, c.PlanPrevState(), opts, []terraform.Hook{
@@ -382,6 +388,15 @@ func (c *ComponentInstance) ApplyModuleTreePlan(ctx context.Context, plan *plans
382388

383389
// If we're destroying and there's nothing to destroy, then we can
384390
// consider this a no-op.
391+
// We still need to report through the hooks that this component instance has been handled.
392+
h := hooksFromContext(ctx)
393+
hookSingle(ctx, hooksFromContext(ctx).PendingComponentInstanceApply, c.Addr())
394+
seq, ctx := hookBegin(ctx, h.BeginComponentInstanceApply, h.ContextAttach, c.Addr())
395+
hookMore(ctx, seq, h.ReportComponentInstanceApplied, &hooks.ComponentInstanceChange{
396+
Addr: c.Addr(),
397+
})
398+
hookMore(ctx, seq, h.EndComponentInstanceApply, c.Addr())
399+
385400
return &ComponentInstanceApplyResult{
386401
FinalState: plan.PriorState, // after refresh
387402
AffectedResourceInstanceObjects: resourceInstanceObjectsAffectedByStackPlan(stackPlan),

0 commit comments

Comments
 (0)