From 7629fc0a602187fd6ac87aa5dc341857840bc8d9 Mon Sep 17 00:00:00 2001 From: igribkov Date: Wed, 16 Aug 2023 22:52:40 +0300 Subject: [PATCH 1/7] add ability to set repo:tag for ContainerRequest FromDockerfile --- container.go | 28 +++++++++++++++++++++++++++- docker.go | 5 +---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index ec2351ea20..042c3dabca 100644 --- a/container.go +++ b/container.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "github.com/google/uuid" "io" "path/filepath" + "strings" "time" "github.com/docker/docker/api/types" @@ -62,6 +64,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 @@ -71,9 +75,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 @@ -204,6 +210,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/docker.go b/docker.go index 4e6574ce36..deeb9906ed 100644 --- a/docker.go +++ b/docker.go @@ -779,10 +779,7 @@ func NewDockerClient() (cli *client.Client, err error) { // 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 { From 6b5d098f7c993a728ac3d6dcabae133df412d518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 16:43:09 +0200 Subject: [PATCH 2/7] chore: add test --- from_dockerfile_test.go | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 from_dockerfile_test.go diff --git a/from_dockerfile_test.go b/from_dockerfile_test.go new file mode 100644 index 0000000000..1da4efc15e --- /dev/null +++ b/from_dockerfile_test.go @@ -0,0 +1,46 @@ +package testcontainers + +import ( + "context" + "path/filepath" + "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{ + 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) + } + }) +} From f61be5f90b0161d35677f333102f73597655ca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 16:56:15 +0200 Subject: [PATCH 3/7] fix: imports --- container.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index dc2fc811db..fd069bd734 100644 --- a/container.go +++ b/container.go @@ -4,12 +4,13 @@ import ( "context" "errors" "fmt" - "github.com/google/uuid" "io" "path/filepath" "strings" "time" + "github.com/google/uuid" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" From da72a773c0f722389addbf1a35058e2a1c73d580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 17:00:13 +0200 Subject: [PATCH 4/7] fix: imports --- container.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/container.go b/container.go index fd069bd734..a0bf8ce3e5 100644 --- a/container.go +++ b/container.go @@ -9,14 +9,13 @@ import ( "strings" "time" - "github.com/google/uuid" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "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" From 77cfaba114c67f70d16bdeeea509e311b3c99d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 17:30:49 +0200 Subject: [PATCH 5/7] chor: add more tests --- from_dockerfile_test.go | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/from_dockerfile_test.go b/from_dockerfile_test.go index 1da4efc15e..d9edcfcb9a 100644 --- a/from_dockerfile_test.go +++ b/from_dockerfile_test.go @@ -3,6 +3,7 @@ package testcontainers import ( "context" "path/filepath" + "strings" "testing" "github.com/docker/docker/api/types" @@ -44,3 +45,73 @@ func TestBuildImageFromDockerfile(t *testing.T) { } }) } + +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) + } + }) +} From ae3ecfcb50d732651a77edf0826be0033e231be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 17:30:59 +0200 Subject: [PATCH 6/7] docs: document the fields in the site --- docs/features/build_from_dockerfile.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/features/build_from_dockerfile.md b/docs/features/build_from_dockerfile.md index 8eec645374..d1b5254ced 100644 --- a/docs/features/build_from_dockerfile.md +++ b/docs/features/build_from_dockerfile.md @@ -12,10 +12,15 @@ req := ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ Context: "/path/to/build/context", Dockerfile: "CustomDockerfile", + Repo: "myrepo", + Tag: "mytag", }, } ``` +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: ```Dockerfile From f32ba9f3d43ac9692803fec2587c80501a091d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 28 Aug 2023 17:38:51 +0200 Subject: [PATCH 7/7] docs: use code snippets --- container_test.go | 2 ++ docker_test.go | 5 ++++- docs/features/build_from_dockerfile.md | 29 +++++++------------------- from_dockerfile_test.go | 2 ++ 4 files changed, 15 insertions(+), 23 deletions(-) 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_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 d1b5254ced..349c4a24c9 100644 --- a/docs/features/build_from_dockerfile.md +++ b/docs/features/build_from_dockerfile.md @@ -7,16 +7,9 @@ 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", - Repo: "myrepo", - Tag: "mytag", - }, - } -``` + +[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. @@ -31,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 index d9edcfcb9a..833ff451cd 100644 --- a/from_dockerfile_test.go +++ b/from_dockerfile_test.go @@ -22,12 +22,14 @@ func TestBuildImageFromDockerfile(t *testing.T) { 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)