Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create networks with random names #1993

Merged
1 change: 1 addition & 0 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,7 @@ func daemonHost(ctx context.Context, p *DockerProvider) (string, error) {
return p.hostCache, nil
}

// Deprecated: use network.New instead
// CreateNetwork returns the object representing a new network identified by its name
func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) (Network, error) {
var err error
Expand Down
202 changes: 0 additions & 202 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
"github.com/testcontainers/testcontainers-go/wait"
)

Expand All @@ -45,86 +44,6 @@ func init() {
}
}

// testNetworkAliases {
func TestContainerAttachedToNewNetwork(t *testing.T) {
aliases := []string{"alias1", "alias2", "alias3"}
networkName := "new-network"
ctx := context.Background()
gcr := GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
ExposedPorts: []string{
nginxDefaultPort,
},
Networks: []string{
networkName,
},
NetworkAliases: map[string][]string{
networkName: aliases,
},
},
Started: true,
}

newNetwork, err := GenericNetwork(ctx, GenericNetworkRequest{
ProviderType: providerType,
NetworkRequest: NetworkRequest{
Name: networkName,
CheckDuplicate: true,
},
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
require.NoError(t, newNetwork.Remove(ctx))
})

nginx, err := GenericContainer(ctx, gcr)

require.NoError(t, err)
terminateContainerOnEnd(t, ctx, nginx)

networks, err := nginx.Networks(ctx)
if err != nil {
t.Fatal(err)
}
if len(networks) != 1 {
t.Errorf("Expected networks 1. Got '%d'.", len(networks))
}
network := networks[0]
if network != networkName {
t.Errorf("Expected network name '%s'. Got '%s'.", networkName, network)
}

networkAliases, err := nginx.NetworkAliases(ctx)
if err != nil {
t.Fatal(err)
}
if len(networkAliases) != 1 {
t.Errorf("Expected network aliases for 1 network. Got '%d'.", len(networkAliases))
}

networkAlias := networkAliases[networkName]

require.NotEmpty(t, networkAlias)

for _, alias := range aliases {
require.Contains(t, networkAlias, alias)
}

networkIP, err := nginx.ContainerIP(ctx)
if err != nil {
t.Fatal(err)
}
if len(networkIP) == 0 {
t.Errorf("Expected an IP address, got %v", networkIP)
}
}

// }

func TestContainerWithHostNetworkOptions(t *testing.T) {
if os.Getenv("XDG_RUNTIME_DIR") != "" {
t.Skip("Skipping test that requires host network access when running in a container")
Expand Down Expand Up @@ -594,54 +513,6 @@ func TestContainerCreation(t *testing.T) {
}
}

func TestContainerIPs(t *testing.T) {
ctx := context.Background()

networkName := "new-network"
newNetwork, err := GenericNetwork(ctx, GenericNetworkRequest{
ProviderType: providerType,
NetworkRequest: NetworkRequest{
Name: networkName,
CheckDuplicate: true,
},
})
if err != nil {
t.Fatal(err)
}

t.Cleanup(func() {
require.NoError(t, newNetwork.Remove(ctx))
})

nginxC, err := GenericContainer(ctx, GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxAlpineImage,
ExposedPorts: []string{
nginxDefaultPort,
},
Networks: []string{
"bridge",
networkName,
},
WaitingFor: wait.ForListeningPort(nginxDefaultPort),
},
Started: true,
})

require.NoError(t, err)
terminateContainerOnEnd(t, ctx, nginxC)

ips, err := nginxC.ContainerIPs(ctx)
if err != nil {
t.Fatal(err)
}

if len(ips) != 2 {
t.Errorf("Expected two IP addresses, got %v", len(ips))
}
}

func TestContainerCreationWithName(t *testing.T) {
ctx := context.Background()

Expand Down Expand Up @@ -1812,66 +1683,6 @@ func TestDockerContainerResources(t *testing.T) {
assert.Equal(t, expected, resp.HostConfig.Ulimits)
}

func TestContainerWithReaperNetwork(t *testing.T) {
if testcontainersdocker.IsWindows() {
t.Skip("Skip for Windows. See https://stackoverflow.com/questions/43784916/docker-for-windows-networking-container-with-multiple-network-interfaces")
}

ctx := context.Background()
networks := []string{
"test_network_" + randomString(),
"test_network_" + randomString(),
}

for _, nw := range networks {
nr := NetworkRequest{
Name: nw,
Attachable: true,
}
n, err := GenericNetwork(ctx, GenericNetworkRequest{
ProviderType: providerType,
NetworkRequest: nr,
})
assert.Nil(t, err)
// use t.Cleanup to run after terminateContainerOnEnd
t.Cleanup(func() {
err := n.Remove(ctx)
assert.NoError(t, err)
})
}

req := ContainerRequest{
Image: nginxAlpineImage,
ExposedPorts: []string{nginxDefaultPort},
WaitingFor: wait.ForAll(
wait.ForListeningPort(nginxDefaultPort),
wait.ForLog("Configuration complete; ready for start up"),
),
Networks: networks,
}

nginxC, err := GenericContainer(ctx, GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: req,
Started: true,
})

require.NoError(t, err)
terminateContainerOnEnd(t, ctx, nginxC)

containerId := nginxC.GetContainerID()

cli, err := NewDockerClientWithOpts(ctx)
assert.Nil(t, err)
defer cli.Close()

cnt, err := cli.ContainerInspect(ctx, containerId)
assert.Nil(t, err)
assert.Equal(t, 2, len(cnt.NetworkSettings.Networks))
assert.NotNil(t, cnt.NetworkSettings.Networks[networks[0]])
assert.NotNil(t, cnt.NetworkSettings.Networks[networks[1]])
}

func TestContainerCapAdd(t *testing.T) {
if providerType == ProviderPodman {
t.Skip("Rootless Podman does not support setting cap-add/cap-drop")
Expand Down Expand Up @@ -2105,19 +1916,6 @@ func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container)
})
}

func randomString() string {
rand.New(rand.NewSource(time.Now().UnixNano()))
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789")
length := 8
var b strings.Builder
for i := 0; i < length; i++ {
b.WriteRune(chars[rand.Intn(len(chars))])
}
return b.String()
}

func TestDockerProviderFindContainerByName(t *testing.T) {
ctx := context.Background()
provider, err := NewDockerProvider(WithLogger(TestLogger(t)))
Expand Down
15 changes: 13 additions & 2 deletions docs/features/common_functional_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,20 @@ You could use this feature to run a custom script, or to run a command that is n

- 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>

By default, the container is started in the default Docker network. If you want to use a different Docker network, you can use the `WithNetwork(networkName string, alias string)` option, which receives the new network name and an alias as parameters, creating the new network, attaching the container to it, and setting the network alias for that network.
By default, the container is started in the default Docker network. If you want to use an already existing Docker network you created in your code, you can use the `network.WithNetwork(alias string, nw *testcontainers.DockerNetwork)` option, which receives an alias as parameter and your network, attaching the container to it, and setting the network alias for that network.

If the network already exists, _Testcontainers for Go_ won't create a new one, but it will attach the container to it and set the network alias.
In the case you need to retrieve the network name, you can simply read it from the struct's `Name` field. E.g. `nw.Name`.

!!!warning
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.

#### WithNewNetwork

- 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>

If you want to attach your containers to a throw-away network, you can use the `network.WithNewNetwork(ctx context.Context, alias string, opts ...network.NetworkCustomizer)` option, which receives an alias as parameter, creating the new network with a random name, attaching the container to it, and setting the network alias for that network.

In the case you need to retrieve the network name, you can use the `Networks(ctx)` method of the `Container` interface, right after it's running, which returns a slice of strings with the names of the networks where the container is attached.

#### Docker type modifiers

Expand Down
31 changes: 24 additions & 7 deletions docs/features/creating_networks.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
# How to create a network

Apart from creating containers, `Testcontainers for Go` also allows you to create networks. This is useful when you need to connect multiple containers to the same network.
Apart from creating containers, `Testcontainers for Go` allows you to create networks. This is useful when you need to connect multiple containers to the same network.

## Usage example
- 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>

<!--codeinclude-->
[Creating a network](../../network_test.go) inside_block:createNetwork
<!--/codeinclude-->
For that, please import the `testcontainers/network` package.

```go
import "github.com/testcontainers/testcontainers-go/network"
```

Then, you can create a network using the `network.New` function. This function receives a variadic list of options that can be used to configure the network.

- `WithAttachable()`
- `WithCheckDuplicate()`
- `WithDriver(driver string)`
- `WithEnableIPv6()`
- `WithInternal()`
- `WithLabels(labels map[string]string)`
- `WithIPAMConfig(config *network.IPAMConfig)`

It's important to mention that the name of the network is automatically generated by the library, and it's not possible to set it manually. However, you can retrieve the name of the network using the `Name` field of the `DockerNetwork` struct returned by the `New` function.

## Usage example

<!--codeinclude-->
[Creating a network with IPAM](../../network_test.go) inside_block:withIPAM
<!--/codeinclude-->
[Creating a network](../../network/network_test.go) inside_block:createNetwork
[Creating a network with options](../../network/network_test.go) inside_block:newNetworkWithOptions
<!--/codeinclude-->
4 changes: 3 additions & 1 deletion docs/features/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Docker provides the ability for you to create custom networks and place containe
!!! tip
Note that _Testcontainers for Go_ allows a container to be on multiple networks including network aliases.

For more information about how to create networks using _Testcontainers for Go_, please refer to the [How to create a network](./creating_networks.md) section.

<!--codeinclude-->
[Creating custom networks](../../docker_test.go) inside_block:testNetworkAliases
[Creating custom networks](../../network/network_test.go) inside_block:testNetworkAliases
<!--/codeinclude-->
2 changes: 1 addition & 1 deletion examples/toxiproxy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.20

require (
github.com/Shopify/toxiproxy/v2 v2.7.0
github.com/docker/docker v24.0.7+incompatible
github.com/go-redis/redis/v8 v8.11.5
github.com/google/uuid v1.4.0
github.com/testcontainers/testcontainers-go v0.26.0
Expand All @@ -21,7 +22,6 @@ require (
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
Expand Down
23 changes: 12 additions & 11 deletions examples/toxiproxy/toxiproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,25 @@ import (
"github.com/go-redis/redis/v8"
"github.com/google/uuid"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
)

func TestToxiproxy(t *testing.T) {
ctx := context.Background()

newNetwork, err := testcontainers.GenericNetwork(ctx, testcontainers.GenericNetworkRequest{
ProviderType: testcontainers.ProviderDocker,
NetworkRequest: testcontainers.NetworkRequest{
Name: "newNetwork",
CheckDuplicate: true,
},
})
newNetwork, err := network.New(ctx, network.WithCheckDuplicate())
if err != nil {
t.Fatal(err)
}

toxiproxyContainer, err := startContainer(ctx, "newNetwork", []string{"toxiproxy"})
networkName := newNetwork.Name

toxiproxyContainer, err := startContainer(ctx, networkName, []string{"toxiproxy"})
if err != nil {
t.Fatal(err)
}

redisContainer, err := setupRedis(ctx, "newNetwork", []string{"redis"})
redisContainer, err := setupRedis(ctx, networkName, []string{"redis"})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -73,7 +69,12 @@ func TestToxiproxy(t *testing.T) {
t.Fatal(err)
}
redisClient := redis.NewClient(options)
defer flushRedis(ctx, *redisClient)
defer func() {
err := flushRedis(ctx, *redisClient)
if err != nil {
t.Fatal(err)
}
}()

// Set data
key := fmt.Sprintf("{user.%s}.favoritefood", uuid.NewString())
Expand Down
2 changes: 2 additions & 0 deletions generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ type GenericContainerRequest struct {
Reuse bool // reuse an existing container if it exists or create a new one. a container name mustn't be empty
}

// Deprecated: will be removed in the future.
// GenericNetworkRequest represents parameters to a generic network
type GenericNetworkRequest struct {
NetworkRequest // embedded request for provider
ProviderType ProviderType // which provider to use, Docker if empty
}

// Deprecated: use network.New instead
// GenericNetwork creates a generic network with parameters
func GenericNetwork(ctx context.Context, req GenericNetworkRequest) (Network, error) {
provider, err := req.ProviderType.GetProvider()
Expand Down