Skip to content

Commit e037960

Browse files
authored
fix: locked release file caused by interruption (#1219)
* fix: locked release file caused by interruption * fix: a lint error
1 parent 6b9c2cd commit e037960

File tree

3 files changed

+74
-9
lines changed

3 files changed

+74
-9
lines changed

pkg/cmd/destroy/destroy.go

+64-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package destroy
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"os"
2021
"strings"
@@ -36,6 +37,7 @@ import (
3637
"kusionstack.io/kusion/pkg/engine/runtime/terraform"
3738
"kusionstack.io/kusion/pkg/log"
3839
"kusionstack.io/kusion/pkg/util/pretty"
40+
"kusionstack.io/kusion/pkg/util/signal"
3941
"kusionstack.io/kusion/pkg/util/terminal"
4042
)
4143

@@ -188,12 +190,66 @@ func (o *DestroyOptions) Run() (err error) {
188190
}
189191
releaseCreated = true
190192

193+
errCh := make(chan error, 1)
194+
defer close(errCh)
195+
196+
// wait for the SIGTERM or SIGINT
197+
go func() {
198+
stopCh := signal.SetupSignalHandler()
199+
<-stopCh
200+
errCh <- errors.New("receive SIGTERM or SIGINT, exit cmd")
201+
}()
202+
203+
// run destroy command
204+
go func() {
205+
errCh <- o.run(rel, storage)
206+
}()
207+
208+
if err = <-errCh; err != nil {
209+
rel.Phase = apiv1.ReleasePhaseFailed
210+
release.UpdateDestroyRelease(storage, rel)
211+
} else {
212+
rel.Phase = apiv1.ReleasePhaseSucceeded
213+
release.UpdateDestroyRelease(storage, rel)
214+
}
215+
216+
return err
217+
}
218+
219+
// run executes the delete command after release is created.
220+
func (o *DestroyOptions) run(rel *apiv1.Release, storage release.Storage) (err error) {
221+
// update release to succeeded or failed
222+
defer func() {
223+
if err != nil {
224+
rel.Phase = apiv1.ReleasePhaseFailed
225+
release.UpdateDestroyRelease(storage, rel)
226+
} else {
227+
rel.Phase = apiv1.ReleasePhaseSucceeded
228+
err = release.UpdateDestroyRelease(storage, rel)
229+
}
230+
}()
231+
232+
// set no style
233+
if o.NoStyle {
234+
pterm.DisableStyling()
235+
}
236+
237+
sp := o.UI.SpinnerPrinter
238+
sp, _ = sp.Start(fmt.Sprintf("Computing destroy changes in the Stack %s...", o.RefStack.Name))
239+
191240
// compute changes for preview
192241
changes, err := o.preview(rel.Spec, rel.State, o.RefProject, o.RefStack, storage)
193242
if err != nil {
243+
if sp != nil {
244+
sp.Fail()
245+
}
194246
return
195247
}
196248

249+
if sp != nil {
250+
sp.Success()
251+
}
252+
197253
// preview
198254
changes.Summary(os.Stdout, o.NoStyle)
199255

@@ -203,16 +259,11 @@ func (o *DestroyOptions) Run() (err error) {
203259
return nil
204260
}
205261

206-
// set no style
207-
if o.NoStyle {
208-
pterm.DisableStyling()
209-
}
210-
211262
// prompt
212263
if !o.Yes {
213264
for {
214265
var input string
215-
input, err = prompt(o.UI)
266+
input, err = prompt(o.UI, rel, storage)
216267
if err != nil {
217268
return
218269
}
@@ -395,13 +446,19 @@ func (o *DestroyOptions) destroy(rel *apiv1.Release, changes *models.Changes, st
395446
return updatedRel, nil
396447
}
397448

398-
func prompt(ui *terminal.UI) (string, error) {
449+
func prompt(ui *terminal.UI, rel *apiv1.Release, storage release.Storage) (string, error) {
399450
options := []string{"yes", "details", "no"}
400451
input, err := ui.InteractiveSelectPrinter.
401452
WithFilter(false).
402453
WithDefaultText(`Do you want to destroy these diffs?`).
403454
WithOptions(options).
404455
WithDefaultOption("details").
456+
// To gracefully exit if interrupted by SIGINT or SIGTERM.
457+
WithOnInterruptFunc(func() {
458+
rel.Phase = apiv1.ReleasePhaseFailed
459+
release.UpdateDestroyRelease(storage, rel)
460+
os.Exit(1)
461+
}).
405462
Show()
406463
if err != nil {
407464
fmt.Printf("Prompt failed: %v\n", err)

pkg/cmd/destroy/destroy_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,13 @@ func mockOperationDestroy(res models.OpResult) {
324324
func TestPrompt(t *testing.T) {
325325
mockey.PatchConvey("prompt error", t, func() {
326326
mockey.Mock((*pterm.InteractiveSelectPrinter).Show).Return("", errors.New("mock error")).Build()
327-
_, err := prompt(terminal.DefaultUI())
327+
_, err := prompt(terminal.DefaultUI(), &apiv1.Release{}, &releasestorages.LocalStorage{})
328328
assert.NotNil(t, err)
329329
})
330330

331331
mockey.PatchConvey("prompt yes", t, func() {
332332
mockPromptOutput("yes")
333-
_, err := prompt(terminal.DefaultUI())
333+
_, err := prompt(terminal.DefaultUI(), &apiv1.Release{}, &releasestorages.LocalStorage{})
334334
assert.Nil(t, err)
335335
})
336336
}

pkg/engine/operation/models/change.go

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"os"
78
"strings"
89

910
"github.com/liu-hm19/pterm"
@@ -262,6 +263,13 @@ func (o *ChangeOrder) PromptDetails(ui *terminal.UI) (string, error) {
262263
WithDefaultText(`Which diff detail do you want to see?`).
263264
WithOptions(options).
264265
WithDefaultOption("all").
266+
// Fixme: interruption during 'apply' or 'destroy' may result in a locked release file.
267+
WithOnInterruptFunc(func() {
268+
hint := `Interruption during 'apply' or 'destroy' may result in a locked release file.
269+
Please use 'kusion release unlock' before executing the next operation.`
270+
fmt.Printf("\n" + hint + "\n")
271+
os.Exit(1)
272+
}).
265273
Show()
266274
if err != nil {
267275
fmt.Printf("Prompt failed: %v\n", err)

0 commit comments

Comments
 (0)