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