196 lines
6.3 KiB
Go
196 lines
6.3 KiB
Go
/*
|
|
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
|
|
}
|
|
|
|
// WithEnv 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()
|
|
}
|