k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/pod/exec_util.go (about)

     1  /*
     2  Copyright 2016 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 pod
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"io"
    23  	"net/url"
    24  	"strings"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/client-go/kubernetes/scheme"
    29  	restclient "k8s.io/client-go/rest"
    30  	"k8s.io/client-go/tools/remotecommand"
    31  	"k8s.io/kubernetes/test/e2e/framework"
    32  
    33  	"github.com/onsi/gomega"
    34  )
    35  
    36  // ExecOptions passed to ExecWithOptions
    37  type ExecOptions struct {
    38  	Command       []string
    39  	Namespace     string
    40  	PodName       string
    41  	ContainerName string
    42  	Stdin         io.Reader
    43  	CaptureStdout bool
    44  	CaptureStderr bool
    45  	// If false, whitespace in std{err,out} will be removed.
    46  	PreserveWhitespace bool
    47  	Quiet              bool
    48  }
    49  
    50  // ExecWithOptions executes a command in the specified container,
    51  // returning stdout, stderr and error. `options` allowed for
    52  // additional parameters to be passed.
    53  func ExecWithOptions(f *framework.Framework, options ExecOptions) (string, string, error) {
    54  	return ExecWithOptionsContext(context.Background(), f, options)
    55  }
    56  
    57  func ExecWithOptionsContext(ctx context.Context, f *framework.Framework, options ExecOptions) (string, string, error) {
    58  	if !options.Quiet {
    59  		framework.Logf("ExecWithOptions %+v", options)
    60  	}
    61  	config, err := framework.LoadConfig()
    62  	framework.ExpectNoError(err, "failed to load restclient config")
    63  
    64  	const tty = false
    65  
    66  	framework.Logf("ExecWithOptions: Clientset creation")
    67  	req := f.ClientSet.CoreV1().RESTClient().Post().
    68  		Resource("pods").
    69  		Name(options.PodName).
    70  		Namespace(options.Namespace).
    71  		SubResource("exec").
    72  		Param("container", options.ContainerName)
    73  	req.VersionedParams(&v1.PodExecOptions{
    74  		Container: options.ContainerName,
    75  		Command:   options.Command,
    76  		Stdin:     options.Stdin != nil,
    77  		Stdout:    options.CaptureStdout,
    78  		Stderr:    options.CaptureStderr,
    79  		TTY:       tty,
    80  	}, scheme.ParameterCodec)
    81  
    82  	var stdout, stderr bytes.Buffer
    83  	framework.Logf("ExecWithOptions: execute(POST %s)", req.URL())
    84  	err = execute(ctx, "POST", req.URL(), config, options.Stdin, &stdout, &stderr, tty)
    85  
    86  	if options.PreserveWhitespace {
    87  		return stdout.String(), stderr.String(), err
    88  	}
    89  	return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
    90  }
    91  
    92  // ExecCommandInContainerWithFullOutput executes a command in the
    93  // specified container and return stdout, stderr and error
    94  func ExecCommandInContainerWithFullOutput(f *framework.Framework, podName, containerName string, cmd ...string) (string, string, error) {
    95  	// TODO (pohly): add context support
    96  	return ExecWithOptions(f, ExecOptions{
    97  		Command:            cmd,
    98  		Namespace:          f.Namespace.Name,
    99  		PodName:            podName,
   100  		ContainerName:      containerName,
   101  		Stdin:              nil,
   102  		CaptureStdout:      true,
   103  		CaptureStderr:      true,
   104  		PreserveWhitespace: false,
   105  	})
   106  }
   107  
   108  // ExecCommandInContainer executes a command in the specified container.
   109  func ExecCommandInContainer(f *framework.Framework, podName, containerName string, cmd ...string) string {
   110  	stdout, stderr, err := ExecCommandInContainerWithFullOutput(f, podName, containerName, cmd...)
   111  	framework.Logf("Exec stderr: %q", stderr)
   112  	framework.ExpectNoError(err,
   113  		"failed to execute command in pod %v, container %v: %v",
   114  		podName, containerName, err)
   115  	return stdout
   116  }
   117  
   118  // ExecShellInContainer executes the specified command on the pod's container.
   119  func ExecShellInContainer(f *framework.Framework, podName, containerName string, cmd string) string {
   120  	return ExecCommandInContainer(f, podName, containerName, "/bin/sh", "-c", cmd)
   121  }
   122  
   123  func execCommandInPod(ctx context.Context, f *framework.Framework, podName string, cmd ...string) string {
   124  	pod, err := NewPodClient(f).Get(ctx, podName, metav1.GetOptions{})
   125  	framework.ExpectNoError(err, "failed to get pod %v", podName)
   126  	gomega.Expect(pod.Spec.Containers).NotTo(gomega.BeEmpty())
   127  	return ExecCommandInContainer(f, podName, pod.Spec.Containers[0].Name, cmd...)
   128  }
   129  
   130  func execCommandInPodWithFullOutput(ctx context.Context, f *framework.Framework, podName string, cmd ...string) (string, string, error) {
   131  	pod, err := NewPodClient(f).Get(ctx, podName, metav1.GetOptions{})
   132  	framework.ExpectNoError(err, "failed to get pod %v", podName)
   133  	gomega.Expect(pod.Spec.Containers).NotTo(gomega.BeEmpty())
   134  	return ExecCommandInContainerWithFullOutput(f, podName, pod.Spec.Containers[0].Name, cmd...)
   135  }
   136  
   137  // ExecShellInPod executes the specified command on the pod.
   138  func ExecShellInPod(ctx context.Context, f *framework.Framework, podName string, cmd string) string {
   139  	return execCommandInPod(ctx, f, podName, "/bin/sh", "-c", cmd)
   140  }
   141  
   142  // ExecShellInPodWithFullOutput executes the specified command on the Pod and returns stdout, stderr and error.
   143  func ExecShellInPodWithFullOutput(ctx context.Context, f *framework.Framework, podName string, cmd string) (string, string, error) {
   144  	return execCommandInPodWithFullOutput(ctx, f, podName, "/bin/sh", "-c", cmd)
   145  }
   146  
   147  func execute(ctx context.Context, method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error {
   148  	exec, err := remotecommand.NewSPDYExecutor(config, method, url)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	return exec.StreamWithContext(ctx, remotecommand.StreamOptions{
   153  		Stdin:  stdin,
   154  		Stdout: stdout,
   155  		Stderr: stderr,
   156  		Tty:    tty,
   157  	})
   158  }