From e1db360fab2f59004830cdf8098ad2d7795df943 Mon Sep 17 00:00:00 2001 From: igribkov Date: Mon, 28 Aug 2023 20:42:51 +0300 Subject: [PATCH] [Enhancement]: add ability to set repo:tag for ContainerRequest FromDockerfile (#1508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Manuel de la Peña --- container.go | 28 +++++- container_test.go | 2 + docker.go | 5 +- docker_test.go | 5 +- docs/features/build_from_dockerfile.md | 30 +++---- from_dockerfile_test.go | 119 +++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 from_dockerfile_test.go diff --git a/container.go b/container.go index 7dbd544ff2..a0bf8ce3e5 100644 --- a/container.go +++ b/container.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "path/filepath" + "strings" "time" "github.com/docker/docker/api/types" @@ -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" @@ -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 @@ -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 @@ -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()) diff --git a/container_test.go b/container_test.go index b417822986..c3c12dcbfe 100644 --- a/container_test.go +++ b/container_test.go @@ -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) @@ -179,6 +180,7 @@ func Test_BuildImageWithContexts(t *testing.T) { return reader, nil }, + // } ExpectedEchoOutput: "this is from the archive", }, { diff --git a/docker.go b/docker.go index 567f6bb840..5ed61f4ada 100644 --- a/docker.go +++ b/docker.go @@ -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 { diff --git a/docker_test.go b/docker_test.go index dbc1030e9b..4a495bf7be 100644 --- a/docker_test.go +++ b/docker_test.go @@ -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"), @@ -1084,6 +1086,7 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { ExposedPorts: []string{"8080/tcp"}, WaitingFor: wait.ForLog("ready"), } + // } genContainerReq := GenericContainerRequest{ ProviderType: providerType, diff --git a/docs/features/build_from_dockerfile.md b/docs/features/build_from_dockerfile.md index 8eec645374..349c4a24c9 100644 --- a/docs/features/build_from_dockerfile.md +++ b/docs/features/build_from_dockerfile.md @@ -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", - }, - } -``` + +[Building From a Dockerfile including Repository and Tag](../../from_dockerfile_test.go) inside_block:fromDockerfileIncludingRepo + + +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: @@ -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, - }, - }, - } -``` + +[Building From a Dockerfile including build arguments](../../docker_test.go) inside_block:fromDockerfileWithBuildArgs + + ## 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 diff --git a/from_dockerfile_test.go b/from_dockerfile_test.go new file mode 100644 index 0000000000..833ff451cd --- /dev/null +++ b/from_dockerfile_test.go @@ -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) + } + }) +}