github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/live_update.go (about) 1 package tiltfile 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strconv" 7 "strings" 8 9 "go.starlark.net/syntax" 10 11 "go.starlark.net/starlark" 12 13 "github.com/tilt-dev/tilt/internal/tiltfile/starkit" 14 "github.com/tilt-dev/tilt/internal/tiltfile/value" 15 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 16 "github.com/tilt-dev/tilt/pkg/model" 17 ) 18 19 const fmtRestartContainerDeprecationError = "Found `restart_container()` LiveUpdate step in resource(s): [%s]. `restart_container()` has been deprecated for k8s resources. We recommend the restart_process extension: https://github.com/tilt-dev/tilt-extensions/tree/master/restart_process. For more information, see https://docs.tilt.dev/live_update_reference.html#restarting-your-process" 20 21 func restartContainerDeprecationError(names []model.ManifestName) string { 22 strs := make([]string, len(names)) 23 for i, n := range names { 24 strs[i] = n.String() 25 } 26 return fmt.Sprintf(fmtRestartContainerDeprecationError, strings.Join(strs, ", ")) 27 } 28 29 // when adding a new type of `liveUpdateStep`, make sure that any tiltfile functions that create them also call 30 // `s.recordLiveUpdateStep` 31 type liveUpdateStep interface { 32 starlark.Value 33 liveUpdateStep() 34 declarationPos() string 35 } 36 37 type liveUpdateFallBackOnStep struct { 38 files []string 39 position syntax.Position 40 } 41 42 var _ starlark.Value = liveUpdateFallBackOnStep{} 43 var _ liveUpdateStep = liveUpdateFallBackOnStep{} 44 45 func (l liveUpdateFallBackOnStep) String() string { 46 return fmt.Sprintf("fall_back_on step: %v'", l.files) 47 } 48 func (l liveUpdateFallBackOnStep) Type() string { return "live_update_fall_back_on_step" } 49 func (l liveUpdateFallBackOnStep) Freeze() {} 50 func (l liveUpdateFallBackOnStep) Truth() starlark.Bool { return len(l.files) > 0 } 51 func (l liveUpdateFallBackOnStep) Hash() (uint32, error) { 52 t := starlark.Tuple{} 53 for _, path := range l.files { 54 t = append(t, starlark.String(path)) 55 } 56 return t.Hash() 57 } 58 func (l liveUpdateFallBackOnStep) liveUpdateStep() {} 59 func (l liveUpdateFallBackOnStep) declarationPos() string { return l.position.String() } 60 61 type liveUpdateSyncStep struct { 62 localPath, remotePath string 63 position syntax.Position 64 } 65 66 var _ starlark.Value = liveUpdateSyncStep{} 67 var _ liveUpdateStep = liveUpdateSyncStep{} 68 69 func (l liveUpdateSyncStep) String() string { 70 return fmt.Sprintf("sync step: '%s'->'%s'", l.localPath, l.remotePath) 71 } 72 func (l liveUpdateSyncStep) Type() string { return "live_update_sync_step" } 73 func (l liveUpdateSyncStep) Freeze() {} 74 func (l liveUpdateSyncStep) Truth() starlark.Bool { 75 return len(l.localPath) > 0 || len(l.remotePath) > 0 76 } 77 func (l liveUpdateSyncStep) Hash() (uint32, error) { 78 return starlark.Tuple{starlark.String(l.localPath), starlark.String(l.remotePath)}.Hash() 79 } 80 func (l liveUpdateSyncStep) liveUpdateStep() {} 81 func (l liveUpdateSyncStep) declarationPos() string { return l.position.String() } 82 83 type liveUpdateRunStep struct { 84 command model.Cmd 85 triggers []string 86 echoOff bool 87 position syntax.Position 88 } 89 90 var _ starlark.Value = liveUpdateRunStep{} 91 var _ liveUpdateStep = liveUpdateRunStep{} 92 93 func (l liveUpdateRunStep) String() string { 94 s := fmt.Sprintf("run step: %s", strconv.Quote(l.command.String())) 95 if len(l.triggers) > 0 { 96 s = fmt.Sprintf("%s (triggers: %s)", s, strings.Join(l.triggers, "; ")) 97 } 98 return s 99 } 100 101 func (l liveUpdateRunStep) Type() string { return "live_update_run_step" } 102 func (l liveUpdateRunStep) Freeze() {} 103 func (l liveUpdateRunStep) Truth() starlark.Bool { 104 return starlark.Bool(!l.command.Empty()) 105 } 106 func (l liveUpdateRunStep) Hash() (uint32, error) { 107 t := starlark.Tuple{starlark.String(l.command.String())} 108 for _, trigger := range l.triggers { 109 t = append(t, starlark.String(trigger)) 110 } 111 return t.Hash() 112 } 113 func (l liveUpdateRunStep) declarationPos() string { return l.position.String() } 114 115 func (l liveUpdateRunStep) liveUpdateStep() {} 116 117 type liveUpdateRestartContainerStep struct { 118 position syntax.Position 119 } 120 121 var _ starlark.Value = liveUpdateRestartContainerStep{} 122 var _ liveUpdateStep = liveUpdateRestartContainerStep{} 123 124 func (l liveUpdateRestartContainerStep) String() string { return "restart_container step" } 125 func (l liveUpdateRestartContainerStep) Type() string { return "live_update_restart_container_step" } 126 func (l liveUpdateRestartContainerStep) Freeze() {} 127 func (l liveUpdateRestartContainerStep) Truth() starlark.Bool { return true } 128 func (l liveUpdateRestartContainerStep) Hash() (uint32, error) { return 0, nil } 129 func (l liveUpdateRestartContainerStep) declarationPos() string { return l.position.String() } 130 func (l liveUpdateRestartContainerStep) liveUpdateStep() {} 131 132 func (s *tiltfileState) recordLiveUpdateStep(step liveUpdateStep) { 133 s.unconsumedLiveUpdateSteps[step.declarationPos()] = step 134 } 135 136 func (s *tiltfileState) liveUpdateFallBackOn(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 137 files := value.NewLocalPathListUnpacker(thread) 138 if err := s.unpackArgs(fn.Name(), args, kwargs, "paths", &files); err != nil { 139 return nil, err 140 } 141 142 ret := liveUpdateFallBackOnStep{ 143 files: files.Value, 144 position: thread.CallFrame(1).Pos, 145 } 146 s.recordLiveUpdateStep(ret) 147 return ret, nil 148 } 149 150 func (s *tiltfileState) liveUpdateSync(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 151 var localPath, remotePath string 152 if err := s.unpackArgs(fn.Name(), args, kwargs, "local_path", &localPath, "remote_path", &remotePath); err != nil { 153 return nil, err 154 } 155 156 ret := liveUpdateSyncStep{ 157 localPath: starkit.AbsPath(thread, localPath), 158 remotePath: remotePath, 159 position: thread.CallFrame(1).Pos, 160 } 161 s.recordLiveUpdateStep(ret) 162 return ret, nil 163 } 164 165 func (s *tiltfileState) liveUpdateRun(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 166 var commandVal starlark.Value 167 var triggers starlark.Value 168 echoOff := false 169 if err := s.unpackArgs(fn.Name(), args, kwargs, 170 "cmd", &commandVal, 171 "trigger?", &triggers, 172 "echo_off?", &echoOff); err != nil { 173 return nil, err 174 } 175 176 command, err := value.ValueToUnixCmd(thread, commandVal, nil, nil) 177 if err != nil { 178 return nil, err 179 } 180 181 triggersSlice := starlarkValueOrSequenceToSlice(triggers) 182 var triggerStrings []string 183 for _, t := range triggersSlice { 184 switch t2 := t.(type) { 185 case starlark.String: 186 triggerStrings = append(triggerStrings, string(t2)) 187 default: 188 return nil, fmt.Errorf("run cmd '%s' triggers contained value '%s' of type '%s'. it may only contain strings", command, t.String(), t.Type()) 189 } 190 } 191 192 ret := liveUpdateRunStep{ 193 command: command, 194 triggers: triggerStrings, 195 echoOff: echoOff, 196 position: thread.CallFrame(1).Pos, 197 } 198 s.recordLiveUpdateStep(ret) 199 return ret, nil 200 } 201 202 func (s *tiltfileState) liveUpdateRestartContainer(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 203 if err := s.unpackArgs(fn.Name(), args, kwargs); err != nil { 204 return nil, err 205 } 206 207 ret := liveUpdateRestartContainerStep{ 208 position: thread.CallFrame(1).Pos, 209 } 210 s.recordLiveUpdateStep(ret) 211 return ret, nil 212 } 213 214 func (s *tiltfileState) liveUpdateFromSteps(t *starlark.Thread, maybeSteps starlark.Value) (v1alpha1.LiveUpdateSpec, error) { 215 var err error 216 217 basePath := starkit.AbsWorkingDir(t) 218 spec := v1alpha1.LiveUpdateSpec{ 219 BasePath: basePath, 220 } 221 222 stepSlice := starlarkValueOrSequenceToSlice(maybeSteps) 223 if len(stepSlice) == 0 { 224 return v1alpha1.LiveUpdateSpec{}, nil 225 } 226 227 noMoreFallbacks := false 228 noMoreSyncs := false 229 noMoreRuns := false 230 for _, v := range stepSlice { 231 step, ok := v.(liveUpdateStep) 232 if !ok { 233 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("'steps' must be a list of live update steps - got value '%v' of type '%s'", v.String(), v.Type()) 234 } 235 236 switch x := step.(type) { 237 238 case liveUpdateFallBackOnStep: 239 if noMoreFallbacks { 240 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("fall_back_on steps must appear at the start of the list") 241 } 242 243 for _, f := range x.files { 244 if filepath.IsAbs(f) { 245 f, err = filepath.Rel(basePath, f) 246 if err != nil { 247 return v1alpha1.LiveUpdateSpec{}, err 248 } 249 } 250 spec.StopPaths = append(spec.StopPaths, f) 251 } 252 253 case liveUpdateSyncStep: 254 if noMoreRuns { 255 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("restart container is only valid as the last step") 256 } 257 if noMoreSyncs { 258 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("all sync steps must precede all run steps") 259 } 260 noMoreFallbacks = true 261 262 localPath := x.localPath 263 if filepath.IsAbs(localPath) { 264 localPath, err = filepath.Rel(basePath, x.localPath) 265 if err != nil { 266 return v1alpha1.LiveUpdateSpec{}, err 267 } 268 } 269 spec.Syncs = append(spec.Syncs, v1alpha1.LiveUpdateSync{ 270 LocalPath: localPath, 271 ContainerPath: x.remotePath, 272 }) 273 274 case liveUpdateRunStep: 275 if noMoreRuns { 276 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("restart container is only valid as the last step") 277 } 278 noMoreFallbacks = true 279 noMoreSyncs = true 280 281 spec.Execs = append(spec.Execs, v1alpha1.LiveUpdateExec{ 282 Args: x.command.Argv, 283 TriggerPaths: x.triggers, 284 EchoOff: x.echoOff, 285 }) 286 287 case liveUpdateRestartContainerStep: 288 noMoreFallbacks = true 289 noMoreSyncs = true 290 noMoreRuns = true 291 spec.Restart = v1alpha1.LiveUpdateRestartStrategyAlways 292 293 default: 294 return v1alpha1.LiveUpdateSpec{}, fmt.Errorf("%s: internal error - unknown liveUpdateStep '%v' of type '%T'", x.declarationPos(), x, x) 295 } 296 297 s.consumeLiveUpdateStep(step) 298 } 299 300 errs := (&v1alpha1.LiveUpdate{Spec: spec}).Validate(s.ctx) 301 if len(errs) > 0 { 302 return v1alpha1.LiveUpdateSpec{}, errs.ToAggregate() 303 } 304 305 return spec, nil 306 } 307 308 func (s *tiltfileState) consumeLiveUpdateStep(stepToConsume liveUpdateStep) { 309 delete(s.unconsumedLiveUpdateSteps, stepToConsume.declarationPos()) 310 } 311 312 func (s *tiltfileState) checkForUnconsumedLiveUpdateSteps() error { 313 if len(s.unconsumedLiveUpdateSteps) > 0 { 314 var errorStrings []string 315 for _, step := range s.unconsumedLiveUpdateSteps { 316 errorStrings = append(errorStrings, fmt.Sprintf("%s: value '%s' of type '%s'", step.declarationPos(), step.String(), step.Type())) 317 } 318 return fmt.Errorf("found %d live_update steps that were created but not used in a live_update:\n%s", 319 len(s.unconsumedLiveUpdateSteps), strings.Join(errorStrings, "\n\t")) 320 } 321 322 return nil 323 }