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