1/*
2Copyright 2016 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	"encoding/json"
21	"io"
22	"net/http"
23	"sync"
24
25	"k8s.io/api/core/v1"
26	"k8s.io/apimachinery/pkg/util/runtime"
27)
28
29// streamProtocolV3 implements version 3 of the streaming protocol for attach
30// and exec. This version adds support for resizing the container's terminal.
31type streamProtocolV3 struct {
32	*streamProtocolV2
33
34	resizeStream io.Writer
35}
36
37var _ streamProtocolHandler = &streamProtocolV3{}
38
39func newStreamProtocolV3(options StreamOptions) streamProtocolHandler {
40	return &streamProtocolV3{
41		streamProtocolV2: newStreamProtocolV2(options).(*streamProtocolV2),
42	}
43}
44
45func (p *streamProtocolV3) createStreams(conn streamCreator) error {
46	// set up the streams from v2
47	if err := p.streamProtocolV2.createStreams(conn); err != nil {
48		return err
49	}
50
51	// set up resize stream
52	if p.Tty {
53		headers := http.Header{}
54		headers.Set(v1.StreamType, v1.StreamTypeResize)
55		var err error
56		p.resizeStream, err = conn.CreateStream(headers)
57		if err != nil {
58			return err
59		}
60	}
61
62	return nil
63}
64
65func (p *streamProtocolV3) handleResizes() {
66	if p.resizeStream == nil || p.TerminalSizeQueue == nil {
67		return
68	}
69	go func() {
70		defer runtime.HandleCrash()
71
72		encoder := json.NewEncoder(p.resizeStream)
73		for {
74			size := p.TerminalSizeQueue.Next()
75			if size == nil {
76				return
77			}
78			if err := encoder.Encode(&size); err != nil {
79				runtime.HandleError(err)
80			}
81		}
82	}()
83}
84
85func (p *streamProtocolV3) stream(conn streamCreator) error {
86	if err := p.createStreams(conn); err != nil {
87		return err
88	}
89
90	// now that all the streams have been created, proceed with reading & copying
91
92	errorChan := watchErrorStream(p.errorStream, &errorDecoderV3{})
93
94	p.handleResizes()
95
96	p.copyStdin()
97
98	var wg sync.WaitGroup
99	p.copyStdout(&wg)
100	p.copyStderr(&wg)
101
102	// we're waiting for stdout/stderr to finish copying
103	wg.Wait()
104
105	// waits for errorStream to finish reading with an error or nil
106	return <-errorChan
107}
108
109type errorDecoderV3 struct {
110	errorDecoderV2
111}
112