github.com/tilt-dev/tilt@v0.36.0/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  	// The minikube client needs to know which minikube profile to talk to.
    36  	//
    37  	// When minikube creates a cluster, it sets the kubeconfig context name and
    38  	// the cluster nameto the name of the profile.
    39  	//
    40  	// The cluster name is better because users
    41  	// don't usually rename it.
    42  	context ClusterName
    43  }
    44  
    45  func ProvideMinikubeClient(context ClusterName) MinikubeClient {
    46  	return minikubeClient{context: context}
    47  }
    48  
    49  func (mc minikubeClient) cmd(ctx context.Context, args ...string) *exec.Cmd {
    50  	args = append([]string{"-p", string(mc.context)}, args...)
    51  	return exec.CommandContext(ctx, "minikube", args...)
    52  }
    53  
    54  func (mc minikubeClient) Version(ctx context.Context) (string, error) {
    55  	cmd := mc.cmd(ctx, "version")
    56  	output, err := cmd.Output()
    57  	if err != nil {
    58  		exitErr, isExitErr := err.(*exec.ExitError)
    59  		if isExitErr {
    60  			return "", fmt.Errorf("Could not read minikube version.\n%s", string(exitErr.Stderr))
    61  		}
    62  		return "", errors.Wrap(err, "Could not read minikube version")
    63  	}
    64  	return minikubeVersionFromOutput(output)
    65  
    66  }
    67  
    68  func minikubeVersionFromOutput(output []byte) (string, error) {
    69  	scanner := bufio.NewScanner(bytes.NewBuffer(output))
    70  	for scanner.Scan() {
    71  		line := scanner.Text()
    72  
    73  		match := versionMatcher.FindStringSubmatch(line)
    74  		if len(match) > 0 {
    75  			return match[1], nil
    76  		}
    77  	}
    78  
    79  	return "", fmt.Errorf("version not found in output:\n%s", string(output))
    80  }
    81  
    82  // Returns:
    83  // - A map of env variables for the minikube docker-env.
    84  // - True if this minikube supports a docker-env, false otherwise
    85  // - An error if minikube doesn't appear to be running.
    86  func (mc minikubeClient) DockerEnv(ctx context.Context) (map[string]string, bool, error) {
    87  	cmd := mc.cmd(ctx, "docker-env", "--shell", "sh")
    88  	output, err := cmd.Output()
    89  	if err != nil {
    90  		exitErr, isExitErr := err.(*exec.ExitError)
    91  		if isExitErr {
    92  			stderr := string(exitErr.Stderr)
    93  			for _, msg := range dockerEnvUnsupportedMsgs {
    94  				if strings.Contains(stderr, msg) {
    95  					return nil, false, nil
    96  				}
    97  			}
    98  
    99  			return nil, false, fmt.Errorf("Could not read docker env from minikube.\n"+
   100  				"Did you forget to run `minikube start`?\n%s", stderr)
   101  		}
   102  		return nil, false, errors.Wrap(err, "Could not read docker env from minikube")
   103  	}
   104  	return dockerEnvFromOutput(output), true, nil
   105  }
   106  
   107  func dockerEnvFromOutput(output []byte) map[string]string {
   108  	result := make(map[string]string)
   109  	scanner := bufio.NewScanner(bytes.NewBuffer(output))
   110  	for scanner.Scan() {
   111  		line := scanner.Text()
   112  
   113  		match := envMatcher.FindStringSubmatch(line)
   114  		if len(match) > 0 {
   115  			result[match[1]] = match[2]
   116  		}
   117  	}
   118  
   119  	return result
   120  }
   121  
   122  func (mc minikubeClient) NodeIP(ctx context.Context) (NodeIP, error) {
   123  	cmd := mc.cmd(ctx, "ip")
   124  	out, err := cmd.Output()
   125  	if err != nil {
   126  		exitErr, isExitErr := err.(*exec.ExitError)
   127  		if isExitErr {
   128  			return "", errors.Wrapf(exitErr, "Could not read node IP from minikube.\n"+
   129  				"Did you forget to run `minikube start`?\n%s", string(exitErr.Stderr))
   130  		}
   131  		return "", errors.Wrapf(err, "Could not read node IP from minikube")
   132  	}
   133  
   134  	return NodeIP(strings.TrimSpace(string(out))), nil
   135  }