github.com/nokia/migrate/v4@v4.16.0/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 nil, 120 containerName) 121 if err != nil { 122 return err 123 } 124 125 d.ContainerId = resp.ID 126 d.ContainerName = containerName 127 128 // then start it 129 if err := d.client.ContainerStart(context.Background(), resp.ID, dockertypes.ContainerStartOptions{}); err != nil { 130 return err 131 } 132 133 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()) 134 for _, v := range resp.Warnings { 135 d.t.Logf("Docker: Warning: %v", v) 136 } 137 return nil 138 } 139 140 func (d *DockerContainer) KeepForDebugging() { 141 if d == nil { 142 return 143 } 144 145 d.keepForDebugging = true 146 } 147 148 func (d *DockerContainer) Remove() error { 149 if d == nil { 150 return errors.New("Cannot remove a nil *DockerContainer") 151 } 152 153 if d.keepForDebugging { 154 return nil 155 } 156 157 if len(d.ContainerId) == 0 { 158 return errors.New("missing containerId") 159 } 160 if err := d.client.ContainerRemove(context.Background(), d.ContainerId, 161 dockertypes.ContainerRemoveOptions{ 162 Force: true, 163 }); err != nil { 164 d.t.Log(err) 165 return err 166 } 167 d.t.Logf("Docker: Removed %v", d.ContainerName) 168 return nil 169 } 170 171 func (d *DockerContainer) Inspect() error { 172 if d == nil { 173 return errors.New("Cannot inspect a nil *DockerContainer") 174 } 175 176 if len(d.ContainerId) == 0 { 177 return errors.New("missing containerId") 178 } 179 resp, err := d.client.ContainerInspect(context.Background(), d.ContainerId) 180 if err != nil { 181 return err 182 } 183 184 d.ContainerJSON = resp 185 d.containerInspected = true 186 return nil 187 } 188 189 func (d *DockerContainer) Logs() (io.ReadCloser, error) { 190 if d == nil { 191 return nil, errors.New("Cannot view logs for a nil *DockerContainer") 192 } 193 if len(d.ContainerId) == 0 { 194 return nil, errors.New("missing containerId") 195 } 196 197 return d.client.ContainerLogs(context.Background(), d.ContainerId, dockertypes.ContainerLogsOptions{ 198 ShowStdout: true, 199 ShowStderr: true, 200 }) 201 } 202 203 func (d *DockerContainer) portMapping(selectFirst bool, cPort int) (containerPort uint, hostIP string, hostPort uint, err error) { // nolint:unparam 204 if !d.containerInspected { 205 if err := d.Inspect(); err != nil { 206 d.t.Fatal(err) 207 } 208 } 209 210 for port, bindings := range d.ContainerJSON.NetworkSettings.Ports { 211 if !selectFirst && port.Int() != cPort { 212 // Skip ahead until we find the port we want 213 continue 214 } 215 for _, binding := range bindings { 216 217 hostPortUint, err := strconv.ParseUint(binding.HostPort, 10, 64) 218 if err != nil { 219 return 0, "", 0, err 220 } 221 222 return uint(port.Int()), binding.HostIP, uint(hostPortUint), nil // nolint: staticcheck 223 } 224 } 225 226 if selectFirst { 227 return 0, "", 0, errors.New("no port binding") 228 } else { 229 return 0, "", 0, errors.New("specified port not bound") 230 } 231 } 232 233 func (d *DockerContainer) Host() string { 234 if d == nil { 235 panic("Cannot get host for a nil *DockerContainer") 236 } 237 _, hostIP, _, err := d.portMapping(true, -1) 238 if err != nil { 239 d.t.Fatal(err) 240 } 241 242 if hostIP == "0.0.0.0" { 243 return "127.0.0.1" 244 } else { 245 return hostIP 246 } 247 } 248 249 func (d *DockerContainer) Port() uint { 250 if d == nil { 251 panic("Cannot get port for a nil *DockerContainer") 252 } 253 _, _, port, err := d.portMapping(true, -1) 254 if err != nil { 255 d.t.Fatal(err) 256 } 257 return port 258 } 259 260 func (d *DockerContainer) PortFor(cPort int) uint { 261 if d == nil { 262 panic("Cannot get port for a nil *DockerContainer") 263 } 264 _, _, port, err := d.portMapping(false, cPort) 265 if err != nil { 266 d.t.Fatal(err) 267 } 268 return port 269 } 270 271 func (d *DockerContainer) NetworkSettings() dockertypes.NetworkSettings { 272 if d == nil { 273 panic("Cannot get network settings for a nil *DockerContainer") 274 } 275 netSettings := d.ContainerJSON.NetworkSettings 276 return *netSettings 277 } 278 279 type dockerImagePullOutput struct { 280 Status string `json:"status"` 281 ProgressDetails struct { 282 Current int `json:"current"` 283 Total int `json:"total"` 284 } `json:"progressDetail"` 285 Id string `json:"id"` 286 Progress string `json:"progress"` 287 } 288 289 func init() { 290 rand.Seed(time.Now().UnixNano()) 291 } 292 293 func pseudoRandStr(n int) string { 294 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz0123456789") 295 b := make([]rune, n) 296 for i := range b { 297 b[i] = letterRunes[rand.Intn(len(letterRunes))] 298 } 299 return string(b) 300 }