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 }