cli_runner.go 4.3 KB
Newer Older
M
Medya Gh 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
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 oci

import (
	"bytes"
M
Medya Gh 已提交
21
	"context"
M
Medya Gh 已提交
22 23 24
	"fmt"
	"io"
	"os/exec"
25
	"runtime"
M
Medya Gh 已提交
26 27 28 29
	"strings"
	"time"

	"github.com/golang/glog"
M
Medya Gh 已提交
30
	"k8s.io/minikube/pkg/minikube/out"
31
	"k8s.io/minikube/pkg/minikube/style"
M
Medya Gh 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
)

// 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
}

// 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()
}

68 69 70
// PrefixCmd adds any needed prefix (such as sudo) to the command
func PrefixCmd(cmd *exec.Cmd) *exec.Cmd {
	if cmd.Args[0] == Podman && runtime.GOOS == "linux" { // want sudo when not running podman-remote
71
		cmdWithSudo := exec.Command("sudo", append([]string{"-n"}, cmd.Args...)...)
72 73 74 75 76 77 78 79 80 81
		cmdWithSudo.Env = cmd.Env
		cmdWithSudo.Dir = cmd.Dir
		cmdWithSudo.Stdin = cmd.Stdin
		cmdWithSudo.Stdout = cmd.Stdout
		cmdWithSudo.Stderr = cmd.Stderr
		cmd = cmdWithSudo
	}
	return cmd
}

M
Medya Gh 已提交
82 83
// runCmd runs a command exec.Command against docker daemon or podman
func runCmd(cmd *exec.Cmd, warnSlow ...bool) (*RunResult, error) {
84 85
	cmd = PrefixCmd(cmd)

M
Medya Gh 已提交
86 87 88 89 90 91
	warn := false
	if len(warnSlow) > 0 {
		warn = warnSlow[0]
	}

	killTime := 19 * time.Second // this will be applied only if warnSlow is true
P
Priya Wadhwa 已提交
92
	warnTime := 2 * time.Second
M
Medya Gh 已提交
93 94 95 96 97 98

	if cmd.Args[1] == "volume" || cmd.Args[1] == "ps" { // volume and ps requires more time than inspect
		killTime = 30 * time.Second
		warnTime = 3 * time.Second
	}

P
Priya Wadhwa 已提交
99 100 101
	ctx, cancel := context.WithTimeout(context.Background(), killTime)
	defer cancel()

M
Medya Gh 已提交
102 103
	if warn { // convert exec.Command to with context
		cmdWithCtx := exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...)
104
		cmdWithCtx.Stdout = cmd.Stdout // copying the original command
M
Medya Gh 已提交
105 106 107 108
		cmdWithCtx.Stderr = cmd.Stderr
		cmd = cmdWithCtx
	}

M
Medya Gh 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
	rr := &RunResult{Args: cmd.Args}
	glog.Infof("Run: %v", rr.Command())

	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)
	}

	if cmd.Stderr == nil {
		var se bytes.Buffer
		errb = io.MultiWriter(&se, &rr.Stderr)
	} else {
		errb = io.MultiWriter(cmd.Stderr, &rr.Stderr)
	}

	cmd.Stdout = outb
	cmd.Stderr = errb

	start := time.Now()
	err := cmd.Run()
	elapsed := time.Since(start)
M
Medya Gh 已提交
133 134 135
	if warn {
		if elapsed > warnTime {
			out.WarningT(`Executing "{{.command}}" took an unusually long time: {{.duration}}`, out.V{"command": rr.Command(), "duration": elapsed})
136 137
			// Don't show any restarting hint, when running podman locally (on linux, with sudo). Only when having a service.
			if cmd.Args[0] != "sudo" {
138
				out.ErrT(style.Tip, `Restarting the {{.name}} service may improve performance.`, out.V{"name": cmd.Args[0]})
139
			}
M
Medya Gh 已提交
140 141 142
		}

		if ctx.Err() == context.DeadlineExceeded {
143
			return rr, context.DeadlineExceeded
M
Medya Gh 已提交
144 145
		}
	}
M
Medya Gh 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159

	if exitError, ok := err.(*exec.ExitError); ok {
		rr.ExitCode = exitError.ExitCode()
	}
	// Decrease log spam
	if elapsed > (1 * time.Second) {
		glog.Infof("Completed: %s: (%s)", rr.Command(), elapsed)
	}
	if err == nil {
		return rr, nil
	}

	return rr, fmt.Errorf("%s: %v\nstdout:\n%s\nstderr:\n%s", rr.Command(), err, rr.Stdout.String(), rr.Stderr.String())
}