-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathssh_cli.go
133 lines (113 loc) · 3.36 KB
/
ssh_cli.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 runner
import (
"context"
"fmt"
"io"
"strconv"
)
var (
ErrSSHCLI = fmt.Errorf("%w: sshcli: ", Err)
ErrSSHCLINoDestination = fmt.Errorf(
"%w: destination must be set", ErrSSHCLI,
)
)
// SSHCLI is a Runner that wraps another Runner, essentially prefixing given
// commands and arguments with "ssh", relevant SSH CLI arguments, and the given
// destination. It then passes this new "ssh" command to the underlying Runner.
//
// This is useful for running commands on remote hosts via SSH, without having
// to use the Go ssh package.
//
// Interactive commands are not supported, meaning SSH password prompts will not
// work, and the remote machine's hostkey should already be known and trusted by
// the ssh CLI client.
type SSHCLI struct {
// Runner is the underlying Runner to run commands with, after wrapping them
// with ssh. If not set, running commands will cause a panic.
Runner Runner
// Destination is the remote SSH destination to connect to, which may be
// specified as either "[user@]hostname" or a URI of the form
// "ssh://[user@]hostname[:port]".
Destination string
// Port is the remote SSH port (-p) flag to use. When 0, no -p flag will be
// used.
Port int
// IdentityFile is the remote SSH identity file (-i) flag to use. When
// empty, no -i flag will be used.
IdentityFile string
// Login is the remote SSH login (-l) flag to use. When empty, no -l flag
// will be used.
Login string
// Args is a string slice of extra arguments to pass to ssh.
Args []string
env []string
}
var _ Runner = &SSHCLI{}
// Run executes the command remotely via ssh by calling Run on the underlying
// Runner.
//
// Will panic if Runner field is nil.
// Will return a error if Destination field is empty.
func (rsc *SSHCLI) Run(
stdin io.Reader,
stdout io.Writer,
stderr io.Writer,
command string,
args ...string,
) error {
sshArgs, err := rsc.args(command, args)
if err != nil {
return err
}
return rsc.Runner.Run(stdin, stdout, stderr, "ssh", sshArgs...)
}
// RunContext executes the command remotely via ssh by calling RunContext on the
// underlying Runner.
//
// Will panic if Runner field is nil.
// Will return a error if Destination field is empty.
func (rsc *SSHCLI) RunContext(
ctx context.Context,
stdin io.Reader,
stdout io.Writer,
stderr io.Writer,
command string,
args ...string,
) error {
sshArgs, err := rsc.args(command, args)
if err != nil {
return err
}
return rsc.Runner.RunContext(ctx, stdin, stdout, stderr, "ssh", sshArgs...)
}
func (rsc *SSHCLI) args(command string, args []string) ([]string, error) {
if rsc.Destination == "" {
return nil, ErrSSHCLINoDestination
}
sshArgs := []string{}
if rsc.Port != 0 {
sshArgs = append(sshArgs, "-p", strconv.Itoa(rsc.Port))
}
if rsc.IdentityFile != "" {
sshArgs = append(sshArgs, "-i", rsc.IdentityFile)
}
if rsc.Login != "" {
sshArgs = append(sshArgs, "-l", rsc.Login)
}
if len(rsc.Args) > 0 {
sshArgs = append(sshArgs, rsc.Args...)
}
sshArgs = append(sshArgs, rsc.Destination, "--")
if len(rsc.env) > 0 {
sshArgs = append(sshArgs, "env")
sshArgs = append(sshArgs, rsc.env...)
}
sshArgs = append(sshArgs, command)
sshArgs = append(sshArgs, args...)
return sshArgs, nil
}
// Env sets the environment by calling Env on the underlying Runner. Will panic
// if Runner field is nil on SSH instance.
func (rsc *SSHCLI) Env(env ...string) {
rsc.env = env
}