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