Skip to content

Commit

Permalink
Merge branch 'main' into consul
Browse files Browse the repository at this point in the history
* main:
  feat (postgres): support for creating and restoring Snapshots (testcontainers#2199)
  fix: apply volume options only to volumes (testcontainers#2201)
  redpanda/test: add admin client call (testcontainers#2200)
  chore(deps): bump cloud.google.com/go/spanner from 1.55.0 to 1.56.0 in /modules/gcloud, cloud.google.com/go/pubsub from 1.35.0 to 1.36.1 in /modules/gcloud, cloud.google.com/go/bigquery from 1.57.1 to 1.58.0 in /modules/gcloud (testcontainers#2197)
  chore(deps): bump github.com/docker/docker from 25.0.1+incompatible to 25.0.2+incompatible (testcontainers#2196)
  fix: go doc reference broken image (testcontainers#2195)
  Add Support for WASM Transforms to Redpanda Module (testcontainers#2170)
  • Loading branch information
mdelapenya committed Feb 14, 2024
2 parents 3af5364 + 48fc228 commit a840894
Show file tree
Hide file tree
Showing 82 changed files with 899 additions and 227 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

**Documentation**

[![GoDoc Reference](https://camo.githubusercontent.com/8609cfcb531fa0f5598a3d4353596fae9336cce3/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f79616e6777656e6d61692f686f772d746f2d6164642d62616467652d696e2d6769746875622d726561646d653f7374617475732e737667)](https://pkg.go.dev/github.com/testcontainers/testcontainers-go)
[![GoDoc Reference](https://pkg.go.dev/badge/github.com/testcontainers/testcontainers-go.svg)](https://pkg.go.dev/github.com/testcontainers/testcontainers-go)

**Social**

Expand Down
213 changes: 213 additions & 0 deletions docker_files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package testcontainers

import (
"context"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/require"

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

func TestCopyFileToContainer(t *testing.T) {
ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second)
defer cnl()

// copyFileOnCreate {
absPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh"))
if err != nil {
t.Fatal(err)
}

container, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "docker.io/bash",
Files: []ContainerFile{
{
HostFilePath: absPath,
ContainerFilePath: "/hello.sh",
FileMode: 0o700,
},
},
Cmd: []string{"bash", "/hello.sh"},
WaitingFor: wait.ForLog("done"),
},
Started: true,
})
// }

require.NoError(t, err)
require.NoError(t, container.Terminate(ctx))
}

func TestCopyFileToRunningContainer(t *testing.T) {
ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second)
defer cnl()

// Not using the assertations here to avoid leaking the library into the example
// copyFileAfterCreate {
waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh"))
if err != nil {
t.Fatal(err)
}
helloPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh"))
if err != nil {
t.Fatal(err)
}

container, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "docker.io/bash:5.2.26",
Files: []ContainerFile{
{
HostFilePath: waitForPath,
ContainerFilePath: "/waitForHello.sh",
FileMode: 0o700,
},
},
Cmd: []string{"bash", "/waitForHello.sh"},
},
Started: true,
})
if err != nil {
t.Fatal(err)
}

err = container.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700)
// }

require.NoError(t, err)

// Give some time to the wait script to catch the hello script being created
err = wait.ForLog("done").WithStartupTimeout(200*time.Millisecond).WaitUntilReady(ctx, container)
require.NoError(t, err)

require.NoError(t, container.Terminate(ctx))
}

func TestCopyDirectoryToContainer(t *testing.T) {
ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second)
defer cnl()

// Not using the assertations here to avoid leaking the library into the example
// copyDirectoryToContainer {
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
t.Fatal(err)
}

container, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "docker.io/bash",
Files: []ContainerFile{
{
HostFilePath: dataDirectory,
// ContainerFile cannot create the parent directory, so we copy the scripts
// to the root of the container instead. Make sure to create the container directory
// before you copy a host directory on create.
ContainerFilePath: "/",
FileMode: 0o700,
},
},
Cmd: []string{"bash", "/testdata/hello.sh"},
WaitingFor: wait.ForLog("done"),
},
Started: true,
})
// }

require.NoError(t, err)
require.NoError(t, container.Terminate(ctx))
}

func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) {
ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second)
defer cnl()

// copyDirectoryToRunningContainerAsFile {
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
t.Fatal(err)
}
waitForPath, err := filepath.Abs(filepath.Join(dataDirectory, "waitForHello.sh"))
if err != nil {
t.Fatal(err)
}

container, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "docker.io/bash",
Files: []ContainerFile{
{
HostFilePath: waitForPath,
ContainerFilePath: "/waitForHello.sh",
FileMode: 0o700,
},
},
Cmd: []string{"bash", "/waitForHello.sh"},
},
Started: true,
})
require.NoError(t, err)

// as the container is started, we can create the directory first
_, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
require.NoError(t, err)

// because the container path is a directory, it will use the copy dir method as fallback
err = container.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700)
if err != nil {
t.Fatal(err)
}
// }

require.NoError(t, err)
require.NoError(t, container.Terminate(ctx))
}

func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) {
ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second)
defer cnl()

// Not using the assertations here to avoid leaking the library into the example
// copyDirectoryToRunningContainerAsDir {
waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh"))
if err != nil {
t.Fatal(err)
}
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
t.Fatal(err)
}

container, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "docker.io/bash",
Files: []ContainerFile{
{
HostFilePath: waitForPath,
ContainerFilePath: "/waitForHello.sh",
FileMode: 0o700,
},
},
Cmd: []string{"bash", "/waitForHello.sh"},
},
Started: true,
})
require.NoError(t, err)

// as the container is started, we can create the directory first
_, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
require.NoError(t, err)

err = container.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700)
if err != nil {
t.Fatal(err)
}
// }

require.NoError(t, err)
require.NoError(t, container.Terminate(ctx))
}
14 changes: 11 additions & 3 deletions docker_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,6 @@ func mapToDockerMounts(containerMounts ContainerMounts) []mount.Mount {
Source: m.Source.Source(),
ReadOnly: m.ReadOnly,
Target: m.Target.Target(),
VolumeOptions: &mount.VolumeOptions{
Labels: GenericLabels(),
},
}

switch typedMounter := m.Source.(type) {
Expand All @@ -117,6 +114,17 @@ func mapToDockerMounts(containerMounts ContainerMounts) []mount.Mount {
// The provided source type has no custom options
}

if mountType == mount.TypeVolume {
if containerMount.VolumeOptions == nil {
containerMount.VolumeOptions = &mount.VolumeOptions{
Labels: make(map[string]string),
}
}
for k, v := range GenericLabels() {
containerMount.VolumeOptions.Labels[k] = v
}
}

mounts = append(mounts, containerMount)
}

Expand Down
20 changes: 20 additions & 0 deletions docs/features/common_functional_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,23 @@ If you need an advanced configuration for the container, you can leverage the fo
- `testcontainers.WithEndpointSettingsModifier`

Please read the [Create containers: Advanced Settings](/features/creating_container.md#advanced-settings) documentation for more information.

#### Customising the ContainerRequest

This option will merge the customized request into the module's own `ContainerRequest`.

```go
container, err := RunContainer(ctx,
/* Other module options */
testcontainers.CustomizeRequest(testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Cmd: []string{"-c", "log_statement=all"},
},
}),
)
```

The above example is updating the predefined command of the image, **appending** them to the module's command.

!!!info
This can't be used to replace the command, only to append options.
76 changes: 22 additions & 54 deletions docs/features/files_and_mounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,26 @@ It is possible to map a Docker volume into the container using the `Mounts` attr
!!!warning
Bind mounts are not supported, as it could not work with remote Docker hosts.

!!!tip
It is recommended to copy data from your local host machine to a test container using the file copy API
described below, as it is much more portable.

## Copying files to a container

If you would like to copy a file to a container, you can do it in two different manners:

1. Adding a list of files in the `ContainerRequest`, which will be copied before the container starts:

```go
ctx := context.Background()

nginxC, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
Files: []ContainerFile{
{
HostFilePath: "./testdata/hello.sh",
ContainerFilePath: "/copies-hello.sh",
FileMode: 0o700,
},
},
},
Started: false,
})
```
<!--codeinclude-->
[Copying a list of files](../../docker_files_test.go) inside_block:copyFileOnCreate
<!--/codeinclude-->

2. Using the `CopyFileToContainer` method on a `running` container:

```go
ctx := context.Background()

nginxC, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
},
Started: true,
})

nginxC.CopyFileToContainer(ctx, "./testdata/hello.sh", "/hello_copy.sh", 0o700)
```
<!--codeinclude-->
[Copying files to a running container](../../docker_files_test.go) inside_block:copyFileAfterCreate
[Wait for hello](../../testdata/waitForHello.sh)
<!--/codeinclude-->

## Copying directories to a container

Expand All @@ -67,30 +45,20 @@ It's important to notice that, when copying the directory to the container, the

You can leverage the very same mechanism used for copying files to a container, but for directories.:

1. The first way is using the `Files` field in the `ContainerRequest` struct, as shown in the previous section, but using the path of a directory as `HostFilePath`.
1. The first way is using the `Files` field in the `ContainerRequest` struct, as shown in the previous section, but using the path of a directory as `HostFilePath`. Like so:

<!--codeinclude-->
[Copying a directory using files](../../docker_files_test.go) inside_block:copyDirectoryToContainer
<!--/codeinclude-->

2. The second way uses the existing `CopyFileToContainer` method, which will internally check if the host path is a directory, calling the `CopyDirToContainer` method if needed:

```go
ctx := context.Background()
// as the container is started, we can create the directory first
_, _, err = myContainer.Exec(ctx, []string{"mkdir", "-p", "/usr/lib/my-software/config"})
// because the container path is a directory, it will use the copy dir method as fallback
err = myContainer.CopyFileToContainer(ctx, "./files", "/usr/lib/my-software/config/files", 0o700)
if err != nil {
// handle error
}
```
<!--codeinclude-->
[Copying a directory to a running container](../../docker_files_test.go) inside_block:copyDirectoryToRunningContainerAsFile
<!--/codeinclude-->

3. The last third way uses the `CopyDirToContainer` method, directly, which, as you probably know, needs the existence of the parent directory in order to copy the directory:

```go
ctx := context.Background()

// as the container is started, we can create the directory first
_, _, err = nginxC.Exec(ctx, []string{"mkdir", "-p", "/usr/lib/my-software/config"})
err = nginxC.CopyDirToContainer(ctx, "./plugins", "/usr/lib/my-software/config/plugins", 0o700)
if err != nil {
// handle error
}
```
<!--codeinclude-->
[Copying a directory to a running container](../../docker_files_test.go) inside_block:copyDirectoryToRunningContainerAsDir
<!--/codeinclude-->
7 changes: 7 additions & 0 deletions docs/features/override_container_command.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ req := ContainerRequest{
}
```

!!!info
If you are using a module, you can use the `testcontainers.CustomizeRequest` option to add arguments to the command. Check the individual module's pages for more information on their commands.

This option will merge the customized request into the module's request, appending any additional `Cmd` arguments to the
module's command. This can't be used to replace the command, only to append options.
Check the individual module's pages for more information on their commands.

## Executing a command

You can execute a command inside a running container, similar to a `docker exec` call:
Expand Down
Loading

0 comments on commit a840894

Please sign in to comment.