forked from ahmetb/go-dexec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexecution.go
executable file
·128 lines (112 loc) · 3.38 KB
/
execution.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
package dexec
import (
"errors"
"fmt"
"io"
"github.com/fsouza/go-dockerclient"
)
// Execution determines how the command is going to be executed. Currently
// the only method is ByCreatingContainer.
type Execution interface {
create(d Docker, cmd []string) error
run(d Docker, stdin io.Reader, stdout, stderr io.Writer) error
wait(d Docker) (int, error)
setEnv(env []string) error
setDir(dir string) error
}
type createContainer struct {
opt docker.CreateContainerOptions
cmd []string
id string // created container id
cw docker.CloseWaiter
}
// ByCreatingContainer is the execution strategy where a new container with specified
// options is created to execute the command.
//
// The container will be created and started with Cmd.Start and will be deleted
// before Cmd.Wait returns.
func ByCreatingContainer(opts docker.CreateContainerOptions) (Execution, error) {
if opts.Config == nil {
return nil, errors.New("dexec: Config is nil")
}
return &createContainer{opt: opts}, nil
}
func (c *createContainer) setEnv(env []string) error {
if len(c.opt.Config.Env) > 0 {
return errors.New("dexec: Config.Env already set")
}
c.opt.Config.Env = env
return nil
}
func (c *createContainer) setDir(dir string) error {
if c.opt.Config.WorkingDir != "" {
return errors.New("dexec: Config.WorkingDir already set")
}
c.opt.Config.WorkingDir = dir
return nil
}
func (c *createContainer) create(d Docker, cmd []string) error {
c.cmd = cmd
if len(c.opt.Config.Cmd) > 0 {
return errors.New("dexec: Config.Cmd already set")
}
if len(c.opt.Config.Entrypoint) > 0 {
return errors.New("dexec: Config.Entrypoint already set")
}
c.opt.Config.AttachStdin = true
c.opt.Config.AttachStdout = true
c.opt.Config.AttachStderr = true
c.opt.Config.OpenStdin = true
c.opt.Config.StdinOnce = true
c.opt.Config.Cmd = cmd // nil // clear cmd
// c.opt.Config.Entrypoint = cmd // set new entrypoint
container, err := d.Client.CreateContainer(c.opt)
if err != nil {
return fmt.Errorf("dexec: failed to create container: %v", err)
}
c.id = container.ID
return nil
}
func (c *createContainer) run(d Docker, stdin io.Reader, stdout, stderr io.Writer) error {
if c.id == "" {
return errors.New("dexec: container is not created")
}
if err := d.Client.StartContainer(c.id, nil); err != nil {
return fmt.Errorf("dexec: failed to start container: %v", err)
}
opts := docker.AttachToContainerOptions{
Container: c.id,
Stdin: true,
Stdout: true,
Stderr: true,
InputStream: stdin,
OutputStream: stdout,
ErrorStream: stderr,
Stream: true,
Logs: true, // include produced output so far
}
cw, err := d.Client.AttachToContainerNonBlocking(opts)
if err != nil {
return fmt.Errorf("dexec: failed to attach container: %v", err)
}
c.cw = cw
return nil
}
func (c *createContainer) wait(d Docker) (exitCode int, err error) {
del := func() error { return d.RemoveContainer(docker.RemoveContainerOptions{ID: c.id, Force: true}) }
defer del()
if c.cw == nil {
return -1, errors.New("dexec: container is not attached")
}
if err = c.cw.Wait(); err != nil {
return -1, fmt.Errorf("dexec: attach error: %v", err)
}
ec, err := d.WaitContainer(c.id)
if err != nil {
return -1, fmt.Errorf("dexec: cannot wait for container: %v", err)
}
if err := del(); err != nil {
return -1, fmt.Errorf("dexec: error deleting container: %v", err)
}
return ec, nil
}