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

     1  package k8s
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"os/exec"
     9  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // This isn't perfect (because it won't unquote the value right) but
    16  // it's good enough for 99% of cases.
    17  var envMatcher = regexp.MustCompile(`export (\w+)="([^"]+)"`)
    18  var versionMatcher = regexp.MustCompile(`^minikube version: v([0-9.]+)$`)
    19  
    20  // Error messages if Minikube is running OK but docker-env is unsupported.
    21  var dockerEnvUnsupportedMsgs = []string{
    22  	"ENV_DRIVER_CONFLICT",
    23  	"ENV_MULTINODE_CONFLICT",
    24  	"ENV_DOCKER_UNAVAILABLE",
    25  	"The docker-env command is only compatible",
    26  }
    27  
    28  type MinikubeClient interface {
    29  	Version(ctx context.Context) (string, error)
    30  	DockerEnv(ctx context.Context) (map[string]string, bool, error)
    31  	NodeIP(ctx context.Context) (NodeIP, error)
    32  }
    33  
    34  type minikubeClient struct {
    35  	context KubeContext
    36  }
    37  
    38  func ProvideMinikubeClient(context KubeContext) MinikubeClient {
    39  	return minikubeClient{context: context}
    40  }
    41  
    42  func (mc minikubeClient) cmd(ctx context.Context, args ...string) *exec.Cmd {
    43  	args = append([]string{"-p", string(mc.context)}, args...)
    44  	return exec.CommandContext(ctx, "minikube", args...)
    45  }
    46  
    47  func (mc minikubeClient) Version(ctx context.Context) (string, error) {
    48  	cmd := mc.cmd(ctx, "version")
    49  	output, err := cmd.Output()
    50  	if err != nil {
    51  		exitErr, isExitErr := err.(*exec.ExitError)
    52  		if isExitErr {
    53  			return "", fmt.Errorf("Could not read minikube version.\n%s", string(exitErr.Stderr))
    54  		}
    55  		return "", errors.Wrap(err, "Could not read minikube version")
    56  	}
    57  	return minikubeVersionFromOutput(output)
    58  
    59  }
    60  
    61  func minikubeVersionFromOutput(output []byte) (string, error) {
    62  	scanner := bufio.NewScanner(bytes.NewBuffer(output))
    63  	for scanner.Scan() {
    64  		line := scanner.Text()
    65  
    66  		match := versionMatcher.FindStringSubmatch(line)
    67  		if len(match) > 0 {
    68  			return match[1], nil
    69  		}
    70  	}
    71  
    72  	return "", fmt.Errorf("version not found in output:\n%s", string(output))
    73  }
    74  
    75  // Returns:
    76  // - A map of env variables for the minikube docker-env.
    77  // - True if this minikube supports a docker-env, false otherwise
    78  // - An error if minikube doesn't appear to be running.
    79  func (mc minikubeClient) DockerEnv(ctx context.Context) (map[string]string, bool, error) {
    80  	cmd := mc.cmd(ctx, "docker-env", "--shell", "sh")
    81  	output, err := cmd.Output()
    82  	if err != nil {
    83  		exitErr, isExitErr := err.(*exec.ExitError)
    84  		if isExitErr {
    85  			stderr := string(exitErr.Stderr)
    86  			for _, msg := range dockerEnvUnsupportedMsgs {
    87  				if strings.Contains(stderr, msg) {
    88  					return nil, false, nil
    89  				}
    90  			}
    91  
    92  			return nil, false, fmt.Errorf("Could not read docker env from minikube.\n"+
    93  				"Did you forget to run `minikube start`?\n%s", stderr)
    94  		}
    95  		return nil, false, errors.Wrap(err, "Could not read docker env from minikube")
    96  	}
    97  	return dockerEnvFromOutput(output), true, nil
    98  }
    99  
   100  func dockerEnvFromOutput(output []byte) map[string]string {
   101  	result := make(map[string]string)
   102  	scanner := bufio.NewScanner(bytes.NewBuffer(output))
   103  	for scanner.Scan() {
   104  		line := scanner.Text()
   105  
   106  		match := envMatcher.FindStringSubmatch(line)
   107  		if len(match) > 0 {
   108  			result[match[1]] = match[2]
   109  		}
   110  	}
   111  
   112  	return result
   113  }
   114  
   115  func (mc minikubeClient) NodeIP(ctx context.Context) (NodeIP, error) {
   116  	cmd := mc.cmd(ctx, "ip")
   117  	out, err := cmd.Output()
   118  	if err != nil {
   119  		exitErr, isExitErr := err.(*exec.ExitError)
   120  		if isExitErr {
   121  			return "", errors.Wrapf(exitErr, "Could not read node IP from minikube.\n"+
   122  				"Did you forget to run `minikube start`?\n%s", string(exitErr.Stderr))
   123  		}
   124  		return "", errors.Wrapf(err, "Could not read node IP from minikube")
   125  	}
   126  
   127  	return NodeIP(strings.TrimSpace(string(out))), nil
   128  }