-
Notifications
You must be signed in to change notification settings - Fork 232
/
ssh.go
133 lines (113 loc) · 3.72 KB
/
ssh.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
package ssh
import (
"context"
"encoding/base64"
"fmt"
"os"
"strings"
"time"
"github.com/crc-org/crc/v2/pkg/crc/constants"
"github.com/crc-org/crc/v2/pkg/crc/errors"
"github.com/crc-org/crc/v2/pkg/crc/logging"
)
type Runner struct {
client Client
}
func CreateRunner(ip string, port int, privateKeys ...string) (*Runner, error) {
client, err := NewClient(constants.DefaultSSHUser, ip, port, privateKeys...)
if err != nil {
return nil, err
}
return &Runner{
client: client,
}, nil
}
func (runner *Runner) Close() {
runner.client.Close()
}
func (runner *Runner) Run(cmd string, args ...string) (string, string, error) {
if len(args) != 0 {
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
}
return runner.runSSHCommand(cmd, false)
}
func (runner *Runner) RunPrivate(cmd string, args ...string) (string, string, error) {
if len(args) != 0 {
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
}
return runner.runSSHCommand(cmd, true)
}
func (runner *Runner) RunPrivileged(reason string, cmdAndArgs ...string) (string, string, error) {
logging.Debugf("Using root access: %s", reason)
commandline := fmt.Sprintf("sudo %s", strings.Join(cmdAndArgs, " "))
return runner.runSSHCommand(commandline, false)
}
func (runner *Runner) copyDataFull(data []byte, destFilename string, mode os.FileMode, privileged bool) error {
var sudo string
if privileged {
sudo = "sudo "
}
logging.Debugf("Creating %s with permissions 0%o in the CRC VM", destFilename, mode)
base64Data := base64.StdEncoding.EncodeToString(data)
command := fmt.Sprintf("%sinstall -m 0%o /dev/null %s && cat <<EOF | base64 --decode | %stee %s\n%s\nEOF", sudo, mode, destFilename, sudo, destFilename, base64Data)
_, _, err := runner.RunPrivate(command)
return err
}
func (runner *Runner) CopyDataPrivileged(data []byte, destFilename string, mode os.FileMode) error {
return runner.copyDataFull(data, destFilename, mode, true)
}
func (runner *Runner) CopyData(data []byte, destFilename string, mode os.FileMode) error {
return runner.copyDataFull(data, destFilename, mode, false)
}
func (runner *Runner) CopyFileFromVM(srcFilename string, destFilename string, mode os.FileMode) error {
command := fmt.Sprintf("sudo base64 %s", srcFilename)
stdout, stderr, err := runner.RunPrivate(command)
if err != nil {
return fmt.Errorf("Failed to get file content %s : %w", stderr, err)
}
rawDecodedText, err := base64.StdEncoding.DecodeString(stdout)
if err != nil {
return err
}
return os.WriteFile(destFilename, rawDecodedText, mode)
}
func (runner *Runner) CopyFile(srcFilename string, destFilename string, mode os.FileMode) error {
data, err := os.ReadFile(srcFilename)
if err != nil {
return err
}
return runner.CopyDataPrivileged(data, destFilename, mode)
}
func (runner *Runner) runSSHCommand(command string, runPrivate bool) (string, string, error) {
if runPrivate {
logging.Debugf("Running SSH command: <hidden>")
} else {
logging.Debugf("Running SSH command: %s", command)
}
stdout, stderr, err := runner.client.Run(command)
if runPrivate {
if err != nil {
logging.Debugf("SSH command failed")
} else {
logging.Debugf("SSH command succeeded")
}
} else {
logging.Debugf("SSH command results: err: %v, output: %s", err, string(stdout))
}
if err != nil {
return string(stdout), string(stderr), fmt.Errorf(`ssh command error:
command : %s
err : %w`+"\n", command, err)
}
return string(stdout), string(stderr), nil
}
func (runner *Runner) WaitForConnectivity(ctx context.Context, timeout time.Duration) error {
checkSSHConnectivity := func() error {
_, _, err := runner.Run("exit 0")
if err != nil {
return &errors.RetriableError{Err: err}
}
return nil
}
return errors.Retry(ctx, timeout, checkSSHConnectivity, time.Second)
}