github.com/yuukihogo/migrate@v3.0.0+incompatible/testing/docker.go (about)

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