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 }