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