Skip to content

Commit

Permalink
feat: delete resources in batches of 10 (#784)
Browse files Browse the repository at this point in the history
Closes #779 

- Delete the resources in batches of 10
- Reduce the complexity of the code by not using `goroutines`. We are
already trying to limit the strain on the API by deleting in batches of
10, therefor waiting the actions of multiple delete calls is enough
"concurrency" for this use case.
  • Loading branch information
jooola authored Jun 19, 2024
1 parent f69d261 commit b9f4b24
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 32 deletions.
73 changes: 41 additions & 32 deletions internal/cmd/base/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"strings"
"sync"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -51,50 +50,60 @@ func (dc *DeleteCmd) CobraCommand(s state.State) *cobra.Command {
return cmd
}

// deleteBatchSize is the batch size when deleting multiple resources in parallel.
const deleteBatchSize = 10

// Run executes a delete command.
func (dc *DeleteCmd) Run(s state.State, cmd *cobra.Command, args []string) error {
errs := make([]error, 0, len(args))
deleted := make([]string, 0, len(args))

for _, batch := range util.Batches(args, deleteBatchSize) {
results := make([]util.ResourceState, len(batch))
actions := make([]*hcloud.Action, 0, len(batch))

wg := sync.WaitGroup{}
wg.Add(len(args))
actions, errs :=
make([]*hcloud.Action, len(args)),
make([]error, len(args))
for i, idOrName := range batch {
results[i] = util.ResourceState{IDOrName: idOrName}

for i, idOrName := range args {
i, idOrName := i, idOrName
go func() {
defer wg.Done()
resource, _, err := dc.Fetch(s, cmd, idOrName)
if err != nil {
errs[i] = err
return
results[i].Error = err
continue
}
if util.IsNil(resource) {
errs[i] = fmt.Errorf("%s not found: %s", dc.ResourceNameSingular, idOrName)
return
results[i].Error = fmt.Errorf("%s not found: %s", dc.ResourceNameSingular, idOrName)
continue
}
actions[i], errs[i] = dc.Delete(s, cmd, resource)
}()
}

wg.Wait()
filtered := util.FilterNil(actions)
var err error
if len(filtered) > 0 {
err = s.WaitForActions(cmd, s, filtered...)
}
action, err := dc.Delete(s, cmd, resource)
if err != nil {
results[i].Error = err
continue
}
if action != nil {
actions = append(actions, action)
}
}

if len(actions) > 0 {
// TODO: We do not check when an action fail for a specific resource
if err := s.WaitForActions(cmd, s, actions...); err != nil {
errs = append(errs, err)
}
}

var actuallyDeleted []string
for i, idOrName := range args {
if errs[i] == nil {
actuallyDeleted = append(actuallyDeleted, idOrName)
for _, result := range results {
if result.Error == nil {
deleted = append(deleted, result.IDOrName)
}
}
}

if len(actuallyDeleted) == 1 {
cmd.Printf("%s %s deleted\n", dc.ResourceNameSingular, actuallyDeleted[0])
} else if len(actuallyDeleted) > 1 {
cmd.Printf("%s %s deleted\n", dc.ResourceNamePlural, strings.Join(actuallyDeleted, ", "))
if len(deleted) == 1 {
cmd.Printf("%s %s deleted\n", dc.ResourceNameSingular, deleted[0])
} else if len(deleted) > 1 {
cmd.Printf("%s %s deleted\n", dc.ResourceNamePlural, strings.Join(deleted, ", "))
}
return errors.Join(append(errs, err)...)

return errors.Join(errs...)
}
6 changes: 6 additions & 0 deletions internal/cmd/util/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package util

type ResourceState struct {
IDOrName string
Error error
}
8 changes: 8 additions & 0 deletions internal/cmd/util/slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package util

func Batches[T any](all []T, size int) (batches [][]T) {
for size < len(all) {
all, batches = all[size:], append(batches, all[:size])
}
return append(batches, all)
}
17 changes: 17 additions & 0 deletions internal/cmd/util/slices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package util

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestBatches(t *testing.T) {
all := []int{1, 2, 3, 4, 5}
batches := Batches(all, 2)

assert.Len(t, batches, 3)
assert.Equal(t, []int{1, 2}, batches[0])
assert.Equal(t, []int{3, 4}, batches[1])
assert.Equal(t, []int{5}, batches[2])
}

0 comments on commit b9f4b24

Please sign in to comment.