Merge pull request #5530 from medyagh/NewCmdRunner
Refactor command runner interface, allow stdin writespull/5787/head
						commit
						ef398338d6
					
				| 
						 | 
				
			
			@ -1029,27 +1029,29 @@ Suggested workarounds:
 | 
			
		|||
		defer conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := r.Run("nslookup kubernetes.io"); err != nil {
 | 
			
		||||
	if _, err := r.RunCmd(exec.Command("nslookup", "kubernetes.io")); err != nil {
 | 
			
		||||
		out.WarningT("VM is unable to resolve DNS hosts: {[.error}}", out.V{"error": err})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Try both UDP and ICMP to assert basic external connectivity
 | 
			
		||||
	if err := r.Run("nslookup k8s.io 8.8.8.8 || nslookup k8s.io 1.1.1.1 || ping -c1 8.8.8.8"); err != nil {
 | 
			
		||||
	if _, err := r.RunCmd(exec.Command("/bin/bash", "-c", "nslookup k8s.io 8.8.8.8 || nslookup k8s.io 1.1.1.1 || ping -c1 8.8.8.8")); err != nil {
 | 
			
		||||
		out.WarningT("VM is unable to directly connect to the internet: {{.error}}", out.V{"error": err})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Try an HTTPS connection to the
 | 
			
		||||
	proxy := os.Getenv("HTTPS_PROXY")
 | 
			
		||||
	opts := "-sS"
 | 
			
		||||
	opts := []string{"-sS"}
 | 
			
		||||
	if proxy != "" && !strings.HasPrefix(proxy, "localhost") && !strings.HasPrefix(proxy, "127.0") {
 | 
			
		||||
		opts = fmt.Sprintf("-x %s %s", proxy, opts)
 | 
			
		||||
		opts = append([]string{"-x", proxy}, opts...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := viper.GetString(imageRepository)
 | 
			
		||||
	if repo == "" {
 | 
			
		||||
		repo = images.DefaultImageRepo
 | 
			
		||||
	}
 | 
			
		||||
	if err := r.Run(fmt.Sprintf("curl %s https://%s/", opts, repo)); err != nil {
 | 
			
		||||
 | 
			
		||||
	opts = append(opts, fmt.Sprintf("https://%s/", repo))
 | 
			
		||||
	if _, err := r.RunCmd(exec.Command("curl", opts...)); err != nil {
 | 
			
		||||
		out.WarningT("VM is unable to connect to the selected image repository: {{.error}}", out.V{"error": err})
 | 
			
		||||
	}
 | 
			
		||||
	return ip
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							| 
						 | 
				
			
			@ -43,6 +43,7 @@ require (
 | 
			
		|||
	github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect
 | 
			
		||||
	github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect
 | 
			
		||||
	github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 | 
			
		||||
	github.com/libvirt/libvirt-go v3.4.0+incompatible
 | 
			
		||||
	github.com/machine-drivers/docker-machine-driver-vmware v0.1.1
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.8
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							| 
						 | 
				
			
			@ -294,6 +294,8 @@ github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt
 | 
			
		|||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
 | 
			
		||||
github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
 | 
			
		||||
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,8 +17,8 @@ limitations under the License.
 | 
			
		|||
package none
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -168,8 +168,8 @@ func (d *Driver) Remove() error {
 | 
			
		|||
		return errors.Wrap(err, "kill")
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Removing: %s", cleanupPaths)
 | 
			
		||||
	cmd := fmt.Sprintf("sudo rm -rf %s", strings.Join(cleanupPaths, " "))
 | 
			
		||||
	if err := d.exec.Run(cmd); err != nil {
 | 
			
		||||
	args := append([]string{"rm", "-rf"}, cleanupPaths...)
 | 
			
		||||
	if _, err := d.exec.RunCmd(exec.Command("sudo", args...)); err != nil {
 | 
			
		||||
		glog.Errorf("cleanup incomplete: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -217,22 +217,20 @@ func (d *Driver) RunSSHCommandFromDriver() error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// stopKubelet idempotently stops the kubelet
 | 
			
		||||
func stopKubelet(exec command.Runner) error {
 | 
			
		||||
func stopKubelet(cr command.Runner) error {
 | 
			
		||||
	glog.Infof("stopping kubelet.service ...")
 | 
			
		||||
	stop := func() error {
 | 
			
		||||
		cmdStop := "sudo systemctl stop kubelet.service"
 | 
			
		||||
		cmdCheck := "sudo systemctl show -p SubState kubelet"
 | 
			
		||||
		err := exec.Run(cmdStop)
 | 
			
		||||
		cmd := exec.Command("sudo", "systemctl", "stop", "kubelet.service")
 | 
			
		||||
		if rr, err := cr.RunCmd(cmd); err != nil {
 | 
			
		||||
			glog.Errorf("temporary error for %q : %v", rr.Command(), err)
 | 
			
		||||
		}
 | 
			
		||||
		cmd = exec.Command("sudo", "systemctl", "show", "-p", "SubState", "kubelet")
 | 
			
		||||
		rr, err := cr.RunCmd(cmd)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("temporary error for %q : %v", cmdStop, err)
 | 
			
		||||
			glog.Errorf("temporary error: for %q : %v", rr.Command(), err)
 | 
			
		||||
		}
 | 
			
		||||
		var out bytes.Buffer
 | 
			
		||||
		errStatus := exec.CombinedOutputTo(cmdCheck, &out)
 | 
			
		||||
		if errStatus != nil {
 | 
			
		||||
			glog.Errorf("temporary error: for %q : %v", cmdCheck, errStatus)
 | 
			
		||||
		}
 | 
			
		||||
		if !strings.Contains(out.String(), "dead") && !strings.Contains(out.String(), "failed") {
 | 
			
		||||
			return fmt.Errorf("unexpected kubelet state: %q", out)
 | 
			
		||||
		if !strings.Contains(rr.Stdout.String(), "dead") && !strings.Contains(rr.Stdout.String(), "failed") {
 | 
			
		||||
			return fmt.Errorf("unexpected kubelet state: %q", rr.Stdout.String())
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -245,13 +243,21 @@ func stopKubelet(exec command.Runner) error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// restartKubelet restarts the kubelet
 | 
			
		||||
func restartKubelet(exec command.Runner) error {
 | 
			
		||||
func restartKubelet(cr command.Runner) error {
 | 
			
		||||
	glog.Infof("restarting kubelet.service ...")
 | 
			
		||||
	return exec.Run("sudo systemctl restart kubelet.service")
 | 
			
		||||
	c := exec.Command("sudo", "systemctl", "restart", "kubelet.service")
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkKubelet returns an error if the kubelet is not running.
 | 
			
		||||
func checkKubelet(exec command.Runner) error {
 | 
			
		||||
func checkKubelet(cr command.Runner) error {
 | 
			
		||||
	glog.Infof("checking for running kubelet ...")
 | 
			
		||||
	return exec.Run("systemctl is-active --quiet service kubelet")
 | 
			
		||||
	c := exec.Command("systemctl", "is-active", "--quiet", "service", "kubelet")
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "check kubelet")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import (
 | 
			
		|||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +142,7 @@ func SetupCerts(cmd command.Runner, k8s config.KubernetesConfig) error {
 | 
			
		|||
 | 
			
		||||
	// configure CA certificates
 | 
			
		||||
	if err := configureCACerts(cmd, caCerts); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "error configuring CA certificates during provisioning %v", err)
 | 
			
		||||
		return errors.Wrapf(err, "Configuring CA certs")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -318,21 +319,21 @@ func collectCACerts() (map[string]string, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// getSubjectHash calculates Certificate Subject Hash for creating certificate symlinks
 | 
			
		||||
func getSubjectHash(cmd command.Runner, filePath string) (string, error) {
 | 
			
		||||
	out, err := cmd.CombinedOutput(fmt.Sprintf("openssl x509 -hash -noout -in '%s'", filePath))
 | 
			
		||||
func getSubjectHash(cr command.Runner, filePath string) (string, error) {
 | 
			
		||||
	rr, err := cr.RunCmd(exec.Command("openssl", "x509", "-hash", "-noout", "-in", filePath))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errors.Wrapf(err, rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stringHash := strings.TrimSpace(out)
 | 
			
		||||
	stringHash := strings.TrimSpace(rr.Stdout.String())
 | 
			
		||||
	return stringHash, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// configureCACerts looks up and installs all uploaded PEM certificates in /usr/share/ca-certificates to system-wide certificate store (/etc/ssl/certs).
 | 
			
		||||
// OpenSSL binary required in minikube ISO
 | 
			
		||||
func configureCACerts(cmd command.Runner, caCerts map[string]string) error {
 | 
			
		||||
func configureCACerts(cr command.Runner, caCerts map[string]string) error {
 | 
			
		||||
	hasSSLBinary := true
 | 
			
		||||
	if err := cmd.Run("which openssl"); err != nil {
 | 
			
		||||
	_, err := cr.RunCmd(exec.Command("openssl", "version"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		hasSSLBinary = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -343,24 +344,25 @@ func configureCACerts(cmd command.Runner, caCerts map[string]string) error {
 | 
			
		|||
	for _, caCertFile := range caCerts {
 | 
			
		||||
		dstFilename := path.Base(caCertFile)
 | 
			
		||||
		certStorePath := path.Join(SSLCertStoreDir, dstFilename)
 | 
			
		||||
		if err := cmd.Run(fmt.Sprintf("sudo test -f '%s'", certStorePath)); err != nil {
 | 
			
		||||
			if err := cmd.Run(fmt.Sprintf("sudo ln -s '%s' '%s'", caCertFile, certStorePath)); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "error making symbol link for certificate %s", caCertFile)
 | 
			
		||||
		_, err := cr.RunCmd(exec.Command("sudo", "test", "-f", certStorePath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if _, err := cr.RunCmd(exec.Command("sudo", "ln", "-s", caCertFile, certStorePath)); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create symlink for %s", caCertFile)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if hasSSLBinary {
 | 
			
		||||
			subjectHash, err := getSubjectHash(cmd, caCertFile)
 | 
			
		||||
			subjectHash, err := getSubjectHash(cr, caCertFile)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "error calculating subject hash for certificate %s", caCertFile)
 | 
			
		||||
				return errors.Wrapf(err, "calculate hash for cacert %s", caCertFile)
 | 
			
		||||
			}
 | 
			
		||||
			subjectHashLink := path.Join(SSLCertStoreDir, fmt.Sprintf("%s.0", subjectHash))
 | 
			
		||||
			if err := cmd.Run(fmt.Sprintf("sudo test -f '%s'", subjectHashLink)); err != nil {
 | 
			
		||||
				if err := cmd.Run(fmt.Sprintf("sudo ln -s '%s' '%s'", certStorePath, subjectHashLink)); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "error making subject hash symbol link for certificate %s", caCertFile)
 | 
			
		||||
			_, err = cr.RunCmd(exec.Command("sudo", "test", "-f", subjectHashLink))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if _, err := cr.RunCmd(exec.Command("sudo", "ln", "-s", certStorePath, subjectHashLink)); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "linking caCertFile %s", caCertFile)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,9 +62,9 @@ func TestSetupCerts(t *testing.T) {
 | 
			
		|||
		certStorePath := path.Join(SSLCertStoreDir, dst)
 | 
			
		||||
		certNameHash := "abcdef"
 | 
			
		||||
		remoteCertHashLink := path.Join(SSLCertStoreDir, fmt.Sprintf("%s.0", certNameHash))
 | 
			
		||||
		cmdMap[fmt.Sprintf("sudo ln -s '%s' '%s'", certFile, certStorePath)] = "1"
 | 
			
		||||
		cmdMap[fmt.Sprintf("openssl x509 -hash -noout -in '%s'", certFile)] = certNameHash
 | 
			
		||||
		cmdMap[fmt.Sprintf("sudo ln -s '%s' '%s'", certStorePath, remoteCertHashLink)] = "1"
 | 
			
		||||
		cmdMap[fmt.Sprintf("sudo ln -s %s %s", certFile, certStorePath)] = "1"
 | 
			
		||||
		cmdMap[fmt.Sprintf("openssl x509 -hash -noout -in %s", certFile)] = certNameHash
 | 
			
		||||
		cmdMap[fmt.Sprintf("sudo ln -s %s %s", certStorePath, remoteCertHashLink)] = "1"
 | 
			
		||||
	}
 | 
			
		||||
	f := command.NewFakeCommandRunner()
 | 
			
		||||
	f.SetCommandToOutput(cmdMap)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ package kubeadm
 | 
			
		|||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
| 
						 | 
				
			
			@ -138,12 +139,11 @@ func NewKubeadmBootstrapper(api libmachine.API) (*Bootstrapper, error) {
 | 
			
		|||
 | 
			
		||||
// GetKubeletStatus returns the kubelet status
 | 
			
		||||
func (k *Bootstrapper) GetKubeletStatus() (string, error) {
 | 
			
		||||
	statusCmd := `sudo systemctl is-active kubelet`
 | 
			
		||||
	status, err := k.c.CombinedOutput(statusCmd)
 | 
			
		||||
	rr, err := k.c.RunCmd(exec.Command("sudo", "systemctl", "is-active", "kubelet"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrap(err, "getting status")
 | 
			
		||||
		return "", errors.Wrapf(err, "getting kublet status. command: %q", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	s := strings.TrimSpace(status)
 | 
			
		||||
	s := strings.TrimSpace(rr.Stdout.String())
 | 
			
		||||
	switch s {
 | 
			
		||||
	case "active":
 | 
			
		||||
		return state.Running.String(), nil
 | 
			
		||||
| 
						 | 
				
			
			@ -224,16 +224,16 @@ func etcdDataDir() string {
 | 
			
		|||
// createCompatSymlinks creates compatibility symlinks to transition running services to new directory structures
 | 
			
		||||
func (k *Bootstrapper) createCompatSymlinks() error {
 | 
			
		||||
	legacyEtcd := "/data/minikube"
 | 
			
		||||
	if err := k.c.Run(fmt.Sprintf("sudo test -d %s", legacyEtcd)); err != nil {
 | 
			
		||||
		glog.Infof("%s check failed, skipping compat symlinks: %v", legacyEtcd, err)
 | 
			
		||||
 | 
			
		||||
	if _, err := k.c.RunCmd(exec.Command("sudo", "test", "-d", legacyEtcd)); err != nil {
 | 
			
		||||
		glog.Infof("%s skipping compat symlinks: %v", legacyEtcd, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glog.Infof("Found %s, creating compatibility symlinks ...", legacyEtcd)
 | 
			
		||||
	cmd := fmt.Sprintf("sudo ln -s %s %s", legacyEtcd, etcdDataDir())
 | 
			
		||||
	out, err := k.c.CombinedOutput(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "cmd failed: %s\n%s\n", cmd, out)
 | 
			
		||||
 | 
			
		||||
	c := exec.Command("sudo", "ln", "-s", legacyEtcd, etcdDataDir())
 | 
			
		||||
	if rr, err := k.c.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "create symlink failed: %s", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -275,11 +275,9 @@ func (k *Bootstrapper) StartCluster(k8s config.KubernetesConfig) error {
 | 
			
		|||
		ignore = append(ignore, "SystemVerification")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := fmt.Sprintf("%s init --config %s %s --ignore-preflight-errors=%s",
 | 
			
		||||
		invokeKubeadm(k8s.KubernetesVersion), yamlConfigPath, extraFlags, strings.Join(ignore, ","))
 | 
			
		||||
	out, err := k.c.CombinedOutput(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "cmd failed: %s\n%s\n", cmd, out)
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s init --config %s %s --ignore-preflight-errors=%s", invokeKubeadm(k8s.KubernetesVersion), yamlConfigPath, extraFlags, strings.Join(ignore, ",")))
 | 
			
		||||
	if rr, err := k.c.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "init failed. cmd: %q", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glog.Infof("Configuring cluster permissions ...")
 | 
			
		||||
| 
						 | 
				
			
			@ -304,22 +302,23 @@ func (k *Bootstrapper) StartCluster(k8s config.KubernetesConfig) error {
 | 
			
		|||
 | 
			
		||||
// adjustResourceLimits makes fine adjustments to pod resources that aren't possible via kubeadm config.
 | 
			
		||||
func (k *Bootstrapper) adjustResourceLimits() error {
 | 
			
		||||
	score, err := k.c.CombinedOutput("cat /proc/$(pgrep kube-apiserver)/oom_adj")
 | 
			
		||||
	rr, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", "cat /proc/$(pgrep kube-apiserver)/oom_adj"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "oom_adj check")
 | 
			
		||||
		return errors.Wrapf(err, "oom_adj check cmd %s. ", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("apiserver oom_adj: %s", score)
 | 
			
		||||
	glog.Infof("apiserver oom_adj: %s", rr.Stdout.String())
 | 
			
		||||
	// oom_adj is already a negative number
 | 
			
		||||
	if strings.HasPrefix(score, "-") {
 | 
			
		||||
	if strings.HasPrefix(rr.Stdout.String(), "-") {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("adjusting apiserver oom_adj to -10")
 | 
			
		||||
 | 
			
		||||
	// Prevent the apiserver from OOM'ing before other pods, as it is our gateway into the cluster.
 | 
			
		||||
	// It'd be preferable to do this via Kubernetes, but kubeadm doesn't have a way to set pod QoS.
 | 
			
		||||
	if err := k.c.Run("echo -10 | sudo tee /proc/$(pgrep kube-apiserver)/oom_adj"); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "oom_adj adjust")
 | 
			
		||||
	if _, err = k.c.RunCmd(exec.Command("/bin/bash", "-c", "echo -10 | sudo tee /proc/$(pgrep kube-apiserver)/oom_adj")); err != nil {
 | 
			
		||||
		return errors.Wrap(err, fmt.Sprintf("oom_adj adjust"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -465,18 +464,20 @@ func (k *Bootstrapper) RestartCluster(k8s config.KubernetesConfig) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Run commands one at a time so that it is easier to root cause failures.
 | 
			
		||||
	for _, cmd := range cmds {
 | 
			
		||||
		if err := k.c.Run(cmd); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "running cmd: %s", cmd)
 | 
			
		||||
	for _, c := range cmds {
 | 
			
		||||
		rr, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", c))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "running cmd: %s", rr.Command())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := k.waitForAPIServer(k8s); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "waiting for apiserver")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// restart the proxy and coredns
 | 
			
		||||
	if err := k.c.Run(fmt.Sprintf("%s phase addon all --config %s", baseCmd, yamlConfigPath)); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "addon phase")
 | 
			
		||||
	if rr, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", fmt.Sprintf("%s phase addon all --config %s", baseCmd, yamlConfigPath))); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, fmt.Sprintf("addon phase cmd:%q", rr.Command()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := k.adjustResourceLimits(); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -496,9 +497,9 @@ func (k *Bootstrapper) waitForAPIServer(k8s config.KubernetesConfig) error {
 | 
			
		|||
	// To give a better error message, first check for process existence via ssh
 | 
			
		||||
	// Needs minutes in case the image isn't cached (such as with v1.10.x)
 | 
			
		||||
	err := wait.PollImmediate(time.Millisecond*300, time.Minute*3, func() (bool, error) {
 | 
			
		||||
		ierr := k.c.Run(`sudo pgrep kube-apiserver`)
 | 
			
		||||
		rr, ierr := k.c.RunCmd(exec.Command("sudo", "pgrep", "kube-apiserver"))
 | 
			
		||||
		if ierr != nil {
 | 
			
		||||
			glog.Warningf("pgrep apiserver: %v", ierr)
 | 
			
		||||
			glog.Warningf("pgrep apiserver: %v cmd: %s", ierr, rr.Command())
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
		return true, nil
 | 
			
		||||
| 
						 | 
				
			
			@ -549,9 +550,9 @@ func (k *Bootstrapper) DeleteCluster(k8s config.KubernetesConfig) error {
 | 
			
		|||
	if version.LT(semver.MustParse("1.11.0")) {
 | 
			
		||||
		cmd = fmt.Sprintf("%s reset", invokeKubeadm(k8s.KubernetesVersion))
 | 
			
		||||
	}
 | 
			
		||||
	out, err := k.c.CombinedOutput(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "kubeadm reset: %s\n%s\n", cmd, out)
 | 
			
		||||
 | 
			
		||||
	if rr, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", cmd)); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "kubeadm reset: cmd: %q", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -567,9 +568,9 @@ func (k *Bootstrapper) PullImages(k8s config.KubernetesConfig) error {
 | 
			
		|||
		return fmt.Errorf("pull command is not supported by kubeadm v%s", version)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := fmt.Sprintf("%s config images pull --config %s", invokeKubeadm(k8s.KubernetesVersion), yamlConfigPath)
 | 
			
		||||
	if err := k.c.Run(cmd); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "running cmd: %s", cmd)
 | 
			
		||||
	rr, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", fmt.Sprintf("%s config images pull --config %s", invokeKubeadm(k8s.KubernetesVersion), yamlConfigPath)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "running cmd: %q", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -663,11 +664,12 @@ func (k *Bootstrapper) UpdateCluster(cfg config.KubernetesConfig) error {
 | 
			
		|||
 | 
			
		||||
	glog.Infof("kubelet %s config:\n%s", cfg.KubernetesVersion, kubeletCfg)
 | 
			
		||||
 | 
			
		||||
	stopCmd := exec.Command("/bin/bash", "-c", "pgrep kubelet && sudo systemctl stop kubelet")
 | 
			
		||||
	// stop kubelet to avoid "Text File Busy" error
 | 
			
		||||
	err = k.c.Run(`pgrep kubelet && sudo systemctl stop kubelet`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.Warningf("unable to stop kubelet: %s", err)
 | 
			
		||||
	if rr, err := k.c.RunCmd(stopCmd); err != nil {
 | 
			
		||||
		glog.Warningf("unable to stop kubelet: %s command: %q output: %q", err, rr.Command(), rr.Output())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := transferBinaries(cfg, k.c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "downloading binaries")
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -681,7 +683,7 @@ func (k *Bootstrapper) UpdateCluster(cfg config.KubernetesConfig) error {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := k.c.Run(`sudo systemctl daemon-reload && sudo systemctl start kubelet`); err != nil {
 | 
			
		||||
	if _, err := k.c.RunCmd(exec.Command("/bin/bash", "-c", "sudo systemctl daemon-reload && sudo systemctl start kubelet")); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "starting kubelet")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,12 +19,14 @@ package cluster
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/command"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MountConfig defines the options available to the Mount command
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +51,7 @@ type MountConfig struct {
 | 
			
		|||
 | 
			
		||||
// mountRunner is the subset of CommandRunner used for mounting
 | 
			
		||||
type mountRunner interface {
 | 
			
		||||
	CombinedOutput(string) (string, error)
 | 
			
		||||
	RunCmd(*exec.Cmd) (*command.RunResult, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mount runs the mount command from the 9p client on the VM to the 9p server on the host
 | 
			
		||||
| 
						 | 
				
			
			@ -58,14 +60,16 @@ func Mount(r mountRunner, source string, target string, c *MountConfig) error {
 | 
			
		|||
		return errors.Wrap(err, "umount")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := fmt.Sprintf("sudo mkdir -m %o -p %s && %s", c.Mode, target, mntCmd(source, target, c))
 | 
			
		||||
	glog.Infof("Will run: %s", cmd)
 | 
			
		||||
	out, err := r.CombinedOutput(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.Infof("%s failed: err=%s, output: %q", cmd, err, out)
 | 
			
		||||
		return errors.Wrap(err, out)
 | 
			
		||||
	if _, err := r.RunCmd(exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo mkdir -m %o -p %s && %s", c.Mode, target, mntCmd(source, target, c)))); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create folder pre-mount")
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("%s output: %q", cmd, out)
 | 
			
		||||
 | 
			
		||||
	rr, err := r.RunCmd(exec.Command("/bin/bash", "-c", mntCmd(source, target, c)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "mount with cmd %s ", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glog.Infof("mount successful: %q", rr.Output())
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -131,20 +135,13 @@ func mntCmd(source string, target string, c *MountConfig) string {
 | 
			
		|||
	return fmt.Sprintf("sudo mount -t %s -o %s %s %s", c.Type, strings.Join(opts, ","), source, target)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// umountCmd returns a command for unmounting
 | 
			
		||||
func umountCmd(target string) string {
 | 
			
		||||
	// grep because findmnt will also display the parent!
 | 
			
		||||
	return fmt.Sprintf("[ \"x$(findmnt -T %s | grep %s)\" != \"x\" ] && sudo umount -f %s || echo ", target, target, target)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Unmount unmounts a path
 | 
			
		||||
func Unmount(r mountRunner, target string) error {
 | 
			
		||||
	cmd := umountCmd(target)
 | 
			
		||||
	glog.Infof("Will run: %s", cmd)
 | 
			
		||||
	out, err := r.CombinedOutput(cmd)
 | 
			
		||||
	glog.Infof("unmount force err=%v, out=%s", err, out)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, out)
 | 
			
		||||
	// grep because findmnt will also display the parent!
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", fmt.Sprintf("[ \"x$(findmnt -T %s | grep %s)\" != \"x\" ] && sudo umount -f %s || echo ", target, target, target))
 | 
			
		||||
	if _, err := r.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unmount")
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("unmount for %s ran successfully", target)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,50 +23,27 @@ import (
 | 
			
		|||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type mockMountRunner struct {
 | 
			
		||||
	cmds []string
 | 
			
		||||
	T    *testing.T
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newMockMountRunner(t *testing.T) *mockMountRunner {
 | 
			
		||||
	return &mockMountRunner{
 | 
			
		||||
		T:    t,
 | 
			
		||||
		cmds: []string{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockMountRunner) CombinedOutput(cmd string) (string, error) {
 | 
			
		||||
	m.cmds = append(m.cmds, cmd)
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMount(t *testing.T) {
 | 
			
		||||
func TestMntCmd(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		source string
 | 
			
		||||
		target string
 | 
			
		||||
		cfg    *MountConfig
 | 
			
		||||
		want   []string
 | 
			
		||||
		want   string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:   "simple",
 | 
			
		||||
			source: "src",
 | 
			
		||||
			target: "target",
 | 
			
		||||
			cfg:    &MountConfig{Type: "9p", Mode: os.FileMode(0700)},
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"[ \"x$(findmnt -T target | grep target)\" != \"x\" ] && sudo umount -f target || echo ",
 | 
			
		||||
				"sudo mkdir -m 700 -p target && sudo mount -t 9p -o dfltgid=0,dfltuid=0 src target",
 | 
			
		||||
			},
 | 
			
		||||
			want:   "sudo mount -t 9p -o dfltgid=0,dfltuid=0 src target",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "named uid",
 | 
			
		||||
			source: "src",
 | 
			
		||||
			target: "target",
 | 
			
		||||
			cfg:    &MountConfig{Type: "9p", Mode: os.FileMode(0700), UID: "docker", GID: "docker"},
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"[ \"x$(findmnt -T target | grep target)\" != \"x\" ] && sudo umount -f target || echo ",
 | 
			
		||||
				"sudo mkdir -m 700 -p target && sudo mount -t 9p -o dfltgid=$(grep ^docker: /etc/group | cut -d: -f3),dfltuid=$(id -u docker) src target",
 | 
			
		||||
			},
 | 
			
		||||
			want:   "sudo mount -t 9p -o dfltgid=$(grep ^docker: /etc/group | cut -d: -f3),dfltuid=$(id -u docker) src target",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "everything",
 | 
			
		||||
| 
						 | 
				
			
			@ -76,10 +53,7 @@ func TestMount(t *testing.T) {
 | 
			
		|||
				"noextend": "",
 | 
			
		||||
				"cache":    "fscache",
 | 
			
		||||
			}},
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"[ \"x$(findmnt -T /target | grep /target)\" != \"x\" ] && sudo umount -f /target || echo ",
 | 
			
		||||
				"sudo mkdir -m 777 -p /target && sudo mount -t 9p -o cache=fscache,dfltgid=72,dfltuid=82,noextend,version=9p2000.u 10.0.0.1 /target",
 | 
			
		||||
			},
 | 
			
		||||
			want: "sudo mount -t 9p -o cache=fscache,dfltgid=72,dfltuid=82,noextend,version=9p2000.u 10.0.0.1 /target",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "version-conflict",
 | 
			
		||||
| 
						 | 
				
			
			@ -88,35 +62,17 @@ func TestMount(t *testing.T) {
 | 
			
		|||
			cfg: &MountConfig{Type: "9p", Mode: os.FileMode(0700), Version: "9p2000.u", Options: map[string]string{
 | 
			
		||||
				"version": "9p2000.L",
 | 
			
		||||
			}},
 | 
			
		||||
			want: []string{
 | 
			
		||||
				"[ \"x$(findmnt -T tgt | grep tgt)\" != \"x\" ] && sudo umount -f tgt || echo ",
 | 
			
		||||
				"sudo mkdir -m 700 -p tgt && sudo mount -t 9p -o dfltgid=0,dfltuid=0,version=9p2000.L src tgt",
 | 
			
		||||
			},
 | 
			
		||||
			want: "sudo mount -t 9p -o dfltgid=0,dfltuid=0,version=9p2000.L src tgt",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range tests {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			r := newMockMountRunner(t)
 | 
			
		||||
			err := Mount(r, tc.source, tc.target, tc.cfg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("Mount(%s, %s, %+v): %v", tc.source, tc.target, tc.cfg, err)
 | 
			
		||||
			}
 | 
			
		||||
			if diff := cmp.Diff(r.cmds, tc.want); diff != "" {
 | 
			
		||||
			got := mntCmd(tc.source, tc.target, tc.cfg)
 | 
			
		||||
			want := tc.want
 | 
			
		||||
			if diff := cmp.Diff(got, want); diff != "" {
 | 
			
		||||
				t.Errorf("command diff (-want +got): %s", diff)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmount(t *testing.T) {
 | 
			
		||||
	r := newMockMountRunner(t)
 | 
			
		||||
	err := Unmount(r, "/mnt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unmount(/mnt): %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	want := []string{"[ \"x$(findmnt -T /mnt | grep /mnt)\" != \"x\" ] && sudo umount -f /mnt || echo "}
 | 
			
		||||
	if diff := cmp.Diff(r.cmds, want); diff != "" {
 | 
			
		||||
		t.Errorf("command diff (-want +got): %s", diff)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,34 +17,28 @@ limitations under the License.
 | 
			
		|||
package command
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/assets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RunResult holds the results of a Runner
 | 
			
		||||
type RunResult struct {
 | 
			
		||||
	Stdout   bytes.Buffer
 | 
			
		||||
	Stderr   bytes.Buffer
 | 
			
		||||
	ExitCode int
 | 
			
		||||
	Args     []string // the args that was passed to Runner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Runner represents an interface to run commands.
 | 
			
		||||
type Runner interface {
 | 
			
		||||
	// Run starts the specified command and waits for it to complete.
 | 
			
		||||
	Run(cmd string) error
 | 
			
		||||
 | 
			
		||||
	// CombinedOutputTo runs the command and stores both command
 | 
			
		||||
	// output and error to out. A typical usage is:
 | 
			
		||||
	//
 | 
			
		||||
	//          var b bytes.Buffer
 | 
			
		||||
	//          CombinedOutput(cmd, &b)
 | 
			
		||||
	//          fmt.Println(b.Bytes())
 | 
			
		||||
	//
 | 
			
		||||
	// Or, you can set out to os.Stdout, the command output and
 | 
			
		||||
	// error would show on your terminal immediately before you
 | 
			
		||||
	// cmd exit. This is useful for a long run command such as
 | 
			
		||||
	// continuously print running logs.
 | 
			
		||||
	CombinedOutputTo(cmd string, out io.Writer) error
 | 
			
		||||
 | 
			
		||||
	// CombinedOutput runs the command and returns its combined standard
 | 
			
		||||
	// output and standard error.
 | 
			
		||||
	CombinedOutput(cmd string) (string, error)
 | 
			
		||||
	// RunCmd runs a cmd of exec.Cmd type. allowing user to set cmd.Stdin, cmd.Stdout,...
 | 
			
		||||
	// not all implementors are guaranteed to handle all the properties of cmd.
 | 
			
		||||
	RunCmd(cmd *exec.Cmd) (*RunResult, error)
 | 
			
		||||
 | 
			
		||||
	// Copy is a convenience method that runs a command to copy a file
 | 
			
		||||
	Copy(assets.CopyableFile) error
 | 
			
		||||
| 
						 | 
				
			
			@ -56,3 +50,29 @@ type Runner interface {
 | 
			
		|||
func getDeleteFileCommand(f assets.CopyableFile) string {
 | 
			
		||||
	return fmt.Sprintf("sudo rm %s", path.Join(f.GetTargetDir(), f.GetTargetName()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Command returns a human readable command string that does not induce eye fatigue
 | 
			
		||||
func (rr RunResult) Command() string {
 | 
			
		||||
	var sb strings.Builder
 | 
			
		||||
	sb.WriteString(rr.Args[0])
 | 
			
		||||
	for _, a := range rr.Args[1:] {
 | 
			
		||||
		if strings.Contains(a, " ") {
 | 
			
		||||
			sb.WriteString(fmt.Sprintf(` "%s"`, a))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		sb.WriteString(fmt.Sprintf(" %s", a))
 | 
			
		||||
	}
 | 
			
		||||
	return sb.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Output returns human-readable output for an execution result
 | 
			
		||||
func (rr RunResult) Output() string {
 | 
			
		||||
	var sb strings.Builder
 | 
			
		||||
	if rr.Stdout.Len() > 0 {
 | 
			
		||||
		sb.WriteString(fmt.Sprintf("-- stdout --\n%s\n-- /stdout --", rr.Stdout.Bytes()))
 | 
			
		||||
	}
 | 
			
		||||
	if rr.Stderr.Len() > 0 {
 | 
			
		||||
		sb.WriteString(fmt.Sprintf("\n** stderr ** \n%s\n** /stderr **", rr.Stderr.Bytes()))
 | 
			
		||||
	}
 | 
			
		||||
	return sb.String()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import (
 | 
			
		|||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
| 
						 | 
				
			
			@ -35,41 +36,45 @@ import (
 | 
			
		|||
// It implements the CommandRunner interface.
 | 
			
		||||
type ExecRunner struct{}
 | 
			
		||||
 | 
			
		||||
// Run starts the specified command in a bash shell and waits for it to complete.
 | 
			
		||||
func (*ExecRunner) Run(cmd string) error {
 | 
			
		||||
	glog.Infoln("Run:", cmd)
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", cmd)
 | 
			
		||||
	if err := c.Run(); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "running command: %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
// RunCmd implements the Command Runner interface to run a exec.Cmd object
 | 
			
		||||
func (*ExecRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) {
 | 
			
		||||
	rr := &RunResult{Args: cmd.Args}
 | 
			
		||||
	glog.Infof("(ExecRunner) Run:  %v", rr.Command())
 | 
			
		||||
 | 
			
		||||
// CombinedOutputTo runs the command and stores both command
 | 
			
		||||
// output and error to out.
 | 
			
		||||
func (*ExecRunner) CombinedOutputTo(cmd string, out io.Writer) error {
 | 
			
		||||
	glog.Infoln("Run with output:", cmd)
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", cmd)
 | 
			
		||||
	c.Stdout = out
 | 
			
		||||
	c.Stderr = out
 | 
			
		||||
	err := c.Run()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "running command: %s\n.", cmd)
 | 
			
		||||
	var outb, errb io.Writer
 | 
			
		||||
	if cmd.Stdout == nil {
 | 
			
		||||
		var so bytes.Buffer
 | 
			
		||||
		outb = io.MultiWriter(&so, &rr.Stdout)
 | 
			
		||||
	} else {
 | 
			
		||||
		outb = io.MultiWriter(cmd.Stdout, &rr.Stdout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CombinedOutput runs the command  in a bash shell and returns its
 | 
			
		||||
// combined standard output and standard error.
 | 
			
		||||
func (e *ExecRunner) CombinedOutput(cmd string) (string, error) {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	err := e.CombinedOutputTo(cmd, &b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrapf(err, "running command: %s\n output: %s", cmd, b.Bytes())
 | 
			
		||||
	if cmd.Stderr == nil {
 | 
			
		||||
		var se bytes.Buffer
 | 
			
		||||
		errb = io.MultiWriter(&se, &rr.Stderr)
 | 
			
		||||
	} else {
 | 
			
		||||
		errb = io.MultiWriter(cmd.Stderr, &rr.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	return b.String(), nil
 | 
			
		||||
 | 
			
		||||
	cmd.Stdout = outb
 | 
			
		||||
	cmd.Stderr = errb
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	err := cmd.Run()
 | 
			
		||||
	elapsed := time.Since(start)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Reduce log spam
 | 
			
		||||
		if elapsed > (1 * time.Second) {
 | 
			
		||||
			glog.Infof("(ExecRunner) Done: %v: (%s)", rr.Command(), elapsed)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if exitError, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
			rr.ExitCode = exitError.ExitCode()
 | 
			
		||||
		}
 | 
			
		||||
		glog.Infof("(ExecRunner) Non-zero exit: %v: %v (%s)\n%s", rr.Command(), err, elapsed, rr.Output())
 | 
			
		||||
		err = errors.Wrapf(err, "command failed: %s\nstdout: %s\nstderr: %s", rr.Command(), rr.Stdout.String(), rr.Stderr.String())
 | 
			
		||||
	}
 | 
			
		||||
	return rr, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy copies a file and its permissions
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,9 +20,13 @@ import (
 | 
			
		|||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sync/syncmap"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/assets"
 | 
			
		||||
| 
						 | 
				
			
			@ -43,34 +47,38 @@ func NewFakeCommandRunner() *FakeCommandRunner {
 | 
			
		|||
	return &FakeCommandRunner{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run returns nil if output has been set for the given command text.
 | 
			
		||||
func (f *FakeCommandRunner) Run(cmd string) error {
 | 
			
		||||
	_, err := f.CombinedOutput(cmd)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
// RunCmd implements the Command Runner interface to run a exec.Cmd object
 | 
			
		||||
func (f *FakeCommandRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) {
 | 
			
		||||
	rr := &RunResult{Args: cmd.Args}
 | 
			
		||||
	glog.Infof("(FakeCommandRunner) Run:  %v", rr.Command())
 | 
			
		||||
 | 
			
		||||
// CombinedOutputTo runs the command and stores both command
 | 
			
		||||
// output and error to out.
 | 
			
		||||
func (f *FakeCommandRunner) CombinedOutputTo(cmd string, out io.Writer) error {
 | 
			
		||||
	value, ok := f.cmdMap.Load(cmd)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("unavailable command: %s", cmd)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	out, ok := f.cmdMap.Load(strings.Join(rr.Args, " "))
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	outStr := ""
 | 
			
		||||
	if out != nil {
 | 
			
		||||
		outStr = out.(string)
 | 
			
		||||
	}
 | 
			
		||||
	_, err := fmt.Fprint(out, value)
 | 
			
		||||
	_, err := buf.WriteString(outStr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return rr, errors.Wrap(err, "Writing outStr to FakeCommandRunner's buffer")
 | 
			
		||||
	}
 | 
			
		||||
	rr.Stdout = buf
 | 
			
		||||
	rr.Stderr = buf
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
	elapsed := time.Since(start)
 | 
			
		||||
 | 
			
		||||
// CombinedOutput returns the set output for a given command text.
 | 
			
		||||
func (f *FakeCommandRunner) CombinedOutput(cmd string) (string, error) {
 | 
			
		||||
	out, ok := f.cmdMap.Load(cmd)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", fmt.Errorf("unavailable command: %s", cmd)
 | 
			
		||||
	if ok {
 | 
			
		||||
		// Reduce log spam
 | 
			
		||||
		if elapsed > (1 * time.Second) {
 | 
			
		||||
			glog.Infof("(FakeCommandRunner) Done: %v: (%s)", rr.Command(), elapsed)
 | 
			
		||||
		}
 | 
			
		||||
	return out.(string), nil
 | 
			
		||||
	} else {
 | 
			
		||||
		glog.Infof("(FakeCommandRunner) Non-zero exit: %v: (%s)\n%s", rr.Command(), elapsed, out)
 | 
			
		||||
		return rr, fmt.Errorf("unavailable command: %s", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	return rr, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy adds the filename, file contents key value pair to the stored map.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,13 +17,17 @@ limitations under the License.
 | 
			
		|||
package command
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/kballard/go-shellquote"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"golang.org/x/sync/errgroup"
 | 
			
		||||
| 
						 | 
				
			
			@ -55,17 +59,6 @@ func (s *SSHRunner) Remove(f assets.CopyableFile) error {
 | 
			
		|||
	return sess.Run(cmd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type singleWriter struct {
 | 
			
		||||
	b  bytes.Buffer
 | 
			
		||||
	mu sync.Mutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *singleWriter) Write(p []byte) (int, error) {
 | 
			
		||||
	w.mu.Lock()
 | 
			
		||||
	defer w.mu.Unlock()
 | 
			
		||||
	return w.b.Write(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// teeSSH runs an SSH command, streaming stdout, stderr to logs
 | 
			
		||||
func teeSSH(s *ssh.Session, cmd string, outB io.Writer, errB io.Writer) error {
 | 
			
		||||
	outPipe, err := s.StdoutPipe()
 | 
			
		||||
| 
						 | 
				
			
			@ -81,13 +74,13 @@ func teeSSH(s *ssh.Session, cmd string, outB io.Writer, errB io.Writer) error {
 | 
			
		|||
	wg.Add(2)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := util.TeePrefix(util.ErrPrefix, errPipe, errB, glog.V(8).Infof); err != nil {
 | 
			
		||||
		if err := teePrefix(util.ErrPrefix, errPipe, errB, glog.V(8).Infof); err != nil {
 | 
			
		||||
			glog.Errorf("tee stderr: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := util.TeePrefix(util.OutPrefix, outPipe, outB, glog.V(8).Infof); err != nil {
 | 
			
		||||
		if err := teePrefix(util.OutPrefix, outPipe, outB, glog.V(8).Infof); err != nil {
 | 
			
		||||
			glog.Errorf("tee stdout: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
| 
						 | 
				
			
			@ -97,12 +90,31 @@ func teeSSH(s *ssh.Session, cmd string, outB io.Writer, errB io.Writer) error {
 | 
			
		|||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run starts a command on the remote and waits for it to return.
 | 
			
		||||
func (s *SSHRunner) Run(cmd string) error {
 | 
			
		||||
	glog.Infof("SSH: %s", cmd)
 | 
			
		||||
// RunCmd implements the Command Runner interface to run a exec.Cmd object
 | 
			
		||||
func (s *SSHRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) {
 | 
			
		||||
	rr := &RunResult{Args: cmd.Args}
 | 
			
		||||
	glog.Infof("(SSHRunner) Run:  %v", rr.Command())
 | 
			
		||||
 | 
			
		||||
	var outb, errb io.Writer
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	if cmd.Stdout == nil {
 | 
			
		||||
		var so bytes.Buffer
 | 
			
		||||
		outb = io.MultiWriter(&so, &rr.Stdout)
 | 
			
		||||
	} else {
 | 
			
		||||
		outb = io.MultiWriter(cmd.Stdout, &rr.Stdout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cmd.Stderr == nil {
 | 
			
		||||
		var se bytes.Buffer
 | 
			
		||||
		errb = io.MultiWriter(&se, &rr.Stderr)
 | 
			
		||||
	} else {
 | 
			
		||||
		errb = io.MultiWriter(cmd.Stderr, &rr.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess, err := s.c.NewSession()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "NewSession")
 | 
			
		||||
		return rr, errors.Wrap(err, "NewSession")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
| 
						 | 
				
			
			@ -112,43 +124,21 @@ func (s *SSHRunner) Run(cmd string) error {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var outB bytes.Buffer
 | 
			
		||||
	var errB bytes.Buffer
 | 
			
		||||
	err = teeSSH(sess, cmd, &outB, &errB)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "command failed: %s\nstdout: %s\nstderr: %s", cmd, outB.String(), errB.String())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CombinedOutputTo runs the command and stores both command
 | 
			
		||||
// output and error to out.
 | 
			
		||||
func (s *SSHRunner) CombinedOutputTo(cmd string, w io.Writer) error {
 | 
			
		||||
	out, err := s.CombinedOutput(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	elapsed := time.Since(start)
 | 
			
		||||
	err = teeSSH(sess, shellquote.Join(cmd.Args...), outb, errb)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Reduce log spam
 | 
			
		||||
		if elapsed > (1 * time.Second) {
 | 
			
		||||
			glog.Infof("(SSHRunner) Done: %v: (%s)", rr.Command(), elapsed)
 | 
			
		||||
		}
 | 
			
		||||
	_, err = w.Write([]byte(out))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CombinedOutput runs the command on the remote and returns its combined
 | 
			
		||||
// standard output and standard error.
 | 
			
		||||
func (s *SSHRunner) CombinedOutput(cmd string) (string, error) {
 | 
			
		||||
	glog.Infoln("Run with output:", cmd)
 | 
			
		||||
	sess, err := s.c.NewSession()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrap(err, "NewSession")
 | 
			
		||||
	} else {
 | 
			
		||||
		if exitError, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
			rr.ExitCode = exitError.ExitCode()
 | 
			
		||||
		}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	var combined singleWriter
 | 
			
		||||
	err = teeSSH(sess, cmd, &combined, &combined)
 | 
			
		||||
	out := combined.b.String()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return out, err
 | 
			
		||||
		glog.Infof("(SSHRunner) Non-zero exit: %v: %v (%s)\n%s", rr.Command(), err, elapsed, rr.Output())
 | 
			
		||||
	}
 | 
			
		||||
	return out, nil
 | 
			
		||||
	return rr, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy copies a file to the remote over SSH.
 | 
			
		||||
| 
						 | 
				
			
			@ -198,3 +188,30 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error {
 | 
			
		|||
	}
 | 
			
		||||
	return g.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// teePrefix copies bytes from a reader to writer, logging each new line.
 | 
			
		||||
func teePrefix(prefix string, r io.Reader, w io.Writer, logger func(format string, args ...interface{})) error {
 | 
			
		||||
	scanner := bufio.NewScanner(r)
 | 
			
		||||
	scanner.Split(bufio.ScanBytes)
 | 
			
		||||
	var line bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		b := scanner.Bytes()
 | 
			
		||||
		if _, err := w.Write(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if bytes.IndexAny(b, "\r\n") == 0 {
 | 
			
		||||
			if line.Len() > 0 {
 | 
			
		||||
				logger("%s%s", prefix, line.String())
 | 
			
		||||
				line.Reset()
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		line.Write(b)
 | 
			
		||||
	}
 | 
			
		||||
	// Catch trailing output in case stream does not end with a newline
 | 
			
		||||
	if line.Len() > 0 {
 | 
			
		||||
		logger("%s%s", prefix, line.String())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2019 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package command
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTeePrefix(t *testing.T) {
 | 
			
		||||
	var in bytes.Buffer
 | 
			
		||||
	var out bytes.Buffer
 | 
			
		||||
	var logged strings.Builder
 | 
			
		||||
 | 
			
		||||
	logSink := func(format string, args ...interface{}) {
 | 
			
		||||
		logged.WriteString("(" + fmt.Sprintf(format, args...) + ")")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Simulate the primary use case: tee in the background. This also helps avoid I/O races.
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := teePrefix(":", &in, &out, logSink); err != nil {
 | 
			
		||||
			t.Errorf("teePrefix: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	in.Write([]byte("goo"))
 | 
			
		||||
	in.Write([]byte("\n"))
 | 
			
		||||
	in.Write([]byte("g\r\n\r\n"))
 | 
			
		||||
	in.Write([]byte("le"))
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	gotBytes := out.Bytes()
 | 
			
		||||
	wantBytes := []byte("goo\ng\r\n\r\nle")
 | 
			
		||||
	if !bytes.Equal(gotBytes, wantBytes) {
 | 
			
		||||
		t.Errorf("output=%q, want: %q", gotBytes, wantBytes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gotLog := logged.String()
 | 
			
		||||
	wantLog := "(:goo)(:g)(:le)"
 | 
			
		||||
	if gotLog != wantLog {
 | 
			
		||||
		t.Errorf("log=%q, want: %q", gotLog, wantLog)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,11 +20,13 @@ import (
 | 
			
		|||
	"bytes"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/bootstrapper/images"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/out"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -124,17 +126,17 @@ func (r *Containerd) Style() out.StyleEnum {
 | 
			
		|||
 | 
			
		||||
// Version retrieves the current version of this runtime
 | 
			
		||||
func (r *Containerd) Version() (string, error) {
 | 
			
		||||
	ver, err := r.Runner.CombinedOutput("containerd --version")
 | 
			
		||||
	c := exec.Command("containerd", "--version")
 | 
			
		||||
	rr, err := r.Runner.RunCmd(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errors.Wrapf(err, "containerd check version.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// containerd github.com/containerd/containerd v1.2.0 c4446665cb9c30056f4998ed953e6d4ff22c7c39
 | 
			
		||||
	words := strings.Split(ver, " ")
 | 
			
		||||
	words := strings.Split(rr.Stdout.String(), " ")
 | 
			
		||||
	if len(words) >= 4 && words[0] == "containerd" {
 | 
			
		||||
		return strings.Replace(words[2], "v", "", 1), nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", fmt.Errorf("unknown version: %q", ver)
 | 
			
		||||
	return "", fmt.Errorf("unknown version: %q", rr.Stdout.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SocketPath returns the path to the socket file for containerd
 | 
			
		||||
| 
						 | 
				
			
			@ -152,13 +154,18 @@ func (r *Containerd) DefaultCNI() bool {
 | 
			
		|||
 | 
			
		||||
// Active returns if containerd is active on the host
 | 
			
		||||
func (r *Containerd) Active() bool {
 | 
			
		||||
	err := r.Runner.Run("systemctl is-active --quiet service containerd")
 | 
			
		||||
	c := exec.Command("systemctl", "is-active", "--quiet", "service", "containerd")
 | 
			
		||||
	_, err := r.Runner.RunCmd(c)
 | 
			
		||||
	return err == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Available returns an error if it is not possible to use this runtime on a host
 | 
			
		||||
func (r *Containerd) Available() error {
 | 
			
		||||
	return r.Runner.Run("command -v containerd")
 | 
			
		||||
	c := exec.Command("which", "containerd")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "check containerd availability.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// generateContainerdConfig sets up /etc/containerd/config.toml
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +181,11 @@ func generateContainerdConfig(cr CommandRunner, imageRepository string, k8sVersi
 | 
			
		|||
	if err := t.Execute(&b, opts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return cr.Run(fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | base64 -d | sudo tee %s", path.Dir(cPath), base64.StdEncoding.EncodeToString(b.Bytes()), cPath))
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | base64 -d | sudo tee %s", path.Dir(cPath), base64.StdEncoding.EncodeToString(b.Bytes()), cPath))
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "generate containerd cfg.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Enable idempotently enables containerd on a host
 | 
			
		||||
| 
						 | 
				
			
			@ -194,18 +205,30 @@ func (r *Containerd) Enable(disOthers bool) error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Otherwise, containerd will fail API requests with 'Unimplemented'
 | 
			
		||||
	return r.Runner.Run("sudo systemctl restart containerd")
 | 
			
		||||
	c := exec.Command("sudo", "systemctl", "restart", "containerd")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "restart containerd")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Disable idempotently disables containerd on a host
 | 
			
		||||
func (r *Containerd) Disable() error {
 | 
			
		||||
	return r.Runner.Run("sudo systemctl stop containerd")
 | 
			
		||||
	c := exec.Command("sudo", "systemctl", "stop", "containerd")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "stop containerd")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadImage loads an image into this runtime
 | 
			
		||||
func (r *Containerd) LoadImage(path string) error {
 | 
			
		||||
	glog.Infof("Loading image: %s", path)
 | 
			
		||||
	return r.Runner.Run(fmt.Sprintf("sudo ctr -n=k8s.io images import %s", path))
 | 
			
		||||
	c := exec.Command("sudo", "ctr", "-n=k8s.io", "images", "import", path)
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "ctr images import")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KubeletOptions returns kubelet options for a containerd
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,11 +21,14 @@ import (
 | 
			
		|||
	"encoding/base64"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/bootstrapper/images"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/command"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
| 
						 | 
				
			
			@ -330,19 +333,20 @@ plugin_dirs = [
 | 
			
		|||
 | 
			
		||||
// listCRIContainers returns a list of containers using crictl
 | 
			
		||||
func listCRIContainers(cr CommandRunner, filter string) ([]string, error) {
 | 
			
		||||
	var content string
 | 
			
		||||
	var err error
 | 
			
		||||
	var rr *command.RunResult
 | 
			
		||||
	state := "Running"
 | 
			
		||||
	if filter != "" {
 | 
			
		||||
		content, err = cr.CombinedOutput(fmt.Sprintf(`sudo crictl ps -a --name=%s --state=%s --quiet`, filter, state))
 | 
			
		||||
		c := exec.Command("sudo", "crictl", "ps", "-a", fmt.Sprintf("--name=%s", filter), fmt.Sprintf("--state=%s", state), "--quiet")
 | 
			
		||||
		rr, err = cr.RunCmd(c)
 | 
			
		||||
	} else {
 | 
			
		||||
		content, err = cr.CombinedOutput(fmt.Sprintf(`sudo crictl ps -a --state=%s --quiet`, state))
 | 
			
		||||
		rr, err = cr.RunCmd(exec.Command("sudo", "crictl", "ps", "-a", fmt.Sprintf("--state=%s", state), "--quiet"))
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var ids []string
 | 
			
		||||
	for _, line := range strings.Split(content, "\n") {
 | 
			
		||||
	for _, line := range strings.Split(rr.Stderr.String(), "\n") {
 | 
			
		||||
		if line != "" {
 | 
			
		||||
			ids = append(ids, line)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +360,13 @@ func killCRIContainers(cr CommandRunner, ids []string) error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Killing containers: %s", ids)
 | 
			
		||||
	return cr.Run(fmt.Sprintf("sudo crictl rm %s", strings.Join(ids, " ")))
 | 
			
		||||
 | 
			
		||||
	args := append([]string{"crictl", "rm"}, ids...)
 | 
			
		||||
	c := exec.Command("sudo", args...)
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "kill cri containers.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stopCRIContainers stops containers using crictl
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +375,13 @@ func stopCRIContainers(cr CommandRunner, ids []string) error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Stopping containers: %s", ids)
 | 
			
		||||
	return cr.Run(fmt.Sprintf("sudo crictl stop %s", strings.Join(ids, " ")))
 | 
			
		||||
	args := append([]string{"crictl", "rm"}, ids...)
 | 
			
		||||
	c := exec.Command("sudo", args...)
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "stop cri containers")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// populateCRIConfig sets up /etc/crictl.yaml
 | 
			
		||||
| 
						 | 
				
			
			@ -383,7 +399,11 @@ image-endpoint: unix://{{.Socket}}
 | 
			
		|||
	if err := t.Execute(&b, opts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return cr.Run(fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | sudo tee %s", path.Dir(cPath), b.String(), cPath))
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | sudo tee %s", path.Dir(cPath), b.String(), cPath))
 | 
			
		||||
	if rr, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "Run: %q", rr.Command())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// generateCRIOConfig sets up /etc/crio/crio.conf
 | 
			
		||||
| 
						 | 
				
			
			@ -399,7 +419,12 @@ func generateCRIOConfig(cr CommandRunner, imageRepository string, k8sVersion str
 | 
			
		|||
	if err := t.Execute(&b, opts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return cr.Run(fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | base64 -d | sudo tee %s", path.Dir(cPath), base64.StdEncoding.EncodeToString(b.Bytes()), cPath))
 | 
			
		||||
 | 
			
		||||
	c := exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | base64 -d | sudo tee %s", path.Dir(cPath), base64.StdEncoding.EncodeToString(b.Bytes()), cPath))
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "generateCRIOConfig.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// criContainerLogCmd returns the command to retrieve the log for a container based on ID
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,9 +18,11 @@ package cruntime
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/out"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,14 +46,15 @@ func (r *CRIO) Style() out.StyleEnum {
 | 
			
		|||
 | 
			
		||||
// Version retrieves the current version of this runtime
 | 
			
		||||
func (r *CRIO) Version() (string, error) {
 | 
			
		||||
	ver, err := r.Runner.CombinedOutput("crio --version")
 | 
			
		||||
	c := exec.Command("crio", "--version")
 | 
			
		||||
	rr, err := r.Runner.RunCmd(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errors.Wrap(err, "crio version.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// crio version 1.13.0
 | 
			
		||||
	// commit: ""
 | 
			
		||||
	line := strings.Split(ver, "\n")[0]
 | 
			
		||||
	line := strings.Split(rr.Stdout.String(), "\n")[0]
 | 
			
		||||
	return strings.Replace(line, "crio version ", "", 1), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +73,18 @@ func (r *CRIO) DefaultCNI() bool {
 | 
			
		|||
 | 
			
		||||
// Available returns an error if it is not possible to use this runtime on a host
 | 
			
		||||
func (r *CRIO) Available() error {
 | 
			
		||||
	return r.Runner.Run("command -v crio")
 | 
			
		||||
	c := exec.Command("which", "crio")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "check crio available.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Active returns if CRIO is active on the host
 | 
			
		||||
func (r *CRIO) Active() bool {
 | 
			
		||||
	err := r.Runner.Run("systemctl is-active --quiet service crio")
 | 
			
		||||
	c := exec.Command("systemctl", "is-active", "--quiet", "service", "crio")
 | 
			
		||||
	_, err := r.Runner.RunCmd(c)
 | 
			
		||||
	return err == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,18 +104,29 @@ func (r *CRIO) Enable(disOthers bool) error {
 | 
			
		|||
	if err := enableIPForwarding(r.Runner); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return r.Runner.Run("sudo systemctl restart crio")
 | 
			
		||||
 | 
			
		||||
	if _, err := r.Runner.RunCmd(exec.Command("sudo", "systemctl", "restart", "crio")); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "enable crio.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Disable idempotently disables CRIO on a host
 | 
			
		||||
func (r *CRIO) Disable() error {
 | 
			
		||||
	return r.Runner.Run("sudo systemctl stop crio")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(exec.Command("sudo", "systemctl", "stop", "crio")); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "disable crio.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadImage loads an image into this runtime
 | 
			
		||||
func (r *CRIO) LoadImage(path string) error {
 | 
			
		||||
	glog.Infof("Loading image: %s", path)
 | 
			
		||||
	return r.Runner.Run(fmt.Sprintf("sudo podman load -i %s", path))
 | 
			
		||||
	c := exec.Command("sudo", "podman", "load", "-i", path)
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "crio load image")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KubeletOptions returns kubelet options for a runtime.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,16 +19,17 @@ package cruntime
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/command"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/out"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CommandRunner is the subset of command.Runner this package consumes
 | 
			
		||||
type CommandRunner interface {
 | 
			
		||||
	Run(string) error
 | 
			
		||||
	CombinedOutput(string) (string, error)
 | 
			
		||||
	RunCmd(cmd *exec.Cmd) (*command.RunResult, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Manager is a common interface for container runtimes
 | 
			
		||||
| 
						 | 
				
			
			@ -130,11 +131,14 @@ func disableOthers(me Manager, cr CommandRunner) error {
 | 
			
		|||
// enableIPForwarding configures IP forwarding, which is handled normally by Docker
 | 
			
		||||
// Context: https://github.com/kubernetes/kubeadm/issues/1062
 | 
			
		||||
func enableIPForwarding(cr CommandRunner) error {
 | 
			
		||||
	if err := cr.Run("sudo modprobe br_netfilter"); err != nil {
 | 
			
		||||
	c := exec.Command("sudo", "modprobe", "br_netfilter")
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "br_netfilter")
 | 
			
		||||
	}
 | 
			
		||||
	if err := cr.Run("sudo sh -c \"echo 1 > /proc/sys/net/ipv4/ip_forward\""); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "ip_forward")
 | 
			
		||||
 | 
			
		||||
	c = exec.Command("sudo", "sh", "-c", "echo 1 > /proc/sys/net/ipv4/ip_forward")
 | 
			
		||||
	if _, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "ip_forward")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,12 +17,16 @@ limitations under the License.
 | 
			
		|||
package cruntime
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	"github.com/google/go-cmp/cmp/cmpopts"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/command"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestName(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,37 +115,93 @@ func NewFakeRunner(t *testing.T) *FakeRunner {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Run a fake command!
 | 
			
		||||
func (f *FakeRunner) CombinedOutput(cmd string) (string, error) {
 | 
			
		||||
	f.cmds = append(f.cmds, cmd)
 | 
			
		||||
 | 
			
		||||
func (f *FakeRunner) RunCmd(cmd *exec.Cmd) (*command.RunResult, error) {
 | 
			
		||||
	xargs := cmd.Args
 | 
			
		||||
	f.cmds = append(f.cmds, xargs...)
 | 
			
		||||
	root := false
 | 
			
		||||
	args := strings.Split(cmd, " ")
 | 
			
		||||
	bin, args := args[0], args[1:]
 | 
			
		||||
	bin, args := xargs[0], xargs[1:]
 | 
			
		||||
	f.t.Logf("bin=%s args=%v", bin, args)
 | 
			
		||||
	if bin == "sudo" {
 | 
			
		||||
		root = true
 | 
			
		||||
		bin, args = args[0], args[1:]
 | 
			
		||||
		bin, args = xargs[1], xargs[2:]
 | 
			
		||||
	}
 | 
			
		||||
	switch bin {
 | 
			
		||||
	case "systemctl":
 | 
			
		||||
		return f.systemctl(args, root)
 | 
			
		||||
	case "docker":
 | 
			
		||||
		return f.docker(args, root)
 | 
			
		||||
	case "crictl":
 | 
			
		||||
		return f.crictl(args, root)
 | 
			
		||||
	case "crio":
 | 
			
		||||
		return f.crio(args, root)
 | 
			
		||||
	case "containerd":
 | 
			
		||||
		return f.containerd(args, root)
 | 
			
		||||
	default:
 | 
			
		||||
		return "", nil
 | 
			
		||||
		s, err := f.systemctl(args, root)
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, err
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		_, err = buf.WriteString(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, errors.Wrap(err, "Writing outStr to FakeRunner's buffer")
 | 
			
		||||
		}
 | 
			
		||||
		rr.Stdout = buf
 | 
			
		||||
		rr.Stderr = buf
 | 
			
		||||
		return rr, err
 | 
			
		||||
	case "docker":
 | 
			
		||||
		s, err := f.docker(args, root)
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, err
 | 
			
		||||
		}
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		_, err = buf.WriteString(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, errors.Wrap(err, "Writing FakeRunner's buffer")
 | 
			
		||||
		}
 | 
			
		||||
		rr.Stdout = buf
 | 
			
		||||
		rr.Stderr = buf
 | 
			
		||||
		return rr, err
 | 
			
		||||
 | 
			
		||||
// Run a fake command!
 | 
			
		||||
func (f *FakeRunner) Run(cmd string) error {
 | 
			
		||||
	_, err := f.CombinedOutput(cmd)
 | 
			
		||||
	return err
 | 
			
		||||
	case "crictl":
 | 
			
		||||
		s, err := f.crictl(args, root)
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, err
 | 
			
		||||
		}
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		_, err = buf.WriteString(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, errors.Wrap(err, "Writing to FakeRunner's buffer")
 | 
			
		||||
		}
 | 
			
		||||
		rr.Stdout = buf
 | 
			
		||||
		rr.Stderr = buf
 | 
			
		||||
		return rr, err
 | 
			
		||||
	case "crio":
 | 
			
		||||
		s, err := f.crio(args, root)
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, err
 | 
			
		||||
		}
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		_, err = buf.WriteString(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, errors.Wrap(err, "Writing to FakeRunner's buffer")
 | 
			
		||||
		}
 | 
			
		||||
		rr.Stdout = buf
 | 
			
		||||
		rr.Stderr = buf
 | 
			
		||||
		return rr, err
 | 
			
		||||
	case "containerd":
 | 
			
		||||
		s, err := f.containerd(args, root)
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		_, err = buf.WriteString(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return rr, errors.Wrap(err, "Writing to FakeRunner's buffer")
 | 
			
		||||
		}
 | 
			
		||||
		rr.Stdout = buf
 | 
			
		||||
		rr.Stderr = buf
 | 
			
		||||
		return rr, err
 | 
			
		||||
	default:
 | 
			
		||||
		rr := &command.RunResult{}
 | 
			
		||||
		return rr, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// docker is a fake implementation of docker
 | 
			
		||||
| 
						 | 
				
			
			@ -150,7 +210,7 @@ func (f *FakeRunner) docker(args []string, _ bool) (string, error) {
 | 
			
		|||
	case "ps":
 | 
			
		||||
		// ps -a --filter="name=apiserver" --format="{{.ID}}"
 | 
			
		||||
		if args[1] == "-a" && strings.HasPrefix(args[2], "--filter") {
 | 
			
		||||
			filter := strings.Split(args[2], `"`)[1]
 | 
			
		||||
			filter := strings.Split(args[2], `r=`)[1]
 | 
			
		||||
			fname := strings.Split(filter, "=")[1]
 | 
			
		||||
			ids := []string{}
 | 
			
		||||
			f.t.Logf("fake docker: Looking for containers matching %q", fname)
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +223,8 @@ func (f *FakeRunner) docker(args []string, _ bool) (string, error) {
 | 
			
		|||
			return strings.Join(ids, "\n"), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "stop":
 | 
			
		||||
		for _, id := range args[1:] {
 | 
			
		||||
		ids := strings.Split(args[1], " ")
 | 
			
		||||
		for _, id := range ids {
 | 
			
		||||
			f.t.Logf("fake docker: Stopping id %q", id)
 | 
			
		||||
			if f.containers[id] == "" {
 | 
			
		||||
				return "", fmt.Errorf("no such container")
 | 
			
		||||
| 
						 | 
				
			
			@ -181,16 +242,16 @@ func (f *FakeRunner) docker(args []string, _ bool) (string, error) {
 | 
			
		|||
 | 
			
		||||
		}
 | 
			
		||||
	case "version":
 | 
			
		||||
 | 
			
		||||
		if args[1] == "--format" && args[2] == "'{{.Server.Version}}'" {
 | 
			
		||||
			return "18.06.2-ce", nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// crio is a fake implementation of crio
 | 
			
		||||
func (f *FakeRunner) crio(args []string, _ bool) (string, error) {
 | 
			
		||||
func (f *FakeRunner) crio(args []string, _ bool) (string, error) { //nolint (result 1 (error) is always nil)
 | 
			
		||||
	if args[0] == "--version" {
 | 
			
		||||
		return "crio version 1.13.0", nil
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -202,6 +263,9 @@ func (f *FakeRunner) containerd(args []string, _ bool) (string, error) {
 | 
			
		|||
	if args[0] == "--version" {
 | 
			
		||||
		return "containerd github.com/containerd/containerd v1.2.0 c4446665cb9c30056f4998ed953e6d4ff22c7c39", nil
 | 
			
		||||
	}
 | 
			
		||||
	if args[0] != "--version" { // doing this to suppress lint "result 1 (error) is always nil"
 | 
			
		||||
		return "", fmt.Errorf("unknown args[0]")
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -253,7 +317,7 @@ func (f *FakeRunner) crictl(args []string, _ bool) (string, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// systemctl is a fake implementation of systemctl
 | 
			
		||||
func (f *FakeRunner) systemctl(args []string, root bool) (string, error) {
 | 
			
		||||
func (f *FakeRunner) systemctl(args []string, root bool) (string, error) { // nolint result 0 (string) is always ""
 | 
			
		||||
	action := args[0]
 | 
			
		||||
	svcs := args[1:]
 | 
			
		||||
	out := ""
 | 
			
		||||
| 
						 | 
				
			
			@ -263,6 +327,7 @@ func (f *FakeRunner) systemctl(args []string, root bool) (string, error) {
 | 
			
		|||
		if arg == "service" {
 | 
			
		||||
			svcs = args[i+1:]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, svc := range svcs {
 | 
			
		||||
| 
						 | 
				
			
			@ -314,8 +379,7 @@ func TestVersion(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
	for _, tc := range tests {
 | 
			
		||||
		t.Run(tc.runtime, func(t *testing.T) {
 | 
			
		||||
			runner := NewFakeRunner(t)
 | 
			
		||||
			r, err := New(Config{Type: tc.runtime, Runner: runner})
 | 
			
		||||
			r, err := New(Config{Type: tc.runtime, Runner: NewFakeRunner(t)})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("New(%s): %v", tc.runtime, err)
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -344,9 +408,9 @@ func TestDisable(t *testing.T) {
 | 
			
		|||
		runtime string
 | 
			
		||||
		want    []string
 | 
			
		||||
	}{
 | 
			
		||||
		{"docker", []string{"sudo systemctl stop docker docker.socket"}},
 | 
			
		||||
		{"crio", []string{"sudo systemctl stop crio"}},
 | 
			
		||||
		{"containerd", []string{"sudo systemctl stop containerd"}},
 | 
			
		||||
		{"docker", []string{"sudo", "systemctl", "stop", "docker", "docker.socket"}},
 | 
			
		||||
		{"crio", []string{"sudo", "systemctl", "stop", "crio"}},
 | 
			
		||||
		{"containerd", []string{"sudo", "systemctl", "stop", "containerd"}},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range tests {
 | 
			
		||||
		t.Run(tc.runtime, func(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import (
 | 
			
		|||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/out"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +48,12 @@ func (r *Docker) Style() out.StyleEnum {
 | 
			
		|||
// Version retrieves the current version of this runtime
 | 
			
		||||
func (r *Docker) Version() (string, error) {
 | 
			
		||||
	// Note: the server daemon has to be running, for this call to return successfully
 | 
			
		||||
	ver, err := r.Runner.CombinedOutput("docker version --format '{{.Server.Version}}'")
 | 
			
		||||
	c := exec.Command("docker", "version", "--format", "'{{.Server.Version}}'")
 | 
			
		||||
	rr, err := r.Runner.RunCmd(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.Split(ver, "\n")[0], nil
 | 
			
		||||
	return strings.Split(rr.Stdout.String(), "\n")[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SocketPath returns the path to the socket file for Docker
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +74,8 @@ func (r *Docker) Available() error {
 | 
			
		|||
 | 
			
		||||
// Active returns if docker is active on the host
 | 
			
		||||
func (r *Docker) Active() bool {
 | 
			
		||||
	err := r.Runner.Run("systemctl is-active --quiet service docker")
 | 
			
		||||
	c := exec.Command("systemctl", "is-active", "--quiet", "service", "docker")
 | 
			
		||||
	_, err := r.Runner.RunCmd(c)
 | 
			
		||||
	return err == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,18 +86,31 @@ func (r *Docker) Enable(disOthers bool) error {
 | 
			
		|||
			glog.Warningf("disableOthers: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return r.Runner.Run("sudo systemctl start docker")
 | 
			
		||||
	c := exec.Command("sudo", "systemctl", "start", "docker")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "enable docker.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Disable idempotently disables Docker on a host
 | 
			
		||||
func (r *Docker) Disable() error {
 | 
			
		||||
	return r.Runner.Run("sudo systemctl stop docker docker.socket")
 | 
			
		||||
	c := exec.Command("sudo", "systemctl", "stop", "docker", "docker.socket")
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "disable docker")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadImage loads an image into this runtime
 | 
			
		||||
func (r *Docker) LoadImage(path string) error {
 | 
			
		||||
	glog.Infof("Loading image: %s", path)
 | 
			
		||||
	return r.Runner.Run(fmt.Sprintf("docker load -i %s", path))
 | 
			
		||||
	c := exec.Command("docker", "load", "-i", path)
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "loadimage docker.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KubeletOptions returns kubelet options for a runtime.
 | 
			
		||||
| 
						 | 
				
			
			@ -108,12 +123,12 @@ func (r *Docker) KubeletOptions() map[string]string {
 | 
			
		|||
// ListContainers returns a list of containers
 | 
			
		||||
func (r *Docker) ListContainers(filter string) ([]string, error) {
 | 
			
		||||
	filter = KubernetesContainerPrefix + filter
 | 
			
		||||
	content, err := r.Runner.CombinedOutput(fmt.Sprintf(`docker ps -a --filter="name=%s" --format="{{.ID}}"`, filter))
 | 
			
		||||
	rr, err := r.Runner.RunCmd(exec.Command("docker", "ps", "-a", fmt.Sprintf("--filter=name=%s", filter), "--format=\"{{.ID}}\""))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, errors.Wrapf(err, "docker ListContainers. ")
 | 
			
		||||
	}
 | 
			
		||||
	var ids []string
 | 
			
		||||
	for _, line := range strings.Split(content, "\n") {
 | 
			
		||||
	for _, line := range strings.Split(rr.Stdout.String(), "\n") {
 | 
			
		||||
		if line != "" {
 | 
			
		||||
			ids = append(ids, line)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +142,12 @@ func (r *Docker) KillContainers(ids []string) error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Killing containers: %s", ids)
 | 
			
		||||
	return r.Runner.Run(fmt.Sprintf("docker rm -f %s", strings.Join(ids, " ")))
 | 
			
		||||
	args := append([]string{"rm", "-f"}, ids...)
 | 
			
		||||
	c := exec.Command("docker", args...)
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "Killing containers docker.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StopContainers stops a running container based on ID
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +156,12 @@ func (r *Docker) StopContainers(ids []string) error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Stopping containers: %s", ids)
 | 
			
		||||
	return r.Runner.Run(fmt.Sprintf("docker stop %s", strings.Join(ids, " ")))
 | 
			
		||||
	args := append([]string{"stop"}, ids...)
 | 
			
		||||
	c := exec.Command("docker", args...)
 | 
			
		||||
	if _, err := r.Runner.RunCmd(c); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "stopping containers docker.")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainerLogCmd returns the command to retrieve the log for a container based on ID
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,11 +22,13 @@ import (
 | 
			
		|||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/bootstrapper"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/command"
 | 
			
		||||
	"k8s.io/minikube/pkg/minikube/cruntime"
 | 
			
		||||
| 
						 | 
				
			
			@ -51,18 +53,30 @@ var importantPods = []string{
 | 
			
		|||
	"kube-controller-manager",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// logRunner is the subset of CommandRunner used for logging
 | 
			
		||||
type logRunner interface {
 | 
			
		||||
	RunCmd(*exec.Cmd) (*command.RunResult, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lookbackwardsCount is how far back to look in a log for problems. This should be large enough to
 | 
			
		||||
// include usage messages from a failed binary, but small enough to not include irrelevant problems.
 | 
			
		||||
const lookBackwardsCount = 200
 | 
			
		||||
 | 
			
		||||
// Follow follows logs from multiple files in tail(1) format
 | 
			
		||||
func Follow(r cruntime.Manager, bs bootstrapper.Bootstrapper, runner command.Runner) error {
 | 
			
		||||
func Follow(r cruntime.Manager, bs bootstrapper.Bootstrapper, cr logRunner) error {
 | 
			
		||||
	cs := []string{}
 | 
			
		||||
	for _, v := range logCommands(r, bs, 0, true) {
 | 
			
		||||
		cs = append(cs, v+" &")
 | 
			
		||||
	}
 | 
			
		||||
	cs = append(cs, "wait")
 | 
			
		||||
	return runner.CombinedOutputTo(strings.Join(cs, " "), os.Stdout)
 | 
			
		||||
 | 
			
		||||
	cmd := exec.Command("/bin/bash", "-c", strings.Join(cs, " "))
 | 
			
		||||
	cmd.Stdout = os.Stdout
 | 
			
		||||
	cmd.Stderr = os.Stdout
 | 
			
		||||
	if _, err := cr.RunCmd(cmd); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "log follow")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsProblem returns whether this line matches a known problem
 | 
			
		||||
| 
						 | 
				
			
			@ -71,15 +85,18 @@ func IsProblem(line string) bool {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// FindProblems finds possible root causes among the logs
 | 
			
		||||
func FindProblems(r cruntime.Manager, bs bootstrapper.Bootstrapper, runner command.Runner) map[string][]string {
 | 
			
		||||
func FindProblems(r cruntime.Manager, bs bootstrapper.Bootstrapper, cr logRunner) map[string][]string {
 | 
			
		||||
	pMap := map[string][]string{}
 | 
			
		||||
	cmds := logCommands(r, bs, lookBackwardsCount, false)
 | 
			
		||||
	for name, cmd := range cmds {
 | 
			
		||||
	for name := range cmds {
 | 
			
		||||
		glog.Infof("Gathering logs for %s ...", name)
 | 
			
		||||
		var b bytes.Buffer
 | 
			
		||||
		err := runner.CombinedOutputTo(cmds[name], &b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Warningf("failed %s: %s: %v", name, cmd, err)
 | 
			
		||||
		c := exec.Command("/bin/bash", "-c", cmds[name])
 | 
			
		||||
		c.Stderr = &b
 | 
			
		||||
		c.Stdout = &b
 | 
			
		||||
 | 
			
		||||
		if rr, err := cr.RunCmd(c); err != nil {
 | 
			
		||||
			glog.Warningf("failed %s: command: %s %v output: %s", name, rr.Command(), err, rr.Output())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		scanner := bufio.NewScanner(&b)
 | 
			
		||||
| 
						 | 
				
			
			@ -129,10 +146,11 @@ func Output(r cruntime.Manager, bs bootstrapper.Bootstrapper, runner command.Run
 | 
			
		|||
		}
 | 
			
		||||
		out.T(out.Empty, "==> {{.name}} <==", out.V{"name": name})
 | 
			
		||||
		var b bytes.Buffer
 | 
			
		||||
 | 
			
		||||
		err := runner.CombinedOutputTo(cmds[name], &b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("failed: %v", err)
 | 
			
		||||
		c := exec.Command("/bin/bash", "-c", cmds[name])
 | 
			
		||||
		c.Stdout = &b
 | 
			
		||||
		c.Stderr = &b
 | 
			
		||||
		if rr, err := runner.RunCmd(c); err != nil {
 | 
			
		||||
			glog.Errorf("command %s failed with error: %v output: %q", rr.Command(), err, rr.Output())
 | 
			
		||||
			failed = append(failed, name)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ var vmProblems = map[string]match{
 | 
			
		|||
		Issues: []int{1926, 4206},
 | 
			
		||||
	},
 | 
			
		||||
	"HYPERKIT_NOT_FOUND": {
 | 
			
		||||
		Regexp:         re(`Driver "hyperkit" not found. Do you have the plugin binary .* accessible in your PATH?`),
 | 
			
		||||
		Regexp:         re(`Driver "hyperkit" not found.`),
 | 
			
		||||
		Advice:         "Please install the minikube hyperkit VM driver, or select an alternative --vm-driver",
 | 
			
		||||
		URL:            "https://minikube.sigs.k8s.io/docs/reference/drivers/hyperkit/",
 | 
			
		||||
		HideCreateLink: true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -156,21 +156,21 @@ func (router *osRouter) Cleanup(route *Route) error {
 | 
			
		|||
	if !exists {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	command := exec.Command("sudo", "route", "-n", "delete", route.DestCIDR.String())
 | 
			
		||||
	stdInAndOut, err := command.CombinedOutput()
 | 
			
		||||
	cmd := exec.Command("sudo", "route", "-n", "delete", route.DestCIDR.String())
 | 
			
		||||
	stdInAndOut, err := cmd.CombinedOutput()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	message := fmt.Sprintf("%s", stdInAndOut)
 | 
			
		||||
	glog.V(4).Infof("%s", message)
 | 
			
		||||
	msg := fmt.Sprintf("%s", stdInAndOut)
 | 
			
		||||
	glog.V(4).Infof("%s", msg)
 | 
			
		||||
	re := regexp.MustCompile("^delete net ([^:]*)$")
 | 
			
		||||
	if !re.MatchString(message) {
 | 
			
		||||
		return fmt.Errorf("error deleting route: %s, %d", message, len(strings.Split(message, "\n")))
 | 
			
		||||
	if !re.MatchString(msg) {
 | 
			
		||||
		return fmt.Errorf("error deleting route: %s, %d", msg, len(strings.Split(msg, "\n")))
 | 
			
		||||
	}
 | 
			
		||||
	// idempotent removal of cluster domain dns
 | 
			
		||||
	resolverFile := fmt.Sprintf("/etc/resolver/%s", route.ClusterDomain)
 | 
			
		||||
	command = exec.Command("sudo", "rm", "-f", resolverFile)
 | 
			
		||||
	if err := command.Run(); err != nil {
 | 
			
		||||
	cmd = exec.Command("sudo", "rm", "-f", resolverFile)
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		return fmt.Errorf("could not remove %s: %s", resolverFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -191,12 +191,12 @@ func writeResolverFile(route *Route) error {
 | 
			
		|||
	if err = tmpFile.Close(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	command := exec.Command("sudo", "mkdir", "-p", "/etc/resolver")
 | 
			
		||||
	if err := command.Run(); err != nil {
 | 
			
		||||
	cmd := exec.Command("sudo", "mkdir", "-p", "/etc/resolver")
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	command = exec.Command("sudo", "cp", "-f", tmpFile.Name(), resolverFile)
 | 
			
		||||
	if err := command.Run(); err != nil {
 | 
			
		||||
	cmd = exec.Command("sudo", "cp", "-f", tmpFile.Name(), resolverFile)
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,6 @@ limitations under the License.
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
| 
						 | 
				
			
			@ -150,34 +149,6 @@ func MaybeChownDirRecursiveToMinikubeUser(dir string) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TeePrefix copies bytes from a reader to writer, logging each new line.
 | 
			
		||||
func TeePrefix(prefix string, r io.Reader, w io.Writer, logger func(format string, args ...interface{})) error {
 | 
			
		||||
	scanner := bufio.NewScanner(r)
 | 
			
		||||
	scanner.Split(bufio.ScanBytes)
 | 
			
		||||
	var line bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		b := scanner.Bytes()
 | 
			
		||||
		if _, err := w.Write(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if bytes.IndexAny(b, "\r\n") == 0 {
 | 
			
		||||
			if line.Len() > 0 {
 | 
			
		||||
				logger("%s%s", prefix, line.String())
 | 
			
		||||
				line.Reset()
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		line.Write(b)
 | 
			
		||||
	}
 | 
			
		||||
	// Catch trailing output in case stream does not end with a newline
 | 
			
		||||
	if line.Len() > 0 {
 | 
			
		||||
		logger("%s%s", prefix, line.String())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplaceChars returns a copy of the src slice with each string modified by the replacer
 | 
			
		||||
func ReplaceChars(src []string, replacer *strings.Replacer) []string {
 | 
			
		||||
	ret := make([]string, len(src))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,7 @@ limitations under the License.
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,44 +41,6 @@ func TestGetBinaryDownloadURL(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTeePrefix(t *testing.T) {
 | 
			
		||||
	var in bytes.Buffer
 | 
			
		||||
	var out bytes.Buffer
 | 
			
		||||
	var logged strings.Builder
 | 
			
		||||
 | 
			
		||||
	logSink := func(format string, args ...interface{}) {
 | 
			
		||||
		logged.WriteString("(" + fmt.Sprintf(format, args...) + ")")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Simulate the primary use case: tee in the background. This also helps avoid I/O races.
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := TeePrefix(":", &in, &out, logSink); err != nil {
 | 
			
		||||
			t.Errorf("TeePrefix: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	in.Write([]byte("goo"))
 | 
			
		||||
	in.Write([]byte("\n"))
 | 
			
		||||
	in.Write([]byte("g\r\n\r\n"))
 | 
			
		||||
	in.Write([]byte("le"))
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	gotBytes := out.Bytes()
 | 
			
		||||
	wantBytes := []byte("goo\ng\r\n\r\nle")
 | 
			
		||||
	if !bytes.Equal(gotBytes, wantBytes) {
 | 
			
		||||
		t.Errorf("output=%q, want: %q", gotBytes, wantBytes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gotLog := logged.String()
 | 
			
		||||
	wantLog := "(:goo)(:g)(:le)"
 | 
			
		||||
	if gotLog != wantLog {
 | 
			
		||||
		t.Errorf("log=%q, want: %q", gotLog, wantLog)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReplaceChars(t *testing.T) {
 | 
			
		||||
	testData := []struct {
 | 
			
		||||
		src         []string
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue