testcontainers

package module
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 5, 2020 License: MIT Imports: 26 Imported by: 0

README

Build Status

When I was working on a Zipkin PR I discovered a nice Java library called testcontainers.

It provides an easy and clean API over the go docker sdk to run, terminate and connect to containers in your tests.

I found myself comfortable programmatically writing the containers I need to run an integration/smoke tests. So I started porting this library in Go.

This is the API I have defined:

package main

import (
	"context"
	"fmt"
	"net/http"
	"testing"

	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
)

func TestNginxLatestReturn(t *testing.T) {
	ctx := context.Background()
	req := testcontainers.ContainerRequest{
		Image:        "nginx",
		ExposedPorts: []string{"80/tcp"},
		WaitingFor:   wait.ForHTTP("/"),
	}
	nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		t.Error(err)
	}
	defer nginxC.Terminate(ctx)
	ip, err := nginxC.Host(ctx)
	if err != nil {
		t.Error(err)
	}
	port, err := nginxC.MappedPort(ctx, "80")
	if err != nil {
		t.Error(err)
	}
	resp, err := http.Get(fmt.Sprintf("http://%s:%s", ip, port.Port()))
	if resp.StatusCode != http.StatusOK {
		t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode)
	}
}

This is a simple example, you can create one container in my case using the nginx image. You can get its IP ip, err := nginxC.GetContainerIpAddress(ctx) and you can use it to make a GET: resp, err := http.Get(fmt.Sprintf("http://%s", ip))

To clean your environment you can defer the container termination defer nginxC.Terminate(ctx, t). t is *testing.T and it is used to notify is the defer failed marking the test as failed.

Build from Dockerfile

Testcontainers-go gives you the ability to build an image and run a container from a Dockerfile.

You can do so by specifying a Context (the filepath to the build context on your local filesystem) and optionally a Dockerfile (defaults to "Dockerfile") like so:

req := ContainerRequest{
		FromDockerfile: testcontainers.FromDockerfile{
			Context: "/path/to/build/context",
			Dockerfile: "CustomDockerfile",
		},
	}

Dynamic Build Context

If you would like to send a build context that you created in code (maybe you have a dynamic Dockerfile), you can send the build context as an io.Reader since the Docker Daemon accepts is as a tar file, you can use the tar package to create your context.

To do this you would use the ContextArchive attribute in the FromDockerfile struct.

var buf bytes.Buffer
tarWriter := tar.NewWriter(&buf)
// ... add some files
if err := tarWriter.Close(); err != nil {
	// do something with err
}
reader := bytes.NewReader(buf.Bytes())
fromDockerfile := testcontainers.FromDockerfile{
	ContextArchive: reader,
}

Please Note if you specify a ContextArchive this will cause testcontainers to ignore the path passed in to Context

Sending a CMD to a Container

If you would like to send a CMD (command) to a container, you can pass it in to the container request via the Cmd field...

req := ContainerRequest{
	Image: "alpine",
	WaitingFor: wait.ForAll(
		wait.ForLog("command override!"),
	),
	Cmd: []string{"echo", "command override!"},
}

Following Container Logs

If you wish to follow container logs, you can set up LogConsumers. The log following functionality follows a producer-consumer model. You will need to explicitly start and stop the producer. As logs are written to either stdout, or stderr (stdin is not supported) they will be forwarded (produced) to any associated LogConsumers. You can associate LogConsumers with the .FollowOutput function.

Please note if you start the producer you should always stop it explicitly.

for example, this consumer will just add logs to a slice

type TestLogConsumer struct {
	Msgs []string
}

func (g *TestLogConsumer) Accept(l Log) {
	g.Msgs = append(g.Msgs, string(l.Content))
}

this can be used like so:

g := TestLogConsumer{
	Msgs: []string{},
}

err := c.StartLogProducer(ctx)
if err != nil {
	// do something with err
}

c.FollowOutput(&g)

// some stuff happens...

err = c.StopLogProducer()
if err != nil {
	// do something with err
}

Documentation

Index

Examples

Constants

View Source
const (
	TestcontainerLabel          = "org.testcontainers.golang"
	TestcontainerLabelSessionID = TestcontainerLabel + ".sessionId"
	TestcontainerLabelIsReaper  = TestcontainerLabel + ".reaper"

	ReaperDefaultImage = "quay.io/testcontainers/ryuk:0.2.3"
)
View Source
const StderrLog = "STDERR"

StderrLog is the log type for STDERR

View Source
const StdoutLog = "STDOUT"

StdoutLog is the log type for STDOUT

Variables

This section is empty.

Functions

func PullImage

func PullImage(ctx context.Context, tag string, providerType ProviderType, credentials string) error

Types

type Container

type Container interface {
	GetContainerID() string                                         // get the container id from the provider
	Endpoint(context.Context, string) (string, error)               // get proto://ip:port string for the first exposed port
	PortEndpoint(context.Context, nat.Port, string) (string, error) // get proto://ip:port string for the given exposed port
	Host(context.Context) (string, error)                           // get host where the container port is exposed
	MappedPort(context.Context, nat.Port) (nat.Port, error)         // get externally mapped port for a container port
	Ports(context.Context) (nat.PortMap, error)                     // get all exposed ports
	SessionID() string                                              // get session id
	Start(context.Context) error                                    // start the container
	Terminate(context.Context) error                                // terminate the container
	Logs(context.Context) (io.ReadCloser, error)                    // Get logs of the container
	FollowOutput(LogConsumer)
	StartLogProducer(context.Context) error
	StopLogProducer() error
	Name(context.Context) (string, error)                        // get container name
	Networks(context.Context) ([]string, error)                  // get container networks
	NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network
	Exec(ctx context.Context, cmd []string) (int, error)
	IP() string
}

Container allows getting info about and controlling a single container instance

func GenericContainer

func GenericContainer(ctx context.Context, req GenericContainerRequest) (Container, error)

GenericContainer creates a generic container with parameters

type ContainerProvider

type ContainerProvider interface {
	CreateContainer(context.Context, ContainerRequest) (Container, error) // create a container without starting it
	RunContainer(context.Context, ContainerRequest) (Container, error)    // create a container and start it
	PullImage(ctx context.Context, tag string, credentials string) error  // pull image
}

ContainerProvider allows the creation of containers on an arbitrary system

type ContainerRequest

type ContainerRequest struct {
	FromDockerfile
	Image           string
	Env             map[string]string
	ExposedPorts    []string // allow specifying protocol info
	Cmd             []string
	Labels          map[string]string
	BindMounts      map[string]string
	VolumeMounts    map[string]string
	Tmpfs           map[string]string
	RegistryCred    string
	WaitingFor      wait.Strategy
	Name            string              // for specifying container name
	Privileged      bool                // for starting privileged container
	Networks        []string            // for specifying network names
	NetworkAliases  map[string][]string // for specifying network aliases
	SkipReaper      bool                // indicates whether we skip setting up a reaper for this
	ReaperImage     string              // alternative reaper image
	AutoRemove      bool                // if set to true, the container will be removed from the host when stopped
	NetworkMode     container.NetworkMode
	AlwaysPullImage bool // Always pull image
}

ContainerRequest represents the parameters used to get a running container

func (*ContainerRequest) GetContext

func (c *ContainerRequest) GetContext() (io.Reader, error)

GetContext retrieve the build context for the request

func (*ContainerRequest) GetDockerfile

func (c *ContainerRequest) GetDockerfile() string

GetDockerfile returns the Dockerfile from the ContainerRequest, defaults to "Dockerfile"

func (*ContainerRequest) ShouldBuildImage

func (c *ContainerRequest) ShouldBuildImage() bool

func (*ContainerRequest) Validate

func (c *ContainerRequest) Validate() error

Validate ensures that the ContainerRequest does not have invalid paramters configured to it ex. make sure you are not specifying both an image as well as a context

type DeprecatedContainer

type DeprecatedContainer interface {
	GetHostEndpoint(ctx context.Context, port string) (string, string, error)
	GetIPAddress(ctx context.Context) (string, error)
	LivenessCheckPorts(ctx context.Context) (nat.PortSet, error)
	Terminate(ctx context.Context) error
}

DeprecatedContainer shows methods that were supported before, but are now deprecated Deprecated: Use Container

type DockerContainer

type DockerContainer struct {
	// Container ID from Docker
	ID         string
	WaitingFor wait.Strategy
	Image      string
	// contains filtered or unexported fields
}

DockerContainer represents a container started using Docker

func (*DockerContainer) Endpoint

func (c *DockerContainer) Endpoint(ctx context.Context, proto string) (string, error)

Endpoint gets proto://host:port string for the first exposed port Will returns just host:port if proto is ""

func (*DockerContainer) Exec

func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, error)

func (*DockerContainer) FollowOutput

func (c *DockerContainer) FollowOutput(consumer LogConsumer)

FollowOutput adds a LogConsumer to be sent logs from the container's STDOUT and STDERR

func (*DockerContainer) GetContainerID

func (c *DockerContainer) GetContainerID() string

func (*DockerContainer) Host

func (c *DockerContainer) Host(ctx context.Context) (string, error)

Host gets host (ip or name) of the docker daemon where the container port is exposed Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel You can use the "TC_HOST" env variable to set this yourself

func (*DockerContainer) IP

func (c *DockerContainer) IP() string

Gets internal container IP

func (*DockerContainer) Logs

Logs will fetch both STDOUT and STDERR from the current container. Returns a ReadCloser and leaves it up to the caller to extract what it wants.

func (*DockerContainer) MappedPort

func (c *DockerContainer) MappedPort(ctx context.Context, port nat.Port) (nat.Port, error)

MappedPort gets externally mapped port for a container port

func (*DockerContainer) Name

func (c *DockerContainer) Name(ctx context.Context) (string, error)

Name gets the name of the container.

func (*DockerContainer) NetworkAliases

func (c *DockerContainer) NetworkAliases(ctx context.Context) (map[string][]string, error)

NetworkAliases gets the aliases of the container for the networks it is attached to.

func (*DockerContainer) Networks

func (c *DockerContainer) Networks(ctx context.Context) ([]string, error)

Networks gets the names of the networks the container is attached to.

func (*DockerContainer) PortEndpoint

func (c *DockerContainer) PortEndpoint(ctx context.Context, port nat.Port, proto string) (string, error)

PortEndpoint gets proto://host:port string for the given exposed port Will returns just host:port if proto is ""

func (*DockerContainer) Ports

func (c *DockerContainer) Ports(ctx context.Context) (nat.PortMap, error)

Ports gets the exposed ports for the container.

func (*DockerContainer) SessionID

func (c *DockerContainer) SessionID() string

SessionID gets the current session id

func (*DockerContainer) Start

func (c *DockerContainer) Start(ctx context.Context) error

Start will start an already created container

func (*DockerContainer) StartLogProducer

func (c *DockerContainer) StartLogProducer(ctx context.Context) error

StartLogProducer will start a concurrent process that will continuously read logs from the container and will send them to each added LogConsumer

func (*DockerContainer) StopLogProducer

func (c *DockerContainer) StopLogProducer() error

StopLogProducer will stop the concurrent process that is reading logs and sending them to each added LogConsumer

func (*DockerContainer) Terminate

func (c *DockerContainer) Terminate(ctx context.Context) error

Terminate is used to kill the container. It is usually triggered by as defer function.

type DockerNetwork

type DockerNetwork struct {
	ID     string // Network ID from Docker
	Driver string
	Name   string
	// contains filtered or unexported fields
}

DockerNetwork represents a network started using Docker

func (*DockerNetwork) Remove

func (n *DockerNetwork) Remove(_ context.Context) error

Remove is used to remove the network. It is usually triggered by as defer function.

type DockerProvider

type DockerProvider struct {
	// contains filtered or unexported fields
}

DockerProvider implements the ContainerProvider interface

func NewDockerProvider

func NewDockerProvider() (*DockerProvider, error)

NewDockerProvider creates a Docker provider with the EnvClient

func (*DockerProvider) BuildImage

func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (string, error)

BuildImage will build and image from context and Dockerfile, then return the tag

func (*DockerProvider) CreateContainer

func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerRequest) (Container, error)

CreateContainer fulfills a request for a container without starting it

Example
ctx := context.Background()
req := ContainerRequest{
	Image:        "nginx",
	ExposedPorts: []string{"80/tcp"},
	WaitingFor:   wait.ForHTTP("/"),
}
nginxC, _ := GenericContainer(ctx, GenericContainerRequest{
	ContainerRequest: req,
	Started:          true,
})
defer nginxC.Terminate(ctx)
Output:

func (*DockerProvider) CreateNetwork

func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) (Network, error)

CreateNetwork returns the object representing a new network identified by its name

func (*DockerProvider) GetNetwork

GetNetwork returns the object representing the network identified by its name

func (*DockerProvider) PullImage

func (p *DockerProvider) PullImage(ctx context.Context, tag string, credentials string) error

func (*DockerProvider) RunContainer

func (p *DockerProvider) RunContainer(ctx context.Context, req ContainerRequest) (Container, error)

RunContainer takes a RequestContainer as input and it runs a container via the docker sdk

type FromDockerfile

type FromDockerfile struct {
	Context        string    // the path to the context of of the docker build
	ContextArchive io.Reader // the tar archive file to send to docker that contains the build context
	Dockerfile     string    // the path from the context to the Dockerfile for the image, defaults to "Dockerfile"
}

FromDockerfile represents the parameters needed to build an image from a Dockerfile rather than using a pre-built one

type GenericContainerRequest

type GenericContainerRequest struct {
	ContainerRequest              // embedded request for provider
	Started          bool         // whether to auto-start the container
	ProviderType     ProviderType // which provider to use, Docker if empty
}

GenericContainerRequest represents parameters to a generic container

type GenericProvider

type GenericProvider interface {
	ContainerProvider
	NetworkProvider
}

GenericProvider represents an abstraction for container and network providers

type ImageBuildInfo

type ImageBuildInfo interface {
	GetContext() (io.Reader, error) // the path to the build context
	GetDockerfile() string          // the relative path to the Dockerfile, including the fileitself
	ShouldBuildImage() bool         // return true if the image needs to be built
}

ImageBuildInfo defines what is needed to build an image

type Log

type Log struct {
	LogType string
	Content []byte
}

Log represents a message that was created by a process, LogType is either "STDOUT" or "STDERR", Content is the byte contents of the message itself

type LogConsumer

type LogConsumer interface {
	Accept(Log)
}

LogConsumer represents any object that can handle a Log, it is up to the LogConsumer instance what to do with the log

type Network

type Network interface {
	Remove(context.Context) error // removes the network
}

Network allows getting info about a single network instance

type NetworkProvider

type NetworkProvider interface {
	CreateNetwork(context.Context, NetworkRequest) (Network, error)            // create a network
	GetNetwork(context.Context, NetworkRequest) (types.NetworkResource, error) // get a network
}

NetworkProvider allows the creation of networks on an arbitrary system

type NetworkRequest

type NetworkRequest struct {
	Driver         string
	CheckDuplicate bool
	Internal       bool
	EnableIPv6     bool
	Name           string
	Labels         map[string]string
	Attachable     bool

	SkipReaper  bool   // indicates whether we skip setting up a reaper for this
	ReaperImage string //alternative reaper registry
}

NetworkRequest represents the parameters used to get a network

type ProviderType

type ProviderType int

ProviderType is an enum for the possible providers

const (
	ProviderDocker ProviderType = iota // Docker is default = 0
)

possible provider types

func (ProviderType) GetProvider

func (t ProviderType) GetProvider() (GenericProvider, error)

GetProvider provides the provider implementation for a certain type

type Reaper

type Reaper struct {
	Provider  ReaperProvider
	SessionID string
	Endpoint  string
}

Reaper is used to start a sidecar container that cleans up resources

func NewReaper

func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error)

NewReaper creates a Reaper with a sessionID to identify containers and a provider to use

func (*Reaper) Connect

func (r *Reaper) Connect() (chan bool, error)

Connect runs a goroutine which can be terminated by sending true into the returned channel

func (*Reaper) Labels

func (r *Reaper) Labels() map[string]string

Labels returns the container labels to use so that this Reaper cleans them up

type ReaperProvider

type ReaperProvider interface {
	RunContainer(ctx context.Context, req ContainerRequest) (Container, error)
}

ReaperProvider represents a provider for the reaper to run itself with The ContainerProvider interface should usually satisfy this as well, so it is pluggable

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL