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 }