karmada/test/e2e/framework/karmadactl.go

146 lines
4.1 KiB
Go

/*
Copyright 2023 The Karmada 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 framework
import (
"bytes"
"fmt"
"net"
"net/url"
"os/exec"
"syscall"
"time"
"k8s.io/client-go/tools/clientcmd"
uexec "k8s.io/utils/exec"
)
type KarmadactlBuilder struct {
cmd *exec.Cmd
timeout <-chan time.Time
}
type TestKubeconfig struct {
KubeConfig string
KarmadaContext string
KarmadactlPath string
Namespace string
}
func NewKarmadactlCommand(kubeConfig, karmadaContext, karmadactlPath, namespace string, timeout time.Duration, args ...string) *KarmadactlBuilder {
builder := new(KarmadactlBuilder)
tk := NewTestKubeconfig(kubeConfig, karmadaContext, karmadactlPath, namespace)
builder.cmd = tk.KarmadactlCmd(args...)
builder.timeout = time.After(timeout)
return builder
}
func NewTestKubeconfig(kubeConfig, karmadaContext, karmadactlpath, namespace string) *TestKubeconfig {
return &TestKubeconfig{
KubeConfig: kubeConfig,
KarmadaContext: karmadaContext,
KarmadactlPath: karmadactlpath,
Namespace: namespace,
}
}
// KarmadactlCmd runs the karmadactl executable through the wrapper script.
func (tk *TestKubeconfig) KarmadactlCmd(args ...string) *exec.Cmd {
defaultArgs := []string{}
if tk.KubeConfig != "" {
defaultArgs = append(defaultArgs, "--"+clientcmd.RecommendedConfigPathFlag+"="+tk.KubeConfig)
// Reference the KubeContext
if tk.KarmadaContext != "" {
defaultArgs = append(defaultArgs, "--karmada-context"+"="+tk.KarmadaContext)
}
}
if tk.Namespace != "" {
defaultArgs = append(defaultArgs, fmt.Sprintf("--namespace=%s", tk.Namespace))
}
karmadactlArgs := append(defaultArgs, args...)
//We allow users to specify path to karmadactl
cmd := exec.Command(tk.KarmadactlPath, karmadactlArgs...) //nolint:gosec
//caller will invoke this and wait on it.
return cmd
}
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
}
func (k *KarmadactlBuilder) ExecOrDie() (string, error) {
str, err := k.exec()
if isTimeout(err) {
return "", fmt.Errorf("timed out waiting for %v", k.cmd)
}
return str, err
}
func (k *KarmadactlBuilder) exec() (string, error) {
stdOut, _, err := k.execWithFullOutput()
return stdOut, err
}
// execWithFullOutput runs the karmadactl executable, and returns the stdout and stderr.
func (k KarmadactlBuilder) execWithFullOutput() (string, string, error) {
var stdout, stderr bytes.Buffer
cmd := k.cmd
cmd.Stdout, cmd.Stderr = &stdout, &stderr
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())
}
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 <-k.timeout:
err := k.cmd.Process.Kill()
if err != nil {
return "", "", fmt.Errorf("after execution timeout, error killing %v:\nCommand stdout:\n%v\nstderr:\n%v\nerror:\n%v", cmd, cmd.Stdout, cmd.Stderr, err)
}
return "", "", fmt.Errorf("timed out waiting for command %v:\nCommand stdout:\n%v\nstderr:\n%v", cmd, cmd.Stdout, cmd.Stderr)
}
return stdout.String(), stderr.String(), nil
}