Skip to content

Commit b1de5a5

Browse files
committed
feat: implement TeardownAndDestroy helper function
The function combines `Teardown`, `Watch` for finalizers to be empty and `Destroy` calls. Fixes: siderolabs/omni#1137 Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
1 parent a06b473 commit b1de5a5

4 files changed

Lines changed: 108 additions & 0 deletions

File tree

pkg/state/conformance/state.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,63 @@ func (suite *StateSuite) TestContextWithTeardown() {
14021402
assertContextIsCanceled(suite.T(), ctx2)
14031403
}
14041404

1405+
// TestTeardownAndDestroy verifies TeardownAndDestroy.
1406+
func (suite *StateSuite) TestTeardownAndDestroy() {
1407+
ns := suite.getNamespace()
1408+
1409+
res := NewPathResource(ns, "tmp/4")
1410+
1411+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
1412+
defer cancel()
1413+
1414+
finalizer := "A"
1415+
1416+
eg := errgroup.Group{}
1417+
1418+
eg.Go(func() error {
1419+
ch := make(chan state.Event)
1420+
1421+
err := suite.State.WatchKind(ctx, NewPathResource(ns, "").Metadata(), ch, state.WithBootstrapContents(true))
1422+
if err != nil {
1423+
return err
1424+
}
1425+
1426+
for {
1427+
select {
1428+
case <-ctx.Done():
1429+
return nil
1430+
case e := <-ch:
1431+
if e.Type != state.Updated && e.Type != state.Created {
1432+
continue
1433+
}
1434+
1435+
if e.Resource.Metadata().Phase() != resource.PhaseTearingDown {
1436+
continue
1437+
}
1438+
1439+
if !e.Resource.Metadata().Finalizers().Has(finalizer) {
1440+
continue
1441+
}
1442+
1443+
if err = suite.State.RemoveFinalizer(ctx, e.Resource.Metadata(), finalizer); err != nil {
1444+
return err
1445+
}
1446+
}
1447+
}
1448+
})
1449+
1450+
suite.Require().NoError(suite.State.Create(ctx, res))
1451+
1452+
suite.Assert().NoError(suite.State.AddFinalizer(ctx, res.Metadata(), finalizer))
1453+
1454+
err := suite.State.TeardownAndDestroy(ctx, res.Metadata())
1455+
suite.Require().NoError(err)
1456+
1457+
cancel()
1458+
1459+
suite.Require().NoError(eg.Wait())
1460+
}
1461+
14051462
func assertContextIsCanceled(t *testing.T, ctx context.Context) { //nolint:revive
14061463
t.Helper()
14071464

pkg/state/options.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,21 @@ func WithDestroyOwner(owner string) DestroyOption {
152152
}
153153
}
154154

155+
// TeardownAndDestroyOption builds TeardownAndDestroyOption.
156+
type TeardownAndDestroyOption func(*TeardownAndDestroyOptions)
157+
158+
// TeardownAndDestroyOptions for the CoreState.TeardownAndDestroy function.
159+
type TeardownAndDestroyOptions struct {
160+
Owner string
161+
}
162+
163+
// WithTeardownAndDestroyOwner checks an owner on the object being destroyed.
164+
func WithTeardownAndDestroyOwner(owner string) TeardownAndDestroyOption {
165+
return func(opts *TeardownAndDestroyOptions) {
166+
opts.Owner = owner
167+
}
168+
}
169+
155170
// WatchOptions for the CoreState.Watch function.
156171
type WatchOptions struct {
157172
StartFromBookmark Bookmark

pkg/state/state.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,11 @@ type State interface {
137137
// The passed in context should be canceled, otherwise the goroutine might leak from this call.
138138
// If the resource doesn't exist, the context is canceled immediately.
139139
ContextWithTeardown(context.Context, resource.Pointer) (context.Context, error)
140+
141+
// TeardownAndDestroy a resource.
142+
//
143+
// If a resource doesn't exist, error is returned.
144+
// It's not an error to tear down a resource which is already being torn down.
145+
// The call blocks until all resource finalizers are empty.
146+
TeardownAndDestroy(context.Context, resource.Pointer, ...TeardownAndDestroyOption) error
140147
}

pkg/state/wrap.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,32 @@ func (state coreWrapper) ContextWithTeardown(ctx context.Context, resourcePointe
208208

209209
return ctx, nil
210210
}
211+
212+
// TeardownAndDestroy a resource.
213+
//
214+
// If a resource doesn't exist, error is returned.
215+
// It's not an error to tear down a resource which is already being torn down.
216+
// The call blocks until all resource finalizers are empty.
217+
func (state coreWrapper) TeardownAndDestroy(ctx context.Context, resourcePointer resource.Pointer, opts ...TeardownAndDestroyOption) error {
218+
var options TeardownAndDestroyOptions
219+
220+
for _, opt := range opts {
221+
opt(&options)
222+
}
223+
224+
ready, err := state.Teardown(ctx, resourcePointer, WithTeardownOwner(options.Owner))
225+
if err != nil {
226+
return err
227+
}
228+
229+
if ready {
230+
return state.Destroy(ctx, resourcePointer, WithDestroyOwner(options.Owner))
231+
}
232+
233+
_, err = state.WatchFor(ctx, resourcePointer, WithFinalizerEmpty())
234+
if err != nil {
235+
return err
236+
}
237+
238+
return state.Destroy(ctx, resourcePointer, WithDestroyOwner(options.Owner))
239+
}

0 commit comments

Comments
 (0)