/
stream.go
166 lines (142 loc) · 3.85 KB
/
stream.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package docker
import (
"errors"
"io"
"os"
"runtime"
"github.com/docker/docker/pkg/term"
)
// OutStream is an output stream used by the DockerCli to write normal program
// output.
type OutStream struct {
out io.Writer
fd uintptr
isTerminal bool
state *term.State
}
func (o *OutStream) Write(p []byte) (int, error) {
return o.out.Write(p)
}
// FD returns the file descriptor number for this stream
func (o *OutStream) FD() uintptr {
return o.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (o *OutStream) IsTerminal() bool {
return o.isTerminal
}
// SetRawTerminal sets raw mode on the output terminal
func (o *OutStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !o.isTerminal {
return nil
}
o.state, err = term.SetRawTerminalOutput(o.fd)
return err
}
// RestoreTerminal restores normal mode to the terminal
func (o *OutStream) RestoreTerminal() {
if o.state != nil {
term.RestoreTerminal(o.fd, o.state)
}
}
// GetTtySize returns the height and width in characters of the tty
func (o *OutStream) GetTtySize() (uint, uint) {
if !o.isTerminal {
return 0, 0
}
ws, err := term.GetWinsize(o.fd)
if err != nil {
log.Debugf("Error getting size: %s", err)
if ws == nil {
return 0, 0
}
}
return uint(ws.Height), uint(ws.Width)
}
// NewOutStream returns a new OutStream object from a Writer
func NewOutStream(out io.Writer) *OutStream {
fd, isTerminal := term.GetFdInfo(out)
return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
}
// InStream is an input stream used by the DockerCli to read user input
type InStream struct {
in io.ReadCloser
fd uintptr
isTerminal bool
state *term.State
}
func (i *InStream) Read(p []byte) (int, error) {
return i.in.Read(p)
}
// Close implements the Closer interface
func (i *InStream) Close() error {
return i.in.Close()
}
// FD returns the file descriptor number for this stream
func (i *InStream) FD() uintptr {
return i.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (i *InStream) IsTerminal() bool {
return i.isTerminal
}
// SetRawTerminal sets raw mode on the input terminal
func (i *InStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !i.isTerminal {
return nil
}
i.state, err = term.SetRawTerminal(i.fd)
return err
}
// RestoreTerminal restores normal mode to the terminal
func (i *InStream) RestoreTerminal() {
if i.state != nil {
term.RestoreTerminal(i.fd, i.state)
}
}
// CheckTty checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error.
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
// In order to attach to a container tty, input stream for the client must
// be a tty itself: redirecting or piping the client standard input is
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
if ttyMode && attachStdin && !i.isTerminal {
eText := "the input device is not a TTY"
if runtime.GOOS == "windows" {
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
}
return errors.New(eText)
}
return nil
}
// NewInStream returns a new InStream object from a ReadCloser
func NewInStream(in io.ReadCloser) *InStream {
fd, isTerminal := term.GetFdInfo(in)
return &InStream{in: in, fd: fd, isTerminal: isTerminal}
}
// Streams is an interface which exposes the standard input and output streams
type Streams interface {
In() *InStream
Out() *OutStream
Err() io.Writer
}
type stream struct {
stdin io.ReadCloser
stdout io.Writer
stderr io.Writer
}
func (s *stream) In() *InStream {
if s.stdin == nil {
return nil
}
return NewInStream(s.stdin)
}
func (s *stream) Out() *OutStream {
if s.stdout == nil {
return nil
}
return NewOutStream(s.stdout)
}
func (s *stream) Err() io.Writer {
return s.stderr
}