1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package remotecommand 18 19import ( 20 "fmt" 21 "io" 22 "net/http" 23 "net/url" 24 25 "k8s.io/klog" 26 27 "k8s.io/apimachinery/pkg/util/httpstream" 28 "k8s.io/apimachinery/pkg/util/remotecommand" 29 restclient "k8s.io/client-go/rest" 30 spdy "k8s.io/client-go/transport/spdy" 31) 32 33// StreamOptions holds information pertaining to the current streaming session: 34// input/output streams, if the client is requesting a TTY, and a terminal size queue to 35// support terminal resizing. 36type StreamOptions struct { 37 Stdin io.Reader 38 Stdout io.Writer 39 Stderr io.Writer 40 Tty bool 41 TerminalSizeQueue TerminalSizeQueue 42} 43 44// Executor is an interface for transporting shell-style streams. 45type Executor interface { 46 // Stream initiates the transport of the standard shell streams. It will transport any 47 // non-nil stream to a remote system, and return an error if a problem occurs. If tty 48 // is set, the stderr stream is not used (raw TTY manages stdout and stderr over the 49 // stdout stream). 50 Stream(options StreamOptions) error 51} 52 53type streamCreator interface { 54 CreateStream(headers http.Header) (httpstream.Stream, error) 55} 56 57type streamProtocolHandler interface { 58 stream(conn streamCreator) error 59} 60 61// streamExecutor handles transporting standard shell streams over an httpstream connection. 62type streamExecutor struct { 63 upgrader spdy.Upgrader 64 transport http.RoundTripper 65 66 method string 67 url *url.URL 68 protocols []string 69} 70 71// NewSPDYExecutor connects to the provided server and upgrades the connection to 72// multiplexed bidirectional streams. 73func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) { 74 wrapper, upgradeRoundTripper, err := spdy.RoundTripperFor(config) 75 if err != nil { 76 return nil, err 77 } 78 return NewSPDYExecutorForTransports(wrapper, upgradeRoundTripper, method, url) 79} 80 81// NewSPDYExecutorForTransports connects to the provided server using the given transport, 82// upgrades the response using the given upgrader to multiplexed bidirectional streams. 83func NewSPDYExecutorForTransports(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL) (Executor, error) { 84 return NewSPDYExecutorForProtocols( 85 transport, upgrader, method, url, 86 remotecommand.StreamProtocolV4Name, 87 remotecommand.StreamProtocolV3Name, 88 remotecommand.StreamProtocolV2Name, 89 remotecommand.StreamProtocolV1Name, 90 ) 91} 92 93// NewSPDYExecutorForProtocols connects to the provided server and upgrades the connection to 94// multiplexed bidirectional streams using only the provided protocols. Exposed for testing, most 95// callers should use NewSPDYExecutor or NewSPDYExecutorForTransports. 96func NewSPDYExecutorForProtocols(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL, protocols ...string) (Executor, error) { 97 return &streamExecutor{ 98 upgrader: upgrader, 99 transport: transport, 100 method: method, 101 url: url, 102 protocols: protocols, 103 }, nil 104} 105 106// Stream opens a protocol streamer to the server and streams until a client closes 107// the connection or the server disconnects. 108func (e *streamExecutor) Stream(options StreamOptions) error { 109 req, err := http.NewRequest(e.method, e.url.String(), nil) 110 if err != nil { 111 return fmt.Errorf("error creating request: %v", err) 112 } 113 114 conn, protocol, err := spdy.Negotiate( 115 e.upgrader, 116 &http.Client{Transport: e.transport}, 117 req, 118 e.protocols..., 119 ) 120 if err != nil { 121 return err 122 } 123 defer conn.Close() 124 125 var streamer streamProtocolHandler 126 127 switch protocol { 128 case remotecommand.StreamProtocolV4Name: 129 streamer = newStreamProtocolV4(options) 130 case remotecommand.StreamProtocolV3Name: 131 streamer = newStreamProtocolV3(options) 132 case remotecommand.StreamProtocolV2Name: 133 streamer = newStreamProtocolV2(options) 134 case "": 135 klog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name) 136 fallthrough 137 case remotecommand.StreamProtocolV1Name: 138 streamer = newStreamProtocolV1(options) 139 } 140 141 return streamer.stream(conn) 142} 143