Skip to content

Commit

Permalink
Merge branch 'main' into windows-workers
Browse files Browse the repository at this point in the history
* main:
  feat: add module to support InfluxDB v1.x (testcontainers#1703)
  feat: authenticate docker on PullImage (testcontainers#2446)
  feat: add distribution-registry module (testcontainers#2341)
  chore(deps): Bumping ChromaGo client version (testcontainers#2402)
  chore(deps): bump github.com/docker/docker from 25.0.3+incompatible to 25.0.5+incompatible (testcontainers#2444)
  feat: support passing io.Reader as ContainerFile (testcontainers#2401)
  • Loading branch information
mdelapenya committed Mar 25, 2024
2 parents ddb0d94 + 32f372c commit b3bd89f
Show file tree
Hide file tree
Showing 135 changed files with 2,387 additions and 232 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ src/pip-delete-this-directory.txt
.DS_Store

TEST-*.xml

**/go.work
8 changes: 8 additions & 0 deletions .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"name": "module / inbucket",
"path": "../modules/inbucket"
},
{
"name": "module / influxdb",
"path": "../modules/influxdb"
},
{
"name": "module / k3s",
"path": "../modules/k3s"
Expand Down Expand Up @@ -149,6 +153,10 @@
"name": "module / redpanda",
"path": "../modules/redpanda"
},
{
"name": "module / registry",
"path": "../modules/registry"
},
{
"name": "module / surrealdb",
"path": "../modules/surrealdb"
Expand Down
16 changes: 15 additions & 1 deletion container.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,25 @@ type FromDockerfile struct {
}

type ContainerFile struct {
HostFilePath string
HostFilePath string // If Reader is present, HostFilePath is ignored
Reader io.Reader // If Reader is present, HostFilePath is ignored
ContainerFilePath string
FileMode int64
}

// validate validates the ContainerFile
func (c *ContainerFile) validate() error {
if c.HostFilePath == "" && c.Reader == nil {
return errors.New("either HostFilePath or Reader must be specified")
}

if c.ContainerFilePath == "" {
return errors.New("ContainerFilePath must be specified")
}

return nil
}

// ContainerRequest represents the parameters used to get a running container
type ContainerRequest struct {
FromDockerfile
Expand Down
73 changes: 73 additions & 0 deletions container_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// This test is testing very internal logic that should not be exported away from this package. We'll
// leave it in the main testcontainers package. Do not use for user facing examples.
package testcontainers

import (
"errors"
"os"
"path/filepath"
"testing"
)

func TestContainerFileValidation(t *testing.T) {
type ContainerFileValidationTestCase struct {
Name string
ExpectedError error
File ContainerFile
}

f, err := os.Open(filepath.Join(".", "testdata", "hello.sh"))
if err != nil {
t.Fatal(err)
}

testTable := []ContainerFileValidationTestCase{
{
Name: "valid container file: has hostfilepath",
File: ContainerFile{
HostFilePath: "/path/to/host",
ContainerFilePath: "/path/to/container",
},
},
{
Name: "valid container file: has reader",
File: ContainerFile{
Reader: f,
ContainerFilePath: "/path/to/container",
},
},
{
Name: "invalid container file",
ExpectedError: errors.New("either HostFilePath or Reader must be specified"),
File: ContainerFile{
HostFilePath: "",
Reader: nil,
ContainerFilePath: "/path/to/container",
},
},
{
Name: "invalid container file",
ExpectedError: errors.New("ContainerFilePath must be specified"),
File: ContainerFile{
HostFilePath: "/path/to/host",
ContainerFilePath: "",
},
},
}

for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
err := testCase.File.validate()
switch {
case err == nil && testCase.ExpectedError == nil:
return
case err == nil && testCase.ExpectedError != nil:
t.Errorf("did not receive expected error: %s", testCase.ExpectedError.Error())
case err != nil && testCase.ExpectedError == nil:
t.Errorf("received unexpected error: %s", err.Error())
case err.Error() != testCase.ExpectedError.Error():
t.Errorf("errors mismatch: %s != %s", err.Error(), testCase.ExpectedError.Error())
}
})
}
}
32 changes: 14 additions & 18 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,20 +1027,6 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
pullOpt := types.ImagePullOptions{
Platform: req.ImagePlatform, // may be empty
}

registry, imageAuth, err := DockerImageAuth(ctx, imageName)
if err != nil {
p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is:%s", registry, imageName, err)
} else {
// see https://github.com/docker/docs/blob/e8e1204f914767128814dca0ea008644709c117f/engine/api/sdk/examples.md?plain=1#L649-L657
encodedJSON, err := json.Marshal(imageAuth)
if err != nil {
p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is:%s", imageName, err)
} else {
pullOpt.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
}
}

if err := p.attemptToPullImage(ctx, imageName, pullOpt); err != nil {
return nil, err
}
Expand Down Expand Up @@ -1245,10 +1231,20 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
// attemptToPullImage tries to pull the image while respecting the ctx cancellations.
// Besides, if the image cannot be pulled due to ErrorNotFound then no need to retry but terminate immediately.
func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pullOpt types.ImagePullOptions) error {
var (
err error
pull io.ReadCloser
)
registry, imageAuth, err := DockerImageAuth(ctx, tag)
if err != nil {
p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is:%s", registry, tag, err)
} else {
// see https://github.com/docker/docs/blob/e8e1204f914767128814dca0ea008644709c117f/engine/api/sdk/examples.md?plain=1#L649-L657
encodedJSON, err := json.Marshal(imageAuth)
if err != nil {
p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is:%s", tag, err)
} else {
pullOpt.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
}
}

var pull io.ReadCloser
err = backoff.Retry(func() error {
pull, err = p.client.ImagePull(ctx, tag, pullOpt)
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion docker_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testcontainers_test

import (
"context"
"os"
"path/filepath"
"testing"
"time"
Expand All @@ -22,12 +23,18 @@ func TestCopyFileToContainer(t *testing.T) {
t.Fatal(err)
}

r, err := os.Open(absPath)
if err != nil {
t.Fatal(err)
}

container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: "docker.io/bash",
Files: []testcontainers.ContainerFile{
{
HostFilePath: absPath,
Reader: r,
HostFilePath: absPath, // will be discarded internally
ContainerFilePath: "/hello.sh",
FileMode: 0o700,
},
Expand Down
10 changes: 10 additions & 0 deletions docs/features/files_and_mounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ If you would like to copy a file to a container, you can do it in two different
[Copying a list of files](../../docker_files_test.go) inside_block:copyFileOnCreate
<!--/codeinclude-->

The `ContainerFile` struct will accept the following fields:

- `HostFilePath`: the path to the file in the host machine. Optional (see below).
- `Reader`: a `io.Reader` that will be used to copy the file to the container. Optional.
- `ContainerFilePath`: the path to the file in the container. Mandatory.
- `Mode`: the file mode, which is optional.

!!!info
If the `Reader` field is set, the `HostFilePath` field will be ignored.

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

<!--codeinclude-->
Expand Down
13 changes: 10 additions & 3 deletions docs/modules/chroma.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Since testcontainers-go <a href="https://github.com/testcontainers/testcontainer

The Testcontainers module for Chroma.

## Resources

- [Chroma Docs](https://docs.trychroma.com/getting-started) - Chroma official documentation.
- [Chroma Cookbook](http://cookbook.chromadb.dev) - Community-driven Chroma cookbook.

## Adding this module to your project dependencies

Please run the following command to add the Chroma module to your Go dependencies:
Expand Down Expand Up @@ -38,7 +43,7 @@ When starting the Chroma container, you can pass options in a variadic way to co
#### Image

If you need to set a different Chroma Docker image, you can use `testcontainers.WithImage` with a valid Docker image
for Chroma. E.g. `testcontainers.WithImage("chromadb/chroma:0.4.22.dev44")`.
for Chroma. E.g. `testcontainers.WithImage("chromadb/chroma:0.4.24")`.

{% include "../features/common_functional_options.md" %}

Expand All @@ -65,20 +70,22 @@ First of all, you need to import the Chroma module and the Swagger client:
```golang
import (
chromago "github.com/amikos-tech/chroma-go"
chromaopenapi "github.com/amikos-tech/chroma-go/swagger"
"github.com/amikos-tech/chroma-go/types"
)
```

Then, you can create a Chroma client using the Chroma module:

<!--codeinclude-->
[Get the client](../../modules/chroma/examples_test.go) inside_block:createClient
[Get the client](../../modules/chroma/examples_test.go) inside_block:getClient
<!--/codeinclude-->

### Working with Collections

<!--codeinclude-->
[Create Collection](../../modules/chroma/examples_test.go) inside_block:createCollection
[List Collections](../../modules/chroma/examples_test.go) inside_block:listCollections
[Add Data to Collection](../../modules/chroma/examples_test.go) inside_block:addData
[Query Collection](../../modules/chroma/examples_test.go) inside_block:queryCollection
[Delete Collection](../../modules/chroma/examples_test.go) inside_block:deleteCollection
<!--/codeinclude-->
88 changes: 88 additions & 0 deletions docs/modules/influxdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# InfluxDB

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>

## Introduction

A testcontainers module for InfluxDB. This module supports v1.x of InfluxDB.

## Adding this module to your project dependencies

Please run the following command to add the InfluxDB module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/influxdb
```

## Usage example

<!--codeinclude-->
[Creating an InfluxDB container](../../modules/influxdb/examples_test.go) inside_block:runInfluxContainer
<!--/codeinclude-->

## Module Reference

The InfluxDB module exposes one entrypoint function to create the container, and this function receives two parameters:

```golang
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*InfluxDbContainer, error) {}
```

- `context.Context`, the Go context.
- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.

### Container Options

When starting the container, you can pass options in a variadic way to configure it.

!!!tip

You can find configuration information for the InfluxDB image on [Docker Hub](https://hub.docker.com/_/influxdb) and a list of possible
environment variables on [InfluxDB documentation](https://docs.influxdata.com/influxdb/v1/administration/config/).

#### Image

To use a different Docker image, you can use the `testcontainers.WithImage` option to specify the
image, E.g. `testcontainers.WithImage("influxdb:1.8.0")`. By default, the 1.8.10 image is used. Note that
`influxdb:latest` will get you a version 2 image which is not supported by this module.


{% include "../features/common_functional_options.md" %}

#### Set username, password and database name

By default, authentication is disabled and no credentials are needed to use the Influx API against the test container.
If you want to test with credentials, include the appropriate environment variables to do so.

#### Init Scripts

While the InfluxDB image will obey the `/docker-entrypoint-initdb.d` directory as is common, that directory does not
exist in the default image. Instead, you can use the `WithInitDb` option to pass a directory which will be copied to
when the container starts. Any `*.sh` or `*.iql` files in the directory will be processed by the image upon startup.
When executing these scripts, the `init-influxdb.sh` script in the image will start the InfluxDB server, run the
scripts, stop the server, and restart the server. This makes it tricky to detect the readiness of the container.
This module looks for that and adds some extra tests for readiness, but these could be fragile.

!!!important
The `WithInitDb` option receives a path to the parent directory of one named `docker-entrypoint-initdb.d`. This is
because the `docker-entrypoint-initdb.d` directory is not present in the image.

#### Custom configuration

If you need to set a custom configuration, you can use `WithConfigFile` option to pass the path to a custom configuration file.

### Container Methods

#### ConnectionUrl

This function is a simple helper to return a URL to the container, using the default `8086` port.

<!--codeinclude-->
[ConnectionUrl](../../modules/influxdb/influxdb_test.go) inside_block:influxConnectionUrl
<!--/codeinclude-->

Please check the existence of two methods: `ConnectionUrl` and `MustConnectionUrl`. The latter is used to avoid the need to handle errors,
while the former is used to return the URL and the error. `MustConnectionUrl` will panic if an error occurs.

!!!info
The `ConnectionUrl` and `MustConnectionUrl` methods only support HTTP connections at the moment.
Loading

0 comments on commit b3bd89f

Please sign in to comment.