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  }