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

     1  package tiltfile
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/distribution/reference"
     7  	"github.com/pkg/errors"
     8  	"go.starlark.net/starlark"
     9  
    10  	"github.com/tilt-dev/tilt/internal/container"
    11  	"github.com/tilt-dev/tilt/internal/controllers/apis/liveupdate"
    12  	"github.com/tilt-dev/tilt/internal/tiltfile/starkit"
    13  	"github.com/tilt-dev/tilt/internal/tiltfile/value"
    14  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    15  	"github.com/tilt-dev/tilt/pkg/model"
    16  )
    17  
    18  type k8sCustomDeploy struct {
    19  	applyCmd  model.Cmd
    20  	deleteCmd model.Cmd
    21  	deps      []string
    22  	ignores   []model.Dockerignore
    23  }
    24  
    25  func (s *tiltfileState) k8sCustomDeploy(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    26  	var name string
    27  	var applyCmdVal, applyCmdBatVal, applyCmdDirVal starlark.Value
    28  	var deleteCmdVal, deleteCmdBatVal, deleteCmdDirVal starlark.Value
    29  	var applyCmdEnv, deleteCmdEnv value.StringStringMap
    30  	var imageSelector, containerSelector string
    31  	var liveUpdateVal starlark.Value
    32  	var imageDeps value.ImageList
    33  
    34  	deps := value.NewLocalPathListUnpacker(thread)
    35  
    36  	if err := s.unpackArgs(fn.Name(), args, kwargs,
    37  		"name", &name,
    38  		"apply_cmd", &applyCmdVal,
    39  		"delete_cmd", &deleteCmdVal,
    40  		"deps", &deps,
    41  		"image_selector?", &imageSelector,
    42  		"live_update?", &liveUpdateVal,
    43  		"apply_dir?", &applyCmdDirVal,
    44  		"apply_env?", &applyCmdEnv,
    45  		"apply_cmd_bat?", &applyCmdBatVal,
    46  		"delete_dir?", &deleteCmdDirVal,
    47  		"delete_env?", &deleteCmdEnv,
    48  		"delete_cmd_bat?", &deleteCmdBatVal,
    49  		"container_selector?", &containerSelector,
    50  		"image_deps?", &imageDeps,
    51  	); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	applyCmd, err := value.ValueGroupToCmdHelper(thread, applyCmdVal, applyCmdBatVal, applyCmdDirVal, applyCmdEnv)
    56  	if err != nil {
    57  		return nil, errors.Wrap(err, "apply_cmd")
    58  	} else if applyCmd.Empty() {
    59  		return nil, fmt.Errorf("k8s_custom_deploy: apply_cmd cannot be empty")
    60  	}
    61  
    62  	deleteCmd, err := value.ValueGroupToCmdHelper(thread, deleteCmdVal, deleteCmdBatVal, deleteCmdDirVal, deleteCmdEnv)
    63  	if err != nil {
    64  		return nil, errors.Wrap(err, "delete_cmd")
    65  	} else if deleteCmd.Empty() {
    66  		return nil, fmt.Errorf("k8s_custom_deploy: delete_cmd cannot be empty")
    67  	}
    68  
    69  	liveUpdate, err := s.liveUpdateFromSteps(thread, liveUpdateVal)
    70  	if err != nil {
    71  		return nil, errors.Wrap(err, "live_update")
    72  	}
    73  
    74  	res, err := s.makeK8sResource(name)
    75  	if err != nil {
    76  		return nil, fmt.Errorf("error making resource for %s: %v", name, err)
    77  	}
    78  
    79  	res.customDeploy = &k8sCustomDeploy{
    80  		applyCmd:  applyCmd,
    81  		deleteCmd: deleteCmd,
    82  		deps:      deps.Value,
    83  		ignores:   customDeployIgnoresForLiveUpdate(liveUpdate),
    84  	}
    85  	for _, imageDep := range imageDeps {
    86  		res.addImageDep(imageDep, true)
    87  	}
    88  
    89  	if !liveupdate.IsEmptySpec(liveUpdate) {
    90  		var ref reference.Named
    91  		var selectorCount int
    92  
    93  		if imageSelector != "" {
    94  			selectorCount++
    95  
    96  			// the ref attached to the image target will be inferred as the image selector
    97  			// for the LiveUpdateSpec by Manifest::InferLiveUpdateSelectors
    98  			ref, err = container.ParseNamed(imageSelector)
    99  			if err != nil {
   100  				return nil, fmt.Errorf("can't parse %q: %v", imageSelector, err)
   101  			}
   102  		}
   103  
   104  		if containerSelector != "" {
   105  			selectorCount++
   106  
   107  			// pre-populate the container name selector as this cannot be inferred from
   108  			// the image target by Manifest::InferLiveUpdateSelectors
   109  			liveUpdate.Selector.Kubernetes = &v1alpha1.LiveUpdateKubernetesSelector{
   110  				ContainerName: containerSelector,
   111  			}
   112  
   113  			// the image target needs a valid ref even though it'll never be
   114  			// built/used, so create one named after the manifest that won't
   115  			// collide with anything else
   116  			fakeImageName := fmt.Sprintf("k8s_custom_deploy:%s", name)
   117  			ref, err = container.ParseNamed(fakeImageName)
   118  			if err != nil {
   119  				return nil, fmt.Errorf("can't parse %q: %v", fakeImageName, err)
   120  			}
   121  		}
   122  
   123  		if selectorCount == 0 {
   124  			return nil, fmt.Errorf("k8s_custom_deploy: no Live Update selector specified")
   125  		} else if selectorCount > 1 {
   126  			return nil, fmt.Errorf("k8s_custom_deploy: cannot specify more than one Live Update selector")
   127  		}
   128  
   129  		img := &dockerImage{
   130  			buildType:        CustomBuild,
   131  			configurationRef: container.NewRefSelector(ref),
   132  			// HACK(milas): this is treated specially in the BuildAndDeployer to
   133  			// 	mark this as a "LiveUpdateOnly" ImageTarget, so that no builds
   134  			// 	will be done, only deploy + Live Update
   135  			customCommand:    model.ToHostCmd(":"),
   136  			customDeps:       deps.Value,
   137  			liveUpdate:       liveUpdate,
   138  			disablePush:      true,
   139  			skipsLocalDocker: true,
   140  			tiltfilePath:     starkit.CurrentExecPath(thread),
   141  		}
   142  		// N.B. even in the case that we're creating a fake image name, we need
   143  		// 	to reference it so that it can be "consumed" by this target to avoid
   144  		// 	producing warnings about unused image targets
   145  		res.addImageDep(ref, false)
   146  
   147  		if err := s.buildIndex.addImage(img); err != nil {
   148  			return nil, err
   149  		}
   150  	}
   151  
   152  	return starlark.None, nil
   153  }
   154  
   155  func customDeployIgnoresForLiveUpdate(spec v1alpha1.LiveUpdateSpec) []model.Dockerignore {
   156  	patternCount := len(spec.Syncs) + len(spec.StopPaths)
   157  	if patternCount == 0 {
   158  		return nil
   159  	}
   160  	di := model.Dockerignore{
   161  		LocalPath: spec.BasePath,
   162  		Patterns:  make([]string, 0, patternCount),
   163  	}
   164  	for _, sync := range spec.Syncs {
   165  		di.Patterns = append(di.Patterns, sync.LocalPath)
   166  	}
   167  	di.Patterns = append(di.Patterns, spec.StopPaths...)
   168  	return []model.Dockerignore{di}
   169  }