-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcmd.go
88 lines (75 loc) · 1.67 KB
/
cmd.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
package iago
import (
"context"
"errors"
"io"
)
// CmdRunner defines an interface for running commands on remote hosts.
// This interface is based on the "exec.Cmd" struct.
type CmdRunner interface {
Run(cmd string) error
RunContext(ctx context.Context, cmd string) error
Start(cmd string) error
Wait() error
StdinPipe() (io.WriteCloser, error)
StdoutPipe() (io.ReadCloser, error)
StderrPipe() (io.ReadCloser, error)
}
// Shell runs a shell command.
type Shell struct {
Command string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// Apply runs the shell command on the host.
func (sa Shell) Apply(ctx context.Context, host Host) (err error) {
cmd, err := host.NewCommand()
if err != nil {
return err
}
goroutines := 0
errChan := make(chan error)
defer func() {
for i := 0; i < goroutines; i++ {
// Drain the error channel; nil errors are discarded by Join.
err = errors.Join(err, <-errChan)
}
}()
if sa.Stdin != nil {
in, err := cmd.StdinPipe()
if err != nil {
return err
}
defer safeClose(in, &err, io.EOF)
go pipe(in, sa.Stdin, errChan)
goroutines++
}
if sa.Stdout != nil {
out, err := cmd.StdoutPipe()
if err != nil {
return err
}
defer safeClose(out, &err, io.EOF)
go pipe(sa.Stdout, out, errChan)
goroutines++
}
if sa.Stderr != nil {
errOut, err := cmd.StderrPipe()
if err != nil {
return err
}
defer safeClose(errOut, &err, io.EOF)
go pipe(sa.Stderr, errOut, errChan)
goroutines++
}
err = cmd.RunContext(ctx, sa.Command)
if err != nil && err != io.EOF {
return err
}
return nil
}
func pipe(dst io.Writer, src io.Reader, errChan chan error) {
_, err := io.Copy(dst, src)
errChan <- err
}