-
Notifications
You must be signed in to change notification settings - Fork 26
/
client.go
116 lines (103 loc) · 2.75 KB
/
client.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
package wishlist
import (
"errors"
"fmt"
"io"
"os"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"
"github.com/muesli/termenv"
gossh "golang.org/x/crypto/ssh"
)
// SSHClient is a SSH client.
type SSHClient interface {
For(e *Endpoint) tea.ExecCommand
}
func createSession(conf *gossh.ClientConfig, e *Endpoint, abort <-chan os.Signal, env ...string) (*gossh.Session, *gossh.Client, closers, error) {
var cl closers
var conn *gossh.Client
var err error
connected := make(chan bool, 1)
if jump := e.ProxyJump; jump == "" {
go func() {
conn, err = gossh.Dial("tcp", e.Address, conf)
connected <- true
}()
} else {
username, addr := splitJump(jump)
jumpConf := &gossh.ClientConfig{
User: FirstNonEmpty(username, conf.User),
Auth: conf.Auth,
HostKeyCallback: conf.HostKeyCallback,
}
go func() {
conn, cl, err = proxyJump(addr, e.Address, jumpConf, conf)
connected <- true
}()
}
select {
case <-connected:
// fallback
break
case <-abort:
if conn != nil {
cl = append(cl, conn.Close)
}
return nil, nil, cl, fmt.Errorf("connection aborted")
}
if err != nil {
return nil, nil, cl, fmt.Errorf("connection failed: %w", err)
}
cl = append(cl, conn.Close)
session, err := conn.NewSession()
if err != nil {
return nil, conn, cl, fmt.Errorf("failed to open session: %w", err)
}
cl = append(cl, session.Close)
for k, v := range e.Environment(env...) {
if err := session.Setenv(k, v); err != nil {
log.Warn("could not set env", "key", k, "value", v, "err", err)
continue
}
log.Info("setting env", "key", k, "value", v)
}
return session, conn, cl, nil
}
func shellAndWait(session *gossh.Session) error {
log.Info("requesting shell")
if err := session.Shell(); err != nil {
return fmt.Errorf("failed to start shell: %w", err)
}
if err := session.Wait(); err != nil {
if errors.Is(err, &gossh.ExitMissingError{}) {
log.Info("exit was missing, assuming exit 0")
return nil
}
return fmt.Errorf("session failed: %w", err)
}
return nil
}
func runAndWait(session *gossh.Session, cmd string) error {
log.Info("running", "command", cmd)
if err := session.Run(cmd); err != nil {
return fmt.Errorf("failed to run %q: %w", cmd, err)
}
return nil
}
type closers []func() error
func (c closers) close() {
for _, closer := range c {
if err := closer(); err != nil {
if errors.Is(err, io.EOF) {
// do not print EOF errors... not a big deal anyway
continue
}
log.Warn("failed to close", "err", err)
}
}
}
func resetPty(w io.Writer) { //nolint:errcheck
_, _ = fmt.Fprint(w, termenv.CSI+termenv.ExitAltScreenSeq)
_, _ = fmt.Fprint(w, termenv.CSI+termenv.ResetSeq+"m")
_, _ = fmt.Fprintf(w, termenv.CSI+termenv.EraseDisplaySeq, 2) //nolint:mnd
}