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