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

     1  package containerupdate
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/tilt-dev/tilt/internal/container"
    13  	"github.com/tilt-dev/tilt/internal/docker"
    14  	"github.com/tilt-dev/tilt/internal/k8s"
    15  	"github.com/tilt-dev/tilt/internal/store/liveupdates"
    16  	"github.com/tilt-dev/tilt/pkg/logger"
    17  	"github.com/tilt-dev/tilt/pkg/model"
    18  )
    19  
    20  type DockerUpdater struct {
    21  	dCli docker.Client
    22  }
    23  
    24  var _ ContainerUpdater = &DockerUpdater{}
    25  
    26  func NewDockerUpdater(dCli docker.Client) *DockerUpdater {
    27  	return &DockerUpdater{dCli: dCli}
    28  }
    29  
    30  func (cu *DockerUpdater) WillBuildToKubeContext(kctx k8s.KubeContext) bool {
    31  	return cu.dCli.Env().WillBuildToKubeContext(kctx)
    32  }
    33  
    34  func (cu *DockerUpdater) UpdateContainer(ctx context.Context, cInfo liveupdates.Container,
    35  	archiveToCopy io.Reader, filesToDelete []string, cmds []model.Cmd, hotReload bool) error {
    36  	l := logger.Get(ctx)
    37  
    38  	err := cu.rmPathsFromContainer(ctx, cInfo.ContainerID, filesToDelete)
    39  	if err != nil {
    40  		return errors.Wrap(err, "rmPathsFromContainer")
    41  	}
    42  
    43  	// Use `tar` to unpack the files into the container.
    44  	//
    45  	// Although docker has a copy API, it's buggy and not well-maintained
    46  	// (whereas the Exec API is part of the CRI and much more battle-tested).
    47  	// Discussion:
    48  	// https://github.com/tilt-dev/tilt/issues/3708
    49  	tarCmd := tarCmd()
    50  	err = cu.dCli.ExecInContainer(ctx, cInfo.ContainerID, tarCmd, archiveToCopy, l.Writer(logger.InfoLvl))
    51  	if err != nil {
    52  		if exitCode, ok := ExtractExitCode(err); ok {
    53  			return wrapTarExecErr(err, tarCmd, exitCode)
    54  		}
    55  		return fmt.Errorf("copying changed files: %w", err)
    56  	}
    57  
    58  	// Exec run's on container
    59  	for i, cmd := range cmds {
    60  		if !cmd.EchoOff {
    61  			l.Infof("[CMD %d/%d] %s", i+1, len(cmds), strings.Join(cmd.Argv, " "))
    62  		}
    63  		err = cu.dCli.ExecInContainer(ctx, cInfo.ContainerID, cmd, nil, l.Writer(logger.InfoLvl))
    64  		if err != nil {
    65  			return fmt.Errorf(
    66  				"executing on container %s: %w",
    67  				cInfo.ContainerID.ShortStr(),
    68  				wrapRunStepError(wrapDockerGenericExecErr(cmd, err)),
    69  			)
    70  		}
    71  	}
    72  
    73  	if hotReload {
    74  		l.Debugf("Hot reload on, skipping container restart: %s", cInfo.DisplayName())
    75  		return nil
    76  	}
    77  
    78  	// Restart container so that entrypoint restarts with the updated files etc.
    79  	l.Debugf("Restarting container: %s", cInfo.DisplayName())
    80  	err = cu.dCli.ContainerRestartNoWait(ctx, cInfo.ContainerID.String())
    81  	if err != nil {
    82  		return errors.Wrap(err, "ContainerRestart")
    83  	}
    84  	return nil
    85  }
    86  
    87  func (cu *DockerUpdater) rmPathsFromContainer(ctx context.Context, cID container.ID, paths []string) error {
    88  	if len(paths) == 0 {
    89  		return nil
    90  	}
    91  
    92  	out := bytes.NewBuffer(nil)
    93  	err := cu.dCli.ExecInContainer(ctx, cID, model.Cmd{Argv: makeRmCmd(paths)}, nil, out)
    94  	if err != nil {
    95  		if docker.IsExitError(err) {
    96  			return fmt.Errorf("Error deleting files from container: %s", out.String())
    97  		}
    98  		return errors.Wrap(err, "Error deleting files from container")
    99  	}
   100  	return nil
   101  }
   102  
   103  func makeRmCmd(paths []string) []string {
   104  	cmd := []string{"rm", "-rf"}
   105  	cmd = append(cmd, paths...)
   106  	return cmd
   107  }
   108  
   109  func wrapDockerGenericExecErr(cmd model.Cmd, err error) error {
   110  	if exitCode, ok := ExtractExitCode(err); ok {
   111  		return NewExecError(cmd, exitCode)
   112  	}
   113  	return err
   114  }