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