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