k8s.io/client-go@v0.22.2/tools/remotecommand/v2.go (about) 1 /* 2 Copyright 2015 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 remotecommand 18 19 import ( 20 "fmt" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "sync" 25 26 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/util/runtime" 28 ) 29 30 // streamProtocolV2 implements version 2 of the streaming protocol for attach 31 // and exec. The original streaming protocol was metav1. As a result, this 32 // version is referred to as version 2, even though it is the first actual 33 // numbered version. 34 type streamProtocolV2 struct { 35 StreamOptions 36 37 errorStream io.Reader 38 remoteStdin io.ReadWriteCloser 39 remoteStdout io.Reader 40 remoteStderr io.Reader 41 } 42 43 var _ streamProtocolHandler = &streamProtocolV2{} 44 45 func newStreamProtocolV2(options StreamOptions) streamProtocolHandler { 46 return &streamProtocolV2{ 47 StreamOptions: options, 48 } 49 } 50 51 func (p *streamProtocolV2) createStreams(conn streamCreator) error { 52 var err error 53 headers := http.Header{} 54 55 // set up error stream 56 headers.Set(v1.StreamType, v1.StreamTypeError) 57 p.errorStream, err = conn.CreateStream(headers) 58 if err != nil { 59 return err 60 } 61 62 // set up stdin stream 63 if p.Stdin != nil { 64 headers.Set(v1.StreamType, v1.StreamTypeStdin) 65 p.remoteStdin, err = conn.CreateStream(headers) 66 if err != nil { 67 return err 68 } 69 } 70 71 // set up stdout stream 72 if p.Stdout != nil { 73 headers.Set(v1.StreamType, v1.StreamTypeStdout) 74 p.remoteStdout, err = conn.CreateStream(headers) 75 if err != nil { 76 return err 77 } 78 } 79 80 // set up stderr stream 81 if p.Stderr != nil && !p.Tty { 82 headers.Set(v1.StreamType, v1.StreamTypeStderr) 83 p.remoteStderr, err = conn.CreateStream(headers) 84 if err != nil { 85 return err 86 } 87 } 88 return nil 89 } 90 91 func (p *streamProtocolV2) copyStdin() { 92 if p.Stdin != nil { 93 var once sync.Once 94 95 // copy from client's stdin to container's stdin 96 go func() { 97 defer runtime.HandleCrash() 98 99 // if p.stdin is noninteractive, p.g. `echo abc | kubectl exec -i <pod> -- cat`, make sure 100 // we close remoteStdin as soon as the copy from p.stdin to remoteStdin finishes. Otherwise 101 // the executed command will remain running. 102 defer once.Do(func() { p.remoteStdin.Close() }) 103 104 if _, err := io.Copy(p.remoteStdin, readerWrapper{p.Stdin}); err != nil { 105 runtime.HandleError(err) 106 } 107 }() 108 109 // read from remoteStdin until the stream is closed. this is essential to 110 // be able to exit interactive sessions cleanly and not leak goroutines or 111 // hang the client's terminal. 112 // 113 // TODO we aren't using go-dockerclient any more; revisit this to determine if it's still 114 // required by engine-api. 115 // 116 // go-dockerclient's current hijack implementation 117 // (https://github.com/fsouza/go-dockerclient/blob/89f3d56d93788dfe85f864a44f85d9738fca0670/client.go#L564) 118 // waits for all three streams (stdin/stdout/stderr) to finish copying 119 // before returning. When hijack finishes copying stdout/stderr, it calls 120 // Close() on its side of remoteStdin, which allows this copy to complete. 121 // When that happens, we must Close() on our side of remoteStdin, to 122 // allow the copy in hijack to complete, and hijack to return. 123 go func() { 124 defer runtime.HandleCrash() 125 defer once.Do(func() { p.remoteStdin.Close() }) 126 127 // this "copy" doesn't actually read anything - it's just here to wait for 128 // the server to close remoteStdin. 129 if _, err := io.Copy(ioutil.Discard, p.remoteStdin); err != nil { 130 runtime.HandleError(err) 131 } 132 }() 133 } 134 } 135 136 func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) { 137 if p.Stdout == nil { 138 return 139 } 140 141 wg.Add(1) 142 go func() { 143 defer runtime.HandleCrash() 144 defer wg.Done() 145 // make sure, packet in queue can be consumed. 146 // block in queue may lead to deadlock in conn.server 147 // issue: https://github.com/kubernetes/kubernetes/issues/96339 148 defer io.Copy(ioutil.Discard, p.remoteStdout) 149 150 if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil { 151 runtime.HandleError(err) 152 } 153 }() 154 } 155 156 func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) { 157 if p.Stderr == nil || p.Tty { 158 return 159 } 160 161 wg.Add(1) 162 go func() { 163 defer runtime.HandleCrash() 164 defer wg.Done() 165 defer io.Copy(ioutil.Discard, p.remoteStderr) 166 167 if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil { 168 runtime.HandleError(err) 169 } 170 }() 171 } 172 173 func (p *streamProtocolV2) stream(conn streamCreator) error { 174 if err := p.createStreams(conn); err != nil { 175 return err 176 } 177 178 // now that all the streams have been created, proceed with reading & copying 179 180 errorChan := watchErrorStream(p.errorStream, &errorDecoderV2{}) 181 182 p.copyStdin() 183 184 var wg sync.WaitGroup 185 p.copyStdout(&wg) 186 p.copyStderr(&wg) 187 188 // we're waiting for stdout/stderr to finish copying 189 wg.Wait() 190 191 // waits for errorStream to finish reading with an error or nil 192 return <-errorChan 193 } 194 195 // errorDecoderV2 interprets the error channel data as plain text. 196 type errorDecoderV2 struct{} 197 198 func (d *errorDecoderV2) decode(message []byte) error { 199 return fmt.Errorf("error executing remote command: %s", message) 200 }