github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/commands/rm.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/docker/buildx/builder"
     9  	"github.com/docker/buildx/store"
    10  	"github.com/docker/buildx/store/storeutil"
    11  	"github.com/docker/buildx/util/cobrautil/completion"
    12  	"github.com/docker/cli/cli/command"
    13  	"github.com/pkg/errors"
    14  	"github.com/spf13/cobra"
    15  	"golang.org/x/sync/errgroup"
    16  )
    17  
    18  type rmOptions struct {
    19  	builders    []string
    20  	keepState   bool
    21  	keepDaemon  bool
    22  	allInactive bool
    23  	force       bool
    24  }
    25  
    26  const (
    27  	rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
    28  )
    29  
    30  func runRm(ctx context.Context, dockerCli command.Cli, in rmOptions) error {
    31  	if in.allInactive && !in.force {
    32  		if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), rmInactiveWarning); err != nil {
    33  			return err
    34  		} else if !ok {
    35  			return nil
    36  		}
    37  	}
    38  
    39  	txn, release, err := storeutil.GetStore(dockerCli)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	defer release()
    44  
    45  	if in.allInactive {
    46  		return rmAllInactive(ctx, txn, dockerCli, in)
    47  	}
    48  
    49  	eg, _ := errgroup.WithContext(ctx)
    50  	for _, name := range in.builders {
    51  		func(name string) {
    52  			eg.Go(func() (err error) {
    53  				defer func() {
    54  					if err == nil {
    55  						_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", name)
    56  					} else {
    57  						_, _ = fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", name, err)
    58  					}
    59  				}()
    60  
    61  				b, err := builder.New(dockerCli,
    62  					builder.WithName(name),
    63  					builder.WithStore(txn),
    64  					builder.WithSkippedValidation(),
    65  				)
    66  				if err != nil {
    67  					return err
    68  				}
    69  
    70  				nodes, err := b.LoadNodes(ctx)
    71  				if err != nil {
    72  					return err
    73  				}
    74  
    75  				if cb := b.ContextName(); cb != "" {
    76  					return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
    77  				}
    78  
    79  				err1 := rm(ctx, nodes, in)
    80  				if err := txn.Remove(b.Name); err != nil {
    81  					return err
    82  				}
    83  				if err1 != nil {
    84  					return err1
    85  				}
    86  
    87  				return nil
    88  			})
    89  		}(name)
    90  	}
    91  
    92  	if err := eg.Wait(); err != nil {
    93  		return errors.New("failed to remove one or more builders")
    94  	}
    95  	return nil
    96  }
    97  
    98  func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
    99  	var options rmOptions
   100  
   101  	cmd := &cobra.Command{
   102  		Use:   "rm [OPTIONS] [NAME] [NAME...]",
   103  		Short: "Remove one or more builder instances",
   104  		RunE: func(cmd *cobra.Command, args []string) error {
   105  			options.builders = []string{rootOpts.builder}
   106  			if len(args) > 0 {
   107  				if options.allInactive {
   108  					return errors.New("cannot specify builder name when --all-inactive is set")
   109  				}
   110  				options.builders = args
   111  			}
   112  			return runRm(cmd.Context(), dockerCli, options)
   113  		},
   114  		ValidArgsFunction: completion.BuilderNames(dockerCli),
   115  	}
   116  
   117  	flags := cmd.Flags()
   118  	flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
   119  	flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the BuildKit daemon running")
   120  	flags.BoolVar(&options.allInactive, "all-inactive", false, "Remove all inactive builders")
   121  	flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
   122  
   123  	return cmd
   124  }
   125  
   126  func rm(ctx context.Context, nodes []builder.Node, in rmOptions) (err error) {
   127  	for _, node := range nodes {
   128  		if node.Driver == nil {
   129  			continue
   130  		}
   131  		// Do not stop the buildkitd daemon when --keep-daemon is provided
   132  		if !in.keepDaemon {
   133  			if err := node.Driver.Stop(ctx, true); err != nil {
   134  				return err
   135  			}
   136  		}
   137  		if err := node.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
   138  			return err
   139  		}
   140  		if node.Err != nil {
   141  			err = node.Err
   142  		}
   143  	}
   144  	return err
   145  }
   146  
   147  func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, in rmOptions) error {
   148  	builders, err := builder.GetBuilders(dockerCli, txn)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
   154  	defer cancel()
   155  
   156  	eg, _ := errgroup.WithContext(timeoutCtx)
   157  	for _, b := range builders {
   158  		func(b *builder.Builder) {
   159  			eg.Go(func() error {
   160  				nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
   161  				if err != nil {
   162  					return errors.Wrapf(err, "cannot load %s", b.Name)
   163  				}
   164  				if b.Dynamic {
   165  					return nil
   166  				}
   167  				if b.Inactive() {
   168  					rmerr := rm(ctx, nodes, in)
   169  					if err := txn.Remove(b.Name); err != nil {
   170  						return err
   171  					}
   172  					_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
   173  					return rmerr
   174  				}
   175  				return nil
   176  			})
   177  		}(b)
   178  	}
   179  
   180  	return eg.Wait()
   181  }