Skip to content

Commit 636c3cb

Browse files
lefinalmdelapenya
andauthored
feat: add image-keep option for built images (#1785)
* feat: add image-keep option for built images Adds an option to keep images if they have been built. This avoids the need to build these images everytime from scratch. * fix: lint file * refactor: move image-keep option to FromDockerfile * docs: add documentation for keeping built images * style: change test names for keeping built images --------- Co-authored-by: Manuel de la Peña <[email protected]>
1 parent 25a7e3f commit 636c3cb

File tree

4 files changed

+86
-3
lines changed

4 files changed

+86
-3
lines changed

container.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ type FromDockerfile struct {
8686
BuildArgs map[string]*string // enable user to pass build args to docker daemon
8787
PrintBuildLog bool // enable user to print build log
8888
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
89+
// KeepImage describes whether DockerContainer.Terminate should not delete the
90+
// container image. Useful for images that are built from a Dockerfile and take a
91+
// long time to build. Keeping the image also Docker to reuse it.
92+
KeepImage bool
8993
}
9094

9195
type ContainerFile struct {
@@ -276,6 +280,10 @@ func (c *ContainerRequest) ShouldBuildImage() bool {
276280
return c.FromDockerfile.Context != "" || c.FromDockerfile.ContextArchive != nil
277281
}
278282

283+
func (c *ContainerRequest) ShouldKeepBuiltImage() bool {
284+
return c.FromDockerfile.KeepImage
285+
}
286+
279287
func (c *ContainerRequest) ShouldPrintBuildLog() bool {
280288
return c.FromDockerfile.PrintBuildLog
281289
}

docker.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ type DockerContainer struct {
5959
WaitingFor wait.Strategy
6060
Image string
6161

62-
isRunning bool
63-
imageWasBuilt bool
62+
isRunning bool
63+
imageWasBuilt bool
64+
// keepBuiltImage makes Terminate not remove the image if imageWasBuilt.
65+
keepBuiltImage bool
6466
provider *DockerProvider
6567
sessionID string
6668
terminationSignal chan bool
@@ -274,7 +276,7 @@ func (c *DockerContainer) Terminate(ctx context.Context) error {
274276
return err
275277
}
276278

277-
if c.imageWasBuilt {
279+
if c.imageWasBuilt && !c.keepBuiltImage {
278280
_, err := c.provider.client.ImageRemove(ctx, c.Image, types.ImageRemoveOptions{
279281
Force: true,
280282
PruneChildren: true,
@@ -1089,6 +1091,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
10891091
WaitingFor: req.WaitingFor,
10901092
Image: imageName,
10911093
imageWasBuilt: req.ShouldBuildImage(),
1094+
keepBuiltImage: req.ShouldKeepBuiltImage(),
10921095
sessionID: testcontainerssession.SessionID(),
10931096
provider: p,
10941097
terminationSignal: termSignal,

docker_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616
"time"
1717

18+
"github.com/docker/docker/api/types"
1819
"github.com/docker/docker/api/types/container"
1920
"github.com/docker/docker/api/types/strslice"
2021
"github.com/docker/docker/api/types/volume"
@@ -2081,3 +2082,57 @@ func TestDockerProviderFindContainerByName(t *testing.T) {
20812082
require.NotNil(t, c)
20822083
assert.Contains(t, c.Names, c1Name)
20832084
}
2085+
2086+
func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) {
2087+
tests := []struct {
2088+
keepBuiltImage bool
2089+
}{
2090+
{keepBuiltImage: true},
2091+
{keepBuiltImage: false},
2092+
}
2093+
2094+
for _, tt := range tests {
2095+
t.Run(fmt.Sprintf("Keep built image: %t", tt.keepBuiltImage), func(t *testing.T) {
2096+
ctx := context.Background()
2097+
// Set up CLI.
2098+
provider, err := NewDockerProvider()
2099+
require.NoError(t, err, "get docker provider should not fail")
2100+
defer func() { _ = provider.Close() }()
2101+
cli := provider.Client()
2102+
// Create container.
2103+
c, err := GenericContainer(ctx, GenericContainerRequest{
2104+
ProviderType: providerType,
2105+
ContainerRequest: ContainerRequest{
2106+
FromDockerfile: FromDockerfile{
2107+
Context: "testdata",
2108+
Dockerfile: "echo.Dockerfile",
2109+
KeepImage: tt.keepBuiltImage,
2110+
},
2111+
},
2112+
})
2113+
require.NoError(t, err, "create container should not fail")
2114+
defer func() { _ = c.Terminate(context.Background()) }()
2115+
// Get the image ID.
2116+
containerName, err := c.Name(ctx)
2117+
require.NoError(t, err, "get container name should not fail")
2118+
containerDetails, err := cli.ContainerInspect(ctx, containerName)
2119+
require.NoError(t, err, "inspect container should not fail")
2120+
containerImage := containerDetails.Image
2121+
t.Cleanup(func() {
2122+
_, _ = cli.ImageRemove(ctx, containerImage, types.ImageRemoveOptions{
2123+
Force: true,
2124+
PruneChildren: true,
2125+
})
2126+
})
2127+
// Now, we terminate the container and check whether the image still exists.
2128+
err = c.Terminate(ctx)
2129+
require.NoError(t, err, "terminate container should not fail")
2130+
_, _, err = cli.ImageInspectWithRaw(ctx, containerImage)
2131+
if tt.keepBuiltImage {
2132+
assert.Nil(t, err, "image should still exist")
2133+
} else {
2134+
assert.NotNil(t, err, "image should not exist anymore")
2135+
}
2136+
})
2137+
}
2138+
}

docs/features/build_from_dockerfile.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,20 @@ req := ContainerRequest{
6666
},
6767
}
6868
```
69+
70+
## Keeping built images
71+
72+
Per default, built images are deleted after being used.
73+
However, some images you build might have no or only minor changes during development.
74+
Building them for each test run might take a lot of time.
75+
You can avoid this by setting `KeepImage` in `FromDockerfile`.
76+
If the image is being kept, cached layers might be reused during building or even the whole image.
77+
78+
```go
79+
req := ContainerRequest{
80+
FromDockerfile: testcontainers.FromDockerfile{
81+
// ...
82+
KeepImage: true,
83+
},
84+
}
85+
```

0 commit comments

Comments
 (0)