Skip to content

Commit a897bf5

Browse files
authored
feat: add more functional options for customising containers (#3156)
* feat: add new option for AlwaysPull image * feat: add new option for Lifecycle Hooks image * feat: add new option for log consumer config * feat: new option for attaching to networks by name * feat: add two more options (no-start and name) * feat: add one more option (image platform) * fix: update error * chore: separate bridge network in options
1 parent d025f0d commit a897bf5

File tree

6 files changed

+355
-5
lines changed

6 files changed

+355
-5
lines changed

docs/features/common_functional_options.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ func (g *TestLogConsumer) Accept(l Log) {
188188
}
189189
```
190190

191+
#### WithLogConsumerConfig
192+
193+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
194+
195+
If you need to set the log consumer config for the container, you can use `testcontainers.WithLogConsumerConfig`. This option completely replaces the existing log consumer config, including the log consumers and the log production options.
196+
191197
#### WithLogger
192198

193199
- Since testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go/releases/tag/v0.29.0"><span class="tc-version">:material-tag: v0.29.0</span></a>
@@ -214,6 +220,26 @@ func TestHandler(t *testing.T) {
214220

215221
Please read the [Following Container Logs](/features/follow_logs) documentation for more information about creating log consumers.
216222

223+
#### WithAlwaysPull
224+
225+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
226+
227+
If you need to pull the image before starting the container, you can use `testcontainers.WithAlwaysPull()`.
228+
229+
#### WithImagePlatform
230+
231+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
232+
233+
If you need to set the platform for a container, you can use `testcontainers.WithImagePlatform(platform string)`.
234+
235+
#### LifecycleHooks
236+
237+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
238+
239+
If you need to set the lifecycle hooks for the container, you can use `testcontainers.WithLifecycleHooks`, which replaces the existing lifecycle hooks with the new ones.
240+
241+
You can also use `testcontainers.WithAdditionalLifecycleHooks`, which appends the new lifecycle hooks to the existing ones.
242+
217243
#### Wait Strategies
218244

219245
If you need to set a different wait strategy for the container, you can use `testcontainers.WithWaitStrategy` with a valid wait strategy.
@@ -282,6 +308,24 @@ In the case you need to retrieve the network name, you can simply read it from t
282308
!!!warning
283309
This option is not checking whether the network exists or not. If you use a network that doesn't exist, the container will start in the default Docker network, as in the default behavior.
284310

311+
#### WithNetworkByName
312+
313+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
314+
315+
If you want to attach your containers to an already existing Docker network by its name, you can use the `network.WithNetworkName(aliases []string, networkName string)` option, which receives an alias as parameter and the network name, attaching the container to it, and setting the network alias for that network.
316+
317+
!!!warning
318+
In case the network name is `bridge`, no aliases are set. This is because network-scoped alias is supported only for containers in user defined networks.
319+
320+
#### WithBridgeNetwork
321+
322+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
323+
324+
If you want to attach your containers to the `bridge` network, you can use the `network.WithBridgeNetwork()` option.
325+
326+
!!!warning
327+
The `bridge` network is the default network for Docker. It's not a user defined network, so it doesn't support network-scoped aliases.
328+
285329
#### WithNewNetwork
286330

287331
- Since testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go/releases/tag/v0.27.0"><span class="tc-version">:material-tag: v0.27.0</span></a>
@@ -335,3 +379,27 @@ ctr, err := mymodule.Run(ctx, "docker.io/myservice:1.2.3",
335379

336380
!!!warning
337381
Reusing a container is experimental and the API is subject to change for a more robust implementation that is not based on container names.
382+
383+
#### WithName
384+
385+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
386+
387+
If you need to set the name of the container, you can use the `testcontainers.WithName` option.
388+
389+
```golang
390+
ctr, err := mymodule.Run(ctx, "docker.io/myservice:1.2.3",
391+
testcontainers.WithName("my-container-name"),
392+
)
393+
```
394+
395+
!!!warning
396+
This option is not checking whether the container name is already in use. If you use a name that is already in use, an error is returned.
397+
At the same time, we discourage using this option as it might lead to unexpected behavior, but we understand that in some cases it might be useful.
398+
399+
#### WithNoStart
400+
401+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
402+
403+
If you need to prevent the container from being started after creation, you can use the `testcontainers.WithNoStart` option.
404+
405+

docs/modules/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,12 @@ In order to simplify the creation of the container for a given module, `Testcont
204204
- `testcontainers.WithTmpfs`: a function that adds tmpfs mounts to the container.
205205
- `testcontainers.WithHostPortAccess`: a function that enables the container to access a port that is already running in the host.
206206
- `testcontainers.WithLogConsumers`: a function that sets the log consumers for the container request.
207+
- `testcontainers.WithLogConsumerConfig`: a function that sets the log consumer config for the container request.
207208
- `testcontainers.WithLogger`: a function that sets the logger for the container request.
209+
- `testcontainers.WithLifecycleHooks`: a function that sets the lifecycle hooks for the container request.
210+
- `testcontainers.WithAdditionalLifecycleHooks`: a function that appends lifecycle hooks to the existing ones for the container request.
211+
- `testcontainers.WithAlwaysPull`: a function that pulls the image before starting the container.
212+
- `testcontainers.WithImagePlatform`: a function that sets the image platform for the container request.
208213
- `testcontainers.WithWaitStrategy`: a function that sets the wait strategy for the container request.
209214
- `testcontainers.WithWaitStrategyAndDeadline`: a function that sets the wait strategy for the container request with a deadline.
210215
- `testcontainers.WithStartupCommand`: a function that sets the execution of a command when the container starts.
@@ -217,6 +222,12 @@ In order to simplify the creation of the container for a given module, `Testcont
217222
- `testcontainers.WithEndpointSettingsModifier`: a function that sets the endpoint settings Docker type for the container request. Please see [Advanced Settings](../features/creating_container.md#advanced-settings) for more information.
218223
- `testcontainers.CustomizeRequest`: a function that merges the default options with the ones provided by the user. Recommended for completely customizing the container request.
219224
- `testcontainers.WithReuseByName`: a function that marks a container to be reused if it exists or create a new one if it doesn't.
225+
- `testcontainers.WithName`: a function that sets the name of the container.
226+
- `testcontainers.WithNoStart`: a function that prevents the container from being started after creation, so it must be started manually.
227+
- `network.WithNetwork`: a function that sets the network and the network aliases for the container request, reusing an already existing network.
228+
- `network.WithNetworkName`: a function that sets the network aliases for an already existing network, by its name.
229+
- `network.WithBridgeNetwork`: a function that sets the container to be attached to the `bridge` network.
230+
- `network.WithNewNetwork`: a function that sets the network aliases for a throw-away network for the container request.
220231

221232
### Update Go dependencies in the modules
222233

network/network.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package network
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67

78
"github.com/docker/docker/api/types/network"
@@ -137,8 +138,18 @@ func WithIPAM(ipam *network.IPAM) CustomizeNetworkOption {
137138
// WithNetwork reuses an already existing network, attaching the container to it.
138139
// Finally it sets the network alias on that network to the given alias.
139140
func WithNetwork(aliases []string, nw *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption {
141+
return WithNetworkName(aliases, nw.Name)
142+
}
143+
144+
// WithNetworkName attachs a container to an already existing network, by its name.
145+
// If the network is not "bridge", it sets the network alias on that network
146+
// to the given alias, else, it returns an error. This is because network-scoped alias
147+
// is supported only for containers in user defined networks.
148+
func WithNetworkName(aliases []string, networkName string) testcontainers.CustomizeRequestOption {
140149
return func(req *testcontainers.GenericContainerRequest) error {
141-
networkName := nw.Name
150+
if networkName == "bridge" {
151+
return errors.New("network-scoped aliases are supported only for containers in user defined networks")
152+
}
142153

143154
// attaching to the network because it was created with success or it already existed.
144155
req.Networks = append(req.Networks, networkName)
@@ -152,6 +163,15 @@ func WithNetwork(aliases []string, nw *testcontainers.DockerNetwork) testcontain
152163
}
153164
}
154165

166+
// WithBridgeNetwork attachs a container to the "bridge" network.
167+
// There is no need to set the network alias, as it is not supported for the "bridge" network.
168+
func WithBridgeNetwork() testcontainers.CustomizeRequestOption {
169+
return func(req *testcontainers.GenericContainerRequest) error {
170+
req.Networks = append(req.Networks, "bridge")
171+
return nil
172+
}
173+
}
174+
155175
// WithNewNetwork creates a new network with random name and customizers, and attaches the container to it.
156176
// Finally it sets the network alias on that network to the given alias.
157177
func WithNewNetwork(ctx context.Context, aliases []string, opts ...NetworkCustomizer) testcontainers.CustomizeRequestOption {

network/network_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,44 @@ func TestWithNetwork(t *testing.T) {
342342
require.Equal(t, expectedLabels, newNetwork.Labels)
343343
}
344344

345+
func TestWithNetworkName(t *testing.T) {
346+
t.Run("bridge/success", func(t *testing.T) {
347+
req := testcontainers.GenericContainerRequest{
348+
ContainerRequest: testcontainers.ContainerRequest{},
349+
}
350+
351+
err := network.WithBridgeNetwork()(&req)
352+
require.NoError(t, err)
353+
354+
require.Len(t, req.Networks, 1)
355+
require.Equal(t, "bridge", req.Networks[0])
356+
})
357+
358+
t.Run("bridge/error/network-scoped-alias", func(t *testing.T) {
359+
req := testcontainers.GenericContainerRequest{
360+
ContainerRequest: testcontainers.ContainerRequest{},
361+
}
362+
363+
err := network.WithNetworkName([]string{"alias"}, "bridge")(&req)
364+
require.Error(t, err)
365+
})
366+
367+
t.Run("user-defined/success", func(t *testing.T) {
368+
req := testcontainers.GenericContainerRequest{
369+
ContainerRequest: testcontainers.ContainerRequest{},
370+
}
371+
372+
err := network.WithNetworkName([]string{"alias"}, "user-defined")(&req)
373+
require.NoError(t, err)
374+
375+
require.Len(t, req.Networks, 1)
376+
require.Equal(t, "user-defined", req.Networks[0])
377+
378+
require.Len(t, req.NetworkAliases, 1)
379+
require.Equal(t, map[string][]string{"user-defined": {"alias"}}, req.NetworkAliases)
380+
})
381+
}
382+
345383
func TestWithSyntheticNetwork(t *testing.T) {
346384
nw := &testcontainers.DockerNetwork{
347385
Name: "synthetic-network",

options.go

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,33 @@ func WithHostPortAccess(ports ...int) CustomizeRequestOption {
106106
}
107107
}
108108

109+
// WithName will set the name of the container.
110+
func WithName(containerName string) CustomizeRequestOption {
111+
return func(req *GenericContainerRequest) error {
112+
if containerName == "" {
113+
return errors.New("container name must be provided")
114+
}
115+
req.Name = containerName
116+
return nil
117+
}
118+
}
119+
120+
// WithNoStart will prevent the container from being started after creation.
121+
func WithNoStart() CustomizeRequestOption {
122+
return func(req *GenericContainerRequest) error {
123+
req.Started = false
124+
return nil
125+
}
126+
}
127+
109128
// WithReuseByName will mark a container to be reused if it exists or create a new one if it doesn't.
110129
// A container name must be provided to identify the container to be reused.
111130
func WithReuseByName(containerName string) CustomizeRequestOption {
112131
return func(req *GenericContainerRequest) error {
113-
if containerName == "" {
114-
return errors.New("container name must be provided for reuse")
132+
if err := WithName(containerName)(req); err != nil {
133+
return err
115134
}
116-
req.Name = containerName
135+
117136
req.Reuse = true
118137
return nil
119138
}
@@ -255,6 +274,17 @@ func WithLogConsumers(consumer ...LogConsumer) CustomizeRequestOption {
255274
}
256275
}
257276

277+
// WithLogConsumerConfig sets the log consumer config for a container.
278+
// Beware that this option completely replaces the existing log consumer config,
279+
// including the log consumers and the log production options,
280+
// so it should be used with care.
281+
func WithLogConsumerConfig(config *LogConsumerConfig) CustomizeRequestOption {
282+
return func(req *GenericContainerRequest) error {
283+
req.LogConsumerCfg = config
284+
return nil
285+
}
286+
}
287+
258288
// Executable represents an executable command to be sent to a container, including options,
259289
// as part of the different lifecycle hooks.
260290
type Executable interface {
@@ -376,6 +406,22 @@ func WithImageMount(source string, subpath string, target ContainerMountTarget)
376406
}
377407
}
378408

409+
// WithAlwaysPull will pull the image before starting the container
410+
func WithAlwaysPull() CustomizeRequestOption {
411+
return func(req *GenericContainerRequest) error {
412+
req.AlwaysPullImage = true
413+
return nil
414+
}
415+
}
416+
417+
// WithImagePlatform sets the platform for a container
418+
func WithImagePlatform(platform string) CustomizeRequestOption {
419+
return func(req *GenericContainerRequest) error {
420+
req.ImagePlatform = platform
421+
return nil
422+
}
423+
}
424+
379425
// WithEntrypoint completely replaces the entrypoint of a container
380426
func WithEntrypoint(entrypoint ...string) CustomizeRequestOption {
381427
return func(req *GenericContainerRequest) error {
@@ -429,6 +475,22 @@ func WithLabels(labels map[string]string) CustomizeRequestOption {
429475
}
430476
}
431477

478+
// WithLifecycleHooks completely replaces the lifecycle hooks for a container
479+
func WithLifecycleHooks(hooks ...ContainerLifecycleHooks) CustomizeRequestOption {
480+
return func(req *GenericContainerRequest) error {
481+
req.LifecycleHooks = hooks
482+
return nil
483+
}
484+
}
485+
486+
// WithAdditionalLifecycleHooks appends lifecycle hooks to the existing ones for a container
487+
func WithAdditionalLifecycleHooks(hooks ...ContainerLifecycleHooks) CustomizeRequestOption {
488+
return func(req *GenericContainerRequest) error {
489+
req.LifecycleHooks = append(req.LifecycleHooks, hooks...)
490+
return nil
491+
}
492+
}
493+
432494
// WithMounts appends the mounts to the mounts for a container
433495
func WithMounts(mounts ...ContainerMount) CustomizeRequestOption {
434496
return func(req *GenericContainerRequest) error {

0 commit comments

Comments
 (0)