From 8bd1d2811e332fdd49714be8482f9a5cef0eef2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 9 Apr 2024 20:30:09 +0200 Subject: [PATCH 01/14] fix: handle dockerignore exclusions properly (#2476) * chore: only include the dockerignore if it contains ignore files * fix: the inclusions must be relative to the context * docs: document the dockerignore feature * chore: only include the dockerignore file if it exists --- container.go | 22 ++++++++++++++++------ container_ignore_test.go | 11 ++++++++++- docs/features/build_from_dockerfile.md | 10 +++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/container.go b/container.go index 249aff32cf..00da75783d 100644 --- a/container.go +++ b/container.go @@ -221,13 +221,17 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) { } c.Context = abs - excluded, err := parseDockerIgnore(abs) + dockerIgnoreExists, excluded, err := parseDockerIgnore(abs) if err != nil { return nil, err } - dockerIgnoreLocation := filepath.Join(abs, ".dockerignore") - includes = append(includes, dockerIgnoreLocation, c.GetDockerfile()) + if dockerIgnoreExists { + // only add .dockerignore if it exists + includes = append(includes, filepath.Join(".dockerignore")) + } + + includes = append(includes, c.GetDockerfile()) buildContext, err := archive.TarWithOptions( c.Context, @@ -240,18 +244,24 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) { return buildContext, nil } -func parseDockerIgnore(targetDir string) ([]string, error) { +// parseDockerIgnore returns if the file exists, the excluded files and an error if any +func parseDockerIgnore(targetDir string) (bool, []string, error) { // based on https://github.com/docker/cli/blob/master/cli/command/image/build/dockerignore.go#L14 fileLocation := filepath.Join(targetDir, ".dockerignore") var excluded []string + exists := false if f, openErr := os.Open(fileLocation); openErr == nil { + defer f.Close() + + exists = true + var err error excluded, err = ignorefile.ReadAll(f) if err != nil { - return excluded, fmt.Errorf("error reading .dockerignore: %w", err) + return true, excluded, fmt.Errorf("error reading .dockerignore: %w", err) } } - return excluded, nil + return exists, excluded, nil } // GetBuildArgs returns the env args to be used when creating from Dockerfile diff --git a/container_ignore_test.go b/container_ignore_test.go index 566697c980..ca89db4d89 100644 --- a/container_ignore_test.go +++ b/container_ignore_test.go @@ -11,23 +11,32 @@ import ( func TestParseDockerIgnore(t *testing.T) { testCases := []struct { filePath string + exists bool expectedErr error expectedExcluded []string }{ { filePath: "./testdata/dockerignore", expectedErr: nil, + exists: true, expectedExcluded: []string{"vendor", "foo", "bar"}, }, { filePath: "./testdata", expectedErr: nil, + exists: true, expectedExcluded: []string{"Dockerfile", "echo.Dockerfile"}, }, + { + filePath: "./testdata/data", + expectedErr: nil, + expectedExcluded: nil, // it's nil because the parseDockerIgnore function uses the zero value of a slice + }, } for _, testCase := range testCases { - excluded, err := parseDockerIgnore(testCase.filePath) + exists, excluded, err := parseDockerIgnore(testCase.filePath) + assert.Equal(t, testCase.exists, exists) assert.Equal(t, testCase.expectedErr, err) assert.Equal(t, testCase.expectedExcluded, excluded) } diff --git a/docs/features/build_from_dockerfile.md b/docs/features/build_from_dockerfile.md index 19ad060d2e..a3be26212a 100644 --- a/docs/features/build_from_dockerfile.md +++ b/docs/features/build_from_dockerfile.md @@ -33,7 +33,6 @@ You can specify them like: If you would like to send a build context that you created in code (maybe you have a dynamic Dockerfile), you can send the build context as an `io.Reader` since the Docker Daemon accepts it as a tar file, you can use the [tar](https://golang.org/pkg/archive/tar/) package to create your context. - To do this you would use the `ContextArchive` attribute in the `FromDockerfile` struct. ```go @@ -52,6 +51,15 @@ fromDockerfile := testcontainers.FromDockerfile{ **Please Note** if you specify a `ContextArchive` this will cause _Testcontainers for Go_ to ignore the path passed in to `Context`. +## Ignoring files in the build context + +The same as Docker has a `.dockerignore` file to ignore files in the build context, _Testcontainers for Go_ also supports this feature. +A `.dockerignore` living in the root of the build context will be used to filter out files that should not be sent to the Docker daemon. +The `.dockerignore` file won't be sent to the Docker daemon either. + +!!! note + At this moment, _Testcontainers for Go_ does not support Dockerfile-specific `.dockerignore` files. + ## Images requiring auth If you are building a local Docker image that is fetched from a Docker image in a registry requiring authentication From a3ff7aa94bd9cc5f9c04437b9de06ff7f61d21a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Saint-F=C3=A9lix?= Date: Tue, 9 Apr 2024 20:30:34 +0200 Subject: [PATCH 02/14] Elasticsearch disable CA retrieval when ssl is disabled (#2475) * skip search for CACert if ssl has been turned off * add tests with and without ssl enabled * add all config keys that disable CA gen, restrict check to version 8 * rename test to match content --- modules/elasticsearch/elasticsearch.go | 15 +++++++ modules/elasticsearch/elasticsearch_test.go | 45 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/modules/elasticsearch/elasticsearch.go b/modules/elasticsearch/elasticsearch.go index 79a364fd01..ea846ecbfd 100644 --- a/modules/elasticsearch/elasticsearch.go +++ b/modules/elasticsearch/elasticsearch.go @@ -127,6 +127,21 @@ func configureAddress(ctx context.Context, c *ElasticsearchContainer) (string, e // The certificate is only available since version 8, and will be located in a well-known location. func configureCertificate(settings *Options, req *testcontainers.GenericContainerRequest) error { if isAtLeastVersion(req.Image, 8) { + // These configuration keys explicitly disable CA generation. + // If any are set we skip the file retrieval. + configKeys := []string{ + "xpack.security.enabled", + "xpack.security.http.ssl.enabled", + "xpack.security.transport.ssl.enabled", + } + for _, configKey := range configKeys { + if value, ok := req.Env[configKey]; ok { + if value == "false" { + return nil + } + } + } + // The container needs a post ready hook to copy the certificate from the container to the host. // This certificate is only available since version 8 req.LifecycleHooks[0].PostReadies = append(req.LifecycleHooks[0].PostReadies, diff --git a/modules/elasticsearch/elasticsearch_test.go b/modules/elasticsearch/elasticsearch_test.go index 31431346e0..0bfdca7793 100644 --- a/modules/elasticsearch/elasticsearch_test.go +++ b/modules/elasticsearch/elasticsearch_test.go @@ -163,6 +163,51 @@ func TestElasticsearch(t *testing.T) { } } +func TestElasticsearch8WithoutSSL(t *testing.T) { + tests := []struct { + name string + configKey string + }{ + { + name: "security disabled", + configKey: "xpack.security.enabled", + }, + { + name: "transport ssl disabled", + configKey: "xpack.security.transport.ssl.enabled", + }, + { + name: "http ssl disabled", + configKey: "xpack.security.http.ssl.enabled", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + container, err := elasticsearch.RunContainer( + ctx, + testcontainers.WithImage(baseImage8), + testcontainers.WithEnv(map[string]string{ + test.configKey: "false", + })) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + if len(container.Settings.CACert) > 0 { + t.Fatal("expected CA cert to be empty") + } + }) + } + +} + func TestElasticsearch8WithoutCredentials(t *testing.T) { ctx := context.Background() From 951abcea90dc76165abd152c3cdc979f62c2bf5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:44:31 +0200 Subject: [PATCH 03/14] chore(deps): bump idna from 3.6 to 3.7 (#2480) Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Pipfile.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 4b44bc7a23..9faa900fdc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -161,11 +161,12 @@ }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], + "index": "pypi", "markers": "python_version >= '3.5'", - "version": "==3.6" + "version": "==3.7" }, "importlib-metadata": { "hashes": [ From 0a268b389f5d41662a0130702c3df7c500c991ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 12 Apr 2024 13:15:38 +0200 Subject: [PATCH 04/14] chore: create TLS certs in a consistent manner (#2478) * fix: remove suspicious filepath.Join * chore: fix lint * fix: handle error * chore: reverse assertion for lint * feat: support generating TLS certificates on the fly * chore: apply to cockroachdb * chore: support saving the cert and priv key files to disk * chore: apply to rabbitmq * chore: simplify * chore: use in redpanda module * chore: lint * chore: set validFrom internally * fix: properly use the new API in redpanda * docs: document the TLS helpers * chore: simplify WithParent to accept the struct directly * chore: use tlscert package instead * fix: use non-deprecated API * docs: update * docs: fix examples * chore: use released version of tlscert * fix: add common name for the node cert --- container.go | 2 +- docker.go | 4 +- docker_exec_test.go | 5 +- docs/features/tls.md | 17 ++ mkdocs.yml | 1 + modules/cockroachdb/certs.go | 175 +++++------------- modules/cockroachdb/go.mod | 1 + modules/cockroachdb/go.sum | 2 + modules/rabbitmq/examples_test.go | 38 +++- modules/rabbitmq/go.mod | 1 + modules/rabbitmq/go.sum | 2 + modules/rabbitmq/rabbitmq_test.go | 35 +++- modules/rabbitmq/testdata/certs/server_ca.pem | 20 -- .../rabbitmq/testdata/certs/server_cert.pem | 21 --- .../rabbitmq/testdata/certs/server_key.pem | 27 --- modules/redpanda/go.mod | 1 + modules/redpanda/go.sum | 2 + modules/redpanda/redpanda_test.go | 98 ++-------- reaper.go | 1 - 19 files changed, 159 insertions(+), 294 deletions(-) create mode 100644 docs/features/tls.md delete mode 100644 modules/rabbitmq/testdata/certs/server_ca.pem delete mode 100644 modules/rabbitmq/testdata/certs/server_cert.pem delete mode 100644 modules/rabbitmq/testdata/certs/server_key.pem diff --git a/container.go b/container.go index 00da75783d..fad8e0a8b2 100644 --- a/container.go +++ b/container.go @@ -228,7 +228,7 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) { if dockerIgnoreExists { // only add .dockerignore if it exists - includes = append(includes, filepath.Join(".dockerignore")) + includes = append(includes, ".dockerignore") } includes = append(includes, c.GetDockerfile()) diff --git a/docker.go b/docker.go index 6d10cf92a0..79f85b8f00 100644 --- a/docker.go +++ b/docker.go @@ -637,6 +637,9 @@ func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byt 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) + if err != nil { + return err + } err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{}) if err != nil { @@ -1506,7 +1509,6 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl Attachable: true, Labels: core.DefaultLabels(core.SessionID()), }) - if err != nil { return "", err } diff --git a/docker_exec_test.go b/docker_exec_test.go index 4716c493ab..11f187c226 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/stretchr/testify/require" + tcexec "github.com/testcontainers/testcontainers-go/exec" ) @@ -133,6 +134,6 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { require.NotNil(t, stdout) require.NotNil(t, stderr) - require.Equal(t, stdout.String(), "stdout\n") - require.Equal(t, stderr.String(), "stderr\n") + require.Equal(t, "stdout\n", stdout.String()) + require.Equal(t, "stderr\n", stderr.String()) } diff --git a/docs/features/tls.md b/docs/features/tls.md new file mode 100644 index 0000000000..fd8b95266d --- /dev/null +++ b/docs/features/tls.md @@ -0,0 +1,17 @@ +# TLS certificates + +Interacting with services that require TLS certificates is a common issue when working with containers. You can create one or more on-the-fly certificates in order to communicate with your services. + +_Testcontainers for Go_ uses a library to generate certificates on-the-fly. This library is called [tlscert](https://github.com/mdelapenya/tlscert). + +### Examples + +In the following example we are going to start an HTTP server with a self-signed certificate. +It exposes one single handler that will return a simple message when accessed. +The example will also create a client that will connect to the server using the generated certificate, +demonstrating how to use the generated certificate to communicate with a service. + + +[Create a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSelfSignedCert +[Sign a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSignSelfSignedCert + diff --git a/mkdocs.yml b/mkdocs.yml index 8239016533..dcfc17e496 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,7 @@ nav: - features/files_and_mounts.md - features/creating_networks.md - features/networking.md + - features/tls.md - features/garbage_collector.md - features/build_from_dockerfile.md - features/docker_auth.md diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go index fc1b399a42..dab738192a 100644 --- a/modules/cockroachdb/certs.go +++ b/modules/cockroachdb/certs.go @@ -1,14 +1,12 @@ package cockroachdb import ( - "crypto/rand" - "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" + "fmt" "net" "time" + + "github.com/mdelapenya/tlscert" ) type TLSConfig struct { @@ -21,138 +19,49 @@ type TLSConfig struct { // NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS. func NewTLSConfig() (*TLSConfig, error) { - caCert, caKey, err := generateCA() - if err != nil { - return nil, err + // exampleSelfSignedCert { + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + SubjectCommonName: "Cockroach Test CA", + Host: "localhost,127.0.0.1", + IsCA: true, + ValidFor: time.Hour, + }) + if caCert == nil { + return nil, fmt.Errorf("failed to generate CA certificate") } - - nodeCert, nodeKey, err := generateNode(caCert, caKey) - if err != nil { - return nil, err + // } + + // exampleSignSelfSignedCert { + nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "node", + SubjectCommonName: "node", + Host: "localhost,127.0.0.1", + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + ValidFor: time.Hour, + Parent: caCert, // using the CA certificate as parent + }) + if nodeCert == nil { + return nil, fmt.Errorf("failed to generate node certificate") } - - clientCert, clientKey, err := generateClient(caCert, caKey) - if err != nil { - return nil, err + // } + + clientCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + SubjectCommonName: defaultUser, + Host: "localhost,127.0.0.1", + ValidFor: time.Hour, + Parent: caCert, // using the CA certificate as parent + }) + if clientCert == nil { + return nil, fmt.Errorf("failed to generate client certificate") } return &TLSConfig{ - CACert: caCert, - NodeCert: nodeCert, - NodeKey: nodeKey, - ClientCert: clientCert, - ClientKey: clientKey, + CACert: caCert.Cert, + NodeCert: nodeCert.Bytes, + NodeKey: nodeCert.KeyBytes, + ClientCert: clientCert.Bytes, + ClientKey: clientCert.KeyBytes, }, nil } - -func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: "Cockroach Test CA", - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - BasicConstraintsValid: true, - IsCA: true, - } - - caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - caBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, caPrivKey.Public(), caPrivKey) - if err != nil { - return nil, nil, err - } - - caCert, err := x509.ParseCertificate(caBytes) - if err != nil { - return nil, nil, err - } - - return caCert, caPrivKey, nil -} - -func generateNode(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: "node", - }, - DNSNames: []string{"localhost"}, - IPAddresses: []net.IP{ - net.IPv4(127, 0, 0, 1), - net.IPv6loopback, - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageServerAuth, - x509.ExtKeyUsageClientAuth, - }, - BasicConstraintsValid: true, - } - - certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey) - if err != nil { - return nil, nil, err - } - - cert := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - certKey := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - return cert, certKey, nil -} - -func generateClient(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) { - template := x509.Certificate{ - SerialNumber: big.NewInt(2019), - Subject: pkix.Name{ - CommonName: defaultUser, - }, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageServerAuth, - x509.ExtKeyUsageClientAuth, - }, - BasicConstraintsValid: true, - } - - certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey) - if err != nil { - return nil, nil, err - } - - cert := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - certKey := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - return cert, certKey, nil -} diff --git a/modules/cockroachdb/go.mod b/modules/cockroachdb/go.mod index 094a61e8b5..8e78d6c8d5 100644 --- a/modules/cockroachdb/go.mod +++ b/modules/cockroachdb/go.mod @@ -36,6 +36,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index 2365978d8b..1ec215bc59 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -73,6 +73,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index a6fc829424..55fcd143da 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -5,9 +5,10 @@ import ( "fmt" "io" "log" - "path/filepath" + "os" "strings" + "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" "github.com/testcontainers/testcontainers-go" @@ -89,10 +90,39 @@ func ExampleRunContainer_withSSL() { // enableSSL { ctx := context.Background() + tmpDir := os.TempDir() + certDirs := tmpDir + "/rabbitmq" + if err := os.MkdirAll(certDirs, 0755); err != nil { + log.Fatalf("failed to create temporary directory: %s", err) + } + defer os.RemoveAll(certDirs) + + // generates the CA certificate and the certificate + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + Host: "localhost,127.0.0.1", + IsCA: true, + ParentDir: certDirs, + }) + if caCert == nil { + log.Fatal("failed to generate CA certificate") + } + + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + IsCA: true, + Parent: caCert, + ParentDir: certDirs, + }) + if cert == nil { + log.Fatal("failed to generate certificate") + } + 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"), + CACertFile: caCert.CertPath, + CertFile: cert.CertPath, + KeyFile: cert.KeyPath, VerificationMode: rabbitmq.SSLVerificationModePeer, FailIfNoCert: true, VerificationDepth: 1, diff --git a/modules/rabbitmq/go.mod b/modules/rabbitmq/go.mod index 4cc07b1c85..2c8134c7dd 100644 --- a/modules/rabbitmq/go.mod +++ b/modules/rabbitmq/go.mod @@ -30,6 +30,7 @@ require ( github.com/klauspost/compress v1.16.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index 3199313edc..79a23800a3 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -63,6 +63,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 7079379421..f1f03f8ba9 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -6,11 +6,11 @@ import ( "crypto/x509" "fmt" "io" - "io/ioutil" - "path/filepath" + "os" "strings" "testing" + "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" "github.com/testcontainers/testcontainers-go" @@ -49,10 +49,33 @@ func TestRunContainer_connectUsingAmqp(t *testing.T) { func TestRunContainer_connectUsingAmqps(t *testing.T) { ctx := context.Background() + tmpDir := t.TempDir() + + caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "ca", + Host: "localhost,127.0.0.1", + IsCA: true, + ParentDir: tmpDir, + }) + if caCert == nil { + t.Fatal("failed to generate CA certificate") + } + + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + IsCA: true, + Parent: caCert, + ParentDir: tmpDir, + }) + if cert == nil { + t.Fatal("failed to generate certificate") + } + 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"), + CACertFile: caCert.CertPath, + CertFile: cert.CertPath, + KeyFile: cert.KeyPath, VerificationMode: rabbitmq.SSLVerificationModePeer, FailIfNoCert: false, VerificationDepth: 1, @@ -80,7 +103,7 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { certs := x509.NewCertPool() - pemData, err := ioutil.ReadFile(sslSettings.CACertFile) + pemData, err := os.ReadFile(sslSettings.CACertFile) if err != nil { t.Fatal(err) } diff --git a/modules/rabbitmq/testdata/certs/server_ca.pem b/modules/rabbitmq/testdata/certs/server_ca.pem deleted file mode 100644 index f22df82909..0000000000 --- a/modules/rabbitmq/testdata/certs/server_ca.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDRzCCAi+gAwIBAgIJAJJIMzvZuRzlMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV -BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMCAXDTE5 -MDUwMjA3MjI0OVoYDzIxMTkwNDA4MDcyMjQ5WjAxMSAwHgYDVQQDDBdUTFNHZW5T -ZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKko8FmfzrLHyZckvdR1oiSZf80m0t66TMqtLat1Oxjh -CjsxvswwJ/m2I5dM48hwZ+0b2ufkvaudLPq/8jDGyONVfjMGlbe1YlmQMDC7YWdI -XM1nCWAZIKaOHwIkfswuVBAdBVYV4Polu6wjVt5edEpl/IWEpPicXjLOY1Fw3q67 -5tP2Mmo6TJg5YqgB4fH4SmajtP3j+H4puQ8ZPIs26mInEgfCyrMWey/oQX8qqMph -pKMEJYE7DHawriFraOooJadJYojbY5H27nmJe8yXURb3wSQSaKnFZL25cmVm2kue -/lw+n+a2wLdHdU4cmghCURalhcXUNZe7UbdRZ9e9r2cCAwEAAaNgMF4wCwYDVR0P -BAQDAgEGMB0GA1UdDgQWBBSZiNur/XHsqSfdWnB1NPi/ql5+tzAfBgNVHSMEGDAW -gBSZiNur/XHsqSfdWnB1NPi/ql5+tzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQAar/db/T7izD4pyh2titl7Dkgp2iTditfgqRlU0yVGiiB6rLmY -sYE2QAuFhgqyRLPcjVV8F39iRJHQ17SGT8e2iAaUTnbQj0AiskKjonF9+quKuVbr -TpYHk+guS0Jn2rU6HK8WQeYZOh3WdLTu4ArXkxywgwVssQQ9JmpTd9YEYePWfs7i -WZB6AQyL9CD3z1j4i1G4ft6pB1Ps5XjznqMZ2//7AUpoRTrettWqorPWwudQ9yna -B4S6KtvpnxUQSeHJW6Q4NvTrOsvHEOCa6OtwYbWmLf+qbpPb8oHt9UF3ze2PJopB -QzsQop1+gPudG0DX0SgyuQT+SsFjYlDazZdZ ------END CERTIFICATE----- diff --git a/modules/rabbitmq/testdata/certs/server_cert.pem b/modules/rabbitmq/testdata/certs/server_cert.pem deleted file mode 100644 index 8a78318338..0000000000 --- a/modules/rabbitmq/testdata/certs/server_cert.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDajCCAlKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH -ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAgFw0xOTA1MDIwNzIy -NDlaGA8yMTE5MDQwODA3MjI0OVowIzEQMA4GA1UEAwwHQzY1U1RUMjEPMA0GA1UE -CgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0QXKtb -KVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWeBH1n0WeXXGd/p/y6P4lRBeWN -BZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJdMHzIzhmrrqXo0u3DT8MmoNR6 -7CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjtCkHPwrc/sBHVS3aivNWGkJzA -eBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDSspHZDFmM1OwVwypnMt4fGgaX -5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9JD5MZ5krA3SrjdwRFS3sQXC3 -nuHqJofFXNkbXQIDAQABo4GYMIGVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMG -A1UdJQQMMAoGCCsGAQUFBwMBMCYGA1UdEQQfMB2CB0M2NVNUVDKCB0M2NVNUVDKC -CWxvY2FsaG9zdDAdBgNVHQ4EFgQURq22sa46tA0SGHhEm9jxGP9aDrswHwYDVR0j -BBgwFoAUmYjbq/1x7Kkn3VpwdTT4v6pefrcwDQYJKoZIhvcNAQELBQADggEBAKUP -7RgmJyMVoHxg46F1fjWVhlF4BbQuEtB8mC+4G4e68lDU/TPAbmB3aj91oQDgBiTd -R2O7U6tyitxxrU2r7rFAHGhFHeyCQ3yZMwydO2V3Nm2Ywzdyk8er4yghjg9FS8tH -egDGDDod3l1yrAbHHuXmzDjnAFwHwRkm5cYUz00/IuZ3sQZ70XofL3KXNj1tAtfK -PSpdSAxSTO99ofjVKjlyywQSZKNbXfqD5DGz8e0rmqPfZ+3zi75E5nEuJ3UI2wXg -LuI4j6FIzNQyei/FdSynktcIm+hefQEyex4cho4C8RYB2S5S8RWrnP9jOzsaQFHn -bHXf7dKwRfA6/u8JmtQ= ------END CERTIFICATE----- diff --git a/modules/rabbitmq/testdata/certs/server_key.pem b/modules/rabbitmq/testdata/certs/server_key.pem deleted file mode 100644 index dfbfb6db7e..0000000000 --- a/modules/rabbitmq/testdata/certs/server_key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAqR0QXKtbKVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWe -BH1n0WeXXGd/p/y6P4lRBeWNBZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJd -MHzIzhmrrqXo0u3DT8MmoNR67CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjt -CkHPwrc/sBHVS3aivNWGkJzAeBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDS -spHZDFmM1OwVwypnMt4fGgaX5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9 -JD5MZ5krA3SrjdwRFS3sQXC3nuHqJofFXNkbXQIDAQABAoIBAA0dxvYZCEIFmrKZ -71jzanDQ5FJvvyhA8H3OmC4r+oZ+uTDu5FmezF2OdkvhbyI9VMi2wsT9T9m+yAxw -QXhyUce3WzeXsv4Em8H55fQykBhOtqPQja/EDeMGVK2ACrXJYRufnDBfKoWEOmQb -kjddgZzjaBDHOWXJA5CTet8ysGOAJBTxyzU69k5Vj9B5abG9CofNzGOFF+Uleff5 -ip3sz7JpDXCex3oEs98veco6+8i/MZNo3BnwB5J+P+2MFFKONfPwuNyKAWBza2/X -66Lk3xXBjLJJ+Ww16jkqueTXEq6GCFXavNfdL9aonth5V5YYR/cj+2u2LM1oj9cJ -bp0xbvUCgYEA2Svq1DyR9cfTwrbc/0J2JfrjavClzDYU2oeO2fSU85WEEjJguaja -17Vdo/UsJtiUiSq4UhI1n0haaIpTBCeF2tHGXVEYZ7ZBi1zzdWbWlDxFmi+rcE57 -ytx5w+iLE366tQEMa/Jn3bly54pG5JZAr9TXkpg9sMbzWZri2ocyU/cCgYEAx1l/ -9X9C/OruDp/MhhmVwKfw/X2+RhZRuv0pPcpJu7/gIoLgaxNj41XSeLqLYMlisaRk -GFU17GFXtfRGE1a3z+jj8UPTP2sHk3w8m0yI+pgWgsvG0TJ0B+XsRfpVxFiIoaEs -3AsBaGR+hrRY1dpaJ9Cu3J9mEeToTpbCzPzVDksCgYEAzwSvWNvYY4u2UFHSvz2S -tMfBzCpUUiNno50/ToN5De4ENPhy/eh5nNDVz7qh+PHSPiNMC2gyV4E4NZlOY5Jt -Zdc8ma35brvtJTVZGxwKBsqhqsYwTeFy3kFnjZn6IX5X6r1yIuCzpEfowdEtnS+h -wDtLuAGKJR6x0UP1Zk0ka6cCgYBGE6I1rJzhx7wTi/0bjtbjuKWwlolSnfnxH5ll -zTyKMXMa7qLxQQm2Gq84HWtthJ2bEMzW+O1RwQ5SOiKAHdXT0mx+nXcfLgKlx+CO -PyNP5DLVm8iyNWgwdpTOLKgFs5GkL8JTP9Mo3VrVA4TO+EkFAgjWKXp6A9vd9IVa -Be7nbQKBgAVtFKuf9nbCMfN+W1gN0vlW2lwxCTa4w0KHgIlGIIvnYVuixSgu9fGt -uylQcQirEjqrdzdVF9L2BQ37ZcLaGh1LoCmx8XVCX/HhbwW2RP798P3Z1P7htm16 -ha5OfuPjHvoZklbYJo6EORJZQehS2VP63pjdnmUeMHPFzrPUevI5 ------END RSA PRIVATE KEY----- diff --git a/modules/redpanda/go.mod b/modules/redpanda/go.mod index c78ba08e85..a31e06220f 100644 --- a/modules/redpanda/go.mod +++ b/modules/redpanda/go.mod @@ -35,6 +35,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/redpanda/go.sum b/modules/redpanda/go.sum index 6394427cd9..e31f78abc6 100644 --- a/modules/redpanda/go.sum +++ b/modules/redpanda/go.sum @@ -65,6 +65,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= +github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index cdb264bd56..9d412be1c0 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -2,8 +2,6 @@ package redpanda_test import ( "context" - "crypto/tls" - "crypto/x509" "fmt" "io" "net/http" @@ -11,6 +9,7 @@ import ( "testing" "time" + "github.com/mdelapenya/tlscert" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kadm" @@ -346,12 +345,17 @@ func TestRedpandaProduceWithAutoCreateTopics(t *testing.T) { } func TestRedpandaWithTLS(t *testing.T) { - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(t, err, "failed to load key pair") + tmp := t.TempDir() + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + ParentDir: tmp, + }) + require.NotNil(t, cert, "failed to generate cert") ctx := context.Background() - container, err := redpanda.RunContainer(ctx, redpanda.WithTLS(localhostCert, localhostKey)) + container, err := redpanda.RunContainer(ctx, redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) require.NoError(t, err) t.Cleanup(func() { @@ -360,13 +364,7 @@ func TestRedpandaWithTLS(t *testing.T) { } }) - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(localhostCert) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - } + tlsConfig := cert.TLSConfig() httpCl := &http.Client{ Timeout: 5 * time.Second, @@ -415,13 +413,19 @@ func TestRedpandaWithTLS(t *testing.T) { } func TestRedpandaWithTLSAndSASL(t *testing.T) { - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(t, err, "failed to load key pair") + tmp := t.TempDir() + + cert := tlscert.SelfSignedFromRequest(tlscert.Request{ + Name: "client", + Host: "localhost,127.0.0.1", + ParentDir: tmp, + }) + require.NotNil(t, cert, "failed to generate cert") ctx := context.Background() container, err := redpanda.RunContainer(ctx, - redpanda.WithTLS(localhostCert, localhostKey), + redpanda.WithTLS(cert.Bytes, cert.KeyBytes), redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithNewServiceAccount("superuser-1", "test"), @@ -435,13 +439,7 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { } }) - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(localhostCert) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - } + tlsConfig := cert.TLSConfig() broker, err := container.KafkaSeedBroker(ctx) require.NoError(t, err) @@ -573,59 +571,3 @@ func TestRedpandaListener_NoNetwork(t *testing.T) { require.Contains(t, err.Error(), "container must be attached to at least one network") } - -// localhostCert is a PEM-encoded TLS cert with SAN IPs -// generated from src/crypto/tls: -// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,localhost --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h -var localhostCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIRAKMykg5qJSCb4L3WtcZznSQwDQYJKoZIhvcNAQELBQAw -EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 -MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAPYcLIhqCsrmqvsY1gWqI1jx3Ytn5Qjfvlg3BPD/YeD4UVouBhgQ -NIIERFCmDUzu52pXYZeCouBIVDWqZKixQf3PyBzAqbFvX0pTsZrOnvjuoahzjEcl -x+CfkIp58mVaV/8v9TyBYCXNuHlI7Pndu/3U5d6npSg8+dTkwW3VZzZyHpsDW+a4 -ByW02NI58LoHzQPMRg9MFToL1qNQy4PFyADf2N/3/SYOkrbSrXA0jYqXE8yvQGYe -LWcoQ+4YkurSS1TgSNEKxrzGj8w4xRjEjRNsLVNWd8uxZkHwv6LXOn4s39ix3jN4 -7OJJHA8fJAWxAP4ThrpM1j5J+Rq1PD380u8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8E -BAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU8gMBt2leRAnGgCQ6pgIYPHY35GAwLAYDVR0RBCUwI4IJbG9jYWxob3N0 -hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQA5F6aw -6JJMsnCjxRGYXb252zqjxOxweawZ2je4UAGSsF27Phm1Bx6/2mzPpgIB0I7xNBFL -ljtqBG/FpH6qWpkkegljL8Z5soXiye/4r1G+V6hadm32/OLQCS//dyq7W1a2uVlS -KdFjoNqRW2PacVQLjnTbP2SJV5CnrJgCsSMXVoNnKdj5gr5ltNNAt9TAJ85iFa5d -rJla/XghtqEOzYtigKPF7EVqRRl4RmPu30hxwDZMT60ptFolfCEeXpDra5uonJMv -ElEbzK8ZzXmvWCj94RjPkGKZs8+SDM2qfKPk5ZW2xJxwqS3tkEkZlj1L+b7zYOlt -aJ65OWCXHLecrgdl ------END CERTIFICATE-----`) - -// localhostKey is the private key for localhostCert. -var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD2HCyIagrK5qr7 -GNYFqiNY8d2LZ+UI375YNwTw/2Hg+FFaLgYYEDSCBERQpg1M7udqV2GXgqLgSFQ1 -qmSosUH9z8gcwKmxb19KU7Gazp747qGoc4xHJcfgn5CKefJlWlf/L/U8gWAlzbh5 -SOz53bv91OXep6UoPPnU5MFt1Wc2ch6bA1vmuAcltNjSOfC6B80DzEYPTBU6C9aj -UMuDxcgA39jf9/0mDpK20q1wNI2KlxPMr0BmHi1nKEPuGJLq0ktU4EjRCsa8xo/M -OMUYxI0TbC1TVnfLsWZB8L+i1zp+LN/Ysd4zeOziSRwPHyQFsQD+E4a6TNY+Sfka -tTw9/NLvAgMBAAECggEBALKxAiSJ2gw4Lyzhe4PhZIjQE+uEI+etjKbAS/YvdwHB -SlAP2pzeJ0G/l1p3NnEFhUDQ8SrwzxHJclsEvNE+4otGsiUuPgd2tdlhqzKbkxFr -MjT8sH14EQgm0uu4Xyb30ayXRZgI16abF7X4HRfOxxAl5EElt+TfYQYSkd8Nc0Mz -bD7g0riSdOKVhNIkUTT1U7x8ClIgff6vbWztOVP4hGezqEKpO/8+JBkg2GLeH3lC -PyuHEb33Foxg7SX35M1a89EKC2p4ER6/nfg6wGYyIsn42gBk1JgQdg23x7c/0WOu -vcw1unNP2kCbnsCeZ6KPRRGXEjbpTqOTzAUOekOeOgECgYEA9/jwK2wrX2J3kJN7 -v6kmxazigXHCa7XmFMgTfdqiQfXfjdi/4u+4KAX04jWra3ZH4KT98ztPOGjb6KhM -hfMldsxON8S9IQPcbDyj+5R77KU4BG/JQBEOX1uzS9KjMVG5e9ZUpG5UnSoSOgyM -oN3DZto7C5ULO2U2MT8JaoGb53cCgYEA/hPNMsCXFairxKy0BCsvJFan93+GIdwM -YoAGLc4Oj67ES8TYC4h9Im5i81JYOjpY4aZeKdj8S+ozmbqqa/iJiAfOr37xOMuX -AQA2T8uhPXXNXA5s6T3LaIXtzL0NmRRZCtuyEGdCidIXub7Bz8LrfsMc+s/jv57f -4IPmW12PPkkCgYBpEdDqBT5nfzh8SRGhR1IHZlbfVE12CDACVDh2FkK0QjNETjgY -N0zHoKZ/hxAoS4jvNdnoyxOpKj0r2sv54enY6X6nALTGnXUzY4p0GhlcTzFqJ9eV -TuTRIPDaytidGCzIvStGNP2jTmVEtXaM3wphtUxZfwCwXRVWToh12Y8uxwKBgA1a -FQp5vHbS6lPnj344lr2eIC2NcgsNeUkj2S9HCNTcJkylB4Vzor/QdTq8NQ66Sjlx -eLlSQc/retK1UIdkBDY10tK+JQcLC+Btlm0TEmIccrJHv8lyCeJwR1LfDHvi6dr8 -OJtMEd8UP1Lvh1fXsnBy6G71xc4oFzPBOrXKcOChAoGACOgyYe47ZizScsUGjCC7 -xARTEolZhtqHKVd5s9oi95P0r7A1gcNx/9YW0rCT2ZD8BD9H++HTE2L+mh3R9zDn -jwDeW7wVZec+oyGdc9L+B1xU25O+88zNLxlRAX8nXJbHdgL83UclmC51GbXejloP -D4ZNvyXf/6E27Ibu6v2p/vs= ------END TESTING KEY-----`)) - -func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } diff --git a/reaper.go b/reaper.go index 3dff6a7c9d..859f8b76de 100644 --- a/reaper.go +++ b/reaper.go @@ -120,7 +120,6 @@ func lookUpReaperContainer(ctx context.Context, sessionID string) (*DockerContai return nil }, backoff.WithContext(exp, ctx)) - if err != nil { return nil, err } From b5f1e1351906c3cac54f0fc14b773c7261aa480c Mon Sep 17 00:00:00 2001 From: Dustin Brown Date: Fri, 12 Apr 2024 10:16:40 -0700 Subject: [PATCH 05/14] support Dolt (#2177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * /modules/dolt: wip, kinda working * /modules/dolt: get tests passing * /{.github,.vscode,docs,mkdocs,modules,sonar-project}: use modulegen tool * /modules/dolt/{dolt.go,examples_test.go}: run linter * /modules/dolt/{dolt.go,examples_test.go}: add methods for cloning * /{docs, modules}: add with creds file * /{docs,modules}: pr feedback, cleanup * /modules/dolt/examples_test.go: remove panics, lint * chore: run mod tidy * chore: include MustConnectionString method * chore: do not use named returns * chore: perform initialisation before the container has started --------- Co-authored-by: Manuel de la Peña --- .github/workflows/ci.yml | 2 +- .vscode/.testcontainers-go.code-workspace | 4 + docs/modules/dolt.md | 81 ++++++ mkdocs.yml | 1 + modules/dolt/Makefile | 5 + modules/dolt/dolt.go | 251 +++++++++++++++++++ modules/dolt/dolt_test.go | 197 +++++++++++++++ modules/dolt/examples_test.go | 97 +++++++ modules/dolt/go.mod | 61 +++++ modules/dolt/go.sum | 190 ++++++++++++++ modules/dolt/testdata/check_clone_private.sh | 21 ++ modules/dolt/testdata/check_clone_public.sh | 6 + modules/dolt/testdata/clone-db.sh | 9 + modules/dolt/testdata/dolt_1_32_4.cnf | 46 ++++ modules/dolt/testdata/schema.sql | 9 + sonar-project.properties | 2 +- 16 files changed, 980 insertions(+), 2 deletions(-) create mode 100644 docs/modules/dolt.md create mode 100644 modules/dolt/Makefile create mode 100644 modules/dolt/dolt.go create mode 100644 modules/dolt/dolt_test.go create mode 100644 modules/dolt/examples_test.go create mode 100644 modules/dolt/go.mod create mode 100644 modules/dolt/go.sum create mode 100755 modules/dolt/testdata/check_clone_private.sh create mode 100755 modules/dolt/testdata/check_clone_public.sh create mode 100644 modules/dolt/testdata/clone-db.sh create mode 100644 modules/dolt/testdata/dolt_1_32_4.cnf create mode 100644 modules/dolt/testdata/schema.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ee4391d22..97b8c4d91c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: matrix: go-version: [1.21.x, 1.x] platform: [ubuntu-latest] - module: [artemis, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, elasticsearch, gcloud, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, vault, weaviate] + module: [artemis, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, dolt, elasticsearch, gcloud, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, vault, weaviate] uses: ./.github/workflows/ci-test-go.yml with: go-version: ${{ matrix.go-version }} diff --git a/.vscode/.testcontainers-go.code-workspace b/.vscode/.testcontainers-go.code-workspace index cf15a887d6..627ecbb03a 100644 --- a/.vscode/.testcontainers-go.code-workspace +++ b/.vscode/.testcontainers-go.code-workspace @@ -45,6 +45,10 @@ "name": "module / couchbase", "path": "../modules/couchbase" }, + { + "name": "module / dolt", + "path": "../modules/dolt" + }, { "name": "module / elasticsearch", "path": "../modules/elasticsearch" diff --git a/docs/modules/dolt.md b/docs/modules/dolt.md new file mode 100644 index 0000000000..3e7fdfc43e --- /dev/null +++ b/docs/modules/dolt.md @@ -0,0 +1,81 @@ +# Dolt + +Not available until the next release of testcontainers-go :material-tag: main + +## Introduction + +The Testcontainers module for Dolt. + +## Adding this module to your project dependencies + +Please run the following command to add the Dolt module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/dolt +``` + +## Usage example + + +[Creating a Dolt container](../../modules/dolt/examples_test.go) inside_block:runDoltContainer + + +## Module reference + +The Dolt module exposes one entrypoint function to create the Dolt container, and this function receives two parameters: + +```golang +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) +``` + +- `context.Context`, the Go context. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Dolt container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Dolt Docker image, you can use `testcontainers.WithImage` with a valid Docker image +for Dolt. E.g. `testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4")`. + +{% include "../features/common_functional_options.md" %} + +#### Set username, password and database name + +If you need to set a different database, and its credentials, you can use `WithUsername`, `WithPassword`, `WithDatabase` +options. + +!!!info +The default values for the username is `root`, for password is `test` and for the default database name is `test`. + +#### Init Scripts + +If you would like to perform DDL or DML operations in the Dolt container, add one or more `*.sql`, `*.sql.gz`, or `*.sh` +scripts to the container request, using the `WithScripts(scriptPaths ...string)`. Those files will be copied under `/docker-entrypoint-initdb.d`. + +#### Clone from remotes + +If you would like to clone data from a remote into the Dolt container, add an `*.sh` +scripts to the container request, using the `WithScripts(scriptPaths ...string)`. Additionally, use `WithDoltCloneRemoteUrl(url string)` to specify +the remote to clone, and use `WithDoltCredsPublicKey(key string)` along with `WithCredsFile(credsFile string)` to authorize the Dolt container to clone from the remote. + + +[Example of Clone script](../../modules/dolt/testdata/clone-db.sh) + + +#### 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 + +#### ConnectionString + +This method returns the connection string to connect to the Dolt container, using the default `3306` port. +It's possible to pass extra parameters to the connection string, e.g. `tls=skip-verify` or `application_name=myapp`, in a variadic way. + + +[Get connection string](../../modules/dolt/dolt_test.go) inside_block:connectionString + diff --git a/mkdocs.yml b/mkdocs.yml index dcfc17e496..6e72971c1e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,7 @@ nav: - modules/cockroachdb.md - modules/consul.md - modules/couchbase.md + - modules/dolt.md - modules/elasticsearch.md - modules/gcloud.md - modules/inbucket.md diff --git a/modules/dolt/Makefile b/modules/dolt/Makefile new file mode 100644 index 0000000000..f22edec419 --- /dev/null +++ b/modules/dolt/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-dolt diff --git a/modules/dolt/dolt.go b/modules/dolt/dolt.go new file mode 100644 index 0000000000..a6da442aa0 --- /dev/null +++ b/modules/dolt/dolt.go @@ -0,0 +1,251 @@ +package dolt + +import ( + "context" + "database/sql" + "fmt" + "path/filepath" + "strings" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + rootUser = "root" + defaultUser = "test" + defaultPassword = "test" + defaultDatabaseName = "test" +) + +const defaultImage = "dolthub/dolt-sql-server:1.32.4" + +// DoltContainer represents the Dolt container type used in the module +type DoltContainer struct { + testcontainers.Container + username string + password string + database string +} + +func WithDefaultCredentials() testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + username := req.Env["DOLT_USER"] + if strings.EqualFold(rootUser, username) { + delete(req.Env, "DOLT_USER") + delete(req.Env, "DOLT_PASSWORD") + } + } +} + +// RunContainer creates an instance of the Dolt container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) { + req := testcontainers.ContainerRequest{ + Image: defaultImage, + ExposedPorts: []string{"3306/tcp", "33060/tcp"}, + Env: map[string]string{ + "DOLT_USER": defaultUser, + "DOLT_PASSWORD": defaultPassword, + "DOLT_DATABASE": defaultDatabaseName, + }, + WaitingFor: wait.ForLog("Server ready. Accepting connections."), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + opts = append(opts, WithDefaultCredentials()) + + for _, opt := range opts { + opt.Customize(&genericContainerReq) + } + + createUser := true + username, ok := req.Env["DOLT_USER"] + if !ok { + username = rootUser + createUser = false + } + password := req.Env["DOLT_PASSWORD"] + + database := req.Env["DOLT_DATABASE"] + if database == "" { + database = defaultDatabaseName + } + + if len(password) == 0 && password == "" && !strings.EqualFold(rootUser, username) { + return nil, fmt.Errorf("empty password can be used only with the root user") + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + if err != nil { + return nil, err + } + + dc := &DoltContainer{container, username, password, database} + + // dolthub/dolt-sql-server does not create user or database, so we do so here + err = dc.initialize(ctx, createUser) + return dc, err +} + +func (c *DoltContainer) initialize(ctx context.Context, createUser bool) error { + connectionString, err := c.initialConnectionString(ctx) + if err != nil { + return err + } + + var db *sql.DB + db, err = sql.Open("mysql", connectionString) + if err != nil { + return err + } + defer func() { + rerr := db.Close() + if err == nil { + err = rerr + } + }() + + if err = db.Ping(); err != nil { + return fmt.Errorf("error pinging db: %w", err) + } + + // create database + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", c.database)) + if err != nil { + return fmt.Errorf("error creating database %s: %w", c.database, err) + } + + if createUser { + // create user + _, err = db.Exec(fmt.Sprintf("CREATE USER IF NOT EXISTS '%s' IDENTIFIED BY '%s';", c.username, c.password)) + if err != nil { + return fmt.Errorf("error creating user %s: %w", c.username, err) + } + + q := fmt.Sprintf("GRANT ALL ON %s.* TO '%s';", c.database, c.username) + // grant user privileges + _, err = db.Exec(q) + if err != nil { + return fmt.Errorf("error creating user %s: %w", c.username, err) + } + } + + return nil +} + +func (c *DoltContainer) initialConnectionString(ctx context.Context) (string, error) { + containerPort, err := c.MappedPort(ctx, "3306/tcp") + if err != nil { + return "", err + } + + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + connectionString := fmt.Sprintf("root:@tcp(%s:%s)/", host, containerPort.Port()) + return connectionString, nil +} + +func (c *DoltContainer) MustConnectionString(ctx context.Context, args ...string) string { + addr, err := c.ConnectionString(ctx, args...) + if err != nil { + panic(err) + } + return addr +} + +func (c *DoltContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { + containerPort, err := c.MappedPort(ctx, "3306/tcp") + if err != nil { + return "", err + } + + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + extraArgs := "" + if len(args) > 0 { + extraArgs = strings.Join(args, "&") + } + if extraArgs != "" { + extraArgs = "?" + extraArgs + } + + connectionString := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s%s", c.username, c.password, host, containerPort.Port(), c.database, extraArgs) + return connectionString, nil +} + +func WithUsername(username string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["DOLT_USER"] = username + } +} + +func WithPassword(password string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["DOLT_PASSWORD"] = password + } +} + +func WithDoltCredsPublicKey(key string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["DOLT_CREDS_PUB_KEY"] = key + } +} + +func WithDoltCloneRemoteUrl(url string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["DOLT_REMOTE_CLONE_URL"] = url + } +} + +func WithDatabase(database string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["DOLT_DATABASE"] = database + } +} + +func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + cf := testcontainers.ContainerFile{ + HostFilePath: configFile, + ContainerFilePath: "/etc/dolt/servercfg.d/server.cnf", + FileMode: 0o755, + } + req.Files = append(req.Files, cf) + } +} + +func WithCredsFile(credsFile string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + cf := testcontainers.ContainerFile{ + HostFilePath: credsFile, + ContainerFilePath: "/root/.dolt/creds/" + filepath.Base(credsFile), + FileMode: 0o755, + } + req.Files = append(req.Files, cf) + } +} + +func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + var initScripts []testcontainers.ContainerFile + for _, script := range scripts { + cf := testcontainers.ContainerFile{ + HostFilePath: script, + ContainerFilePath: "/docker-entrypoint-initdb.d/" + filepath.Base(script), + FileMode: 0o755, + } + initScripts = append(initScripts, cf) + } + req.Files = append(req.Files, initScripts...) + } +} diff --git a/modules/dolt/dolt_test.go b/modules/dolt/dolt_test.go new file mode 100644 index 0000000000..253eb356fb --- /dev/null +++ b/modules/dolt/dolt_test.go @@ -0,0 +1,197 @@ +package dolt_test + +import ( + "context" + "database/sql" + "os" + "path/filepath" + "testing" + + // Import mysql into the scope of this package (required) + _ "github.com/go-sql-driver/mysql" + + "github.com/testcontainers/testcontainers-go/modules/dolt" +) + +func TestDolt(t *testing.T) { + ctx := context.Background() + + container, err := dolt.RunContainer(ctx) + if err != nil { + t.Fatal(err) + } + + // Clean up the container after the test is complete + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + // perform assertions + // connectionString { + connectionString, err := container.ConnectionString(ctx) + // } + if err != nil { + t.Fatal(err) + } + + db, err := sql.Open("mysql", connectionString) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + if err = db.Ping(); err != nil { + t.Errorf("error pinging db: %+v\n", err) + } + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + + " `col_1` VARCHAR(128) NOT NULL, \n" + + " `col_2` VARCHAR(128) NOT NULL, \n" + + " PRIMARY KEY (`col_1`, `col_2`) \n" + + ")") + if err != nil { + t.Errorf("error creating table: %+v\n", err) + } +} + +func TestDoltWithNonRootUserAndEmptyPassword(t *testing.T) { + ctx := context.Background() + + _, err := dolt.RunContainer(ctx, + dolt.WithDatabase("foo"), + dolt.WithUsername("test"), + dolt.WithPassword("")) + if err.Error() != "empty password can be used only with the root user" { + t.Fatal(err) + } +} + +func TestDoltWithPublicRemoteCloneUrl(t *testing.T) { + ctx := context.Background() + + _, err := dolt.RunContainer(ctx, + dolt.WithDatabase("foo"), + dolt.WithUsername("test"), + dolt.WithPassword("test"), + dolt.WithScripts(filepath.Join("testdata", "check_clone_public.sh")), + dolt.WithDoltCloneRemoteUrl("fake-remote-url")) + if err != nil { + t.Fatal(err) + } +} + +func createTestCredsFile(t *testing.T) string { + file, err := os.CreateTemp(os.TempDir(), "prefix") + if err != nil { + t.Fatal(err) + } + defer file.Close() + _, err = file.WriteString("some-fake-creds") + if err != nil { + t.Fatal(err) + } + return file.Name() +} + +func TestDoltWithPrivateRemoteCloneUrl(t *testing.T) { + ctx := context.Background() + + filename := createTestCredsFile(t) + defer os.RemoveAll(filename) + _, err := dolt.RunContainer(ctx, + dolt.WithDatabase("foo"), + dolt.WithUsername("test"), + dolt.WithPassword("test"), + dolt.WithScripts(filepath.Join("testdata", "check_clone_private.sh")), + dolt.WithDoltCloneRemoteUrl("fake-remote-url"), + dolt.WithDoltCredsPublicKey("fake-public-key"), + dolt.WithCredsFile(filename)) + if err != nil { + t.Fatal(err) + } +} + +func TestDoltWithRootUserAndEmptyPassword(t *testing.T) { + ctx := context.Background() + + container, err := dolt.RunContainer(ctx, + dolt.WithDatabase("foo"), + dolt.WithUsername("root"), + dolt.WithPassword("")) + if err != nil { + t.Fatal(err) + } + + // Clean up the container after the test is complete + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + // perform assertions + connectionString := container.MustConnectionString(ctx) + + db, err := sql.Open("mysql", connectionString) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + if err = db.Ping(); err != nil { + t.Errorf("error pinging db: %+v\n", err) + } + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + + " `col_1` VARCHAR(128) NOT NULL, \n" + + " `col_2` VARCHAR(128) NOT NULL, \n" + + " PRIMARY KEY (`col_1`, `col_2`) \n" + + ")") + if err != nil { + t.Errorf("error creating table: %+v\n", err) + } +} + +func TestDoltWithScripts(t *testing.T) { + ctx := context.Background() + + container, err := dolt.RunContainer(ctx, + dolt.WithScripts(filepath.Join("testdata", "schema.sql"))) + if err != nil { + t.Fatal(err) + } + + // Clean up the container after the test is complete + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + // perform assertions + connectionString := container.MustConnectionString(ctx) + + db, err := sql.Open("mysql", connectionString) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + if err = db.Ping(); err != nil { + t.Errorf("error pinging db: %+v\n", err) + } + stmt, err := db.Prepare("SELECT name from profile") + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + row := stmt.QueryRow() + var name string + err = row.Scan(&name) + if err != nil { + t.Errorf("error fetching data") + } + if name != "profile 1" { + t.Fatal("The expected record was not found in the database.") + } +} diff --git a/modules/dolt/examples_test.go b/modules/dolt/examples_test.go new file mode 100644 index 0000000000..ceb6a79a14 --- /dev/null +++ b/modules/dolt/examples_test.go @@ -0,0 +1,97 @@ +package dolt_test + +import ( + "context" + "database/sql" + "fmt" + "log" + "path/filepath" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/dolt" +) + +func ExampleRunContainer() { + // runDoltContainer { + ctx := context.Background() + + doltContainer, err := dolt.RunContainer(ctx, + testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4"), + dolt.WithConfigFile(filepath.Join("testdata", "dolt_1_32_4.cnf")), + dolt.WithDatabase("foo"), + dolt.WithUsername("root"), + dolt.WithPassword("password"), + dolt.WithScripts(filepath.Join("testdata", "schema.sql")), + ) + if err != nil { + log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic + } + + // Clean up the container + defer func() { + if err := doltContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + } + }() + // } + + state, err := doltContainer.State(ctx) + if err != nil { + log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + } + + fmt.Println(state.Running) + + // Output: + // true +} + +func ExampleRunContainer_connect() { + ctx := context.Background() + + doltContainer, err := dolt.RunContainer(ctx, + testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4"), + dolt.WithConfigFile(filepath.Join("testdata", "dolt_1_32_4.cnf")), + dolt.WithDatabase("foo"), + dolt.WithUsername("bar"), + dolt.WithPassword("password"), + dolt.WithScripts(filepath.Join("testdata", "schema.sql")), + ) + if err != nil { + log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic + } + + defer func() { + if err := doltContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + } + }() + + connectionString := doltContainer.MustConnectionString(ctx) + + db, err := sql.Open("mysql", connectionString) + if err != nil { + log.Fatalf("failed to open database connection: %s", err) // nolint:gocritic + } + defer db.Close() + + if err = db.Ping(); err != nil { + log.Fatalf("failed to ping database: %s", err) // nolint:gocritic + } + stmt, err := db.Prepare("SELECT dolt_version();") + if err != nil { + log.Fatalf("failed to prepate sql statement: %s", err) // nolint:gocritic + } + defer stmt.Close() + row := stmt.QueryRow() + version := "" + err = row.Scan(&version) + if err != nil { + log.Fatalf("failed to scan row: %s", err) // nolint:gocritic + } + + fmt.Println(version) + + // Output: + // 1.32.4 +} diff --git a/modules/dolt/go.mod b/modules/dolt/go.mod new file mode 100644 index 0000000000..de3b871432 --- /dev/null +++ b/modules/dolt/go.mod @@ -0,0 +1,61 @@ +module github.com/testcontainers/testcontainers-go/modules/dolt + +go 1.21 + +require ( + github.com/go-sql-driver/mysql v1.7.1 + github.com/testcontainers/testcontainers-go v0.30.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.12 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.5+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.16.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/dolt/go.sum b/modules/dolt/go.sum new file mode 100644 index 0000000000..8f711685dd --- /dev/null +++ b/modules/dolt/go.sum @@ -0,0 +1,190 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= +github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/dolt/testdata/check_clone_private.sh b/modules/dolt/testdata/check_clone_private.sh new file mode 100755 index 0000000000..8a1d744d12 --- /dev/null +++ b/modules/dolt/testdata/check_clone_private.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ -z "$DOLT_CREDS_PUB_KEY" ]; then + echo "failed: DOLT_CREDS_PUB_KEY was unset" + exit 1; +fi + +if [ -z "$DOLT_REMOTE_CLONE_URL" ]; then + echo "failed: DOLT_REMOTE_CLONE_URL was unset" + exit 1; +fi + +if [ -z "$DOLT_REMOTE_CLONE_URL" ]; then + echo "failed: DOLT_REMOTE_CLONE_URL was unset" + exit 1; +fi + +if [ -z "$(ls -A /root/.dolt/creds)" ]; then + echo "failed: no creds found" + exit 1; +fi diff --git a/modules/dolt/testdata/check_clone_public.sh b/modules/dolt/testdata/check_clone_public.sh new file mode 100755 index 0000000000..183181ac0a --- /dev/null +++ b/modules/dolt/testdata/check_clone_public.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +if [ -z "$DOLT_REMOTE_CLONE_URL" ]; then + echo "failed: DOLT_REMOTE_CLONE_URL was unset" + exit 1; +fi diff --git a/modules/dolt/testdata/clone-db.sh b/modules/dolt/testdata/clone-db.sh new file mode 100644 index 0000000000..aea3172e7a --- /dev/null +++ b/modules/dolt/testdata/clone-db.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# use credentials for remote +if [ -n "$DOLT_CREDS_PUB_KEY" ]; then + dolt creds use "$DOLT_CREDS_PUB_KEY" +fi + +# clone +dolt sql -q "CALL DOLT_CLONE('$DOLT_REMOTE_CLONE_URL', '$DOLT_DATABASE');" diff --git a/modules/dolt/testdata/dolt_1_32_4.cnf b/modules/dolt/testdata/dolt_1_32_4.cnf new file mode 100644 index 0000000000..5fd6bdccf7 --- /dev/null +++ b/modules/dolt/testdata/dolt_1_32_4.cnf @@ -0,0 +1,46 @@ +log_level: info + +behavior: + read_only: false + autocommit: true + persistence_behavior: load + disable_client_multi_statements: false + dolt_transaction_commit: false + event_scheduler: "ON" + +user: + name: "root" + password: "" + +listener: + host: localhost + port: 3306 + max_connections: 100 + read_timeout_millis: 28800000 + write_timeout_millis: 28800000 + tls_key: null + tls_cert: null + require_secure_transport: null + allow_cleartext_passwords: null + +performance: + query_parallelism: null + +data_dir: /var/lib/dolt + +cfg_dir: /etc/dolt/doltcfg.d + +metrics: + labels: {} + host: null + port: -1 + +remotesapi: {} + +privilege_file: .doltcfg/privileges.db + +branch_control_file: .doltcfg/branch_control.db + +user_session_vars: [] + +jwks: [] diff --git a/modules/dolt/testdata/schema.sql b/modules/dolt/testdata/schema.sql new file mode 100644 index 0000000000..4a736a0434 --- /dev/null +++ b/modules/dolt/testdata/schema.sql @@ -0,0 +1,9 @@ +CREATE DATABASE IF NOT EXISTS test; +USE test; +CREATE TABLE IF NOT EXISTS profile ( + id MEDIUMINT NOT NULL AUTO_INCREMENT, + name VARCHAR(30) NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO profile (name) values ('profile 1'); diff --git a/sonar-project.properties b/sonar-project.properties index 131ecb3369..dcef78cc69 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -18,4 +18,4 @@ sonar.test.inclusions=**/*_test.go sonar.test.exclusions=**/vendor/** sonar.go.coverage.reportPaths=**/coverage.out -sonar.go.tests.reportPaths=TEST-unit.xml,examples/nginx/TEST-unit.xml,examples/toxiproxy/TEST-unit.xml,modulegen/TEST-unit.xml,modules/artemis/TEST-unit.xml,modules/cassandra/TEST-unit.xml,modules/chroma/TEST-unit.xml,modules/clickhouse/TEST-unit.xml,modules/cockroachdb/TEST-unit.xml,modules/compose/TEST-unit.xml,modules/consul/TEST-unit.xml,modules/couchbase/TEST-unit.xml,modules/elasticsearch/TEST-unit.xml,modules/gcloud/TEST-unit.xml,modules/inbucket/TEST-unit.xml,modules/influxdb/TEST-unit.xml,modules/k3s/TEST-unit.xml,modules/k6/TEST-unit.xml,modules/kafka/TEST-unit.xml,modules/localstack/TEST-unit.xml,modules/mariadb/TEST-unit.xml,modules/milvus/TEST-unit.xml,modules/minio/TEST-unit.xml,modules/mockserver/TEST-unit.xml,modules/mongodb/TEST-unit.xml,modules/mssql/TEST-unit.xml,modules/mysql/TEST-unit.xml,modules/nats/TEST-unit.xml,modules/neo4j/TEST-unit.xml,modules/ollama/TEST-unit.xml,modules/openfga/TEST-unit.xml,modules/openldap/TEST-unit.xml,modules/opensearch/TEST-unit.xml,modules/postgres/TEST-unit.xml,modules/pulsar/TEST-unit.xml,modules/qdrant/TEST-unit.xml,modules/rabbitmq/TEST-unit.xml,modules/redis/TEST-unit.xml,modules/redpanda/TEST-unit.xml,modules/registry/TEST-unit.xml,modules/surrealdb/TEST-unit.xml,modules/vault/TEST-unit.xml,modules/weaviate/TEST-unit.xml +sonar.go.tests.reportPaths=TEST-unit.xml,examples/nginx/TEST-unit.xml,examples/toxiproxy/TEST-unit.xml,modulegen/TEST-unit.xml,modules/artemis/TEST-unit.xml,modules/cassandra/TEST-unit.xml,modules/chroma/TEST-unit.xml,modules/clickhouse/TEST-unit.xml,modules/cockroachdb/TEST-unit.xml,modules/compose/TEST-unit.xml,modules/consul/TEST-unit.xml,modules/couchbase/TEST-unit.xml,modules/dolt/TEST-unit.xml,modules/elasticsearch/TEST-unit.xml,modules/gcloud/TEST-unit.xml,modules/inbucket/TEST-unit.xml,modules/influxdb/TEST-unit.xml,modules/k3s/TEST-unit.xml,modules/k6/TEST-unit.xml,modules/kafka/TEST-unit.xml,modules/localstack/TEST-unit.xml,modules/mariadb/TEST-unit.xml,modules/milvus/TEST-unit.xml,modules/minio/TEST-unit.xml,modules/mockserver/TEST-unit.xml,modules/mongodb/TEST-unit.xml,modules/mssql/TEST-unit.xml,modules/mysql/TEST-unit.xml,modules/nats/TEST-unit.xml,modules/neo4j/TEST-unit.xml,modules/ollama/TEST-unit.xml,modules/openfga/TEST-unit.xml,modules/openldap/TEST-unit.xml,modules/opensearch/TEST-unit.xml,modules/postgres/TEST-unit.xml,modules/pulsar/TEST-unit.xml,modules/qdrant/TEST-unit.xml,modules/rabbitmq/TEST-unit.xml,modules/redis/TEST-unit.xml,modules/redpanda/TEST-unit.xml,modules/registry/TEST-unit.xml,modules/surrealdb/TEST-unit.xml,modules/vault/TEST-unit.xml,modules/weaviate/TEST-unit.xml From 4002fc20c6de3fc03c677b10cf2570e31e9b1c36 Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Mon, 15 Apr 2024 19:58:38 -0400 Subject: [PATCH 06/14] feat: Bump default postgres version (#2481) * Bump default postgres version * Bump to use latest pg * Bump version from non-ancient version --------- Co-authored-by: bstrausser --- docs/modules/postgres.md | 2 +- modules/postgres/examples_test.go | 2 +- modules/postgres/postgres.go | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md index 47cb9115dc..683ed29162 100644 --- a/docs/modules/postgres.md +++ b/docs/modules/postgres.md @@ -41,7 +41,7 @@ When starting the Postgres container, you can pass options in a variadic way to #### Image If you need to set a different Postgres Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Postgres. E.g. `testcontainers.WithImage("docker.io/postgres:9.6")`. +for Postgres. E.g. `testcontainers.WithImage("docker.io/postgres:16-alpine")`. {% include "../features/common_functional_options.md" %} diff --git a/modules/postgres/examples_test.go b/modules/postgres/examples_test.go index ee164fdb6a..20fc50d696 100644 --- a/modules/postgres/examples_test.go +++ b/modules/postgres/examples_test.go @@ -21,7 +21,7 @@ func ExampleRunContainer() { dbPassword := "password" postgresContainer, err := postgres.RunContainer(ctx, - testcontainers.WithImage("docker.io/postgres:15.2-alpine"), + testcontainers.WithImage("docker.io/postgres:16-alpine"), postgres.WithInitScripts(filepath.Join("testdata", "init-user-db.sh")), postgres.WithConfigFile(filepath.Join("testdata", "my-postgres.conf")), postgres.WithDatabase(dbName), diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index cb3d01694b..cd349494c8 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -13,7 +13,7 @@ import ( const ( defaultUser = "postgres" defaultPassword = "postgres" - defaultPostgresImage = "docker.io/postgres:11-alpine" + defaultPostgresImage = "docker.io/postgres:16-alpine" defaultSnapshotName = "migrated_template" ) @@ -26,10 +26,9 @@ type PostgresContainer struct { snapshotName string } - // MustConnectionString panics if the address cannot be determined. func (c *PostgresContainer) MustConnectionString(ctx context.Context, args ...string) string { - addr, err := c.ConnectionString(ctx,args...) + addr, err := c.ConnectionString(ctx, args...) if err != nil { panic(err) } From e45c2faeac8aa6ab59a4e91f643b2ef9cb1de03c Mon Sep 17 00:00:00 2001 From: Guillaume St-Pierre Date: Thu, 18 Apr 2024 11:59:45 -0400 Subject: [PATCH 07/14] fix(postgres): Fix the non-default dbname error (#2489) * Fix the non-default dbname error The linked issue described in great detail an issue where we assumed everyone would use the default database user, whose home DB defaults to the postgres database. When that was not the case, the snapshots would fail silently as the user would not connect to the right database to take the commands. This PR fixes the issue by adding the dbname by default in the command, and adds a test to validate this works as intended. In addition, it also adds some logic to handle any error that does not cause the exec command to fail, such as database access failures. Run the added test to test this works as intended. Closes #2474 * Document the postgres dbname issue in the docs --- docs/modules/postgres.md | 6 +++ modules/postgres/postgres.go | 31 +++++++++++- modules/postgres/postgres_test.go | 81 +++++++++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md index 683ed29162..ebd1ed919e 100644 --- a/docs/modules/postgres.md +++ b/docs/modules/postgres.md @@ -97,6 +97,12 @@ This example shows the usage of the postgres module's Snapshot feature to give e to recreate the database container on every test or run heavy scripts to clean your database. This makes the individual tests very modular, since they always run on a brand-new database. +!!!tip + You should never pass the `"postgres"` system database as the container database name if you want to use snapshots. + The Snapshot logic requires dropping the connected database and using the system database to run commands, which will + not work if the database for the container is set to `"postgres"`. + + [Test with a reusable Postgres container](../../modules/postgres/postgres_test.go) inside_block:snapshotAndReset diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index cd349494c8..ccc66ba12a 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -3,6 +3,7 @@ package postgres import ( "context" "fmt" + "io" "net" "path/filepath" "strings" @@ -184,6 +185,10 @@ func (c *PostgresContainer) Snapshot(ctx context.Context, opts ...SnapshotOption snapshotName = config.snapshotName } + if c.dbName == "postgres" { + return fmt.Errorf("cannot snapshot the postgres system database as it cannot be dropped to be restored") + } + // execute the commands to create the snapshot, in order cmds := []string{ // Drop the snapshot database if it already exists @@ -195,10 +200,19 @@ func (c *PostgresContainer) Snapshot(ctx context.Context, opts ...SnapshotOption } for _, cmd := range cmds { - _, _, err := c.Exec(ctx, []string{"psql", "-U", c.user, "-c", cmd}) + exitCode, reader, err := c.Exec(ctx, []string{"psql", "-U", c.user, "-d", c.dbName, "-c", cmd}) if err != nil { return err } + if exitCode != 0 { + buf := new(strings.Builder) + _, err := io.Copy(buf, reader) + if err != nil { + return fmt.Errorf("non-zero exit code for snapshot command, could not read command output: %w", err) + } + + return fmt.Errorf("non-zero exit code for snapshot command: %s", buf.String()) + } } c.snapshotName = snapshotName @@ -219,6 +233,10 @@ func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) snapshotName = config.snapshotName } + if c.dbName == "postgres" { + return fmt.Errorf("cannot restore the postgres system database as it cannot be dropped to be restored") + } + // execute the commands to restore the snapshot, in order cmds := []string{ // Drop the entire database by connecting to the postgres global database @@ -228,10 +246,19 @@ func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) } for _, cmd := range cmds { - _, _, err := c.Exec(ctx, []string{"psql", "-U", c.user, "-d", "postgres", "-c", cmd}) + exitCode, reader, err := c.Exec(ctx, []string{"psql", "-v", "ON_ERROR_STOP=1", "-U", c.user, "-d", "postgres", "-c", cmd}) if err != nil { return err } + if exitCode != 0 { + buf := new(strings.Builder) + _, err := io.Copy(buf, reader) + if err != nil { + return fmt.Errorf("non-zero exit code for restore command, could not read command output: %w", err) + } + + return fmt.Errorf("non-zero exit code for restore command: %s", buf.String()) + } } return nil diff --git a/modules/postgres/postgres_test.go b/modules/postgres/postgres_test.go index 2a74e147f6..9dc2194685 100644 --- a/modules/postgres/postgres_test.go +++ b/modules/postgres/postgres_test.go @@ -87,12 +87,12 @@ func TestPostgres(t *testing.T) { connStr, err := container.ConnectionString(ctx, "sslmode=disable", "application_name=test") // } require.NoError(t, err) - - mustConnStr := container.MustConnectionString(ctx,"sslmode=disable", "application_name=test") - if mustConnStr!=connStr{ + + mustConnStr := container.MustConnectionString(ctx, "sslmode=disable", "application_name=test") + if mustConnStr != connStr { t.Errorf("ConnectionString was not equal to MustConnectionString") } - + // Ensure connection string is using generic format id, err := container.MappedPort(ctx, "5432/tcp") require.NoError(t, err) @@ -327,3 +327,76 @@ func TestSnapshot(t *testing.T) { }) // } } + +func TestSnapshotWithOverrides(t *testing.T) { + ctx := context.Background() + + dbname := "other-db" + user := "other-user" + password := "other-password" + + container, err := postgres.RunContainer( + ctx, + testcontainers.WithImage("docker.io/postgres:16-alpine"), + postgres.WithDatabase(dbname), + postgres.WithUsername(user), + postgres.WithPassword(password), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(5*time.Second)), + ) + if err != nil { + t.Fatal(err) + } + + _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + if err != nil { + t.Fatal(err) + } + + err = container.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + dbURL, err := container.ConnectionString(ctx) + if err != nil { + t.Fatal(err) + } + + t.Run("Test that the restore works when not using defaults", func(t *testing.T) { + _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "INSERT INTO users(name, age) VALUES ('test', 42)"}) + if err != nil { + t.Fatal(err) + } + + // Doing the restore before we connect since this resets the pgx connection + err = container.Restore(ctx) + if err != nil { + t.Fatal(err) + } + + conn, err := pgx.Connect(context.Background(), dbURL) + if err != nil { + t.Fatal(err) + } + defer conn.Close(context.Background()) + + var count int64 + err = conn.QueryRow(context.Background(), "SELECT COUNT(1) FROM users").Scan(&count) + if err != nil { + t.Fatal(err) + } + + if count != 0 { + t.Fatalf("Expected %d to equal `0`", count) + } + }) +} From 23348449a84465c5c3d99ff4da012620bdd8080a Mon Sep 17 00:00:00 2001 From: Patrick Jahn <33724206+p-jahn@users.noreply.github.com> Date: Sat, 20 Apr 2024 20:50:09 +0200 Subject: [PATCH 08/14] fix: fallback to URL-path when parsing auth config URL without scheme (#2488) --- docker_auth.go | 13 +++++++++++-- docker_auth_test.go | 29 ++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docker_auth.go b/docker_auth.go index e6fcfedf83..04b8527ccb 100644 --- a/docker_auth.go +++ b/docker_auth.go @@ -13,11 +13,14 @@ import ( "github.com/testcontainers/testcontainers-go/internal/core" ) +// defaultRegistryFn is variable overwritten in tests to check for behaviour with different default values. +var defaultRegistryFn = defaultRegistry + // DockerImageAuth returns the auth config for the given Docker image, extracting first its Docker registry. // Finally, it will use the credential helpers to extract the information from the docker config file // for that registry, if it exists. func DockerImageAuth(ctx context.Context, image string) (string, registry.AuthConfig, error) { - defaultRegistry := defaultRegistry(ctx) + defaultRegistry := defaultRegistryFn(ctx) reg := core.ExtractRegistry(image, defaultRegistry) cfgs, err := getDockerAuthConfigs() @@ -44,7 +47,13 @@ func getRegistryAuth(reg string, cfgs map[string]registry.AuthConfig) (registry. continue } - if keyURL.Host == reg { + host := keyURL.Host + if keyURL.Scheme == "" { + // url.Parse: The url may be relative (a path, without a host) [...] + host = keyURL.Path + } + + if host == reg { return cfg, true } } diff --git a/docker_auth_test.go b/docker_auth_test.go index 514cf753c7..33602a4347 100644 --- a/docker_auth_test.go +++ b/docker_auth_test.go @@ -153,7 +153,34 @@ func TestGetDockerConfig(t *testing.T) { }`) registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) - require.Equal(t, err, dockercfg.ErrCredentialsNotFound) + require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) + require.Empty(t, cfg) + + assert.Equal(t, imageReg, registry) + }) + + t.Run("fail to match registry authentication by host with empty URL scheme creds and missing default", func(t *testing.T) { + origDefaultRegistryFn := defaultRegistryFn + t.Cleanup(func() { + defaultRegistryFn = origDefaultRegistryFn + }) + defaultRegistryFn = func(ctx context.Context) string { + return "" + } + + base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret + imageReg := "" + imagePath := "image:latest" + + t.Setenv("DOCKER_AUTH_CONFIG", `{ + "auths": { + "example-auth.com": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } + }, + "credsStore": "desktop" + }`) + + registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) + require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) require.Empty(t, cfg) assert.Equal(t, imageReg, registry) From 539284ceb47a41d6ce45301f8793fbaaf89a3a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Sat, 20 Apr 2024 21:33:35 +0200 Subject: [PATCH 09/14] chore(deps): bump golang.org/x/net in modules (minio, gcloud, weaviate, compose, qdrant, couchbase, k3s, milvus, mockserver, pulsar, kafka) (#2505) * chore(deps): bump golang.org/x/net in /modules/kafka Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/pulsar Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/mockserver Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/milvus Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net from 0.19.0 to 0.23.0 in /modules/k3s Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.19.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/couchbase Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/qdrant Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/compose Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/weaviate Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/gcloud Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): bump golang.org/x/net in /modules/minio Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- modules/compose/go.mod | 8 ++++---- modules/compose/go.sum | 16 ++++++++-------- modules/couchbase/go.mod | 4 ++-- modules/couchbase/go.sum | 8 ++++---- modules/gcloud/go.mod | 6 +++--- modules/gcloud/go.sum | 12 ++++++------ modules/k3s/go.mod | 6 +++--- modules/k3s/go.sum | 12 ++++++------ modules/kafka/go.mod | 6 +++--- modules/kafka/go.sum | 12 ++++++------ modules/milvus/go.mod | 6 +++--- modules/milvus/go.sum | 12 ++++++------ modules/minio/go.mod | 6 +++--- modules/minio/go.sum | 12 ++++++------ modules/mockserver/go.mod | 4 ++-- modules/mockserver/go.sum | 12 ++++++------ modules/pulsar/go.mod | 6 +++--- modules/pulsar/go.sum | 16 ++++++++-------- modules/qdrant/go.mod | 4 ++-- modules/qdrant/go.sum | 8 ++++---- modules/weaviate/go.mod | 4 ++-- modules/weaviate/go.sum | 8 ++++---- 22 files changed, 94 insertions(+), 94 deletions(-) diff --git a/modules/compose/go.mod b/modules/compose/go.mod index 5a23abc6f0..96cdd1fd24 100644 --- a/modules/compose/go.mod +++ b/modules/compose/go.mod @@ -162,13 +162,13 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/modules/compose/go.sum b/modules/compose/go.sum index 825db4485b..118a1e9066 100644 --- a/modules/compose/go.sum +++ b/modules/compose/go.sum @@ -561,8 +561,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -588,8 +588,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= @@ -630,13 +630,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/modules/couchbase/go.mod b/modules/couchbase/go.mod index eebede780f..c5bdcc1dab 100644 --- a/modules/couchbase/go.mod +++ b/modules/couchbase/go.mod @@ -64,8 +64,8 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect diff --git a/modules/couchbase/go.sum b/modules/couchbase/go.sum index e3e55a9a0b..c427cf5ffe 100644 --- a/modules/couchbase/go.sum +++ b/modules/couchbase/go.sum @@ -210,8 +210,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -233,8 +233,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/modules/gcloud/go.mod b/modules/gcloud/go.mod index d30178c451..fca56a88c9 100644 --- a/modules/gcloud/go.mod +++ b/modules/gcloud/go.mod @@ -82,13 +82,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.14.0 // indirect diff --git a/modules/gcloud/go.sum b/modules/gcloud/go.sum index 806737017f..b49307580a 100644 --- a/modules/gcloud/go.sum +++ b/modules/gcloud/go.sum @@ -240,8 +240,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -264,8 +264,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -293,8 +293,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/modules/k3s/go.mod b/modules/k3s/go.mod index 6d9e8828c2..02c2f8e4ec 100644 --- a/modules/k3s/go.mod +++ b/modules/k3s/go.mod @@ -69,10 +69,10 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.16.1 // indirect diff --git a/modules/k3s/go.sum b/modules/k3s/go.sum index 09e4a891cf..07bcfedbc2 100644 --- a/modules/k3s/go.sum +++ b/modules/k3s/go.sum @@ -187,8 +187,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -206,10 +206,10 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index 2e54a892c9..a4ef694489 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -65,10 +65,10 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 3e264cf1a3..10a0ddc6b1 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -167,8 +167,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -185,8 +185,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -208,8 +208,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/modules/milvus/go.mod b/modules/milvus/go.mod index 5591b5f30c..1f04dbd6e0 100644 --- a/modules/milvus/go.mod +++ b/modules/milvus/go.mod @@ -63,9 +63,9 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/modules/milvus/go.sum b/modules/milvus/go.sum index 1074a14694..b208e7dfe8 100644 --- a/modules/milvus/go.sum +++ b/modules/milvus/go.sum @@ -370,8 +370,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -411,8 +411,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -420,8 +420,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/modules/minio/go.mod b/modules/minio/go.mod index 6bbb9d6f9e..8d046dac8a 100644 --- a/modules/minio/go.mod +++ b/modules/minio/go.mod @@ -57,12 +57,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect diff --git a/modules/minio/go.sum b/modules/minio/go.sum index 449cb41eae..efa1cb4dcd 100644 --- a/modules/minio/go.sum +++ b/modules/minio/go.sum @@ -146,8 +146,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -158,8 +158,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -176,8 +176,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/modules/mockserver/go.mod b/modules/mockserver/go.mod index 292f65fe93..dccc77f441 100644 --- a/modules/mockserver/go.mod +++ b/modules/mockserver/go.mod @@ -52,8 +52,8 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/modules/mockserver/go.sum b/modules/mockserver/go.sum index ef9ccee0e3..f0610c297a 100644 --- a/modules/mockserver/go.sum +++ b/modules/mockserver/go.sum @@ -139,8 +139,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -157,12 +157,12 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/pulsar/go.mod b/modules/pulsar/go.mod index 31fe1cc202..7e6558e460 100644 --- a/modules/pulsar/go.mod +++ b/modules/pulsar/go.mod @@ -81,10 +81,10 @@ require ( go.uber.org/atomic v1.7.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect diff --git a/modules/pulsar/go.sum b/modules/pulsar/go.sum index 55fcd701d3..2a968ae190 100644 --- a/modules/pulsar/go.sum +++ b/modules/pulsar/go.sum @@ -452,8 +452,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -522,12 +522,12 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -535,8 +535,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/modules/qdrant/go.mod b/modules/qdrant/go.mod index 2b12dbe4dc..508656b2e5 100644 --- a/modules/qdrant/go.mod +++ b/modules/qdrant/go.mod @@ -52,8 +52,8 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect diff --git a/modules/qdrant/go.sum b/modules/qdrant/go.sum index 6adf49c349..ad417ad0a6 100644 --- a/modules/qdrant/go.sum +++ b/modules/qdrant/go.sum @@ -136,8 +136,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -153,8 +153,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/modules/weaviate/go.mod b/modules/weaviate/go.mod index e619994145..3d0fd5b2fb 100644 --- a/modules/weaviate/go.mod +++ b/modules/weaviate/go.mod @@ -70,9 +70,9 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/modules/weaviate/go.sum b/modules/weaviate/go.sum index 8f174f4768..c8702b6acc 100644 --- a/modules/weaviate/go.sum +++ b/modules/weaviate/go.sum @@ -276,8 +276,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -310,8 +310,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 66f272126dc63238e395ac9625aff4e560c46fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 22 Apr 2024 12:39:53 +0200 Subject: [PATCH 10/14] feat: support Ryuk for the compose module (#2485) * feat: add testcontainers labels to compose containers * feat: support reaper for compose * chore: increase ryuk reconnection timeout on CI * chore: cache containers on UP * chore: more tuning for compose * chore: more consistent assertion * chore: the compose stack asks for the reaper, but each container then connects to it * chore: use different error groups the first time wait is called, the context is cancelled * chore: the lookup method include cache checks * chore: update tests to make them deterministic * chore: rename local compose testss * chore: support returning the dynamic port in the helper function * chore: try with default reconnection timeout * feat: support removing networks from compose * chore: support naming test services with local and api It will allow the tests to be more deterministic, as there could be service containers started from the local test suite with the same name as in the API test suite. * Revert "chore: try with default reconnection timeout" This reverts commit 336760c3011f4d535a9e8f345d02b750d2fa525c. * fix: typo --- .github/workflows/ci-test-go.yml | 2 + docker.go | 9 + modules/compose/compose.go | 23 ++ modules/compose/compose_api.go | 126 +++++++++- modules/compose/compose_api_test.go | 220 +++++++++++++----- modules/compose/compose_builder_test.go | 169 ++++++++++++++ modules/compose/compose_test.go | 110 +++++---- .../compose/testdata/docker-compose-build.yml | 4 +- .../testdata/docker-compose-complex.yml | 8 +- .../docker-compose-container-name.yml | 6 +- .../docker-compose-no-exposed-ports.yml | 2 +- .../testdata/docker-compose-override.yml | 6 +- .../testdata/docker-compose-postgres.yml | 4 +- .../testdata/docker-compose-simple.yml | 4 +- .../testdata/docker-compose-volume.yml | 4 +- reaper.go | 3 +- 16 files changed, 557 insertions(+), 143 deletions(-) create mode 100644 modules/compose/compose_builder_test.go diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index a56dab64a4..36f1e6de4f 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -50,6 +50,8 @@ jobs: continue-on-error: ${{ !inputs.fail-fast }} env: TESTCONTAINERS_RYUK_DISABLED: "${{ inputs.ryuk-disabled }}" + RYUK_CONNECTION_TIMEOUT: "${{ inputs.project-directory == 'modules/compose' && '5m' || '60s' }}" + RYUK_RECONNECTION_TIMEOUT: "${{ inputs.project-directory == 'modules/compose' && '30s' || '10s' }}" steps: - name: Setup rootless Docker if: ${{ inputs.rootless-docker }} diff --git a/docker.go b/docker.go index 79f85b8f00..426232b229 100644 --- a/docker.go +++ b/docker.go @@ -98,6 +98,11 @@ func (c *DockerContainer) SetProvider(provider *DockerProvider) { c.provider = provider } +// SetTerminationSignal sets the termination signal for the container +func (c *DockerContainer) SetTerminationSignal(signal chan bool) { + c.terminationSignal = signal +} + func (c *DockerContainer) GetContainerID() string { return c.ID } @@ -846,6 +851,10 @@ func (n *DockerNetwork) Remove(ctx context.Context) error { return n.provider.client.NetworkRemove(ctx, n.ID) } +func (n *DockerNetwork) SetTerminationSignal(signal chan bool) { + n.terminationSignal = signal +} + // DockerProvider implements the ContainerProvider interface type DockerProvider struct { *DockerProviderOptions diff --git a/modules/compose/compose.go b/modules/compose/compose.go index 242caef501..94e2021b90 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -3,6 +3,7 @@ package compose import ( "context" "errors" + "fmt" "path/filepath" "runtime" "strings" @@ -121,6 +122,25 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { return nil, err } + reaperProvider, err := testcontainers.NewDockerProvider() + if err != nil { + return nil, fmt.Errorf("failed to create reaper provider for compose: %w", err) + } + + tcConfig := reaperProvider.Config() + + var composeReaper *testcontainers.Reaper + if !tcConfig.RyukDisabled { + // NewReaper is deprecated: we need to find a way to create the reaper for compose + // bypassing the deprecation. + r, err := testcontainers.NewReaper(context.Background(), testcontainers.SessionID(), reaperProvider, "") + if err != nil { + return nil, fmt.Errorf("failed to create reaper for compose: %w", err) + } + + composeReaper = r + } + composeAPI := &dockerCompose{ name: composeOptions.Identifier, configs: composeOptions.Paths, @@ -129,6 +149,9 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { dockerClient: dockerCli.Client(), waitStrategies: make(map[string]wait.Strategy), containers: make(map[string]*testcontainers.DockerContainer), + networks: make(map[string]*testcontainers.DockerNetwork), + sessionID: testcontainers.SessionID(), + reaper: composeReaper, } return composeAPI, nil diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index 409875537c..62a8061e97 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -11,6 +11,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/pkg/api" + dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" @@ -134,6 +135,9 @@ type dockerCompose struct { // used in ServiceContainer(...) function to avoid calls to the Docker API containers map[string]*testcontainers.DockerContainer + // cache for networks in the compose stack + networks map[string]*testcontainers.DockerNetwork + // docker/compose API service instance used to control the compose stack composeService api.Service @@ -147,6 +151,12 @@ type dockerCompose struct { // compiled compose project // can be nil if the stack wasn't started yet project *types.Project + + // sessionID is used to identify the reaper session + sessionID string + + // reaper is used to clean up containers after the stack is stopped + reaper *testcontainers.Reaper } func (d *dockerCompose) ServiceContainer(ctx context.Context, svcName string) (*testcontainers.DockerContainer, error) { @@ -235,26 +245,89 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { return err } + err = d.lookupNetworks(ctx) + if err != nil { + return err + } + + if d.reaper != nil { + for _, n := range d.networks { + termSignal, err := d.reaper.Connect() + if err != nil { + return fmt.Errorf("failed to connect to reaper: %w", err) + } + n.SetTerminationSignal(termSignal) + + // Cleanup on error, otherwise set termSignal to nil before successful return. + defer func() { + if termSignal != nil { + termSignal <- true + } + }() + } + } + + errGrpContainers, errGrpCtx := errgroup.WithContext(ctx) + + for _, srv := range d.project.Services { + // we are going to connect each container to the reaper + srv := srv + errGrpContainers.Go(func() error { + dc, err := d.lookupContainer(errGrpCtx, srv.Name) + if err != nil { + return err + } + + if d.reaper != nil { + termSignal, err := d.reaper.Connect() + if err != nil { + return fmt.Errorf("failed to connect to reaper: %w", err) + } + dc.SetTerminationSignal(termSignal) + + // Cleanup on error, otherwise set termSignal to nil before successful return. + defer func() { + if termSignal != nil { + termSignal <- true + } + }() + } + + d.containers[srv.Name] = dc + + return nil + }) + } + + // wait here for the containers lookup to finish + if err := errGrpContainers.Wait(); err != nil { + return err + } + if len(d.waitStrategies) == 0 { return nil } - errGrp, errGrpCtx := errgroup.WithContext(ctx) + errGrpWait, errGrpCtx := errgroup.WithContext(ctx) for svc, strategy := range d.waitStrategies { // pinning the variables svc := svc strategy := strategy - errGrp.Go(func() error { + errGrpWait.Go(func() error { target, err := d.lookupContainer(errGrpCtx, svc) if err != nil { return err } + + // cache all the containers on compose.up + d.containers[svc] = target + return strategy.WaitUntilReady(errGrpCtx, target) }) } - return errGrp.Wait() + return errGrpWait.Wait() } func (d *dockerCompose) WaitForService(s string, strategy wait.Strategy) ComposeStack { @@ -327,6 +400,34 @@ func (d *dockerCompose) lookupContainer(ctx context.Context, svcName string) (*t return container, nil } +func (d *dockerCompose) lookupNetworks(ctx context.Context) error { + d.containersLock.Lock() + defer d.containersLock.Unlock() + + listOptions := dockertypes.NetworkListOptions{ + Filters: filters.NewArgs( + filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, d.name)), + ), + } + + networks, err := d.dockerClient.NetworkList(ctx, listOptions) + if err != nil { + return err + } + + for _, n := range networks { + dn := &testcontainers.DockerNetwork{ + ID: n.ID, + Name: n.Name, + Driver: n.Driver, + } + + d.networks[n.ID] = dn + } + + return nil +} + func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, error) { const nameAndDefaultConfigPath = 2 projectOptions := make([]cli.ProjectOptionsFn, len(d.projectOptions), len(d.projectOptions)+nameAndDefaultConfigPath) @@ -353,6 +454,11 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err api.ConfigFilesLabel: strings.Join(proj.ComposeFiles, ","), api.OneoffLabel: "False", // default, will be overridden by `run` command } + + for k, label := range testcontainers.GenericLabels() { + s.CustomLabels[k] = label + } + for i, envFile := range compiledOptions.EnvFiles { // add a label for each env file, indexed by its position s.CustomLabels[fmt.Sprintf("%s.%d", api.EnvironmentFileLabel, i)] = envFile @@ -361,6 +467,20 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err proj.Services[i] = s } + for key, n := range proj.Networks { + n.Labels = map[string]string{ + api.ProjectLabel: proj.Name, + api.NetworkLabel: n.Name, + api.VersionLabel: api.ComposeVersion, + } + + for k, label := range testcontainers.GenericLabels() { + n.Labels[k] = label + } + + proj.Networks[key] = n + } + return proj, nil } diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index b639b87eab..0cf9fcf4f1 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -15,18 +15,12 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/wait" ) -const ( - simpleCompose = "docker-compose-simple.yml" - complexCompose = "docker-compose-complex.yml" - composeWithVolume = "docker-compose-volume.yml" - testdataPackage = "testdata" -) - func TestDockerComposeAPI(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -41,7 +35,7 @@ func TestDockerComposeAPI(t *testing.T) { } func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -54,20 +48,20 @@ func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { err = compose. // Appending with _1 as given in the Java Test-Containers Example - WaitForService("mysql-1", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WaitForService("non-existent-srv-1", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) require.Error(t, err, "Expected error to be thrown because service with wait strategy is not running") - require.Equal(t, "no container found for service name mysql-1", err.Error()) + require.Equal(t, "no container found for service name non-existent-srv-1", err.Error()) serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -79,7 +73,7 @@ func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -87,12 +81,12 @@ func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithRunServices(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -104,22 +98,121 @@ func TestDockerComposeAPIWithRunServices(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). - Up(ctx, Wait(true), RunServices("nginx")) + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + Up(ctx, Wait(true), RunServices("api-nginx")) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - _, err = compose.ServiceContainer(context.Background(), "mysql") + _, err = compose.ServiceContainer(context.Background(), "api-mysql") require.Error(t, err, "Make sure there is no mysql container") assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") +} + +func TestDockerComposeAPI_TestcontainersLabelsArePresent(t *testing.T) { + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + t.Cleanup(func() { + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") + + // all the services in the compose has the Testcontainers Labels + for _, serviceName := range serviceNames { + c, err := compose.ServiceContainer(context.Background(), serviceName) + require.NoError(t, err, "compose.ServiceContainer()") + + inspect, err := compose.dockerClient.ContainerInspect(ctx, c.GetContainerID()) + require.NoError(t, err, "dockerClient.ContainerInspect()") + + for key, label := range testcontainers.GenericLabels() { + assert.Contains(t, inspect.Config.Labels, key, "Label %s is not present in container %s", key, c.GetContainerID()) + assert.Equal(t, label, inspect.Config.Labels[key], "Label %s value is not correct in container %s", key, c.GetContainerID()) + } + } +} + +func TestDockerComposeAPI_WithReaper(t *testing.T) { + config.Reset() // reset the config using the internal method to avoid the sync.Once + tcConfig := config.Read() + if tcConfig.RyukDisabled { + t.Skip("Ryuk is disabled, skipping test") + } + + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + // reaper is enabled, so we don't need to manually stop the containers: Ryuk will do it for us + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") +} + +func TestDockerComposeAPI_WithoutReaper(t *testing.T) { + config.Reset() // reset the config using the internal method to avoid the sync.Once + tcConfig := config.Read() + if !tcConfig.RyukDisabled { + t.Skip("Ryuk is enabled, skipping test") + } + + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + t.Cleanup(func() { + // because reaper is disabled, we need to manually stop the containers + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + Up(ctx, Wait(true)) + + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithStopServices(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerComposeWith( WithStackFiles(path), WithLogger(testcontainers.TestLogger(t))) @@ -137,11 +230,11 @@ func TestDockerComposeAPIWithStopServices(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") // close mysql container in purpose - mysqlContainer, err := compose.ServiceContainer(context.Background(), "mysql") + mysqlContainer, err := compose.ServiceContainer(context.Background(), "api-mysql") require.NoError(t, err, "Get mysql container") stopTimeout := 10 * time.Second @@ -152,11 +245,11 @@ func TestDockerComposeAPIWithStopServices(t *testing.T) { state, err := mysqlContainer.State(ctx) require.NoError(t, err) assert.False(t, state.Running) - assert.Equal(t, "exited", state.Status) + assert.Contains(t, []string{"exited", "removing"}, state.Status) } func TestDockerComposeAPIWithWaitForService(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -171,7 +264,7 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -179,11 +272,11 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -198,7 +291,7 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -206,11 +299,11 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithContainerName(t *testing.T) { - path := filepath.Join(testdataPackage, "docker-compose-container-name.yml") + path := RenderComposeWithName(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -225,7 +318,7 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -233,11 +326,11 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { - path := filepath.Join(testdataPackage, "docker-compose-no-exposed-ports.yml") + path := RenderComposeWithoutExposedPorts(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -249,7 +342,7 @@ func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("nginx", wait.ForLog("Configuration complete; ready for start up")). + WaitForService("api-nginx", wait.ForLog("Configuration complete; ready for start up")). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -257,11 +350,11 @@ func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -273,8 +366,8 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). - WaitForService("nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). + WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") @@ -282,12 +375,12 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -302,7 +395,7 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService("nginx_1", wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). + WaitForService("api-nginx_1", wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). Up(ctx, Wait(true)) // Verify that an error is thrown and not nil @@ -312,11 +405,11 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") } func TestDockerComposeAPIComplex(t *testing.T) { - path := filepath.Join(testdataPackage, complexCompose) + path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -332,14 +425,14 @@ func TestDockerComposeAPIComplex(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithEnvironment(t *testing.T) { identifier := testNameHash(t.Name()) - path := filepath.Join(testdataPackage, simpleCompose) + path, _ := RenderComposeSimple(t) compose, err := NewDockerComposeWith(WithStackFiles(path), identifier) require.NoError(t, err, "NewDockerCompose()") @@ -361,20 +454,21 @@ func TestDockerComposeAPIWithEnvironment(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 1) - assert.Contains(t, serviceNames, "nginx") + assert.Contains(t, serviceNames, "api-nginx") present := map[string]string{ "bar": "BAR", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, identifier.String(), "nginx", present, absent) + assertContainerEnvironmentVariables(t, identifier.String(), "api-nginx", present, absent) } func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { + simple, _ := RenderComposeSimple(t) composeFiles := ComposeStackFiles{ - filepath.Join(testdataPackage, simpleCompose), - filepath.Join(testdataPackage, "docker-compose-postgres.yml"), - filepath.Join(testdataPackage, "docker-compose-override.yml"), + simple, + RenderComposePostgres(t), + RenderComposeOverride(t), } identifier := testNameHash(t.Name()) @@ -400,20 +494,20 @@ func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { serviceNames := compose.Services() assert.Len(t, serviceNames, 3) - assert.Contains(t, serviceNames, "nginx") - assert.Contains(t, serviceNames, "mysql") - assert.Contains(t, serviceNames, "postgres") + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-mysql") + assert.Contains(t, serviceNames, "api-postgres") present := map[string]string{ "bar": "BAR", "foo": "FOO", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, identifier.String(), "nginx", present, absent) + assertContainerEnvironmentVariables(t, identifier.String(), "api-nginx", present, absent) } func TestDockerComposeAPIWithVolume(t *testing.T) { - path := filepath.Join(testdataPackage, composeWithVolume) + path := RenderComposeWithVolume(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -429,7 +523,7 @@ func TestDockerComposeAPIWithVolume(t *testing.T) { } func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { - path := filepath.Join(testdataPackage, composeWithVolume) + path := RenderComposeWithVolume(t) identifier := uuid.New().String() stackFiles := WithStackFiles(path) compose, err := NewDockerComposeWith(stackFiles, StackIdentifier(identifier)) @@ -456,7 +550,7 @@ func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { func TestDockerComposeAPIWithBuild(t *testing.T) { t.Skip("Skipping test because of the opentelemetry dependencies issue. See https://github.com/open-telemetry/opentelemetry-go/issues/4476#issuecomment-1840547010") - path := filepath.Join(testdataPackage, "docker-compose-build.yml") + path := RenderComposeWithBuild(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") @@ -468,7 +562,7 @@ func TestDockerComposeAPIWithBuild(t *testing.T) { t.Cleanup(cancel) err = compose. - WaitForService("echo", wait.ForHTTP("/env").WithPort("8080/tcp")). + WaitForService("api-echo", wait.ForHTTP("/env").WithPort("8080/tcp")). Up(ctx, Wait(true)) require.NoError(t, err, "compose.Up()") diff --git a/modules/compose/compose_builder_test.go b/modules/compose/compose_builder_test.go new file mode 100644 index 0000000000..fbfe37baa3 --- /dev/null +++ b/modules/compose/compose_builder_test.go @@ -0,0 +1,169 @@ +package compose + +import ( + "fmt" + "html/template" + "io" + "net" + "os" + "path/filepath" + "testing" +) + +const ( + testdataPackage = "testdata" +) + +func RenderComposeComplex(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t), getFreePort(t)} + + return writeTemplate(t, "docker-compose-complex.yml", ports...), ports +} + +func RenderComposeComplexForLocal(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t), getFreePort(t)} + + return writeTemplateWithSrvType(t, "docker-compose-complex.yml", "local", ports...), ports +} + +func RenderComposeOverride(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-override.yml", getFreePort(t)) +} + +func RenderComposeOverrideForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-override.yml", "local", getFreePort(t)) +} + +func RenderComposePostgres(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-postgres.yml", getFreePort(t)) +} + +func RenderComposePostgresForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-postgres.yml", "local", getFreePort(t)) +} + +func RenderComposeSimple(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t)} + return writeTemplate(t, "docker-compose-simple.yml", ports...), ports +} + +func RenderComposeSimpleForLocal(t *testing.T) (string, []int) { + t.Helper() + + ports := []int{getFreePort(t)} + return writeTemplateWithSrvType(t, "docker-compose-simple.yml", "local", ports...), ports +} + +func RenderComposeWithBuild(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-build.yml", getFreePort(t)) +} + +func RenderComposeWithName(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-container-name.yml", getFreePort(t)) +} + +func RenderComposeWithNameForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-container-name.yml", "local", getFreePort(t)) +} + +func RenderComposeWithoutExposedPorts(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-no-exposed-ports.yml") +} + +func RenderComposeWithoutExposedPortsForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-no-exposed-ports.yml", "local") +} + +func RenderComposeWithVolume(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-volume.yml", getFreePort(t)) +} + +func RenderComposeWithVolumeForLocal(t *testing.T) string { + t.Helper() + + return writeTemplateWithSrvType(t, "docker-compose-volume.yml", "local", getFreePort(t)) +} + +// getFreePort asks the kernel for a free open port that is ready to use. +func getFreePort(t *testing.T) int { + t.Helper() + + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to resolve TCP address: %v", err) + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + t.Fatalf("failed to listen on TCP address: %v", err) + } + defer l.Close() + + return l.Addr().(*net.TCPAddr).Port +} + +func writeTemplate(t *testing.T, templateFile string, port ...int) string { + return writeTemplateWithSrvType(t, templateFile, "api", port...) +} + +func writeTemplateWithSrvType(t *testing.T, templateFile string, srvType string, port ...int) string { + t.Helper() + + tmpDir := t.TempDir() + composeFile := filepath.Join(tmpDir, "docker-compose.yml") + + tmpl, err := template.ParseFiles(filepath.Join(testdataPackage, templateFile)) + if err != nil { + t.Fatalf("parsing template file: %s", err) + } + + values := map[string]interface{}{} + for i, p := range port { + values[fmt.Sprintf("Port_%d", i)] = p + } + + values["ServiceType"] = srvType + + output, err := os.Create(composeFile) + if err != nil { + t.Fatalf("creating output file: %s", err) + } + defer output.Close() + + executeTemplateFile := func(templateFile *template.Template, wr io.Writer, data any) error { + return templateFile.Execute(wr, data) + } + + err = executeTemplateFile(tmpl, output, values) + if err != nil { + t.Fatalf("executing template file: %s", err) + } + + return composeFile +} diff --git a/modules/compose/compose_test.go b/modules/compose/compose_test.go index c87e204725..2453507b06 100644 --- a/modules/compose/compose_test.go +++ b/modules/compose/compose_test.go @@ -20,11 +20,6 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -var ( - complexComposeTestFile string = filepath.Join("testdata", "docker-compose-complex.yml") - simpleComposeTestFile string = filepath.Join("testdata", "docker-compose-simple.yml") -) - func ExampleNewLocalDockerCompose() { path := "/path/to/docker-compose.yml" @@ -103,7 +98,7 @@ func ExampleLocalDockerCompose_WithEnv() { } func TestLocalDockerCompose(t *testing.T) { - path := simpleComposeTestFile + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -120,8 +115,8 @@ func TestLocalDockerCompose(t *testing.T) { checkIfError(t, err) } -func TestDockerComposeStrategyForInvalidService(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeStrategyForInvalidService(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -135,16 +130,16 @@ func TestDockerComposeStrategyForInvalidService(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService(compose.Format("non-existent-srv", "1"), ports[0], wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Invoke() require.Error(t, err.Error, "Expected error to be thrown because service with wait strategy is not running") assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitLogStrategy(t *testing.T) { - path := complexComposeTestFile +func TestLocalDockerComposeWithWaitLogStrategy(t *testing.T) { + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -158,17 +153,17 @@ func TestDockerComposeWithWaitLogStrategy(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). // Appending with _1 as given in the Java Test-Containers Example - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). + WithExposedService(compose.Format("local-mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } -func TestDockerComposeWithWaitForService(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithWaitForService(t *testing.T) { + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -184,16 +179,16 @@ func TestDockerComposeWithWaitForService(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WaitForService(compose.Format("nginx", "1"), wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WaitForService(compose.Format("local-nginx", "1"), wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitForShortLifespanService(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-short-lifespan.yml") +func TestLocalDockerComposeWithWaitForShortLifespanService(t *testing.T) { + path := filepath.Join(testdataPackage, "docker-compose-short-lifespan.yml") identifier := strings.ToLower(uuid.New().String()) @@ -217,8 +212,8 @@ func TestDockerComposeWithWaitForShortLifespanService(t *testing.T) { assert.Contains(t, compose.Services, "tzatziki") } -func TestDockerComposeWithWaitHTTPStrategy(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithWaitHTTPStrategy(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -234,16 +229,16 @@ func TestDockerComposeWithWaitHTTPStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-nginx", "1"), ports[0], wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithContainerName(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-container-name.yml") +func TestLocalDockerComposeWithContainerName(t *testing.T) { + path := RenderComposeWithNameForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -259,16 +254,16 @@ func TestDockerComposeWithContainerName(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginxy", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService("local-nginxy", 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-no-exposed-ports.yml") +func TestLocalDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { + path := RenderComposeWithoutExposedPortsForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -281,16 +276,16 @@ func TestDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.ForLog("Configuration complete; ready for start up")). + WithExposedService(compose.Format("local-nginx", "1"), 9080, wait.ForLog("Configuration complete; ready for start up")). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } -func TestDockerComposeWithMultipleWaitStrategies(t *testing.T) { - path := complexComposeTestFile +func TestLocalDockerComposeWithMultipleWaitStrategies(t *testing.T) { + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -303,18 +298,18 @@ func TestDockerComposeWithMultipleWaitStrategies(t *testing.T) { err := compose. WithCommand([]string{"up", "-d"}). - WithExposedService(compose.Format("mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). - WithExposedService(compose.Format("nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-mysql", "1"), 13306, wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). + WithExposedService(compose.Format("local-nginx", "1"), 9080, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Invoke() checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } -func TestDockerComposeWithFailedStrategy(t *testing.T) { - path := simpleComposeTestFile +func TestLocalDockerComposeWithFailedStrategy(t *testing.T) { + path, ports := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -330,18 +325,18 @@ func TestDockerComposeWithFailedStrategy(t *testing.T) { WithEnv(map[string]string{ "bar": "BAR", }). - WithExposedService("nginx_1", 9080, wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). + WithExposedService("local-nginx_1", ports[0], wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). Invoke() // Verify that an error is thrown and not nil // A specific error message matcher is not asserted since the docker library can change the return message, breaking this test require.Error(t, err.Error, "Expected error to be thrown because of a wrong suplied wait strategy") assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") } func TestLocalDockerComposeComplex(t *testing.T) { - path := complexComposeTestFile + path, _ := RenderComposeComplexForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -358,12 +353,12 @@ func TestLocalDockerComposeComplex(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 2) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") } func TestLocalDockerComposeWithEnvironment(t *testing.T) { - path := simpleComposeTestFile + path, _ := RenderComposeSimpleForLocal(t) identifier := strings.ToLower(uuid.New().String()) @@ -383,20 +378,21 @@ func TestLocalDockerComposeWithEnvironment(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 1) - assert.Contains(t, compose.Services, "nginx") + assert.Contains(t, compose.Services, "local-nginx") present := map[string]string{ "bar": "BAR", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, compose.Identifier, "nginx", present, absent) + assertContainerEnvironmentVariables(t, compose.Identifier, "local-nginx", present, absent) } func TestLocalDockerComposeWithMultipleComposeFiles(t *testing.T) { + simple, _ := RenderComposeSimpleForLocal(t) composeFiles := []string{ - simpleComposeTestFile, - filepath.Join("testdata", "docker-compose-postgres.yml"), - filepath.Join("testdata", "docker-compose-override.yml"), + simple, + RenderComposePostgresForLocal(t), + RenderComposeOverrideForLocal(t), } identifier := strings.ToLower(uuid.New().String()) @@ -418,20 +414,20 @@ func TestLocalDockerComposeWithMultipleComposeFiles(t *testing.T) { checkIfError(t, err) assert.Len(t, compose.Services, 3) - assert.Contains(t, compose.Services, "nginx") - assert.Contains(t, compose.Services, "mysql") - assert.Contains(t, compose.Services, "postgres") + assert.Contains(t, compose.Services, "local-nginx") + assert.Contains(t, compose.Services, "local-mysql") + assert.Contains(t, compose.Services, "local-postgres") present := map[string]string{ "bar": "BAR", "foo": "FOO", } absent := map[string]string{} - assertContainerEnvironmentVariables(t, compose.Identifier, "nginx", present, absent) + assertContainerEnvironmentVariables(t, compose.Identifier, "local-nginx", present, absent) } func TestLocalDockerComposeWithVolume(t *testing.T) { - path := filepath.Join("testdata", "docker-compose-volume.yml") + path := RenderComposeWithVolumeForLocal(t) identifier := strings.ToLower(uuid.New().String()) diff --git a/modules/compose/testdata/docker-compose-build.yml b/modules/compose/testdata/docker-compose-build.yml index 1728e177bd..88c6ccda40 100644 --- a/modules/compose/testdata/docker-compose-build.yml +++ b/modules/compose/testdata/docker-compose-build.yml @@ -1,11 +1,11 @@ version: '3' services: - echo: + {{ .ServiceType }}-echo: build: dockerfile: echoserver.Dockerfile environment: FOO: "Hello, World!" ports: - target: 8080 - published: 8080 + published: {{ .Port_0 }} protocol: tcp \ No newline at end of file diff --git a/modules/compose/testdata/docker-compose-complex.yml b/modules/compose/testdata/docker-compose-complex.yml index 46a674ca04..f2eacf026a 100644 --- a/modules/compose/testdata/docker-compose-complex.yml +++ b/modules/compose/testdata/docker-compose-complex.yml @@ -1,13 +1,13 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine ports: - - "9080:80" - mysql: + - "{{ .Port_0 }}:80" + {{ .ServiceType }}-mysql: image: docker.io/mysql:8.0.36 environment: - MYSQL_DATABASE=db - MYSQL_ROOT_PASSWORD=my-secret-pw ports: - - "13306:3306" \ No newline at end of file + - "{{ .Port_1 }}:3306" \ No newline at end of file diff --git a/modules/compose/testdata/docker-compose-container-name.yml b/modules/compose/testdata/docker-compose-container-name.yml index db54e1d82f..b7e497fd84 100644 --- a/modules/compose/testdata/docker-compose-container-name.yml +++ b/modules/compose/testdata/docker-compose-container-name.yml @@ -1,9 +1,9 @@ version: '3' services: - nginx: - container_name: nginxy + {{ .ServiceType }}-nginx: + container_name: {{ .ServiceType }}-nginxy image: docker.io/nginx:stable-alpine environment: bar: ${bar} ports: - - "9080:80" + - "{{ .Port_0 }}:80" diff --git a/modules/compose/testdata/docker-compose-no-exposed-ports.yml b/modules/compose/testdata/docker-compose-no-exposed-ports.yml index d1e27913e1..58da1351f4 100644 --- a/modules/compose/testdata/docker-compose-no-exposed-ports.yml +++ b/modules/compose/testdata/docker-compose-no-exposed-ports.yml @@ -1,6 +1,6 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine ports: - "80" diff --git a/modules/compose/testdata/docker-compose-override.yml b/modules/compose/testdata/docker-compose-override.yml index 3fcb135947..8a44e78631 100644 --- a/modules/compose/testdata/docker-compose-override.yml +++ b/modules/compose/testdata/docker-compose-override.yml @@ -1,10 +1,10 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine - mysql: + {{ .ServiceType }}-mysql: image: docker.io/mysql:8.0.36 environment: MYSQL_RANDOM_ROOT_PASSWORD: Y ports: - - "13306:3306" + - "{{ .Port_0 }}:3306" diff --git a/modules/compose/testdata/docker-compose-postgres.yml b/modules/compose/testdata/docker-compose-postgres.yml index db6720eb3e..7988a43266 100644 --- a/modules/compose/testdata/docker-compose-postgres.yml +++ b/modules/compose/testdata/docker-compose-postgres.yml @@ -1,8 +1,8 @@ version: '3' services: - postgres: + {{ .ServiceType }}-postgres: image: docker.io/postgres:14 environment: POSTGRES_PASSWORD: s3cr3t ports: - - "15432:5432" + - "{{ .Port_0 }}:5432" diff --git a/modules/compose/testdata/docker-compose-simple.yml b/modules/compose/testdata/docker-compose-simple.yml index ddb324cbf7..90b9a4be87 100644 --- a/modules/compose/testdata/docker-compose-simple.yml +++ b/modules/compose/testdata/docker-compose-simple.yml @@ -1,9 +1,9 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine environment: bar: ${bar} foo: ${foo} ports: - - "9080:80" + - "{{ .Port_0 }}:80" diff --git a/modules/compose/testdata/docker-compose-volume.yml b/modules/compose/testdata/docker-compose-volume.yml index cc14090c53..81add904d0 100644 --- a/modules/compose/testdata/docker-compose-volume.yml +++ b/modules/compose/testdata/docker-compose-volume.yml @@ -1,6 +1,6 @@ version: '3' services: - nginx: + {{ .ServiceType }}-nginx: image: docker.io/nginx:stable-alpine volumes: - type: volume @@ -11,7 +11,7 @@ services: environment: bar: ${bar} ports: - - "9080:80" + - "{{ .Port_0 }}:80" volumes: mydata: diff --git a/reaper.go b/reaper.go index 859f8b76de..54feb90cbe 100644 --- a/reaper.go +++ b/reaper.go @@ -46,7 +46,8 @@ type ReaperProvider interface { } // NewReaper creates a Reaper with a sessionID to identify containers and a provider to use -// Deprecated: it's not possible to create a reaper anymore. +// Deprecated: it's not possible to create a reaper anymore. Compose module uses this method +// to create a reaper for the compose stack. func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error) { return reuseOrCreateReaper(ctx, sessionID, provider) } From c94d40d7031658939313605d54a9fdaa4ac66957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 22 Apr 2024 16:21:16 +0200 Subject: [PATCH 11/14] chore: add funding button for testcontainers (#2510) --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..9889f256ef --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [testcontainers] From 201b3de0bbd264c663e36eff12f87e1fde22c8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 22 Apr 2024 17:49:20 +0200 Subject: [PATCH 12/14] feat: support passing io.Reader for compose files when creating a compose instance (#2509) * feat: support passing io.Reader when creating a compose instance * docs: change title --- docs/features/docker_compose.md | 21 ++++++++++-- modules/compose/compose.go | 40 ++++++++++++++--------- modules/compose/compose_api.go | 50 +++++++++++++++++++++++++++++ modules/compose/compose_api_test.go | 41 +++++++++++++++++++++++ 4 files changed, 135 insertions(+), 17 deletions(-) diff --git a/docs/features/docker_compose.md b/docs/features/docker_compose.md index 352f2c84eb..bfb4f90b16 100644 --- a/docs/features/docker_compose.md +++ b/docs/features/docker_compose.md @@ -20,7 +20,7 @@ Because `compose` v2 is implemented in Go it's possible for _Testcontainers for 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. -### Basic examples +### Usage Use the convenience `NewDockerCompose(...)` constructor which creates a random identifier and takes a variable number of stack files: @@ -53,7 +53,24 @@ func TestSomething(t *testing.T) { } ``` -Use the advanced `NewDockerComposeWith(...)` constructor allowing you to specify an identifier: +Use the advanced `NewDockerComposeWith(...)` constructor allowing you to customise the compose execution with options: + +- `StackIdentifier`: the identifier for the stack, which is used to name the network and containers. If not passed, a random identifier is generated. +- `WithStackFiles`: specify the Docker Compose stack files to use, as a variadic argument of string paths where the stack files are located. +- `WithStackReaders`: specify the Docker Compose stack files to use, as a variadic argument of `io.Reader` instances. It will create a temporary file in the temp dir of the given O.S., that will be removed after the `Down` method is called. You can use both `WithComposeStackFiles` and `WithComposeStackReaders` at the same time. + +#### Compose Up options + +- `RemoveOrphans`: remove orphaned containers after the stack is stopped. +- `Wait`: will wait until the containers reached the running|healthy state. + +#### Compose Down options + +- `RemoveImages`: remove images after the stack is stopped. The `RemoveImagesAll` option will remove all images, while `RemoveImagesLocal` will remove only the images that don't have a tag. +- `RemoveOrphans`: remove orphaned containers after the stack is stopped. +- `RemoveVolumes`: remove volumes after the stack is stopped. + +#### Example ```go package example_test diff --git a/modules/compose/compose.go b/modules/compose/compose.go index 94e2021b90..90f3fd804d 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "path/filepath" "runtime" "strings" @@ -27,9 +28,10 @@ const ( var ErrNoStackConfigured = errors.New("no stack files configured") type composeStackOptions struct { - Identifier string - Paths []string - Logger testcontainers.Logging + Identifier string + Paths []string + temporaryPaths map[string]bool + Logger testcontainers.Logging } type ComposeStackOption interface { @@ -95,14 +97,21 @@ func WithStackFiles(filePaths ...string) ComposeStackOption { return ComposeStackFiles(filePaths) } +// WithStackReaders supports reading the compose file/s from a reader. +// This function will panic if it's no possible to read the content from the reader. +func WithStackReaders(readers ...io.Reader) ComposeStackOption { + return ComposeStackReaders(readers) +} + func NewDockerCompose(filePaths ...string) (*dockerCompose, error) { return NewDockerComposeWith(WithStackFiles(filePaths...)) } func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { composeOptions := composeStackOptions{ - Identifier: uuid.New().String(), - Logger: testcontainers.Logger, + Identifier: uuid.New().String(), + temporaryPaths: make(map[string]bool), + Logger: testcontainers.Logger, } for i := range opts { @@ -142,16 +151,17 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { } composeAPI := &dockerCompose{ - name: composeOptions.Identifier, - configs: composeOptions.Paths, - logger: composeOptions.Logger, - composeService: compose.NewComposeService(dockerCli), - dockerClient: dockerCli.Client(), - waitStrategies: make(map[string]wait.Strategy), - containers: make(map[string]*testcontainers.DockerContainer), - networks: make(map[string]*testcontainers.DockerNetwork), - sessionID: testcontainers.SessionID(), - reaper: composeReaper, + name: composeOptions.Identifier, + configs: composeOptions.Paths, + temporaryConfigs: composeOptions.temporaryPaths, + logger: composeOptions.Logger, + composeService: compose.NewComposeService(dockerCli), + dockerClient: dockerCli.Client(), + waitStrategies: make(map[string]wait.Strategy), + containers: make(map[string]*testcontainers.DockerContainer), + networks: make(map[string]*testcontainers.DockerNetwork), + sessionID: testcontainers.SessionID(), + reaper: composeReaper, } return composeAPI, nil diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index 62a8061e97..8f8ea5320b 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -3,9 +3,14 @@ package compose import ( "context" "fmt" + "io" + "os" + "path/filepath" "sort" + "strconv" "strings" "sync" + "time" "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/types" @@ -43,9 +48,12 @@ func RunServices(serviceNames ...string) StackUpOption { }) } +// Deprecated: will be removed in the next major release // IgnoreOrphans - Ignore legacy containers for services that are not defined in the project type IgnoreOrphans bool +// Deprecated: will be removed in the next major release +// //nolint:unused func (io IgnoreOrphans) applyToStackUp(co *api.CreateOptions, _ *api.StartOptions) { co.IgnoreOrphans = bool(io) @@ -87,6 +95,40 @@ func (ri RemoveImages) applyToStackDown(o *stackDownOptions) { } } +type ComposeStackReaders []io.Reader + +func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) { + f := make([]string, len(r)) + baseName := "docker-compose-%d.yml" + for i, reader := range r { + tmp := os.TempDir() + tmp = filepath.Join(tmp, strconv.FormatInt(time.Now().UnixNano(), 10)) + err := os.MkdirAll(tmp, 0755) + if err != nil { + panic(err) + } + + name := fmt.Sprintf(baseName, i) + + bs, err := io.ReadAll(reader) + if err != nil { + panic(err) + } + + err = os.WriteFile(filepath.Join(tmp, name), bs, 0644) + if err != nil { + panic(err) + } + + f[i] = filepath.Join(tmp, name) + + // mark the file for removal as it was generated on the fly + o.temporaryPaths[f[i]] = true + } + + o.Paths = f +} + type ComposeStackFiles []string func (f ComposeStackFiles) applyToComposeStack(o *composeStackOptions) { @@ -121,6 +163,9 @@ type dockerCompose struct { // paths to stack files that will be considered when compiling the final compose project configs []string + // used to remove temporary files that were generated on the fly + temporaryConfigs map[string]bool + // used to set logger in DockerContainer logger testcontainers.Logging @@ -186,6 +231,11 @@ func (d *dockerCompose) Down(ctx context.Context, opts ...StackDownOption) error for i := range opts { opts[i].applyToStackDown(&options) } + defer func() { + for cfg := range d.temporaryConfigs { + _ = os.Remove(cfg) + } + }() return d.composeService.Down(ctx, d.name, options.DownOptions) } diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index 0cf9fcf4f1..d703934669 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "hash/fnv" + "os" "path/filepath" + "strings" "testing" "time" @@ -429,6 +431,45 @@ func TestDockerComposeAPIComplex(t *testing.T) { assert.Contains(t, serviceNames, "api-mysql") } +func TestDockerComposeAPIWithStackReader(t *testing.T) { + identifier := testNameHash(t.Name()) + + composeContent := `version: '3.7' +services: + api-nginx: + image: docker.io/nginx:stable-alpine + environment: + bar: ${bar} + foo: ${foo} +` + + compose, err := NewDockerComposeWith(WithStackReaders(strings.NewReader(composeContent)), identifier) + require.NoError(t, err, "NewDockerCompose()") + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WithEnv(map[string]string{ + "foo": "FOO", + "bar": "BAR", + }). + Up(ctx, Wait(true)) + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + assert.Len(t, serviceNames, 1) + assert.Contains(t, serviceNames, "api-nginx") + + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + + // check files where removed + f, err := os.Stat(compose.configs[0]) + require.Error(t, err, "File should be removed") + require.True(t, os.IsNotExist(err), "File should be removed") + require.Nil(t, f, "File should be removed") +} func TestDockerComposeAPIWithEnvironment(t *testing.T) { identifier := testNameHash(t.Name()) From 39f63ecb5e81dcdd0d5108f8819ddb5d131206f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 22 Apr 2024 22:30:41 +0200 Subject: [PATCH 13/14] feat: support overriding the default recreate options for compose (#2511) * feat: support overriding the default recreate options for compose * chore: validate recreation values --- docs/features/docker_compose.md | 8 ++++++-- modules/compose/compose.go | 14 ++++++++++++++ modules/compose/compose_api.go | 23 +++++++++++++++++++++++ modules/compose/compose_api_test.go | 17 +++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/docs/features/docker_compose.md b/docs/features/docker_compose.md index bfb4f90b16..8a2d068792 100644 --- a/docs/features/docker_compose.md +++ b/docs/features/docker_compose.md @@ -61,7 +61,9 @@ Use the advanced `NewDockerComposeWith(...)` constructor allowing you to customi #### Compose Up options -- `RemoveOrphans`: remove orphaned containers after the stack is stopped. +- `Recreate`: recreate the containers. If any other value than `api.RecreateNever`, `api.RecreateForce` or `api.RecreateDiverged` is provided, the default value `api.RecreateForce` will be used. +- `RecreateDependencies`: recreate dependent containers. If any other value than `api.RecreateNever`, `api.RecreateForce` or `api.RecreateDiverged` is provided, the default value `api.RecreateForce` will be used. +- `RemoveOrphans`: remove orphaned containers when the stack is upped. - `Wait`: will wait until the containers reached the running|healthy state. #### Compose Down options @@ -80,6 +82,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/docker/compose/v2/pkg/api" tc "github.com/testcontainers/testcontainers-go/modules/compose" ) @@ -95,7 +99,7 @@ func TestSomethingElse(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - require.NoError(t, compose.Up(ctx, tc.Wait(true)), "compose.Up()") + require.NoError(t, compose.Up(ctx, tc.WithRecreate(api.RecreateNever), tc.Wait(true)), "compose.Up()") // do some testing here } diff --git a/modules/compose/compose.go b/modules/compose/compose.go index 90f3fd804d..cc4a6f9b8a 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -93,6 +93,20 @@ type waitService struct { publishedPort int } +// WithRecreate defines the strategy to apply on existing containers. If any other value than +// api.RecreateNever, api.RecreateForce or api.RecreateDiverged is provided, the default value +// api.RecreateForce will be used. +func WithRecreate(recreate string) StackUpOption { + return Recreate(recreate) +} + +// WithRecreateDependencies defines the strategy to apply on container dependencies. If any other value than +// api.RecreateNever, api.RecreateForce or api.RecreateDiverged is provided, the default value +// api.RecreateForce will be used. +func WithRecreateDependencies(recreate string) StackUpOption { + return RecreateDependencies(recreate) +} + func WithStackFiles(filePaths ...string) ComposeStackOption { return ComposeStackFiles(filePaths) } diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index 8f8ea5320b..d80d8e9fa7 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -59,6 +59,29 @@ func (io IgnoreOrphans) applyToStackUp(co *api.CreateOptions, _ *api.StartOption co.IgnoreOrphans = bool(io) } +// Recreate will recreate the containers that are already running +type Recreate string + +func (r Recreate) applyToStackUp(o *stackUpOptions) { + o.Recreate = validateRecreate(string(r)) +} + +// RecreateDependencies will recreate the dependencies of the services that are already running +type RecreateDependencies string + +func (r RecreateDependencies) applyToStackUp(o *stackUpOptions) { + o.RecreateDependencies = validateRecreate(string(r)) +} + +func validateRecreate(r string) string { + switch r { + case api.RecreateDiverged, api.RecreateForce, api.RecreateNever: + return r + default: + return api.RecreateForce + } +} + // RemoveOrphans will clean up containers that are not declared on the compose model but own the same labels type RemoveOrphans bool diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index d703934669..1a203347ec 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/docker/compose/v2/pkg/api" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/volume" "github.com/google/uuid" @@ -563,6 +564,22 @@ func TestDockerComposeAPIWithVolume(t *testing.T) { require.NoError(t, err, "compose.Up()") } +func TestDockerComposeAPIWithRecreate(t *testing.T) { + path, _ := RenderComposeComplex(t) + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + t.Cleanup(func() { + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + }) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose.Up(ctx, WithRecreate(api.RecreateNever), WithRecreateDependencies(api.RecreateNever), Wait(true)) + require.NoError(t, err, "compose.Up()") +} + func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { path := RenderComposeWithVolume(t) identifier := uuid.New().String() From ab604374ddda627b2540741eaa8eb52b6da741a5 Mon Sep 17 00:00:00 2001 From: Patrick Jahn <33724206+p-jahn@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:20:07 +0200 Subject: [PATCH 14/14] fix: don't retry on permanent APIClient errors (#2506) * fix: don't retry on permanent APIClient errors * fix: add more tests for un-retryable scenarios --- docker.go | 26 ++++++- docker_test.go | 207 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 228 insertions(+), 5 deletions(-) diff --git a/docker.go b/docker.go index 426232b229..9d3ae6f629 100644 --- a/docker.go +++ b/docker.go @@ -895,8 +895,7 @@ func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (st resp, err = p.client.ImageBuild(ctx, buildOptions.Context, buildOptions) if err != nil { buildError = errors.Join(buildError, err) - var enf errdefs.ErrNotFound - if errors.As(err, &enf) { + if isPermanentClientError(err) { return backoff.Permanent(err) } Logger.Printf("Failed to build image: %s, will retry", err) @@ -1175,6 +1174,9 @@ func (p *DockerProvider) waitContainerCreation(ctx context.Context, name string) return container, backoff.Retry(func() error { c, err := p.findContainerByName(ctx, name) if err != nil { + if !errdefs.IsNotFound(err) && isPermanentClientError(err) { + return backoff.Permanent(err) + } return err } @@ -1275,8 +1277,7 @@ func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pul err = backoff.Retry(func() error { pull, err = p.client.ImagePull(ctx, tag, pullOpt) if err != nil { - var enf errdefs.ErrNotFound - if errors.As(err, &enf) { + if isPermanentClientError(err) { return backoff.Permanent(err) } Logger.Printf("Failed to pull image: %s, will retry", err) @@ -1612,3 +1613,20 @@ func (p *DockerProvider) SaveImages(ctx context.Context, output string, images . func (p *DockerProvider) PullImage(ctx context.Context, image string) error { return p.attemptToPullImage(ctx, image, types.ImagePullOptions{}) } + +var permanentClientErrors = []func(error) bool{ + errdefs.IsNotFound, + errdefs.IsInvalidParameter, + errdefs.IsUnauthorized, + errdefs.IsForbidden, + errdefs.IsNotImplemented, +} + +func isPermanentClientError(err error) bool { + for _, isErrFn := range permanentClientErrors { + if isErrFn(err) { + return true + } + } + return false +} diff --git a/docker_test.go b/docker_test.go index bc29e854d0..f41a002a3b 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1,6 +1,7 @@ package testcontainers import ( + "bytes" "context" "errors" "fmt" @@ -18,6 +19,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/client" "github.com/docker/docker/errdefs" "github.com/docker/go-units" "github.com/stretchr/testify/assert" @@ -1219,7 +1221,7 @@ func TestContainerNonExistentImage(t *testing.T) { var nf errdefs.ErrNotFound if !errors.As(err, &nf) { - t.Fatalf("the error should have bee an errdefs.ErrNotFound: %v", err) + t.Fatalf("the error should have been an errdefs.ErrNotFound: %v", err) } }) @@ -2057,3 +2059,206 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { }) } } + +// errMockCli is a mock implementation of client.APIClient, which is handy for simulating +// error returns in retry scenarios. +type errMockCli struct { + client.APIClient + + err error + imageBuildCount int + containerListCount int + imagePullCount int +} + +func (f *errMockCli) ImageBuild(_ context.Context, _ io.Reader, _ types.ImageBuildOptions) (types.ImageBuildResponse, error) { + f.imageBuildCount++ + return types.ImageBuildResponse{Body: io.NopCloser(&bytes.Buffer{})}, f.err +} + +func (f *errMockCli) ContainerList(_ context.Context, _ container.ListOptions) ([]types.Container, error) { + f.containerListCount++ + return []types.Container{{}}, f.err +} + +func (f *errMockCli) ImagePull(_ context.Context, _ string, _ types.ImagePullOptions) (io.ReadCloser, error) { + f.imagePullCount++ + return io.NopCloser(&bytes.Buffer{}), f.err +} + +func (f *errMockCli) Close() error { + return nil +} + +func TestDockerProvider_BuildImage_Retries(t *testing.T) { + tests := []struct { + name string + errReturned error + shouldRetry bool + }{ + { + name: "no retry on success", + errReturned: nil, + shouldRetry: false, + }, + { + name: "no retry when a resource is not found", + errReturned: errdefs.NotFound(errors.New("not available")), + shouldRetry: false, + }, + { + name: "no retry when parameters are invalid", + errReturned: errdefs.InvalidParameter(errors.New("invalid")), + shouldRetry: false, + }, + { + name: "no retry when resource access not authorized", + errReturned: errdefs.Unauthorized(errors.New("not authorized")), + shouldRetry: false, + }, + { + name: "no retry when resource access is forbidden", + errReturned: errdefs.Forbidden(errors.New("forbidden")), + shouldRetry: false, + }, + { + name: "no retry when not implemented by provider", + errReturned: errdefs.NotImplemented(errors.New("unkown method")), + shouldRetry: false, + }, + { + name: "retry on non-permanent error", + errReturned: errors.New("whoops"), + shouldRetry: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewDockerProvider() + require.NoError(t, err) + m := &errMockCli{err: tt.errReturned} + p.client = m + + // give a chance to retry + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, _ = p.BuildImage(ctx, &ContainerRequest{}) + + assert.Greater(t, m.imageBuildCount, 0) + assert.Equal(t, tt.shouldRetry, m.imageBuildCount > 1) + }) + } +} + +func TestDockerProvider_waitContainerCreation_retries(t *testing.T) { + tests := []struct { + name string + errReturned error + shouldRetry bool + }{ + { + name: "no retry on success", + errReturned: nil, + shouldRetry: false, + }, + { + name: "no retry when parameters are invalid", + errReturned: errdefs.InvalidParameter(errors.New("invalid")), + shouldRetry: false, + }, + { + name: "no retry when not implemented by provider", + errReturned: errdefs.NotImplemented(errors.New("unkown method")), + shouldRetry: false, + }, + { + name: "retry when not found", + errReturned: errdefs.NotFound(errors.New("not there yet")), + shouldRetry: true, + }, + { + name: "retry on non-permanent error", + errReturned: errors.New("whoops"), + shouldRetry: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewDockerProvider() + require.NoError(t, err) + m := &errMockCli{err: tt.errReturned} + p.client = m + + // give a chance to retry + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, _ = p.waitContainerCreation(ctx, "someID") + + assert.Greater(t, m.containerListCount, 0) + assert.Equal(t, tt.shouldRetry, m.containerListCount > 1) + }) + } +} + +func TestDockerProvider_attemptToPullImage_retries(t *testing.T) { + tests := []struct { + name string + errReturned error + shouldRetry bool + }{ + { + name: "no retry on success", + errReturned: nil, + shouldRetry: false, + }, + { + name: "no retry when a resource is not found", + errReturned: errdefs.NotFound(errors.New("not available")), + shouldRetry: false, + }, + { + name: "no retry when parameters are invalid", + errReturned: errdefs.InvalidParameter(errors.New("invalid")), + shouldRetry: false, + }, + { + name: "no retry when resource access not authorized", + errReturned: errdefs.Unauthorized(errors.New("not authorized")), + shouldRetry: false, + }, + { + name: "no retry when resource access is forbidden", + errReturned: errdefs.Forbidden(errors.New("forbidden")), + shouldRetry: false, + }, + { + name: "no retry when not implemented by provider", + errReturned: errdefs.NotImplemented(errors.New("unkown method")), + shouldRetry: false, + }, + { + name: "retry on non-permanent error", + errReturned: errors.New("whoops"), + shouldRetry: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewDockerProvider() + require.NoError(t, err) + m := &errMockCli{err: tt.errReturned} + p.client = m + + // give a chance to retry + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _ = p.attemptToPullImage(ctx, "someTag", types.ImagePullOptions{}) + + assert.Greater(t, m.imagePullCount, 0) + assert.Equal(t, tt.shouldRetry, m.imagePullCount > 1) + }) + } +}