k8s.io/kubernetes@v1.29.3/test/e2e/framework/kubectl/builder.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kubectl
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/url"
    25  	"os"
    26  	"os/exec"
    27  	"strings"
    28  	"syscall"
    29  	"time"
    30  
    31  	"k8s.io/client-go/tools/clientcmd"
    32  	uexec "k8s.io/utils/exec"
    33  
    34  	"k8s.io/kubernetes/test/e2e/framework"
    35  )
    36  
    37  // KubectlBuilder is used to build, customize and execute a kubectl Command.
    38  // Add more functions to customize the builder as needed.
    39  type KubectlBuilder struct {
    40  	cmd     *exec.Cmd
    41  	timeout <-chan time.Time
    42  }
    43  
    44  // NewKubectlCommand returns a KubectlBuilder for running kubectl.
    45  func NewKubectlCommand(namespace string, args ...string) *KubectlBuilder {
    46  	b := new(KubectlBuilder)
    47  	tk := NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, namespace)
    48  	b.cmd = tk.KubectlCmd(args...)
    49  	return b
    50  }
    51  
    52  // AppendEnv appends the given environment and returns itself.
    53  func (b *KubectlBuilder) AppendEnv(env []string) *KubectlBuilder {
    54  	if b.cmd.Env == nil {
    55  		b.cmd.Env = os.Environ()
    56  	}
    57  	b.cmd.Env = append(b.cmd.Env, env...)
    58  	return b
    59  }
    60  
    61  // WithTimeout sets the given timeout and returns itself.
    62  func (b *KubectlBuilder) WithTimeout(t <-chan time.Time) *KubectlBuilder {
    63  	b.timeout = t
    64  	return b
    65  }
    66  
    67  // WithStdinData sets the given data to stdin and returns itself.
    68  func (b KubectlBuilder) WithStdinData(data string) *KubectlBuilder {
    69  	b.cmd.Stdin = strings.NewReader(data)
    70  	return &b
    71  }
    72  
    73  // WithStdinReader sets the given reader and returns itself.
    74  func (b KubectlBuilder) WithStdinReader(reader io.Reader) *KubectlBuilder {
    75  	b.cmd.Stdin = reader
    76  	return &b
    77  }
    78  
    79  // ExecOrDie runs the kubectl executable or dies if error occurs.
    80  func (b KubectlBuilder) ExecOrDie(namespace string) string {
    81  	str, err := b.Exec()
    82  	// In case of i/o timeout error, try talking to the apiserver again after 2s before dying.
    83  	// Note that we're still dying after retrying so that we can get visibility to triage it further.
    84  	if isTimeout(err) {
    85  		framework.Logf("Hit i/o timeout error, talking to the server 2s later to see if it's temporary.")
    86  		time.Sleep(2 * time.Second)
    87  		retryStr, retryErr := RunKubectl(namespace, "version")
    88  		framework.Logf("stdout: %q", retryStr)
    89  		framework.Logf("err: %v", retryErr)
    90  	}
    91  	framework.ExpectNoError(err)
    92  	return str
    93  }
    94  
    95  func isTimeout(err error) bool {
    96  	switch err := err.(type) {
    97  	case *url.Error:
    98  		if err, ok := err.Err.(net.Error); ok && err.Timeout() {
    99  			return true
   100  		}
   101  	case net.Error:
   102  		if err.Timeout() {
   103  			return true
   104  		}
   105  	}
   106  	return false
   107  }
   108  
   109  // Exec runs the kubectl executable.
   110  func (b KubectlBuilder) Exec() (string, error) {
   111  	stdout, _, err := b.ExecWithFullOutput()
   112  	return stdout, err
   113  }
   114  
   115  // ExecWithFullOutput runs the kubectl executable, and returns the stdout and stderr.
   116  func (b KubectlBuilder) ExecWithFullOutput() (string, string, error) {
   117  	var stdout, stderr bytes.Buffer
   118  	cmd := b.cmd
   119  	cmd.Stdout, cmd.Stderr = &stdout, &stderr
   120  
   121  	framework.Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args[1:], " ")) // skip arg[0] as it is printed separately
   122  	if err := cmd.Start(); err != nil {
   123  		return "", "", fmt.Errorf("error starting %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err)
   124  	}
   125  	errCh := make(chan error, 1)
   126  	go func() {
   127  		errCh <- cmd.Wait()
   128  	}()
   129  	select {
   130  	case err := <-errCh:
   131  		if err != nil {
   132  			var rc = 127
   133  			if ee, ok := err.(*exec.ExitError); ok {
   134  				rc = int(ee.Sys().(syscall.WaitStatus).ExitStatus())
   135  				framework.Logf("rc: %d", rc)
   136  			}
   137  			return stdout.String(), stderr.String(), uexec.CodeExitError{
   138  				Err:  fmt.Errorf("error running %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err),
   139  				Code: rc,
   140  			}
   141  		}
   142  	case <-b.timeout:
   143  		b.cmd.Process.Kill()
   144  		return "", "", fmt.Errorf("timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v", cmd, cmd.Stdout, cmd.Stderr)
   145  	}
   146  	framework.Logf("stderr: %q", stderr.String())
   147  	framework.Logf("stdout: %q", stdout.String())
   148  	return stdout.String(), stderr.String(), nil
   149  }
   150  
   151  // RunKubectlOrDie is a convenience wrapper over kubectlBuilder
   152  func RunKubectlOrDie(namespace string, args ...string) string {
   153  	return NewKubectlCommand(namespace, args...).ExecOrDie(namespace)
   154  }
   155  
   156  // RunKubectl is a convenience wrapper over kubectlBuilder
   157  func RunKubectl(namespace string, args ...string) (string, error) {
   158  	return NewKubectlCommand(namespace, args...).Exec()
   159  }
   160  
   161  // RunKubectlWithFullOutput is a convenience wrapper over kubectlBuilder
   162  // It will also return the command's stderr.
   163  func RunKubectlWithFullOutput(namespace string, args ...string) (string, string, error) {
   164  	return NewKubectlCommand(namespace, args...).ExecWithFullOutput()
   165  }
   166  
   167  // RunKubectlOrDieInput is a convenience wrapper over kubectlBuilder that takes input to stdin
   168  func RunKubectlOrDieInput(namespace string, data string, args ...string) string {
   169  	return NewKubectlCommand(namespace, args...).WithStdinData(data).ExecOrDie(namespace)
   170  }
   171  
   172  // RunKubectlInput is a convenience wrapper over kubectlBuilder that takes input to stdin
   173  func RunKubectlInput(namespace string, data string, args ...string) (string, error) {
   174  	return NewKubectlCommand(namespace, args...).WithStdinData(data).Exec()
   175  }
   176  
   177  // RunKubemciWithKubeconfig is a convenience wrapper over RunKubemciCmd
   178  func RunKubemciWithKubeconfig(args ...string) (string, error) {
   179  	if framework.TestContext.KubeConfig != "" {
   180  		args = append(args, "--"+clientcmd.RecommendedConfigPathFlag+"="+framework.TestContext.KubeConfig)
   181  	}
   182  	return RunKubemciCmd(args...)
   183  }
   184  
   185  // RunKubemciCmd is a convenience wrapper over kubectlBuilder to run kubemci.
   186  // It assumes that kubemci exists in PATH.
   187  func RunKubemciCmd(args ...string) (string, error) {
   188  	// kubemci is assumed to be in PATH.
   189  	kubemci := "kubemci"
   190  	b := new(KubectlBuilder)
   191  	args = append(args, "--gcp-project="+framework.TestContext.CloudConfig.ProjectID)
   192  
   193  	b.cmd = exec.Command(kubemci, args...)
   194  	return b.Exec()
   195  }