/
ssh_binary.go
173 lines (148 loc) · 4.33 KB
/
ssh_binary.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
167
168
169
170
171
172
173
package ssh
import (
"context"
"errors"
"fmt"
"io"
"os/exec"
"strings"
"syscall"
"github.com/tychoish/jasper/executor"
"github.com/tychoish/jasper/options"
)
// execSSHBinary runs remote processes using the SSH binary.
type execSSHBinary struct {
cmd *exec.Cmd
destination string
remoteOpts []string
cmdArgs []string
dir string
env []string
}
func ExecutorResolverBinary(ctx context.Context, opts *options.Create) options.ResolveExecutor {
return func(ctx context.Context, args []string) (executor.Executor, error) {
if opts.Remote == nil {
return nil, executor.ErrNotConfigured
}
return NewSSHBinary(ctx, opts.Remote.String(), opts.Remote.Args, args), nil
}
}
// NewSSHBinary returns an Executor that creates processes using the SSH binary.
func NewSSHBinary(ctx context.Context, destination string, opts []string, args []string) executor.Executor {
return &execSSHBinary{
destination: destination,
remoteOpts: opts,
cmdArgs: args,
// The actual arguments to SSH will be resolved when Start is called.
cmd: exec.CommandContext(ctx, "ssh"),
}
}
// Args returns the arguments to the process.
func (e *execSSHBinary) Args() []string {
return e.cmdArgs
}
// SetEnv sets the remote process environment.
func (e *execSSHBinary) SetEnv(env []string) {
e.env = env
}
// Env returns the remote process environment.
func (e *execSSHBinary) Env() []string {
return e.env
}
// SetDir sets the remote process working directory.
func (e *execSSHBinary) SetDir(dir string) {
e.dir = dir
}
// Dir returns the remote process working directory.
func (e *execSSHBinary) Dir() string {
return e.dir
}
// SetStdin sets the remote process standard input.
func (e *execSSHBinary) SetStdin(stdin io.Reader) {
e.cmd.Stdin = stdin
}
// SetStdin sets the remote process standard output.
func (e *execSSHBinary) SetStdout(stdout io.Writer) {
e.cmd.Stdout = stdout
}
// SetStdin sets the remote process standard error.
func (e *execSSHBinary) SetStderr(stderr io.Writer) {
e.cmd.Stderr = stderr
}
func (e *execSSHBinary) Stdout() io.Writer {
return e.cmd.Stdout
}
func (e *execSSHBinary) Stderr() io.Writer {
return e.cmd.Stderr
}
// Start begins running the remote process using the SSH binary.
func (e *execSSHBinary) Start() error {
var resolvedArgs string
if e.dir != "" {
resolvedArgs += fmt.Sprintf("cd '%s' && ", e.dir)
}
if len(e.env) != 0 {
resolvedArgs += strings.Join(e.env, " ") + " "
}
resolvedArgs += strings.Join(e.cmdArgs, " ")
path, err := exec.LookPath("ssh")
if err != nil {
return fmt.Errorf("could not find SSH binary: %w", err)
}
e.cmd.Path = path
e.cmd.Args = []string{"ssh"}
e.cmd.Args = append(e.cmd.Args, e.remoteOpts...)
e.cmd.Args = append(e.cmd.Args, e.destination)
e.cmd.Args = append(e.cmd.Args, resolvedArgs)
return e.cmd.Start()
}
// Wait returns the reuslt of waiting for the remote process to finish.
func (e *execSSHBinary) Wait() error {
if e.cmd == nil {
return errors.New("cannot wait on an unstarted process")
}
return e.cmd.Wait()
}
// Signal sends a signal to the SSH binary.
func (e *execSSHBinary) Signal(sig syscall.Signal) error {
if e.cmd == nil || e.cmd.Process == nil {
return errors.New("cannot signal an unstarted process")
}
return e.cmd.Process.Signal(sig)
}
// PID returns the PID of the local SSH binary process.
func (e *execSSHBinary) PID() int {
if e.cmd == nil || e.cmd.Process == nil {
return -1
}
return e.cmd.Process.Pid
}
// ExitCode returns the exit code of the SSH binary process, or -1 if the
// process is not finished.
func (e *execSSHBinary) ExitCode() int {
if e.cmd == nil || e.cmd.ProcessState == nil {
return -1
}
status := e.cmd.ProcessState.Sys().(syscall.WaitStatus)
return status.ExitStatus()
}
// Success returns whether or not the process ran successfully.
func (e *execSSHBinary) Success() bool {
if e.cmd == nil || e.cmd.ProcessState == nil {
return false
}
return e.cmd.ProcessState.Success()
}
// SignalInfo returns information about the signals the SSH binary process has
// received.
func (e *execSSHBinary) SignalInfo() (sig syscall.Signal, signaled bool) {
if e.cmd == nil || e.cmd.ProcessState == nil {
return syscall.Signal(-1), false
}
status := e.cmd.ProcessState.Sys().(syscall.WaitStatus)
return status.Signal(), status.Signaled()
}
// Close is a no-op.
func (e *execSSHBinary) Close() error {
return nil
}