Skip to content

Commit

Permalink
Merge branch 'master' of github.com:testcontainers/testcontainers-go
Browse files Browse the repository at this point in the history
  • Loading branch information
01101101M committed Jun 17, 2021
2 parents 399c0a6 + ddd3e32 commit a4ac403
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 9 deletions.
25 changes: 19 additions & 6 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,21 @@ type Container interface {

// ImageBuildInfo defines what is needed to build an image
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
GetContext() (io.Reader, error) // the path to the build context
GetDockerfile() string // the relative path to the Dockerfile, including the fileitself
ShouldPrintBuildLog() bool // allow build log to be printed to stdout
ShouldBuildImage() bool // return true if the image needs to be built
GetBuildArgs() map[string]*string // return the environment args used to build the from Dockerfile
}

// FromDockerfile represents the parameters needed to build an image from a Dockerfile
// rather than using a pre-built one
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"
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"
BuildArgs map[string]*string // enable user to pass build args to docker daemon
PrintBuildLog bool // enable user to print build log
}

// ContainerRequest represents the parameters used to get a running container
Expand Down Expand Up @@ -150,6 +154,11 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) {
return buildContext, nil
}

// GetBuildArgs returns the env args to be used when creating from Dockerfile
func (c *ContainerRequest) GetBuildArgs() map[string]*string {
return c.FromDockerfile.BuildArgs
}

// GetDockerfile returns the Dockerfile from the ContainerRequest, defaults to "Dockerfile"
func (c *ContainerRequest) GetDockerfile() string {
f := c.FromDockerfile.Dockerfile
Expand All @@ -164,6 +173,10 @@ func (c *ContainerRequest) ShouldBuildImage() bool {
return c.FromDockerfile.Context != "" || c.FromDockerfile.ContextArchive != nil
}

func (c *ContainerRequest) ShouldPrintBuildLog() bool {
return c.FromDockerfile.PrintBuildLog
}

func (c *ContainerRequest) validateContextAndImage() error {
if c.FromDockerfile.Context != "" && c.Image != "" {
return errors.New("you cannot specify both an Image and Context in a ContainerRequest")
Expand Down
33 changes: 32 additions & 1 deletion docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import (
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/go-connections/nat"
"github.com/google/uuid"
"github.com/moby/term"
"github.com/pkg/errors"

"github.com/testcontainers/testcontainers-go/wait"
Expand Down Expand Up @@ -174,6 +176,11 @@ func (c *DockerContainer) Start(ctx context.Context) error {

// Terminate is used to kill the container. It is usually triggered by as defer function.
func (c *DockerContainer) Terminate(ctx context.Context) error {
select {
// close reaper if it was created
case c.terminationSignal <- true:
default:
}
err := c.provider.client.ContainerRemove(ctx, c.GetContainerID(), types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
Expand Down Expand Up @@ -356,10 +363,14 @@ func (c *DockerContainer) CopyFileToContainer(ctx context.Context, hostFilePath
// from the container and will send them to each added LogConsumer
func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
go func() {
since := ""
// if the socket is closed we will make additional logs request with updated Since timestamp
BEGIN:
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Since: since,
}

ctx, cancel := context.WithTimeout(ctx, time.Second*5)
Expand All @@ -385,6 +396,12 @@ func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
h := make([]byte, 8)
_, err := r.Read(h)
if err != nil {
// proper type matching requires https://go-review.googlesource.com/c/go/+/250357/ (go 1.16)
if strings.Contains(err.Error(), "use of closed connection") {
now := time.Now()
since = fmt.Sprintf("%d.%09d", now.Unix(), int64(now.Nanosecond()))
goto BEGIN
}
// this explicitly ignores errors
// because we want to keep procesing even if one of our reads fails
continue
Expand All @@ -396,7 +413,7 @@ func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
}
logType := h[0]
if logType > 2 {
panic(fmt.Sprintf("received inavlid log type: %d", logType))
panic(fmt.Sprintf("received invalid log type: %d", logType))
}

// a map of the log type --> int representation in the header, notice the first is blank, this is stdin, but the go docker client doesn't allow following that in logs
Expand Down Expand Up @@ -440,6 +457,11 @@ type DockerNetwork struct {

// Remove is used to remove the network. It is usually triggered by as defer function.
func (n *DockerNetwork) Remove(ctx context.Context) error {
select {
// close reaper if it was created
case n.terminationSignal <- true:
default:
}
return n.provider.client.NetworkRemove(ctx, n.ID)
}

Expand Down Expand Up @@ -478,6 +500,7 @@ func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (st
}

buildOptions := types.ImageBuildOptions{
BuildArgs: img.GetBuildArgs(),
Dockerfile: img.GetDockerfile(),
Context: buildContext,
Tags: []string{repoTag},
Expand All @@ -488,6 +511,14 @@ func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (st
return "", err
}

if img.ShouldPrintBuildLog() {
termFd, isTerm := term.GetFdInfo(os.Stderr)
err = jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stderr, termFd, isTerm, nil)
if err != nil {
return "", err
}
}

// need to read the response from Docker, I think otherwise the image
// might not finish building before continuing to execute here
buf := new(bytes.Buffer)
Expand Down
110 changes: 110 additions & 0 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
Expand Down Expand Up @@ -892,6 +894,114 @@ func Test_BuildContainerFromDockerfile(t *testing.T) {
}
}

func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) {
t.Log("getting ctx")
ctx := context.Background()

ba := "build args value"

t.Log("got ctx, creating container request")
req := ContainerRequest{
FromDockerfile: FromDockerfile{
Context: "./testresources",
Dockerfile: "args.Dockerfile",
BuildArgs: map[string]*string{
"FOO": &ba,
},
},
ExposedPorts: []string{"8080/tcp"},
WaitingFor: wait.ForLog("ready"),
}

genContainerReq := GenericContainerRequest{
ContainerRequest: req,
Started: true,
}

c, err := GenericContainer(ctx, genContainerReq)

if err != nil {
t.Fatal(err)
}

ep, err := c.Endpoint(ctx, "http")
if err != nil {
t.Fatal(err)
}

resp, err := http.Get(ep + "/env")

if err != nil {
t.Fatal(err)
}

body, err := ioutil.ReadAll(resp.Body)

if err != nil {
t.Fatal(err)
}

assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, ba, string(body))

defer func() {
t.Log("terminating container")
err := c.Terminate(ctx)
if err != nil {
t.Fatal(err)
}
t.Log("terminated container")
}()
}

func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) {
rescueStdout := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w

t.Log("getting ctx")
ctx := context.Background()
t.Log("got ctx, creating container request")

req := ContainerRequest{
FromDockerfile: FromDockerfile{
Context: "./testresources",
Dockerfile: "buildlog.Dockerfile",
PrintBuildLog: true,
},
}

genContainerReq := GenericContainerRequest{
ContainerRequest: req,
Started: true,
}

c, err := GenericContainer(ctx, genContainerReq)

if err != nil {
t.Fatal(err)
}

defer func() {
t.Log("terminating container")
err := c.Terminate(ctx)
if err != nil {
t.Fatal(err)
}
t.Log("terminated container")
}()

w.Close()
out, _ := ioutil.ReadAll(r)
os.Stdout = rescueStdout
temp := strings.Split(string(out), "\n")

if temp[0] != "Step 1/1 : FROM alpine" {
t.Errorf("Expected stout firstline to be %s. Got '%s'.", "Step 1/2 : FROM alpine", temp[0])
}

}

func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) {
ctx := context.Background()
req := ContainerRequest{
Expand Down
21 changes: 21 additions & 0 deletions docs/features/build_from_dockerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ req := ContainerRequest{
}
```

If your Dockerfile expects build args:

```Dockerfile
FROM alpine

ARG FOO

```
You can specify them like:

```go
req := ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: "/path/to/build/context",
Dockerfile: "CustomDockerfile",
BuildArgs: map[string]*string {
"FOO": "BAR",
},
},
}
```
## 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
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart/gotest.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ look.
ready to receive any traffic. In this, case we check for the logs we know come
from Redis, telling us that it is ready to accept requests.

When you use `ExposedPorts` you have to image yourself using `docker run -p
When you use `ExposedPorts` you have to imagine yourself using `docker run -p
<port>`. When you do so, `dockerd` maps the selected `<port>` from inside the
container to a random one available on your host.

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/google/uuid v1.2.0
github.com/moby/sys/mount v0.2.0 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
Expand Down Expand Up @@ -402,6 +403,8 @@ github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
Expand Down Expand Up @@ -701,6 +704,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
9 changes: 8 additions & 1 deletion logconsumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"testing"
"time"

"github.com/testcontainers/testcontainers-go/wait"
"gotest.tools/assert"
Expand Down Expand Up @@ -81,12 +82,18 @@ func Test_LogConsumerGetsCalled(t *testing.T) {
t.Fatal(err)
}

time.Sleep(10 * time.Second)

_, err = http.Get(ep + fmt.Sprintf("/stdout?echo=%s", lastMessage))
if err != nil {
t.Fatal(err)
}

<-g.Ack
select {
case <-g.Ack:
case <-time.After(5 * time.Second):
t.Fatal("never received final log message")
}
c.StopLogProducer()

// get rid of the server "ready" log
Expand Down
11 changes: 11 additions & 0 deletions testresources/args.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.13-alpine

ARG FOO

ENV FOO=$FOO

WORKDIR /app

COPY echoserver.go .

CMD go run echoserver.go
1 change: 1 addition & 0 deletions testresources/buildlog.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM alpine
Loading

0 comments on commit a4ac403

Please sign in to comment.