Skip to content

Commit

Permalink
[Enhancement]: add ability to set repo:tag for ContainerRequest FromD…
Browse files Browse the repository at this point in the history
…ockerfile (#1508)

* add ability to set repo:tag for ContainerRequest FromDockerfile

* chore: add test

* fix: imports

* fix: imports

* chor: add more tests

* docs: document the fields in the site

* docs: use code snippets

---------

Co-authored-by: igribkov <i.gribkov@robovoice.ru>
Co-authored-by: Manuel de la Peña <mdelapenya@gmail.com>
  • Loading branch information
3 people committed Aug 28, 2023
1 parent 7863b82 commit e1db360
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 26 deletions.
28 changes: 27 additions & 1 deletion container.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"path/filepath"
"strings"
"time"

"github.com/docker/docker/api/types"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/archive"
"github.com/docker/go-connections/nat"
"github.com/google/uuid"

tcexec "github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
Expand Down Expand Up @@ -63,6 +65,8 @@ type Container interface {
type ImageBuildInfo interface {
GetContext() (io.Reader, error) // the path to the build context
GetDockerfile() string // the relative path to the Dockerfile, including the fileitself
GetRepo() string // get repo label for image
GetTag() string // get tag label for image
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
Expand All @@ -72,9 +76,11 @@ type ImageBuildInfo interface {
// 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
Context string // the path to the context 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"
Repo string // the repo label for image, defaults to UUID
Tag string // the tag label for image, defaults to UUID
BuildArgs map[string]*string // enable user to pass build args to docker daemon
PrintBuildLog bool // enable user to print build log
AuthConfigs map[string]registry.AuthConfig // Deprecated. Testcontainers will detect registry credentials automatically. Enable auth configs to be able to pull from an authenticated docker registry
Expand Down Expand Up @@ -205,6 +211,26 @@ func (c *ContainerRequest) GetDockerfile() string {
return f
}

// GetRepo returns the Repo label for image from the ContainerRequest, defaults to UUID
func (c *ContainerRequest) GetRepo() string {
r := c.FromDockerfile.Repo
if r == "" {
return uuid.NewString()
}

return strings.ToLower(r)
}

// GetTag returns the Tag label for image from the ContainerRequest, defaults to UUID
func (c *ContainerRequest) GetTag() string {
t := c.FromDockerfile.Tag
if t == "" {
return uuid.NewString()
}

return strings.ToLower(t)
}

// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry
func (c *ContainerRequest) GetAuthConfigs() map[string]registry.AuthConfig {
images, err := testcontainersdocker.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
Expand Down
2 changes: 2 additions & 0 deletions container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func Test_BuildImageWithContexts(t *testing.T) {
testCases := []TestCase{
{
Name: "test build from context archive",
// fromDockerfileWithContextArchive {
ContextArchive: func() (io.Reader, error) {
var buf bytes.Buffer
tarWriter := tar.NewWriter(&buf)
Expand Down Expand Up @@ -179,6 +180,7 @@ func Test_BuildImageWithContexts(t *testing.T) {

return reader, nil
},
// }
ExpectedEchoOutput: "this is from the archive",
},
{
Expand Down
5 changes: 1 addition & 4 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,10 +773,7 @@ var _ ContainerProvider = (*DockerProvider)(nil)

// BuildImage will build and image from context and Dockerfile, then return the tag
func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (string, error) {
repo := uuid.New()
tag := uuid.New()

repoTag := fmt.Sprintf("%s:%s", repo, tag)
repoTag := fmt.Sprintf("%s:%s", img.GetRepo(), img.GetTag())

buildContext, err := img.GetContext()
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,9 +1070,11 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) {
t.Log("getting ctx")
ctx := context.Background()

t.Log("got ctx, creating container request")

// fromDockerfileWithBuildArgs {
ba := "build args value"

t.Log("got ctx, creating container request")
req := ContainerRequest{
FromDockerfile: FromDockerfile{
Context: filepath.Join(".", "testdata"),
Expand All @@ -1084,6 +1086,7 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) {
ExposedPorts: []string{"8080/tcp"},
WaitingFor: wait.ForLog("ready"),
}
// }

genContainerReq := GenericContainerRequest{
ProviderType: providerType,
Expand Down
30 changes: 10 additions & 20 deletions docs/features/build_from_dockerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ 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:

```go
req := ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: "/path/to/build/context",
Dockerfile: "CustomDockerfile",
},
}
```
<!--codeinclude-->
[Building From a Dockerfile including Repository and Tag](../../from_dockerfile_test.go) inside_block:fromDockerfileIncludingRepo
<!--/codeinclude-->

As you can see, you can also specify the `Repo` and `Tag` optional fields to use for the image. If not passed, the
image will be built with a random name and tag.

If your Dockerfile expects build args:

Expand All @@ -26,18 +24,10 @@ ARG FOO
```
You can specify them like:

```go
val := "BAR"
req := ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: "/path/to/build/context",
Dockerfile: "CustomDockerfile",
BuildArgs: map[string]*string {
"FOO": &val,
},
},
}
```
<!--codeinclude-->
[Building From a Dockerfile including build arguments](../../docker_test.go) inside_block:fromDockerfileWithBuildArgs
<!--/codeinclude-->

## 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
119 changes: 119 additions & 0 deletions from_dockerfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package testcontainers

import (
"context"
"path/filepath"
"strings"
"testing"

"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
)

func TestBuildImageFromDockerfile(t *testing.T) {
provider, err := NewDockerProvider()
if err != nil {
t.Fatal(err)
}
defer provider.Close()

cli := provider.Client()

ctx := context.Background()

tag, err := provider.BuildImage(ctx, &ContainerRequest{
// fromDockerfileIncludingRepo {
FromDockerfile: FromDockerfile{
Context: filepath.Join("testdata"),
Dockerfile: "echo.Dockerfile",
Repo: "test-repo",
Tag: "test-tag",
},
// }
})
assert.Nil(t, err)
assert.Equal(t, "test-repo:test-tag", tag)

_, _, err = cli.ImageInspectWithRaw(ctx, tag)
assert.Nil(t, err)

t.Cleanup(func() {
_, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{
Force: true,
PruneChildren: true,
})
if err != nil {
t.Fatal(err)
}
})
}

func TestBuildImageFromDockerfile_NoRepo(t *testing.T) {
provider, err := NewDockerProvider()
if err != nil {
t.Fatal(err)
}
defer provider.Close()

cli := provider.Client()

ctx := context.Background()

tag, err := provider.BuildImage(ctx, &ContainerRequest{
FromDockerfile: FromDockerfile{
Context: filepath.Join("testdata"),
Dockerfile: "echo.Dockerfile",
Repo: "test-repo",
},
})
assert.Nil(t, err)
assert.True(t, strings.HasPrefix(tag, "test-repo:"))

_, _, err = cli.ImageInspectWithRaw(ctx, tag)
assert.Nil(t, err)

t.Cleanup(func() {
_, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{
Force: true,
PruneChildren: true,
})
if err != nil {
t.Fatal(err)
}
})
}

func TestBuildImageFromDockerfile_NoTag(t *testing.T) {
provider, err := NewDockerProvider()
if err != nil {
t.Fatal(err)
}
defer provider.Close()

cli := provider.Client()

ctx := context.Background()

tag, err := provider.BuildImage(ctx, &ContainerRequest{
FromDockerfile: FromDockerfile{
Context: filepath.Join("testdata"),
Dockerfile: "echo.Dockerfile",
Tag: "test-tag",
},
})
assert.Nil(t, err)
assert.True(t, strings.HasSuffix(tag, ":test-tag"))

_, _, err = cli.ImageInspectWithRaw(ctx, tag)
assert.Nil(t, err)

t.Cleanup(func() {
_, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{
Force: true,
PruneChildren: true,
})
if err != nil {
t.Fatal(err)
}
})
}

0 comments on commit e1db360

Please sign in to comment.