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