diff --git a/docker.go b/docker.go index ec703ac913..c946103556 100644 --- a/docker.go +++ b/docker.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "io/fs" "net/url" "os" "path/filepath" @@ -602,19 +603,41 @@ func (c *DockerContainer) CopyFileToContainer(ctx context.Context, hostFilePath return c.CopyDirToContainer(ctx, hostFilePath, containerFilePath, fileMode) } - fileContent, err := os.ReadFile(hostFilePath) + f, err := os.Open(hostFilePath) if err != nil { return err } - return c.CopyToContainer(ctx, fileContent, containerFilePath, fileMode) + defer f.Close() + + info, err := f.Stat() + if err != nil { + return err + } + + // In Go 1.22 os.File is always an io.WriterTo. However, testcontainers + // currently allows Go 1.21, so we need to trick the compiler a little. + var file fs.File = f + return c.copyToContainer(ctx, func(tw io.Writer) error { + // Attempt optimized writeTo, implemented in linux + if wt, ok := file.(io.WriterTo); ok { + _, err := wt.WriteTo(tw) + return err + } + _, err := io.Copy(tw, f) + return err + }, info.Size(), containerFilePath, fileMode) } // CopyToContainer copies fileContent data to a file in container func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byte, containerFilePath string, fileMode int64) error { - buffer, err := tarFile(fileContent, containerFilePath, fileMode) - if err != nil { + return c.copyToContainer(ctx, func(tw io.Writer) error { + _, err := tw.Write(fileContent) return err - } + }, int64(len(fileContent)), containerFilePath, fileMode) +} + +func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func(tw io.Writer) error, fileContentSize int64, containerFilePath string, fileMode int64) error { + buffer, err := tarFile(containerFilePath, fileContent, fileContentSize, fileMode) err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{}) if err != nil { @@ -1574,7 +1597,8 @@ func (p *DockerProvider) SaveImages(ctx context.Context, output string, images . _ = imageReader.Close() }() - _, err = io.Copy(outputFile, imageReader) + // Attempt optimized readFrom, implemented in linux + _, err = outputFile.ReadFrom(imageReader) if err != nil { return fmt.Errorf("writing images to output %w", err) } diff --git a/docker_exec_test.go b/docker_exec_test.go index 7e1cd4bc2f..4716c493ab 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -1,44 +1,16 @@ package testcontainers import ( + "bytes" "context" "io" - "strings" "testing" + "github.com/docker/docker/pkg/stdcopy" "github.com/stretchr/testify/require" - tcexec "github.com/testcontainers/testcontainers-go/exec" ) -func TestExecWithMultiplexedResponse(t *testing.T) { - ctx := context.Background() - req := ContainerRequest{ - Image: nginxAlpineImage, - } - - container, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, - ContainerRequest: req, - Started: true, - }) - - require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - - code, reader, err := container.Exec(ctx, []string{"ls", "/usr/share/nginx"}, tcexec.Multiplexed()) - require.NoError(t, err) - require.Zero(t, code) - require.NotNil(t, reader) - - b, err := io.ReadAll(reader) - require.NoError(t, err) - require.NotNil(t, b) - - str := string(b) - require.Equal(t, "html\n", str) -} - func TestExecWithOptions(t *testing.T) { tests := []struct { name string @@ -79,7 +51,6 @@ func TestExecWithOptions(t *testing.T) { } container, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, ContainerRequest: req, Started: true, }) @@ -106,14 +77,13 @@ func TestExecWithOptions(t *testing.T) { } } -func TestExecWithMultiplexedStderrResponse(t *testing.T) { +func TestExecWithMultiplexedResponse(t *testing.T) { ctx := context.Background() req := ContainerRequest{ Image: nginxAlpineImage, } container, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, ContainerRequest: req, Started: true, }) @@ -121,9 +91,9 @@ func TestExecWithMultiplexedStderrResponse(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"ls", "/non-existing-directory"}, tcexec.Multiplexed()) + code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}, tcexec.Multiplexed()) require.NoError(t, err) - require.NotZero(t, code) + require.Zero(t, code) require.NotNil(t, reader) b, err := io.ReadAll(reader) @@ -131,7 +101,8 @@ func TestExecWithMultiplexedStderrResponse(t *testing.T) { require.NotNil(t, b) str := string(b) - require.Contains(t, str, "No such file or directory") + require.Contains(t, str, "stdout") + require.Contains(t, str, "stderr") } func TestExecWithNonMultiplexedResponse(t *testing.T) { @@ -141,7 +112,6 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { } container, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, ContainerRequest: req, Started: true, }) @@ -149,15 +119,20 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"ls", "/usr/share/nginx"}) + code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) - b, err := io.ReadAll(reader) + var stdout bytes.Buffer + var stderr bytes.Buffer + + written, err := stdcopy.StdCopy(&stdout, &stderr, reader) require.NoError(t, err) - require.NotNil(t, b) + require.NotZero(t, written) + require.NotNil(t, stdout) + require.NotNil(t, stderr) - str := string(b) - require.True(t, strings.HasSuffix(str, "html\n")) + require.Equal(t, stdout.String(), "stdout\n") + require.Equal(t, stderr.String(), "stderr\n") } diff --git a/docker_test.go b/docker_test.go index 45e276b1ed..bc29e854d0 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1854,7 +1854,7 @@ func TestContainerWithNoUserID(t *testing.T) { } func TestGetGatewayIP(t *testing.T) { - // When using docker-compose with DinD mode, and using host port or http wait strategy + // When using docker compose with DinD mode, and using host port or http wait strategy // It's need to invoke GetGatewayIP for get the host provider, err := providerType.GetProvider(WithLogger(TestLogger(t))) if err != nil { diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 288093941c..1572bb461b 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -22,7 +22,7 @@ Using the `WithImageSubstitutors` options, you could define your own substitutio If you need to either pass additional environment variables to a container or override them, you can use `testcontainers.WithEnv` for example: ```golang -postgres, err = postgresModule.RunContainer(ctx, testcontainers.WithEnv(map[string]string{"POSTGRES_INITDB_ARGS", "--no-sync"})) +postgres, err = postgresModule.RunContainer(ctx, testcontainers.WithEnv(map[string]string{"POSTGRES_INITDB_ARGS": "--no-sync"})) ``` #### WithLogConsumers diff --git a/docs/features/docker_compose.md b/docs/features/docker_compose.md index ea2a56bc19..352f2c84eb 100644 --- a/docs/features/docker_compose.md +++ b/docs/features/docker_compose.md @@ -7,7 +7,7 @@ This is intended to be useful on projects where Docker Compose is already used in dev or other environments to define services that an application may be dependent upon. -## Using `docker-compose` directly +## Using `docker compose` directly !!!warning The minimal version of Go required to use this module is **1.21**. @@ -16,9 +16,9 @@ dependent upon. go get github.com/testcontainers/testcontainers-go/modules/compose ``` -Because `docker-compose` v2 is implemented in Go it's possible for _Testcontainers for Go_ to +Because `compose` v2 is implemented in Go it's possible for _Testcontainers for Go_ to use [`github.com/docker/compose`](https://github.com/docker/compose) directly and skip any process execution/_docker-compose-in-a-container_ scenario. -The `ComposeStack` API exposes this variant of using `docker-compose` in an easy way. +The `ComposeStack` API exposes this variant of using `docker compose` in an easy way. ### Basic examples @@ -88,14 +88,14 @@ func TestSomethingElse(t *testing.T) { To interact with service containers after a stack was started it is possible to get an `*tc.DockerContainer` instance via the `ServiceContainer(...)` function. The function takes a **service name** (and a `context.Context`) and returns either a `*tc.DockerContainer` or an `error`. -This is different to the previous `LocalDockerCompose` API where service containers were accessed via their **container name** e.g. `mysql_1` or `mysql-1` (depending on the version of `docker-compose`). +This is different to the previous `LocalDockerCompose` API where service containers were accessed via their **container name** e.g. `mysql_1` or `mysql-1` (depending on the version of `docker compose`). Furthermore, there's the convenience function `Serices()` to get a list of all services **defined** by the current project. Note that not all of them need necessarily be correctly started as the information is based on the given compose files. ### Wait strategies -Just like with regular test containers you can also apply wait strategies to `docker-compose` services. +Just like with regular test containers you can also apply wait strategies to `docker compose` services. The `ComposeStack.WaitForService(...)` function allows you to apply a wait strategy to **a service by name**. All wait strategies are executed in parallel to both improve startup performance by not blocking too long and to fail early if something's wrong. @@ -139,7 +139,7 @@ func TestSomethingWithWaiting(t *testing.T) { ### Compose environment -`docker-compose` supports expansion based on environment variables. +`docker compose` supports expansion based on environment variables. The `ComposeStack` supports this as well in two different variants: - `ComposeStack.WithEnv(m map[string]string) ComposeStack` to parameterize stacks from your test code @@ -150,14 +150,14 @@ The `ComposeStack` supports this as well in two different variants: Also have a look at [ComposeStack](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#ComposeStack) docs for further information. -## Usage of `docker-compose` binary +## Usage of the `docker compose` binary -_Node:_ this API is deprecated and superseded by `ComposeStack` which takes advantage of `docker-compose` v2 being +_Node:_ this API is deprecated and superseded by `ComposeStack` which takes advantage of `compose` v2 being implemented in Go as well by directly using the upstream project. You can override Testcontainers' default behaviour and make it use a -docker-compose binary installed on the local machine. This will generally yield -an experience that is closer to running docker-compose locally, with the caveat +docker compose binary installed on the local machine. This will generally yield +an experience that is closer to running docker compose locally, with the caveat that Docker Compose needs to be present on dev and CI machines. ### Examples diff --git a/exec/processor.go b/exec/processor.go index c4d40f1e73..c4f9e1b100 100644 --- a/exec/processor.go +++ b/exec/processor.go @@ -82,10 +82,6 @@ func Multiplexed() ProcessOption { <-done - if errBuff.Bytes() != nil { - opts.Reader = &errBuff - } else { - opts.Reader = &outBuff - } + opts.Reader = io.MultiReader(&outBuff, &errBuff) }) } diff --git a/file.go b/file.go index 4c1f3ee32b..a6743cc9e4 100644 --- a/file.go +++ b/file.go @@ -110,7 +110,7 @@ func tarDir(src string, fileMode int64) (*bytes.Buffer, error) { } // tarFile compress a single file using tar + gzip algorithms -func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer, error) { +func tarFile(basePath string, fileContent func(tw io.Writer) error, fileContentSize int64, fileMode int64) (*bytes.Buffer, error) { buffer := &bytes.Buffer{} zr := gzip.NewWriter(buffer) @@ -119,12 +119,12 @@ func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer hdr := &tar.Header{ Name: basePath, Mode: fileMode, - Size: int64(len(fileContent)), + Size: fileContentSize, } if err := tw.WriteHeader(hdr); err != nil { return buffer, err } - if _, err := tw.Write(fileContent); err != nil { + if err := fileContent(tw); err != nil { return buffer, err } diff --git a/file_test.go b/file_test.go index c1fc9f0704..e5e660dee7 100644 --- a/file_test.go +++ b/file_test.go @@ -119,7 +119,10 @@ func Test_TarFile(t *testing.T) { t.Fatal(err) } - buff, err := tarFile(b, "Docker.file", 0o755) + buff, err := tarFile("Docker.file", func(tw io.Writer) error { + _, err := tw.Write(b) + return err + }, int64(len(b)), 0o755) if err != nil { t.Fatal(err) } diff --git a/modules/compose/compose.go b/modules/compose/compose.go index 75bf55e0aa..242caef501 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -155,11 +155,12 @@ func NewLocalDockerCompose(filePaths []string, identifier string, opts ...LocalD opts[idx].ApplyToLocalCompose(dc.LocalDockerComposeOptions) } - dc.Executable = "docker-compose" + dc.Executable = "docker" if runtime.GOOS == "windows" { - dc.Executable = "docker-compose.exe" + dc.Executable = "docker.exe" } + dc.composeSubcommand = "compose" dc.ComposeFilePaths = filePaths dc.absComposeFilePaths = make([]string, len(filePaths)) diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index b0ec87ed29..409875537c 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -34,7 +34,7 @@ func (f stackDownOptionFunc) applyToStackDown(do *api.DownOptions) { f(do) } -// RunServices is comparable to 'docker-compose run' as it only creates a subset of containers +// RunServices is comparable to 'docker compose run' as it only creates a subset of containers // instead of all services defined by the project func RunServices(serviceNames ...string) StackUpOption { return stackUpOptionFunc(func(o *stackUpOptions) { @@ -231,7 +231,6 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { Wait: upOptions.Wait, }, }) - if err != nil { return err } diff --git a/modules/compose/compose_local.go b/modules/compose/compose_local.go index c008e2ec9d..2be2daf152 100644 --- a/modules/compose/compose_local.go +++ b/modules/compose/compose_local.go @@ -41,12 +41,14 @@ func (c composeVersion2) Format(parts ...string) string { return strings.Join(parts, "-") } +// Deprecated: use ComposeStack instead // LocalDockerCompose represents a Docker Compose execution using local binary -// docker-compose or docker-compose.exe, depending on the underlying platform +// docker compose or docker.exe compose, depending on the underlying platform type LocalDockerCompose struct { ComposeVersion *LocalDockerComposeOptions Executable string + composeSubcommand string ComposeFilePaths []string absComposeFilePaths []string Identifier string @@ -58,17 +60,20 @@ type LocalDockerCompose struct { } type ( + // Deprecated: it will be removed in the next major release // LocalDockerComposeOptions defines options applicable to LocalDockerCompose LocalDockerComposeOptions struct { Logger testcontainers.Logging } + // Deprecated: it will be removed in the next major release // LocalDockerComposeOption defines a common interface to modify LocalDockerComposeOptions // These options can be passed to NewLocalDockerCompose in a variadic way to customize the returned LocalDockerCompose instance LocalDockerComposeOption interface { ApplyToLocalCompose(opts *LocalDockerComposeOptions) } + // Deprecated: it will be removed in the next major release // LocalDockerComposeOptionsFunc is a shorthand to implement the LocalDockerComposeOption interface LocalDockerComposeOptionsFunc func(opts *LocalDockerComposeOptions) ) @@ -86,6 +91,7 @@ func WithLogger(logger testcontainers.Logging) ComposeLoggerOption { } } +// Deprecated: it will be removed in the next major release func (o ComposeLoggerOption) ApplyToLocalCompose(opts *LocalDockerComposeOptions) { opts.Logger = o.logger } @@ -94,15 +100,18 @@ func (o ComposeLoggerOption) applyToComposeStack(opts *composeStackOptions) { opts.Logger = o.logger } +// Deprecated: it will be removed in the next major release func (f LocalDockerComposeOptionsFunc) ApplyToLocalCompose(opts *LocalDockerComposeOptions) { f(opts) } -// Down executes docker-compose down +// Deprecated: it will be removed in the next major release +// Down executes docker compose down func (dc *LocalDockerCompose) Down() ExecError { return executeCompose(dc, []string{"down", "--remove-orphans", "--volumes"}) } +// Deprecated: it will be removed in the next major release func (dc *LocalDockerCompose) getDockerComposeEnvironment() map[string]string { environment := map[string]string{} @@ -117,10 +126,12 @@ func (dc *LocalDockerCompose) getDockerComposeEnvironment() map[string]string { return environment } +// Deprecated: it will be removed in the next major release func (dc *LocalDockerCompose) containerNameFromServiceName(service, separator string) string { return dc.Identifier + separator + service } +// Deprecated: it will be removed in the next major release func (dc *LocalDockerCompose) applyStrategyToRunningContainer() error { cli, err := testcontainers.NewDockerClientWithOpts(context.Background()) if err != nil { @@ -163,17 +174,19 @@ func (dc *LocalDockerCompose) applyStrategyToRunningContainer() error { err = strategy.WaitUntilReady(context.Background(), dockercontainer) if err != nil { - return fmt.Errorf("Unable to apply wait strategy %v to service %s due to %w", strategy, k.service, err) + return fmt.Errorf("unable to apply wait strategy %v to service %s due to %w", strategy, k.service, err) } } return nil } +// Deprecated: it will be removed in the next major release // Invoke invokes the docker compose func (dc *LocalDockerCompose) Invoke() ExecError { return executeCompose(dc, dc.Cmd) } +// Deprecated: it will be removed in the next major release // WaitForService sets the strategy for the service that is to be waited on func (dc *LocalDockerCompose) WaitForService(service string, strategy wait.Strategy) DockerCompose { dc.waitStrategySupplied = true @@ -181,18 +194,21 @@ func (dc *LocalDockerCompose) WaitForService(service string, strategy wait.Strat return dc } +// Deprecated: it will be removed in the next major release // WithCommand assigns the command func (dc *LocalDockerCompose) WithCommand(cmd []string) DockerCompose { dc.Cmd = cmd return dc } +// Deprecated: it will be removed in the next major release // WithEnv assigns the environment func (dc *LocalDockerCompose) WithEnv(env map[string]string) DockerCompose { dc.Env = env return dc } +// Deprecated: it will be removed in the next major release // WithExposedService sets the strategy for the service that is to be waited on. If multiple strategies // are given for a single service running on different ports, both strategies will be applied on the same container func (dc *LocalDockerCompose) WithExposedService(service string, port int, strategy wait.Strategy) DockerCompose { @@ -201,7 +217,8 @@ func (dc *LocalDockerCompose) WithExposedService(service string, port int, strat return dc } -// determineVersion checks which version of docker-compose is installed +// Deprecated: it will be removed in the next major release +// determineVersion checks which version of docker compose is installed // depending on the version services names are composed in a different way func (dc *LocalDockerCompose) determineVersion() error { execErr := executeCompose(dc, []string{"version", "--short"}) @@ -232,6 +249,7 @@ func (dc *LocalDockerCompose) determineVersion() error { return nil } +// Deprecated: it will be removed in the next major release // validate checks if the files to be run in the compose are valid YAML files, setting up // references to all services in them func (dc *LocalDockerCompose) validate() error { @@ -336,11 +354,12 @@ func execute( } } +// Deprecated: it will be removed in the next major release func executeCompose(dc *LocalDockerCompose, args []string) ExecError { if which(dc.Executable) != nil { return ExecError{ Command: []string{dc.Executable}, - Error: fmt.Errorf("Local Docker Compose not found. Is %s on the PATH?", dc.Executable), + Error: fmt.Errorf("Local Docker not found. Is %s on the PATH?", dc.Executable), } } @@ -349,7 +368,8 @@ func executeCompose(dc *LocalDockerCompose, args []string) ExecError { environment[k] = v } - var cmds []string + // initialise the command with the compose subcommand + cmds := []string{dc.composeSubcommand} pwd := "." if len(dc.absComposeFilePaths) > 0 { pwd, _ = filepath.Split(dc.absComposeFilePaths[0]) @@ -367,7 +387,7 @@ func executeCompose(dc *LocalDockerCompose, args []string) ExecError { if err != nil { args := strings.Join(dc.Cmd, " ") return ExecError{ - Command: []string{dc.Executable}, + Command: []string{dc.Executable, args}, Error: fmt.Errorf("Local Docker compose exited abnormally whilst running %s: [%v]. %s", dc.Executable, args, err.Error()), } } diff --git a/modules/compose/compose_test.go b/modules/compose/compose_test.go index bffec7bd94..c87e204725 100644 --- a/modules/compose/compose_test.go +++ b/modules/compose/compose_test.go @@ -33,7 +33,7 @@ func ExampleNewLocalDockerCompose() { func ExampleLocalDockerCompose() { _ = LocalDockerCompose{ - Executable: "docker-compose", + Executable: "docker compose", ComposeFilePaths: []string{ "/path/to/docker-compose.yml", "/path/to/docker-compose-1.yml", diff --git a/modules/neo4j/config.go b/modules/neo4j/config.go index 9d0bc70816..1b62363cb9 100644 --- a/modules/neo4j/config.go +++ b/modules/neo4j/config.go @@ -123,9 +123,9 @@ func formatNeo4jConfig(name string) string { // the commercial licence agreement of Neo4j Enterprise Edition. The license // agreement is available at https://neo4j.com/terms/licensing/. func WithAcceptCommercialLicenseAgreement() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - req.Env["NEO4J_ACCEPT_LICENSE_AGREEMENT"] = "yes" - } + return testcontainers.WithEnv(map[string]string{ + "NEO4J_ACCEPT_LICENSE_AGREEMENT": "yes", + }) } // WithAcceptEvaluationLicenseAgreement sets the environment variable @@ -134,7 +134,7 @@ func WithAcceptCommercialLicenseAgreement() testcontainers.CustomizeRequestOptio // agreement is available at https://neo4j.com/terms/enterprise_us/. Please // read the terms of the evaluation agreement before you accept. func WithAcceptEvaluationLicenseAgreement() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - req.Env["NEO4J_ACCEPT_LICENSE_AGREEMENT"] = "eval" - } + return testcontainers.WithEnv(map[string]string{ + "NEO4J_ACCEPT_LICENSE_AGREEMENT": "eval", + }) } diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index 9fb28212e1..a6dec1a779 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -48,7 +48,7 @@ func (c *RabbitMQContainer) AmqpURL(ctx context.Context) (string, error) { // AmqpURL returns the URL for AMQPS clients. func (c *RabbitMQContainer) AmqpsURL(ctx context.Context) (string, error) { - endpoint, err := c.PortEndpoint(ctx, nat.Port(DefaultAMQPPort), "") + endpoint, err := c.PortEndpoint(ctx, nat.Port(DefaultAMQPSPort), "") if err != nil { return "", err } diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 0c85c66607..7079379421 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -2,8 +2,12 @@ package rabbitmq_test import ( "context" + "crypto/tls" + "crypto/x509" "fmt" "io" + "io/ioutil" + "path/filepath" "strings" "testing" @@ -42,6 +46,59 @@ func TestRunContainer_connectUsingAmqp(t *testing.T) { } } +func TestRunContainer_connectUsingAmqps(t *testing.T) { + ctx := context.Background() + + sslSettings := rabbitmq.SSLSettings{ + CACertFile: filepath.Join("testdata", "certs", "server_ca.pem"), + CertFile: filepath.Join("testdata", "certs", "server_cert.pem"), + KeyFile: filepath.Join("testdata", "certs", "server_key.pem"), + VerificationMode: rabbitmq.SSLVerificationModePeer, + FailIfNoCert: false, + VerificationDepth: 1, + } + + rabbitmqContainer, err := rabbitmq.RunContainer(ctx, rabbitmq.WithSSL(sslSettings)) + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := rabbitmqContainer.Terminate(ctx); err != nil { + t.Fatal(err) + } + }() + + amqpsURL, err := rabbitmqContainer.AmqpsURL(ctx) + if err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(amqpsURL, "amqps") { + t.Fatal(fmt.Errorf("AMQPS Url should begin with `amqps`")) + } + + certs := x509.NewCertPool() + + pemData, err := ioutil.ReadFile(sslSettings.CACertFile) + if err != nil { + t.Fatal(err) + } + certs.AppendCertsFromPEM(pemData) + + amqpsConnection, err := amqp.DialTLS(amqpsURL, &tls.Config{InsecureSkipVerify: false, RootCAs: certs}) + if err != nil { + t.Fatal(err) + } + + if amqpsConnection.IsClosed() { + t.Fatal(fmt.Errorf("AMQPS Connection unexpectdely closed")) + } + if err = amqpsConnection.Close(); err != nil { + t.Fatal(err) + } +} + func TestRunContainer_withAllSettings(t *testing.T) { ctx := context.Background()