1
2
3
4 package testing
5
6 import (
7 "bufio"
8 "context"
9 "encoding/json"
10 "errors"
11 "fmt"
12 dockertypes "github.com/docker/docker/api/types"
13 dockercontainer "github.com/docker/docker/api/types/container"
14 dockernetwork "github.com/docker/docker/api/types/network"
15 dockerclient "github.com/docker/docker/client"
16 "github.com/hashicorp/go-multierror"
17 "io"
18 "math/rand"
19 "strconv"
20 "strings"
21 "testing"
22 "time"
23 )
24
25 func NewDockerContainer(t testing.TB, image string, env []string, cmd []string) (*DockerContainer, error) {
26 c, err := dockerclient.NewClientWithOpts(
27 dockerclient.FromEnv,
28 dockerclient.WithAPIVersionNegotiation(),
29 )
30 if err != nil {
31 return nil, err
32 }
33
34 if cmd == nil {
35 cmd = make([]string, 0)
36 }
37
38 contr := &DockerContainer{
39 t: t,
40 client: c,
41 ImageName: image,
42 ENV: env,
43 Cmd: cmd,
44 }
45
46 if err := contr.PullImage(); err != nil {
47 return nil, err
48 }
49
50 if err := contr.Start(); err != nil {
51 return nil, err
52 }
53
54 return contr, nil
55 }
56
57
58 type DockerContainer struct {
59 t testing.TB
60 client *dockerclient.Client
61 ImageName string
62 ENV []string
63 Cmd []string
64 ContainerId string
65 ContainerName string
66 ContainerJSON dockertypes.ContainerJSON
67 containerInspected bool
68 keepForDebugging bool
69 }
70
71 func (d *DockerContainer) PullImage() (err error) {
72 if d == nil {
73 return errors.New("Cannot pull image on a nil *DockerContainer")
74 }
75 d.t.Logf("Docker: Pull image %v", d.ImageName)
76 r, err := d.client.ImagePull(context.Background(), d.ImageName, dockertypes.ImagePullOptions{})
77 if err != nil {
78 return err
79 }
80 defer func() {
81 if errClose := r.Close(); errClose != nil {
82 err = multierror.Append(errClose)
83 }
84 }()
85
86
87 bf := bufio.NewScanner(r)
88 for bf.Scan() {
89 var resp dockerImagePullOutput
90 if err := json.Unmarshal(bf.Bytes(), &resp); err != nil {
91 return err
92 }
93 if strings.HasPrefix(resp.Status, "Status: ") {
94 d.t.Logf("Docker: %v", resp.Status)
95 }
96 }
97 return bf.Err()
98 }
99
100 func (d *DockerContainer) Start() error {
101 if d == nil {
102 return errors.New("Cannot start a nil *DockerContainer")
103 }
104
105 containerName := fmt.Sprintf("migrate_test_%s", pseudoRandStr(10))
106
107
108 resp, err := d.client.ContainerCreate(context.Background(),
109 &dockercontainer.Config{
110 Image: d.ImageName,
111 Labels: map[string]string{"migrate_test": "true"},
112 Env: d.ENV,
113 Cmd: d.Cmd,
114 },
115 &dockercontainer.HostConfig{
116 PublishAllPorts: true,
117 },
118 &dockernetwork.NetworkingConfig{},
119 nil,
120 containerName)
121 if err != nil {
122 return err
123 }
124
125 d.ContainerId = resp.ID
126 d.ContainerName = containerName
127
128
129 if err := d.client.ContainerStart(context.Background(), resp.ID, dockertypes.ContainerStartOptions{}); err != nil {
130 return err
131 }
132
133 d.t.Logf("Docker: Started container %v (%v) for image %v listening at %v:%v", resp.ID[0:12], containerName, d.ImageName, d.Host(), d.Port())
134 for _, v := range resp.Warnings {
135 d.t.Logf("Docker: Warning: %v", v)
136 }
137 return nil
138 }
139
140 func (d *DockerContainer) KeepForDebugging() {
141 if d == nil {
142 return
143 }
144
145 d.keepForDebugging = true
146 }
147
148 func (d *DockerContainer) Remove() error {
149 if d == nil {
150 return errors.New("Cannot remove a nil *DockerContainer")
151 }
152
153 if d.keepForDebugging {
154 return nil
155 }
156
157 if len(d.ContainerId) == 0 {
158 return errors.New("missing containerId")
159 }
160 if err := d.client.ContainerRemove(context.Background(), d.ContainerId,
161 dockertypes.ContainerRemoveOptions{
162 Force: true,
163 }); err != nil {
164 d.t.Log(err)
165 return err
166 }
167 d.t.Logf("Docker: Removed %v", d.ContainerName)
168 return nil
169 }
170
171 func (d *DockerContainer) Inspect() error {
172 if d == nil {
173 return errors.New("Cannot inspect a nil *DockerContainer")
174 }
175
176 if len(d.ContainerId) == 0 {
177 return errors.New("missing containerId")
178 }
179 resp, err := d.client.ContainerInspect(context.Background(), d.ContainerId)
180 if err != nil {
181 return err
182 }
183
184 d.ContainerJSON = resp
185 d.containerInspected = true
186 return nil
187 }
188
189 func (d *DockerContainer) Logs() (io.ReadCloser, error) {
190 if d == nil {
191 return nil, errors.New("Cannot view logs for a nil *DockerContainer")
192 }
193 if len(d.ContainerId) == 0 {
194 return nil, errors.New("missing containerId")
195 }
196
197 return d.client.ContainerLogs(context.Background(), d.ContainerId, dockertypes.ContainerLogsOptions{
198 ShowStdout: true,
199 ShowStderr: true,
200 })
201 }
202
203 func (d *DockerContainer) portMapping(selectFirst bool, cPort int) (containerPort uint, hostIP string, hostPort uint, err error) {
204 if !d.containerInspected {
205 if err := d.Inspect(); err != nil {
206 d.t.Fatal(err)
207 }
208 }
209
210 for port, bindings := range d.ContainerJSON.NetworkSettings.Ports {
211 if !selectFirst && port.Int() != cPort {
212
213 continue
214 }
215 for _, binding := range bindings {
216
217 hostPortUint, err := strconv.ParseUint(binding.HostPort, 10, 64)
218 if err != nil {
219 return 0, "", 0, err
220 }
221
222 return uint(port.Int()), binding.HostIP, uint(hostPortUint), nil
223 }
224 }
225
226 if selectFirst {
227 return 0, "", 0, errors.New("no port binding")
228 } else {
229 return 0, "", 0, errors.New("specified port not bound")
230 }
231 }
232
233 func (d *DockerContainer) Host() string {
234 if d == nil {
235 panic("Cannot get host for a nil *DockerContainer")
236 }
237 _, hostIP, _, err := d.portMapping(true, -1)
238 if err != nil {
239 d.t.Fatal(err)
240 }
241
242 if hostIP == "0.0.0.0" {
243 return "127.0.0.1"
244 } else {
245 return hostIP
246 }
247 }
248
249 func (d *DockerContainer) Port() uint {
250 if d == nil {
251 panic("Cannot get port for a nil *DockerContainer")
252 }
253 _, _, port, err := d.portMapping(true, -1)
254 if err != nil {
255 d.t.Fatal(err)
256 }
257 return port
258 }
259
260 func (d *DockerContainer) PortFor(cPort int) uint {
261 if d == nil {
262 panic("Cannot get port for a nil *DockerContainer")
263 }
264 _, _, port, err := d.portMapping(false, cPort)
265 if err != nil {
266 d.t.Fatal(err)
267 }
268 return port
269 }
270
271 func (d *DockerContainer) NetworkSettings() dockertypes.NetworkSettings {
272 if d == nil {
273 panic("Cannot get network settings for a nil *DockerContainer")
274 }
275 netSettings := d.ContainerJSON.NetworkSettings
276 return *netSettings
277 }
278
279 type dockerImagePullOutput struct {
280 Status string `json:"status"`
281 ProgressDetails struct {
282 Current int `json:"current"`
283 Total int `json:"total"`
284 } `json:"progressDetail"`
285 Id string `json:"id"`
286 Progress string `json:"progress"`
287 }
288
289 func init() {
290 rand.Seed(time.Now().UnixNano())
291 }
292
293 func pseudoRandStr(n int) string {
294 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz0123456789")
295 b := make([]rune, n)
296 for i := range b {
297 b[i] = letterRunes[rand.Intn(len(letterRunes))]
298 }
299 return string(b)
300 }
301
View as plain text