Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/gitpod-cli/pkg/supervisor/terminal-attach.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package supervisor
6
7
import (
8
"context"
9
"io"
10
"os"
11
"os/signal"
12
"syscall"
13
14
"github.com/creack/pty"
15
"github.com/gitpod-io/gitpod/supervisor/api"
16
log "github.com/sirupsen/logrus"
17
"golang.org/x/term"
18
"golang.org/x/xerrors"
19
)
20
21
type AttachToTerminalOpts struct {
22
Interactive bool
23
ForceResize bool
24
Token string
25
}
26
27
func (client *SupervisorClient) AttachToTerminal(ctx context.Context, alias string, opts AttachToTerminalOpts) (int, error) {
28
// Copy to stdout/stderr
29
listen, err := client.Terminal.Listen(ctx, &api.ListenTerminalRequest{
30
Alias: alias,
31
})
32
if err != nil {
33
return 0, xerrors.Errorf("cannot attach to terminal: %w", err)
34
}
35
var exitCode int
36
errchan := make(chan error, 5)
37
go func() {
38
for {
39
resp, err := listen.Recv()
40
if err != nil {
41
errchan <- err
42
}
43
os.Stdout.Write(resp.GetData())
44
terminalExitCode := resp.GetExitCode()
45
if terminalExitCode > 0 {
46
exitCode = int(terminalExitCode)
47
}
48
}
49
}()
50
51
// Set stdin in raw mode.
52
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
53
if err != nil {
54
return 0, xerrors.Errorf("cannot attach to terminal: %w", err)
55
}
56
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
57
58
if opts.Interactive {
59
// Handle pty size.
60
ch := make(chan os.Signal, 1)
61
signal.Notify(ch, syscall.SIGWINCH)
62
go func() {
63
for range ch {
64
size, err := pty.GetsizeFull(os.Stdin)
65
if err != nil {
66
log.WithError(err).Error("cannot determine stdin's terminal size")
67
continue
68
}
69
70
req := &api.SetTerminalSizeRequest{
71
Alias: alias,
72
Size: &api.TerminalSize{
73
Cols: uint32(size.Cols),
74
Rows: uint32(size.Rows),
75
WidthPx: uint32(size.X),
76
HeightPx: uint32(size.Y),
77
},
78
}
79
80
var expectResize bool
81
if opts.ForceResize {
82
req.Priority = &api.SetTerminalSizeRequest_Force{Force: true}
83
expectResize = true
84
} else if opts.Token != "" {
85
req.Priority = &api.SetTerminalSizeRequest_Token{Token: opts.Token}
86
expectResize = true
87
}
88
89
_, err = client.Terminal.SetSize(ctx, req)
90
if err != nil && expectResize {
91
log.WithError(err).Error("cannot set terminal size")
92
continue
93
}
94
}
95
}()
96
ch <- syscall.SIGWINCH // Initial resize.
97
98
// Copy stdin to the pty and the pty to stdout.
99
go func() {
100
buf := make([]byte, 32*1024)
101
for {
102
n, err := os.Stdin.Read(buf)
103
if n > 0 {
104
_, serr := client.Terminal.Write(ctx, &api.WriteTerminalRequest{Alias: alias, Stdin: buf[:n]})
105
if serr != nil {
106
errchan <- err
107
return
108
}
109
}
110
if err != nil {
111
errchan <- err
112
return
113
}
114
}
115
}()
116
}
117
118
// wait indefinitely
119
select {
120
case err := <-errchan:
121
if err != io.EOF {
122
return 0, err
123
} else {
124
return exitCode, nil
125
}
126
case <-ctx.Done():
127
return 0, nil
128
}
129
}
130
131