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