github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/e2e/helper_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net"
     7  	"strconv"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/app/internal"
    13  	"github.com/jackpal/gateway"
    14  	"gotest.tools/assert"
    15  	"gotest.tools/fs"
    16  	"gotest.tools/icmd"
    17  )
    18  
    19  // readFile returns the content of the file at the designated path normalizing
    20  // line endings by removing any \r.
    21  func readFile(t *testing.T, path string) string {
    22  	t.Helper()
    23  	content, err := ioutil.ReadFile(path)
    24  	assert.NilError(t, err, "missing '"+path+"' file")
    25  	return strings.Replace(string(content), "\r", "", -1)
    26  }
    27  
    28  type dindSwarmAndRegistryInfo struct {
    29  	swarmAddress    string
    30  	registryAddress string
    31  	configuredCmd   icmd.Cmd
    32  	configDir       string
    33  	tmpDir          *fs.Dir
    34  	stopRegistry    func()
    35  	registryLogs    func() string
    36  	dockerCmd       func(...string) string
    37  	execCmd         func(...string) string
    38  	localCmd        func(...string) string
    39  }
    40  
    41  func runWithDindSwarmAndRegistry(t *testing.T, todo func(dindSwarmAndRegistryInfo)) {
    42  	cmd, cleanup := dockerCli.createTestCmd()
    43  	defer cleanup()
    44  
    45  	tmpDir := fs.NewDir(t, t.Name())
    46  	defer tmpDir.Remove()
    47  
    48  	var configDir string
    49  	for _, val := range cmd.Env {
    50  		if ok := strings.HasPrefix(val, "DOCKER_CONFIG="); ok {
    51  			configDir = strings.Replace(val, "DOCKER_CONFIG=", "", 1)
    52  		}
    53  	}
    54  
    55  	// Initialize the info struct
    56  	runner := dindSwarmAndRegistryInfo{configuredCmd: cmd, configDir: configDir, tmpDir: tmpDir}
    57  
    58  	// Func to execute command locally
    59  	runLocalCmd := func(params ...string) string {
    60  		if len(params) == 0 {
    61  			return ""
    62  		}
    63  		cmd := icmd.Command(params[0], params[1:]...)
    64  		result := icmd.RunCmd(cmd)
    65  		return result.Combined()
    66  	}
    67  	// Func to execute docker cli commands
    68  	runDockerCmd := func(params ...string) string {
    69  		runner.configuredCmd.Command = dockerCli.Command(params...)
    70  		result := icmd.RunCmd(runner.configuredCmd)
    71  		result.Assert(t, icmd.Success)
    72  		return result.Combined()
    73  	}
    74  
    75  	// The dind doesn't have the cnab-app-base image so we save it in order to load it later
    76  	runDockerCmd("save", fmt.Sprintf("docker/cnab-app-base:%s", internal.Version), "-o", tmpDir.Join("cnab-app-base.tar.gz"))
    77  
    78  	// Busybox is used in a few e2e test, let's pre-load it
    79  	runDockerCmd("pull", "busybox:1.30.1")
    80  	runDockerCmd("save", "busybox:1.30.1", "-o", tmpDir.Join("busybox.tar.gz"))
    81  
    82  	// we have a difficult constraint here:
    83  	// - the registry must be reachable from the client side (for cnab-to-oci, which does not use the docker daemon to access the registry)
    84  	// - the registry must be reachable from the dind daemon on the same address/port
    85  	// - the installer image need to target the same docker context (dind) as the client, while running on default (or another) context, which means we can't use 'localhost'
    86  	// Solution found is: use host external IP (not loopback) so accessing from within installer container will reach the right container
    87  
    88  	registry := NewContainer("registry:2", 5000)
    89  	registry.Start(t, "-e", "REGISTRY_VALIDATION_MANIFESTS_URLS_ALLOW=[^http]",
    90  		"-e", "REGISTRY_HTTP_ADDR=0.0.0.0:5000")
    91  	defer registry.StopNoFail()
    92  	registryAddress := registry.GetAddress(t)
    93  
    94  	swarm := NewContainer("docker:19.03.3-dind", 2375, "--insecure-registry", registryAddress)
    95  	swarm.Start(t, "-e", "DOCKER_TLS_CERTDIR=") // Disable certificate generate on DinD startup
    96  	defer swarm.Stop(t)
    97  	swarmAddress := swarm.GetAddress(t)
    98  
    99  	// Initialize the info struct
   100  	runner.registryAddress = registryAddress
   101  	runner.swarmAddress = swarmAddress
   102  	runner.stopRegistry = registry.StopNoFail
   103  	runner.registryLogs = registry.Logs(t)
   104  
   105  	runDockerCmd("context", "create", "swarm-context", "--docker", fmt.Sprintf(`"host=tcp://%s"`, swarmAddress), "--default-stack-orchestrator", "swarm")
   106  
   107  	runner.configuredCmd.Env = append(runner.configuredCmd.Env, "DOCKER_CONTEXT=swarm-context", "DOCKER_INSTALLER_CONTEXT=swarm-context")
   108  
   109  	// Initialize the swarm
   110  	runDockerCmd("swarm", "init")
   111  	// Load the needed base cnab image into the swarm docker engine
   112  	runDockerCmd("load", "-i", tmpDir.Join("cnab-app-base.tar.gz"))
   113  	// Pre-load busybox image used by a few e2e tests
   114  	runDockerCmd("load", "-i", tmpDir.Join("busybox.tar.gz"))
   115  
   116  	runner.localCmd = runLocalCmd
   117  	runner.dockerCmd = runDockerCmd
   118  	runner.execCmd = func(params ...string) string {
   119  		args := append([]string{"docker", "exec", "-t", swarm.container}, params...)
   120  		return runLocalCmd(args...)
   121  	}
   122  	todo(runner)
   123  }
   124  
   125  func build(t *testing.T, cmd icmd.Cmd, dockerCli dockerCliCommand, ref, path string) {
   126  	iidfile := fs.NewFile(t, "iid")
   127  	defer iidfile.Remove()
   128  	cmd.Command = dockerCli.Command("app", "build", "--iidfile", iidfile.Path(), "-t", ref, path)
   129  	icmd.RunCmd(cmd).Assert(t, icmd.Success)
   130  	_, err := ioutil.ReadFile(iidfile.Path())
   131  	assert.NilError(t, err)
   132  }
   133  
   134  // Container represents a docker container
   135  type Container struct {
   136  	image           string
   137  	privatePort     int
   138  	address         string
   139  	container       string
   140  	parentContainer string
   141  	args            []string
   142  }
   143  
   144  // NewContainer creates a new Container
   145  func NewContainer(image string, privatePort int, args ...string) *Container {
   146  	return &Container{
   147  		image:       image,
   148  		privatePort: privatePort,
   149  		args:        args,
   150  	}
   151  }
   152  
   153  // Start starts a new docker container on a random port
   154  func (c *Container) Start(t *testing.T, dockerArgs ...string) {
   155  	args := []string{"run", "--rm", "--privileged", "-d", "-P"}
   156  	args = append(args, dockerArgs...)
   157  	args = append(args, c.image)
   158  	args = append(args, c.args...)
   159  	result := icmd.RunCommand(dockerCli.path, args...).Assert(t, icmd.Success)
   160  	c.container = strings.Trim(result.Stdout(), " \r\n")
   161  	time.Sleep(time.Second * 3)
   162  }
   163  
   164  // StartWithContainerNetwork starts a new container using an existing container network
   165  func (c *Container) StartWithContainerNetwork(t *testing.T, other *Container, dockerArgs ...string) {
   166  	args := []string{"run", "--rm", "--privileged", "-d", "--network=container:" + other.container}
   167  	args = append(args, dockerArgs...)
   168  	args = append(args, c.image)
   169  	args = append(args, c.args...)
   170  	result := icmd.RunCommand(dockerCli.path, args...).Assert(t, icmd.Success)
   171  	c.container = strings.Trim(result.Stdout(), " \r\n")
   172  	time.Sleep(time.Second * 3)
   173  	c.parentContainer = other.container
   174  }
   175  
   176  // Stop terminates this container
   177  func (c *Container) Stop(t *testing.T) {
   178  	icmd.RunCommand(dockerCli.path, "stop", c.container).Assert(t, icmd.Success)
   179  }
   180  
   181  // StopNoFail terminates this container
   182  func (c *Container) StopNoFail() {
   183  	icmd.RunCommand(dockerCli.path, "stop", c.container)
   184  }
   185  
   186  // GetAddress returns the host:port this container listens on
   187  func (c *Container) GetAddress(t *testing.T) string {
   188  	if c.address != "" {
   189  		return c.address
   190  	}
   191  	ip := c.getIP(t)
   192  	port := c.getPort(t)
   193  	c.address = fmt.Sprintf("%s:%v", ip, port)
   194  	return c.address
   195  }
   196  
   197  func (c *Container) getPort(t *testing.T) string {
   198  	result := icmd.RunCommand(dockerCli.path, "port", c.container, strconv.Itoa(c.privatePort)).Assert(t, icmd.Success)
   199  	port := strings.Trim(strings.Split(result.Stdout(), ":")[1], " \r\n")
   200  	return port
   201  }
   202  
   203  var host string
   204  
   205  func (c *Container) getIP(t *testing.T) string {
   206  	if host != "" {
   207  		return host
   208  	}
   209  	// Discover default gateway
   210  	gw, err := gateway.DiscoverGateway()
   211  	assert.NilError(t, err)
   212  
   213  	// Search for the interface configured on the same network as the gateway
   214  	addrs, err := net.InterfaceAddrs()
   215  	assert.NilError(t, err)
   216  	for _, a := range addrs {
   217  		if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
   218  			net1 := ipnet.IP.Mask(ipnet.Mask).String()
   219  			net2 := gw.Mask(ipnet.Mask).String()
   220  			if net1 == net2 {
   221  				host = ipnet.IP.String()
   222  				break
   223  			}
   224  		}
   225  	}
   226  	return host
   227  }
   228  
   229  func (c *Container) Logs(t *testing.T) func() string {
   230  	return func() string {
   231  		return icmd.RunCommand(dockerCli.path, "logs", c.container).Assert(t, icmd.Success).Combined()
   232  	}
   233  }