diff --git a/commands/rm.go b/commands/rm.go index 6b026c3d526..65293ec6995 100644 --- a/commands/rm.go +++ b/commands/rm.go @@ -9,7 +9,6 @@ import ( "github.com/docker/buildx/store" "github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/util/cobrautil/completion" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/moby/buildkit/util/appcontext" "github.com/pkg/errors" @@ -18,7 +17,7 @@ import ( ) type rmOptions struct { - builder string + builders []string keepState bool keepDaemon bool allInactive bool @@ -46,33 +45,52 @@ func runRm(dockerCli command.Cli, in rmOptions) error { return rmAllInactive(ctx, txn, dockerCli, in) } - b, err := builder.New(dockerCli, - builder.WithName(in.builder), - builder.WithStore(txn), - builder.WithSkippedValidation(), - ) - if err != nil { - return err - } + eg, _ := errgroup.WithContext(ctx) + for _, name := range in.builders { + func(name string) { + eg.Go(func() (err error) { + defer func() { + if err == nil { + _, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", name) + } else { + _, _ = fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", name, err) + } + }() - nodes, err := b.LoadNodes(ctx) - if err != nil { - return err - } + b, err := builder.New(dockerCli, + builder.WithName(name), + builder.WithStore(txn), + builder.WithSkippedValidation(), + ) + if err != nil { + return err + } - if cb := b.ContextName(); cb != "" { - return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb) - } + nodes, err := b.LoadNodes(ctx) + if err != nil { + return err + } - err1 := rm(ctx, nodes, in) - if err := txn.Remove(b.Name); err != nil { - return err - } - if err1 != nil { - return err1 + if cb := b.ContextName(); cb != "" { + return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb) + } + + err1 := rm(ctx, nodes, in) + if err := txn.Remove(b.Name); err != nil { + return err + } + if err1 != nil { + return err1 + } + + return nil + }) + }(name) } - _, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name) + if err := eg.Wait(); err != nil { + return errors.New("failed to remove one or more builders") + } return nil } @@ -80,16 +98,15 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { var options rmOptions cmd := &cobra.Command{ - Use: "rm [NAME]", - Short: "Remove a builder instance", - Args: cli.RequiresMaxArgs(1), + Use: "rm [OPTIONS] [NAME] [NAME...]", + Short: "Remove one or more builder instances", RunE: func(cmd *cobra.Command, args []string) error { - options.builder = rootOpts.builder + options.builders = []string{rootOpts.builder} if len(args) > 0 { if options.allInactive { return errors.New("cannot specify builder name when --all-inactive is set") } - options.builder = args[0] + options.builders = args } return runRm(dockerCli, options) }, diff --git a/docs/reference/buildx.md b/docs/reference/buildx.md index 5a201ed8c00..0cd8b755da6 100644 --- a/docs/reference/buildx.md +++ b/docs/reference/buildx.md @@ -20,7 +20,7 @@ Extended build capabilities with BuildKit | [`inspect`](buildx_inspect.md) | Inspect current builder instance | | [`ls`](buildx_ls.md) | List builder instances | | [`prune`](buildx_prune.md) | Remove build cache | -| [`rm`](buildx_rm.md) | Remove a builder instance | +| [`rm`](buildx_rm.md) | Remove one or more builder instances | | [`stop`](buildx_stop.md) | Stop builder instance | | [`use`](buildx_use.md) | Set the current builder instance | | [`version`](buildx_version.md) | Show buildx version information | diff --git a/docs/reference/buildx_rm.md b/docs/reference/buildx_rm.md index 7ddad887425..9ca7fb695da 100644 --- a/docs/reference/buildx_rm.md +++ b/docs/reference/buildx_rm.md @@ -1,11 +1,11 @@ # buildx rm ```text -docker buildx rm [NAME] +docker buildx rm [OPTIONS] [NAME] [NAME...] ``` -Remove a builder instance +Remove one or more builder instances ### Options diff --git a/tests/integration_test.go b/tests/integration_test.go index 6b48f171c31..e38e46cab5d 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -28,6 +28,7 @@ func TestIntegration(t *testing.T) { tests = append(tests, imagetoolsTests...) tests = append(tests, versionTests...) tests = append(tests, createTests...) + tests = append(tests, rmTests...) testIntegration(t, tests...) } diff --git a/tests/rm.go b/tests/rm.go index 6c412f15e25..40cf8c94269 100644 --- a/tests/rm.go +++ b/tests/rm.go @@ -1,7 +1,11 @@ package tests import ( + "strings" + "testing" + "github.com/moby/buildkit/util/testutil/integration" + "github.com/stretchr/testify/require" ) func rmCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) { @@ -10,3 +14,47 @@ func rmCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) { out, err := cmd.CombinedOutput() return string(out), err } + +var rmTests = []func(t *testing.T, sb integration.Sandbox){ + testRm, + testRmMulti, +} + +func testRm(t *testing.T, sb integration.Sandbox) { + if sb.Name() != "docker-container" { + t.Skip("only testing for docker-container driver") + } + + out, err := rmCmd(sb, withArgs("default")) + require.Error(t, err, out) // can't remove a docker builder + + out, err = createCmd(sb, withArgs("--driver", "docker-container")) + require.NoError(t, err, out) + builderName := strings.TrimSpace(out) + + out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap")) + require.NoError(t, err, out) + + out, err = rmCmd(sb, withArgs(builderName)) + require.NoError(t, err, out) +} + +func testRmMulti(t *testing.T, sb integration.Sandbox) { + if sb.Name() != "docker-container" { + t.Skip("only testing for docker-container driver") + } + + var builderNames []string + for i := 0; i < 3; i++ { + out, err := createCmd(sb, withArgs("--driver", "docker-container")) + require.NoError(t, err, out) + builderName := strings.TrimSpace(out) + + out, err = inspectCmd(sb, withArgs(builderName, "--bootstrap")) + require.NoError(t, err, out) + builderNames = append(builderNames, builderName) + } + + out, err := rmCmd(sb, withArgs(builderNames...)) + require.NoError(t, err, out) +}