forked from donny-dont/drone-exec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
156 lines (134 loc) · 3.89 KB
/
util.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
package docker
import (
"errors"
"io"
"os"
log "github.com/Sirupsen/logrus"
"github.com/samalba/dockerclient"
)
var (
ErrTimeout = errors.New("Timeout")
ErrLogging = errors.New("Logs not available")
)
var (
// options to fetch the stdout and stderr logs
logOpts = &dockerclient.LogOptions{
Stdout: true,
Stderr: true,
}
)
func Run(client dockerclient.Client, conf *dockerclient.ContainerConfig, auth *dockerclient.AuthConfig, pull bool, outw, errw io.Writer) (*dockerclient.ContainerInfo, error) {
if outw == nil {
outw = os.Stdout
}
if errw == nil {
errw = os.Stdout
}
// fetches the container information.
info, err := Start(client, conf, auth, pull)
if err != nil {
return nil, err
}
// ensures the container is always stopped
// and ready to be removed.
defer func() {
client.StopContainer(info.Id, 5)
client.KillContainer(info.Id, "9")
}()
// channel listening for errors while the
// container is running async.
errc := make(chan error, 1)
infoc := make(chan *dockerclient.ContainerInfo, 1)
go func() {
// options to fetch the stdout and stderr logs
// by tailing the output.
logOptsTail := &dockerclient.LogOptions{
Follow: true,
Stdout: true,
Stderr: true,
}
// It's possible that the docker logs endpoint returns before the container
// is done, we'll naively resume up to 5 times if when the logs unblocks
// the container is still reported to be running.
for attempts := 0; attempts < 5; attempts++ {
if attempts > 0 {
// When resuming the stream, only grab the last line when starting
// the tailing.
logOptsTail.Tail = 1
}
// blocks and waits for the container to finish
// by streaming the logs (to /dev/null). Ideally
// we could use the `wait` function instead
rc, err := client.ContainerLogs(info.Id, logOptsTail)
if err != nil {
log.Errorf("Error tailing %s. %s\n", conf.Image, err)
errc <- err
return
}
defer rc.Close()
_, err = StdCopy(outw, errw, rc)
if err != nil {
log.Errorf("Error streaming docker logs for %s. %s\n", conf.Image, err)
errc <- err
return
}
// fetches the container information
info, err := client.InspectContainer(info.Id)
if err != nil {
log.Errorf("Error getting exit code for %s. %s\n", conf.Image, err)
errc <- err
return
}
if !info.State.Running {
// The container is no longer running, there should be no more logs to tail.
infoc <- info
return
}
log.Debugf("Attempting to resume log tailing after %d attempts.\n", attempts)
}
errc <- errors.New("Maximum number of attempts made while tailing logs.")
}()
select {
case info := <-infoc:
return info, nil
case err := <-errc:
return info, err
}
}
func Start(client dockerclient.Client, conf *dockerclient.ContainerConfig, auth *dockerclient.AuthConfig, pull bool) (*dockerclient.ContainerInfo, error) {
// force-pull the image if specified.
if pull {
log.Printf("Pulling image %s", conf.Image)
client.PullImage(conf.Image, auth)
}
// attempts to create the contianer
id, err := client.CreateContainer(conf, "", auth)
if err != nil {
log.Printf("Pulling image %s", conf.Image)
// and pull the image and re-create if that fails
err = client.PullImage(conf.Image, auth)
if err != nil {
log.Errorf("Error pulling %s. %s\n", conf.Image, err)
return nil, err
}
id, err = client.CreateContainer(conf, "", auth)
if err != nil {
log.Errorf("Error creating %s. %s\n", conf.Image, err)
client.RemoveContainer(id, true, true)
return nil, err
}
}
// fetches the container information
info, err := client.InspectContainer(id)
if err != nil {
log.Errorf("Error inspecting %s. %s\n", conf.Image, err)
client.RemoveContainer(id, true, true)
return nil, err
}
// starts the container
err = client.StartContainer(id, &conf.HostConfig)
if err != nil {
log.Errorf("Error starting %s. %s\n", conf.Image, err)
}
return info, err
}