forked from ahmetb/go-dexec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcmd.go
executable file
·241 lines (213 loc) · 6.4 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
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package dexec
import (
"bytes"
"errors"
"io"
"io/ioutil"
"github.com/fsouza/go-dockerclient"
)
// Docker contains connection to Docker API.
// Use github.com/fsouza/go-dockerclient to initialize *docker.Client.
type Docker struct {
*docker.Client
}
// Command returns the Cmd struct to execute the named program with given
// arguments using specified execution method.
//
// For each new Cmd, you should create a new instance for "method" argument.
func (d Docker) Command(method Execution, /*name string,*/ arg ...string) *Cmd {
return &Cmd{Method: method, /*Path: name,*/ Args: arg, docker: d}
}
// Cmd represents an external command being prepared or run.
//
// A Cmd cannot be reused after calling its Run, Output or CombinedOutput
// methods.
type Cmd struct {
// Method provides the execution strategy for the context of the Cmd.
// An instance of Method should not be reused between Cmds.
Method Execution
// Path is the path or name of the command in the container.
Path string
// Arguments to the command in the container, excluding the command
// name as the first argument.
Args []string
// Env is environment variables to the command. If Env is nil, Run will use
// Env specified on Method or pre-built container image.
Env []string
// Dir specifies the working directory of the command. If Dir is the empty
// string, Run uses Dir specified on Method or pre-built container image.
Dir string
// Stdin specifies the process's standard input.
// If Stdin is nil, the process reads from the null device (os.DevNull).
//
// Run will not close the underlying handle if the Reader is an *os.File
// differently than os/exec.
Stdin io.Reader
// Stdout and Stderr specify the process's standard output and error.
// If either is nil, they will be redirected to the null device (os.DevNull).
//
// Run will not close the underlying handles if they are *os.File differently
// than os/exec.
Stdout io.Writer
Stderr io.Writer
docker Docker
started bool
closeAfterWait []io.Closer
}
// Start starts the specified command but does not wait for it to complete.
func (c *Cmd) Start() error {
if c.Dir != "" {
if err := c.Method.setDir(c.Dir); err != nil {
return err
}
}
if c.Env != nil {
if err := c.Method.setEnv(c.Env); err != nil {
return err
}
}
if c.started {
return errors.New("dexec: already started")
}
c.started = true
if c.Stdin == nil {
c.Stdin = empty
}
if c.Stdout == nil {
c.Stdout = ioutil.Discard
}
if c.Stderr == nil {
c.Stderr = ioutil.Discard
}
cmd := c.Args
// cmd := append([]string{c.Path}, c.Args...)
if err := c.Method.create(c.docker, cmd); err != nil {
return err
}
if err := c.Method.run(c.docker, c.Stdin, c.Stdout, c.Stderr); err != nil {
return err
}
return nil
}
// Wait waits for the command to exit. It must have been started by Start.
//
// If the container exits with a non-zero exit code, the error is of type
// *ExitError. Other error types may be returned for I/O problems and such.
//
// Different than os/exec.Wait, this method will not release any resources
// associated with Cmd (such as file handles).
func (c *Cmd) Wait() error {
defer closeFds(c.closeAfterWait)
if !c.started {
return errors.New("dexec: not started")
}
ec, err := c.Method.wait(c.docker)
if err != nil {
return err
}
if ec != 0 {
return &ExitError{ExitCode: ec}
}
return nil
}
// Run starts the specified command and waits for it to complete.
//
// If the command runs successfully and copying streams are done as expected,
// the error is nil.
//
// If the container exits with a non-zero exit code, the error is of type
// *ExitError. Other error types may be returned for I/O problems and such.
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
// CombinedOutput runs the command and returns its combined standard output and
// standard error.
//
// Docker API does not have strong guarantees over ordering of messages. For instance:
// >&1 echo out; >&2 echo err
// may result in "out\nerr\n" as well as "err\nout\n" from this method.
func (c *Cmd) CombinedOutput() ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("dexec: Stdout already set")
}
if c.Stderr != nil {
return nil, errors.New("dexec: Stderr already set")
}
var b bytes.Buffer
c.Stdout, c.Stderr = &b, &b
err := c.Run()
return b.Bytes(), err
}
// Output runs the command and returns its standard output.
//
// If the container exits with a non-zero exit code, the error is of type
// *ExitError. Other error types may be returned for I/O problems and such.
//
// If c.Stderr was nil, Output populates ExitError.Stderr.
func (c *Cmd) Output() ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("dexec: Stdout already set")
}
var stdout, stderr bytes.Buffer
c.Stdout = &stdout
captureErr := c.Stderr == nil
if captureErr {
c.Stderr = &stderr
}
err := c.Run()
if err != nil && captureErr {
if ee, ok := err.(*ExitError); ok {
ee.Stderr = stderr.Bytes()
}
}
return stdout.Bytes(), err
}
// StdinPipe returns a pipe that will be connected to the command's standard input
// when the command starts.
//
// Different than os/exec.StdinPipe, returned io.WriteCloser should be closed by user.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
if c.Stdin != nil {
return nil, errors.New("dexec: Stdin already set")
}
pr, pw := io.Pipe()
c.Stdin = pr
return pw, nil
}
// StdoutPipe returns a pipe that will be connected to the command's standard output when
// the command starts.
//
// Wait will close the pipe after seeing the command exit or in error conditions.
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
if c.Stdout != nil {
return nil, errors.New("dexec: Stdout already set")
}
pr, pw := io.Pipe()
c.Stdout = pw
c.closeAfterWait = append(c.closeAfterWait, pw)
return pr, nil
}
// StderrPipe returns a pipe that will be connected to the command's standard error when
// the command starts.
//
// Wait will close the pipe after seeing the command exit or in error conditions.
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
if c.Stderr != nil {
return nil, errors.New("dexec: Stderr already set")
}
pr, pw := io.Pipe()
c.Stderr = pw
c.closeAfterWait = append(c.closeAfterWait, pw)
return pr, nil
}
func closeFds(l []io.Closer) {
for _, fd := range l {
fd.Close()
}
}
type emptyReader struct{}
func (r *emptyReader) Read(b []byte) (int, error) { return 0, io.EOF }
var empty = &emptyReader{}