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  }