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