github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/cli/docker_prune.go (about)

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/spf13/cobra"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	"github.com/tilt-dev/tilt/internal/analytics"
    14  	"github.com/tilt-dev/tilt/internal/container"
    15  	ctrltiltfile "github.com/tilt-dev/tilt/internal/controllers/apis/tiltfile"
    16  	"github.com/tilt-dev/tilt/internal/docker"
    17  	"github.com/tilt-dev/tilt/internal/engine/dockerprune"
    18  	"github.com/tilt-dev/tilt/internal/k8s"
    19  	"github.com/tilt-dev/tilt/internal/tiltfile"
    20  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    21  	"github.com/tilt-dev/tilt/pkg/logger"
    22  	"github.com/tilt-dev/tilt/pkg/model"
    23  )
    24  
    25  type dockerPruneCmd struct {
    26  	fileName string
    27  }
    28  
    29  type dpDeps struct {
    30  	dCli docker.Client
    31  	kCli k8s.Client
    32  	tfl  tiltfile.TiltfileLoader
    33  }
    34  
    35  func newDPDeps(dCli docker.Client, kCli k8s.Client, tfl tiltfile.TiltfileLoader) dpDeps {
    36  	return dpDeps{
    37  		dCli: dCli,
    38  		kCli: kCli,
    39  		tfl:  tfl,
    40  	}
    41  }
    42  
    43  func (c *dockerPruneCmd) name() model.TiltSubcommand { return "docker-prune" }
    44  
    45  func (c *dockerPruneCmd) register() *cobra.Command {
    46  	cmd := &cobra.Command{
    47  		Use:   "docker-prune",
    48  		Short: "Run docker prune as Tilt does",
    49  	}
    50  
    51  	addTiltfileFlag(cmd, &c.fileName)
    52  
    53  	return cmd
    54  }
    55  
    56  func (c *dockerPruneCmd) run(ctx context.Context, args []string) error {
    57  	a := analytics.Get(ctx)
    58  	a.Incr("cmd.dockerPrune", nil)
    59  	defer a.Flush(time.Second)
    60  
    61  	if !logger.Get(ctx).Level().ShouldDisplay(logger.VerboseLvl) {
    62  		// Docker Pruner filters output when nothing is pruned if not in verbose
    63  		// logging mode, which is suitable for when it runs in the background
    64  		// during `tilt up`, but we always want to include that for the CLI cmd
    65  		// N.B. we only override if we're not already showing verbose so that
    66  		// 	`--debug` flag isn't impacted
    67  		l := logger.NewLogger(logger.VerboseLvl, os.Stdout)
    68  		ctx = logger.WithLogger(ctx, l)
    69  	}
    70  
    71  	deps, err := wireDockerPrune(ctx, a, "docker-prune")
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	tlr := deps.tfl.Load(ctx, ctrltiltfile.MainTiltfile(c.fileName, args), nil)
    77  	if tlr.Error != nil {
    78  		return tlr.Error
    79  	}
    80  
    81  	imgSelectors, err := resolveImageSelectors(ctx, deps.kCli, &tlr)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	dp := dockerprune.NewDockerPruner(deps.dCli)
    87  
    88  	// TODO: print the commands being run
    89  	dp.Prune(ctx, tlr.DockerPruneSettings.MaxAge, tlr.DockerPruneSettings.KeepRecent, imgSelectors)
    90  
    91  	return nil
    92  }
    93  
    94  // resolveImageSelectors finds image references from a tiltfile.TiltfileLoadResult object.
    95  //
    96  // The Kubernetes client is used to resolve the correct image names if a local registry is in use.
    97  //
    98  // This method is brittle and duplicates some logic from the actual reconcilers.
    99  // In the future, we hope to have a mode where we can launch the full apiserver
   100  // with all resources in a "disabled" state and rely on the API, but that's not
   101  // possible currently.
   102  func resolveImageSelectors(ctx context.Context, kCli k8s.Client, tlr *tiltfile.TiltfileLoadResult) ([]container.RefSelector, error) {
   103  	for _, m := range tlr.Manifests {
   104  		if err := m.InferImageProperties(); err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	var reg *v1alpha1.RegistryHosting
   110  	if tlr.HasOrchestrator(model.OrchestratorK8s) {
   111  		// k8s.Client::LocalRegistry will return an empty registry on any error,
   112  		// so ensure the client is actually functional first
   113  		if _, err := kCli.CheckConnected(ctx); err != nil {
   114  			return nil, fmt.Errorf("determining local registry: %v", err)
   115  		}
   116  		reg = kCli.LocalRegistry(ctx)
   117  	}
   118  
   119  	clusters := map[string]*v1alpha1.Cluster{
   120  		v1alpha1.ClusterNameDefault: {
   121  			ObjectMeta: metav1.ObjectMeta{Name: v1alpha1.ClusterNameDefault},
   122  			Spec:       v1alpha1.ClusterSpec{DefaultRegistry: reg},
   123  		},
   124  	}
   125  
   126  	imgSelectors := model.LocalRefSelectorsForManifests(tlr.Manifests, clusters)
   127  	if len(imgSelectors) != 0 && logger.Get(ctx).Level().ShouldDisplay(logger.DebugLvl) {
   128  		var sb strings.Builder
   129  		for _, is := range imgSelectors {
   130  			sb.WriteString("  - ")
   131  			sb.WriteString(is.RefFamiliarString())
   132  			sb.WriteRune('\n')
   133  		}
   134  
   135  		logger.Get(ctx).Debugf("Running Docker Prune for images:\n%s", sb.String())
   136  	}
   137  
   138  	return imgSelectors, nil
   139  }