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  }