github.com/tilt-dev/tilt@v0.36.0/internal/localexec/localexec.go (about) 1 // Package localexec provides constructs for uniform execution of local processes, 2 // specifically conversion from model.Cmd to exec.Cmd. 3 package localexec 4 5 import ( 6 "errors" 7 "fmt" 8 "os" 9 "os/exec" 10 "strconv" 11 "strings" 12 13 "github.com/tilt-dev/tilt/pkg/logger" 14 "github.com/tilt-dev/tilt/pkg/model" 15 ) 16 17 // Common environment for local exec commands. 18 type Env struct { 19 osEnviron func() []string 20 baseEnv func() []string 21 22 // TODO(nicksantos): get rid of pairs in favor of baseEnv. 23 pairs []kvPair 24 } 25 26 func EmptyEnv() *Env { 27 e := &Env{osEnviron: os.Environ} 28 e.baseEnv = func() []string { 29 return e.osEnviron() 30 } 31 return e 32 } 33 34 // We want to make sure all exec commands use the same kubernetes 35 // config as tilt. To accomplish this, we lazily generate a frozen 36 // kubeconfig to inject into the env. 37 type KubeconfigPathOnce func() string 38 39 func DefaultEnv(port model.WebPort, host model.WebHost, kubeconfigPathOnce KubeconfigPathOnce) *Env { 40 e := &Env{osEnviron: os.Environ} 41 e.baseEnv = func() []string { 42 env := e.osEnviron() 43 if kubeconfigPath := kubeconfigPathOnce(); kubeconfigPath != "" { 44 env = append(env, fmt.Sprintf("KUBECONFIG=%s", kubeconfigPath)) 45 } 46 return env 47 } 48 49 // if Tilt was invoked with `tilt up --port=XXXXX`, local() calls to use the Tilt API will fail due to trying to 50 // connect to the default port, so explicitly populate the TILT_PORT environment variable 51 e.Add("TILT_PORT", strconv.Itoa(int(port))) 52 53 // some Tilt commands, such as `tilt dump engine`, also require the host 54 e.Add("TILT_HOST", string(host)) 55 56 // We don't really want to collect analytics when extensions use 'tilt get'/'tilt apply'. 57 e.Add("TILT_DISABLE_ANALYTICS", "1") 58 59 return e 60 } 61 62 func (e *Env) Add(k, v string) { 63 e.pairs = append(e.pairs, kvPair{Key: k, Value: v}) 64 } 65 66 // ExecCmd creates a stdlib exec.Cmd instance suitable for execution by the local engine. 67 // 68 // The resulting command will inherit the parent process (i.e. `tilt`) environment, then 69 // have command specific environment overrides applied, and finally, additional conditional 70 // environment to improve logging output. 71 // 72 // NOTE: To avoid confusion with ExecCmdContext, this method accepts a logger instance 73 // directly rather than using logger.Get(ctx); the returned exec.Cmd from this function 74 // will NOT be associated with any context. 75 func (e *Env) ExecCmd(cmd model.Cmd, l logger.Logger) (*exec.Cmd, error) { 76 if len(cmd.Argv) == 0 { 77 return nil, errors.New("empty cmd") 78 } 79 c := exec.Command(cmd.Argv[0], cmd.Argv[1:]...) 80 e.populateExecCmd(c, cmd, l) 81 return c, nil 82 } 83 84 func (e *Env) populateExecCmd(c *exec.Cmd, cmd model.Cmd, l logger.Logger) { 85 c.Dir = cmd.Dir 86 // env precedence: parent process (i.e. tilt) -> logger -> command 87 // dupes are left for Go stdlib to handle (API guarantees last wins) 88 execEnv := e.baseEnv() 89 90 execEnv = logger.PrepareEnv(l, execEnv) 91 for _, kv := range e.pairs { 92 execEnv = addEnvIfNotPresent(execEnv, kv.Key, kv.Value) 93 } 94 95 execEnv = append(execEnv, cmd.Env...) 96 c.Env = execEnv 97 } 98 99 type kvPair struct { 100 Key string 101 Value string 102 } 103 104 func addEnvIfNotPresent(env []string, key, value string) []string { 105 prefix := key + "=" 106 for _, e := range env { 107 if strings.HasPrefix(e, prefix) { 108 return env 109 } 110 } 111 112 return append(env, key+"="+value) 113 }