github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/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/client-go/tools/remotecommand"
    11  	"k8s.io/kubectl/pkg/scheme"
    12  
    13  	"github.com/tilt-dev/tilt/internal/container"
    14  )
    15  
    16  func (k *K8sClient) Exec(_ context.Context, podID PodID, cName container.Name, n Namespace, cmd []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
    17  	req := k.core.RESTClient().Post().
    18  		Resource("pods").
    19  		Namespace(n.String()).
    20  		Name(podID.String()).
    21  		SubResource("exec").
    22  		Param("container", cName.String())
    23  	req.VersionedParams(&corev1.PodExecOptions{
    24  		Container: cName.String(),
    25  		Command:   cmd,
    26  		Stdin:     stdin != nil,
    27  		Stdout:    stdout != nil,
    28  		Stderr:    stderr != nil,
    29  	}, scheme.ParameterCodec)
    30  
    31  	exec, err := remotecommand.NewSPDYExecutor(k.restConfig, "POST", req.URL())
    32  	if err != nil {
    33  		return fmt.Errorf("establishing connection: %w", err)
    34  	}
    35  
    36  	err = exec.Stream(remotecommand.StreamOptions{
    37  		Stdin:  stdin,
    38  		Stdout: stdout,
    39  		Stderr: stderr,
    40  	})
    41  	if err != nil {
    42  		if err.Error() == "" {
    43  			// Executor::Stream() attempts to decode metav1.Status errors to
    44  			// handle non-zero exit codes from commands; unfortunately, for
    45  			// all _other_ failure cases, the error returned is the `Message`
    46  			// field, which might be empty and there's no way for us to further
    47  			// introspect at this point, so a generic message is the best we
    48  			// can do here
    49  			return errors.New("unknown server failure")
    50  		}
    51  		return err
    52  	}
    53  	return nil
    54  }