| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| 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 kubectl |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "net" |
| "net/url" |
| "os" |
| "os/exec" |
| "strings" |
| "syscall" |
| "time" |
| |
| "k8s.io/client-go/tools/clientcmd" |
| uexec "k8s.io/utils/exec" |
| |
| "k8s.io/kubernetes/test/e2e/framework" |
| ) |
| |
| // KubectlBuilder is used to build, customize and execute a kubectl Command. |
| // Add more functions to customize the builder as needed. |
| type KubectlBuilder struct { |
| cmd *exec.Cmd |
| timeout <-chan time.Time |
| } |
| |
| // NewKubectlCommand returns a KubectlBuilder for running kubectl. |
| func NewKubectlCommand(namespace string, args ...string) *KubectlBuilder { |
| b := new(KubectlBuilder) |
| tk := NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, namespace) |
| b.cmd = tk.KubectlCmd(args...) |
| return b |
| } |
| |
| // AppendEnv appends the given environment and returns itself. |
| func (b *KubectlBuilder) AppendEnv(env []string) *KubectlBuilder { |
| if b.cmd.Env == nil { |
| b.cmd.Env = os.Environ() |
| } |
| b.cmd.Env = append(b.cmd.Env, env...) |
| return b |
| } |
| |
| // WithTimeout sets the given timeout and returns itself. |
| func (b *KubectlBuilder) WithTimeout(t <-chan time.Time) *KubectlBuilder { |
| b.timeout = t |
| return b |
| } |
| |
| // WithStdinData sets the given data to stdin and returns itself. |
| func (b KubectlBuilder) WithStdinData(data string) *KubectlBuilder { |
| b.cmd.Stdin = strings.NewReader(data) |
| return &b |
| } |
| |
| // WithStdinReader sets the given reader and returns itself. |
| func (b KubectlBuilder) WithStdinReader(reader io.Reader) *KubectlBuilder { |
| b.cmd.Stdin = reader |
| return &b |
| } |
| |
| // ExecOrDie runs the kubectl executable or dies if error occurs. |
| func (b KubectlBuilder) ExecOrDie(namespace string) string { |
| str, err := b.Exec() |
| // In case of i/o timeout error, try talking to the apiserver again after 2s before dying. |
| // Note that we're still dying after retrying so that we can get visibility to triage it further. |
| if isTimeout(err) { |
| framework.Logf("Hit i/o timeout error, talking to the server 2s later to see if it's temporary.") |
| time.Sleep(2 * time.Second) |
| retryStr, retryErr := RunKubectl(namespace, "version") |
| framework.Logf("stdout: %q", retryStr) |
| framework.Logf("err: %v", retryErr) |
| } |
| framework.ExpectNoError(err) |
| return str |
| } |
| |
| func isTimeout(err error) bool { |
| switch err := err.(type) { |
| case *url.Error: |
| if err, ok := err.Err.(net.Error); ok && err.Timeout() { |
| return true |
| } |
| case net.Error: |
| if err.Timeout() { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // Exec runs the kubectl executable. |
| func (b KubectlBuilder) Exec() (string, error) { |
| stdout, _, err := b.ExecWithFullOutput() |
| return stdout, err |
| } |
| |
| // ExecWithFullOutput runs the kubectl executable, and returns the stdout and stderr. |
| func (b KubectlBuilder) ExecWithFullOutput() (string, string, error) { |
| var stdout, stderr bytes.Buffer |
| cmd := b.cmd |
| cmd.Stdout, cmd.Stderr = &stdout, &stderr |
| |
| framework.Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately |
| if err := cmd.Start(); err != nil { |
| return "", "", fmt.Errorf("error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err) |
| } |
| errCh := make(chan error, 1) |
| go func() { |
| errCh <- cmd.Wait() |
| }() |
| select { |
| case err := <-errCh: |
| if err != nil { |
| var rc = 127 |
| if ee, ok := err.(*exec.ExitError); ok { |
| rc = int(ee.Sys().(syscall.WaitStatus).ExitStatus()) |
| framework.Logf("rc: %d", rc) |
| } |
| return stdout.String(), stderr.String(), uexec.CodeExitError{ |
| Err: fmt.Errorf("error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err), |
| Code: rc, |
| } |
| } |
| case <-b.timeout: |
| b.cmd.Process.Kill() |
| return "", "", fmt.Errorf("timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v", cmd, cmd.Stdout, cmd.Stderr) |
| } |
| framework.Logf("stderr: %q", stderr.String()) |
| framework.Logf("stdout: %q", stdout.String()) |
| return stdout.String(), stderr.String(), nil |
| } |
| |
| // RunKubectlOrDie is a convenience wrapper over kubectlBuilder |
| func RunKubectlOrDie(namespace string, args ...string) string { |
| return NewKubectlCommand(namespace, args...).ExecOrDie(namespace) |
| } |
| |
| // RunKubectl is a convenience wrapper over kubectlBuilder |
| func RunKubectl(namespace string, args ...string) (string, error) { |
| return NewKubectlCommand(namespace, args...).Exec() |
| } |
| |
| // RunKubectlWithFullOutput is a convenience wrapper over kubectlBuilder |
| // It will also return the command's stderr. |
| func RunKubectlWithFullOutput(namespace string, args ...string) (string, string, error) { |
| return NewKubectlCommand(namespace, args...).ExecWithFullOutput() |
| } |
| |
| // RunKubectlOrDieInput is a convenience wrapper over kubectlBuilder that takes input to stdin |
| func RunKubectlOrDieInput(namespace string, data string, args ...string) string { |
| return NewKubectlCommand(namespace, args...).WithStdinData(data).ExecOrDie(namespace) |
| } |
| |
| // RunKubectlInput is a convenience wrapper over kubectlBuilder that takes input to stdin |
| func RunKubectlInput(namespace string, data string, args ...string) (string, error) { |
| return NewKubectlCommand(namespace, args...).WithStdinData(data).Exec() |
| } |
| |
| // RunKubemciWithKubeconfig is a convenience wrapper over RunKubemciCmd |
| func RunKubemciWithKubeconfig(args ...string) (string, error) { |
| if framework.TestContext.KubeConfig != "" { |
| args = append(args, "--"+clientcmd.RecommendedConfigPathFlag+"="+framework.TestContext.KubeConfig) |
| } |
| return RunKubemciCmd(args...) |
| } |
| |
| // RunKubemciCmd is a convenience wrapper over kubectlBuilder to run kubemci. |
| // It assumes that kubemci exists in PATH. |
| func RunKubemciCmd(args ...string) (string, error) { |
| // kubemci is assumed to be in PATH. |
| kubemci := "kubemci" |
| b := new(KubectlBuilder) |
| args = append(args, "--gcp-project="+framework.TestContext.CloudConfig.ProjectID) |
| |
| b.cmd = exec.Command(kubemci, args...) |
| return b.Exec() |
| } |