github.com/tilt-dev/tilt@v0.36.0/internal/k8s/exec.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	corev1 "k8s.io/api/core/v1"
    10  	"k8s.io/apimachinery/pkg/util/httpstream"
    11  	"k8s.io/client-go/tools/remotecommand"
    12  	"k8s.io/kubectl/pkg/scheme"
    13  
    14  	"github.com/tilt-dev/tilt/internal/container"
    15  )
    16  
    17  func (k *K8sClient) Exec(ctx context.Context, podID PodID, cName container.Name, n Namespace, cmd []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
    18  	req := k.core.RESTClient().Post().
    19  		Resource("pods").
    20  		Namespace(n.String()).
    21  		Name(podID.String()).
    22  		SubResource("exec").
    23  		Param("container", cName.String())
    24  	req.VersionedParams(&corev1.PodExecOptions{
    25  		Container: cName.String(),
    26  		Command:   cmd,
    27  		Stdin:     stdin != nil,
    28  		Stdout:    stdout != nil,
    29  		Stderr:    stderr != nil,
    30  	}, scheme.ParameterCodec)
    31  
    32  	spdyExec, err := remotecommand.NewSPDYExecutor(k.restConfig, "POST", req.URL())
    33  	if err != nil {
    34  		return fmt.Errorf("failed to create spdy executor: %w", err)
    35  	}
    36  	websocketExec, err := remotecommand.NewWebSocketExecutor(k.restConfig, "GET", req.URL().String())
    37  	if err != nil {
    38  		return fmt.Errorf("failed to create websocket executor: %w", err)
    39  	}
    40  
    41  	exec, _ := remotecommand.NewFallbackExecutor(websocketExec, spdyExec, func(err error) bool {
    42  		return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
    43  	})
    44  
    45  	err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
    46  		Stdin:  stdin,
    47  		Stdout: stdout,
    48  		Stderr: stderr,
    49  	})
    50  	if err != nil {
    51  		if err.Error() == "" {
    52  			// Executor::Stream() attempts to decode metav1.Status errors to
    53  			// handle non-zero exit codes from commands; unfortunately, for
    54  			// all _other_ failure cases, the error returned is the `Message`
    55  			// field, which might be empty and there's no way for us to further
    56  			// introspect at this point, so a generic message is the best we
    57  			// can do here
    58  			return errors.New("unknown server failure")
    59  		}
    60  		return err
    61  	}
    62  	return nil
    63  }