From 0a87c6fbab616b05972c8f28663723d169e547cc Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Sun, 25 Feb 2024 20:17:43 +0000 Subject: [PATCH] chore!: return error from Customize Change ContainerCustomizer.Customize method to return an error so that options can handle errors gracefully instead of relying on panic or just a log entry, neither of which are user friendly. Enable errcheck linter to ensure that errors that aren't handled are reported. Run go mod tidy on k3s and weaviate to allow tests to be run using go 1.22. Run gofumpt on a few files to satisfy golangci-lint. Fix direct comparison with http.ErrServerClosed flagged by errcheck. Fixes #2266 BREAKING CHANGE: `ContainerCustomizer.Customize` now returns an error. --- .golangci.yml | 1 + docker.go | 1 - docs/features/common_functional_options.md | 17 +++++++ modules/artemis/artemis.go | 16 +++++-- modules/cassandra/cassandra.go | 12 +++-- modules/chroma/chroma.go | 4 +- modules/clickhouse/clickhouse.go | 38 ++++++++++----- modules/cockroachdb/cockroachdb.go | 4 +- modules/cockroachdb/options.go | 3 +- modules/compose/compose_api.go | 1 - modules/consul/consul.go | 12 +++-- modules/couchbase/couchbase.go | 8 +++- modules/couchbase/options.go | 9 ++-- modules/elasticsearch/elasticsearch.go | 4 +- modules/elasticsearch/options.go | 3 +- modules/gcloud/bigquery.go | 5 +- modules/gcloud/bigtable.go | 5 +- modules/gcloud/datastore.go | 5 +- modules/gcloud/firestore.go | 5 +- modules/gcloud/gcloud.go | 11 +++-- modules/gcloud/pubsub.go | 5 +- modules/gcloud/spanner.go | 5 +- modules/inbucket/inbucket.go | 4 +- modules/k3s/go.mod | 4 +- modules/k3s/go.sum | 16 +++++++ modules/k3s/k3s.go | 8 +++- modules/k6/k6.go | 20 ++++++-- modules/kafka/kafka.go | 8 +++- modules/localstack/localstack.go | 4 +- modules/localstack/types.go | 8 +++- modules/mariadb/mariadb.go | 36 ++++++++++---- modules/milvus/milvus.go | 4 +- modules/minio/minio.go | 12 +++-- modules/mockserver/mockserver.go | 4 +- modules/mongodb/mongodb.go | 12 +++-- modules/mssql/mssql.go | 12 +++-- modules/mysql/mysql.go | 28 ++++++++--- modules/nats/nats.go | 4 +- modules/nats/options.go | 3 +- modules/neo4j/config.go | 41 +++++++++++----- modules/neo4j/neo4j.go | 4 +- modules/openldap/openldap.go | 20 ++++++-- modules/opensearch/opensearch.go | 4 +- modules/opensearch/options.go | 3 +- modules/postgres/postgres.go | 24 +++++++--- modules/pulsar/pulsar.go | 20 ++++++-- modules/qdrant/qdrant.go | 4 +- modules/rabbitmq/options.go | 3 +- modules/rabbitmq/rabbitmq.go | 20 ++++++-- modules/redis/options_test.go | 9 ++-- modules/redis/redis.go | 17 +++++-- modules/redpanda/options.go | 5 +- modules/redpanda/redpanda.go | 5 +- modules/vault/vault.go | 12 +++-- modules/weaviate/go.mod | 4 +- modules/weaviate/go.sum | 14 ++++++ modules/weaviate/weaviate.go | 4 +- network/network.go | 56 ++++++++++++++-------- network/network_test.go | 12 +++-- options.go | 54 ++++++++++++++------- options_test.go | 14 ++++-- reaper.go | 1 - wait/testdata/main.go | 3 +- 63 files changed, 523 insertions(+), 191 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 37f3bdc2623..1791b9caac5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,7 @@ linters: - misspell - nonamedreturns - testifylint + - errcheck linters-settings: errorlint: diff --git a/docker.go b/docker.go index 33a73cae736..941db6de8bd 100644 --- a/docker.go +++ b/docker.go @@ -1495,7 +1495,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/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 595f87e73d5..7c173be986d 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -129,3 +129,20 @@ The above example is updating the predefined command of the image, **appending** !!!info This can't be used to replace the command, only to append options. + +!!!info + The interface definition for `ContainerCustomizer` was changed to allow + errors the be correctly processed, specifically `Customize` method was + changed from: + +```go +Customize(req *GenericContainerRequest) +``` + +To: + +```go +Customize(req *GenericContainerRequest) error +``` + +- Not available until the next release of testcontainers-go :material-tag: main diff --git a/modules/artemis/artemis.go b/modules/artemis/artemis.go index a3c26702ad3..33e03583cc1 100644 --- a/modules/artemis/artemis.go +++ b/modules/artemis/artemis.go @@ -49,16 +49,20 @@ func (c *Container) ConsoleURL(ctx context.Context) (string, error) { // WithCredentials sets the administrator credentials. The default is artemis:artemis. func WithCredentials(user, password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["ARTEMIS_USER"] = user req.Env["ARTEMIS_PASSWORD"] = password + + return nil } } // WithAnonymousLogin enables anonymous logins. func WithAnonymousLogin() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["ANONYMOUS_LOGIN"] = "true" + + return nil } } @@ -67,8 +71,10 @@ func WithAnonymousLogin() testcontainers.CustomizeRequestOption { // Setting this value will override the default. // See the documentation on `artemis create` for available options. func WithExtraArgs(args string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["EXTRA_ARGS"] = args + + return nil } } @@ -91,7 +97,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&req) + if err := opt.Customize(&req); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, req) diff --git a/modules/cassandra/cassandra.go b/modules/cassandra/cassandra.go index c37c10d90df..10df6e991c4 100644 --- a/modules/cassandra/cassandra.go +++ b/modules/cassandra/cassandra.go @@ -41,19 +41,21 @@ func (c *CassandraContainer) ConnectionHost(ctx context.Context) (string, error) // It will also set the "configFile" parameter to the path of the config file // as a command line argument to the container. func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: "/etc/cassandra/cassandra.yaml", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } // WithInitScripts sets the init cassandra queries to be run when the container starts func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { var initScripts []testcontainers.ContainerFile var execs []testcontainers.Executable for _, script := range scripts { @@ -68,7 +70,7 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { } req.Files = append(req.Files, initScripts...) - testcontainers.WithAfterReadyCommand(execs...)(req) + return testcontainers.WithAfterReadyCommand(execs...)(req) } } @@ -100,7 +102,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/chroma/chroma.go b/modules/chroma/chroma.go index 239e0e6fa09..8e79348807d 100644 --- a/modules/chroma/chroma.go +++ b/modules/chroma/chroma.go @@ -33,7 +33,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/clickhouse/clickhouse.go b/modules/clickhouse/clickhouse.go index b133d98752f..5c74ffa488c 100644 --- a/modules/clickhouse/clickhouse.go +++ b/modules/clickhouse/clickhouse.go @@ -101,10 +101,10 @@ func renderZookeeperConfig(settings ZookeeperOptions) ([]byte, error) { // WithZookeeper pass a config to connect clickhouse with zookeeper and make clickhouse as cluster func WithZookeeper(host, port string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { f, err := os.CreateTemp("", "clickhouse-tc-config-") if err != nil { - panic(err) + return fmt.Errorf("temporary file: %w", err) } defer f.Close() @@ -112,10 +112,10 @@ func WithZookeeper(host, port string) testcontainers.CustomizeRequestOption { // write data to the temporary file data, err := renderZookeeperConfig(ZookeeperOptions{Host: host, Port: port}) if err != nil { - panic(err) + return fmt.Errorf("zookeeper config: %w", err) } if _, err := f.Write(data); err != nil { - panic(err) + return fmt.Errorf("write zookeeper config: %w", err) } cf := testcontainers.ContainerFile{ HostFilePath: f.Name(), @@ -123,12 +123,14 @@ func WithZookeeper(host, port string) testcontainers.CustomizeRequestOption { FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } // WithInitScripts sets the init scripts to be run when the container starts func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { initScripts := []testcontainers.ContainerFile{} for _, script := range scripts { cf := testcontainers.ContainerFile{ @@ -139,6 +141,8 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { initScripts = append(initScripts, cf) } req.Files = append(req.Files, initScripts...) + + return nil } } @@ -146,13 +150,15 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { // It will also set the "configFile" parameter to the path of the config file // as a command line argument to the container. func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: "/etc/clickhouse-server/config.d/config.xml", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } @@ -160,13 +166,15 @@ func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { // It will also set the "configFile" parameter to the path of the config file // as a command line argument to the container. func WithYamlConfigFile(configFile string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: "/etc/clickhouse-server/config.d/config.yaml", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } @@ -174,8 +182,10 @@ func WithYamlConfigFile(configFile string) testcontainers.CustomizeRequestOption // It can be used to define a different name for the default database that is created when the image is first started. // If it is not specified, then the default value("clickhouse") will be used. func WithDatabase(dbName string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["CLICKHOUSE_DB"] = dbName + + return nil } } @@ -183,8 +193,10 @@ func WithDatabase(dbName string) testcontainers.CustomizeRequestOption { // It is required for you to use the ClickHouse image. It must not be empty or undefined. // This environment variable sets the password for ClickHouse. func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["CLICKHOUSE_PASSWORD"] = password + + return nil } } @@ -193,12 +205,14 @@ func WithPassword(password string) testcontainers.CustomizeRequestOption { // It will create the specified user with superuser power. // If it is not specified, then the default user of clickhouse will be used. func WithUsername(user string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { if user == "" { user = defaultUser } req.Env["CLICKHOUSE_USER"] = user + + return nil } } @@ -225,7 +239,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/cockroachdb/cockroachdb.go b/modules/cockroachdb/cockroachdb.go index 14cd78292fe..7ab24b98fb3 100644 --- a/modules/cockroachdb/cockroachdb.go +++ b/modules/cockroachdb/cockroachdb.go @@ -96,7 +96,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(Option); ok { apply(&o) } - opt.Customize(&req) + if err := opt.Customize(&req); err != nil { + return nil, err + } } // modify request diff --git a/modules/cockroachdb/options.go b/modules/cockroachdb/options.go index 84cc1435939..a2211d77e76 100644 --- a/modules/cockroachdb/options.go +++ b/modules/cockroachdb/options.go @@ -26,8 +26,9 @@ var _ testcontainers.ContainerCustomizer = (*Option)(nil) type Option func(*options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } // WithDatabase sets the name of the database to use. diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index 70bc0718973..bff0b84ae79 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -231,7 +231,6 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { Wait: upOptions.Wait, }, }) - if err != nil { return err } diff --git a/modules/consul/consul.go b/modules/consul/consul.go index 08a6e0ece2f..4cba3a49319 100644 --- a/modules/consul/consul.go +++ b/modules/consul/consul.go @@ -40,20 +40,24 @@ func (c *ConsulContainer) ApiEndpoint(ctx context.Context) (string, error) { // WithConfigString takes in a JSON string of keys and values to define a configuration to be used by the instance. func WithConfigString(config string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["CONSUL_LOCAL_CONFIG"] = config + + return nil } } // WithConfigFile takes in a path to a JSON file to define a configuration to be used by the instance. func WithConfigFile(configPath string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configPath, ContainerFilePath: "/consul/config/node.json", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } @@ -76,7 +80,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&containerReq) + if err := opt.Customize(&containerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, containerReq) diff --git a/modules/couchbase/couchbase.go b/modules/couchbase/couchbase.go index c81c420c8be..d9b468edb0f 100644 --- a/modules/couchbase/couchbase.go +++ b/modules/couchbase/couchbase.go @@ -85,7 +85,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } // transfer options to the config @@ -663,10 +665,12 @@ type serviceCustomizer struct { enabledService Service } -func (c serviceCustomizer) Customize(req *testcontainers.GenericContainerRequest) { +func (c serviceCustomizer) Customize(req *testcontainers.GenericContainerRequest) error { for _, port := range c.enabledService.ports { req.ExposedPorts = append(req.ExposedPorts, port+"/tcp") } + + return nil } // withService creates a serviceCustomizer for the given service. diff --git a/modules/couchbase/options.go b/modules/couchbase/options.go index 51c60009c99..64acf24292d 100644 --- a/modules/couchbase/options.go +++ b/modules/couchbase/options.go @@ -40,8 +40,9 @@ type credentialsCustomizer struct { password string } -func (c credentialsCustomizer) Customize(req *testcontainers.GenericContainerRequest) { +func (c credentialsCustomizer) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP, we want to simply transfer the credentials to the container + return nil } // WithAdminCredentials sets the username and password for the administrator user. @@ -73,8 +74,9 @@ type bucketCustomizer struct { buckets []bucket } -func (c bucketCustomizer) Customize(req *testcontainers.GenericContainerRequest) { +func (c bucketCustomizer) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP, we want to simply transfer the buckets to the container + return nil } // WithBucket adds buckets to the couchbase container @@ -96,8 +98,9 @@ type indexStorageCustomizer struct { mode indexStorageMode } -func (c indexStorageCustomizer) Customize(req *testcontainers.GenericContainerRequest) { +func (c indexStorageCustomizer) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP, we want to simply transfer the index storage mode to the container + return nil } // WithBucket adds buckets to the couchbase container diff --git a/modules/elasticsearch/elasticsearch.go b/modules/elasticsearch/elasticsearch.go index 79a364fd014..66a9e0fbcbd 100644 --- a/modules/elasticsearch/elasticsearch.go +++ b/modules/elasticsearch/elasticsearch.go @@ -65,7 +65,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(Option); ok { apply(settings) } - opt.Customize(&req) + if err := opt.Customize(&req); err != nil { + return nil, err + } } // Transfer the certificate settings to the container request diff --git a/modules/elasticsearch/options.go b/modules/elasticsearch/options.go index 97f75f5c52b..ed801c3b096 100644 --- a/modules/elasticsearch/options.go +++ b/modules/elasticsearch/options.go @@ -28,8 +28,9 @@ var _ testcontainers.ContainerCustomizer = (*Option)(nil) type Option func(*Options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } // WithPassword sets the password for the Elasticsearch container. diff --git a/modules/gcloud/bigquery.go b/modules/gcloud/bigquery.go index b9672d56b86..fe93f544858 100644 --- a/modules/gcloud/bigquery.go +++ b/modules/gcloud/bigquery.go @@ -20,7 +20,10 @@ func RunBigQueryContainer(ctx context.Context, opts ...testcontainers.ContainerC Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } req.Cmd = []string{"--project", settings.ProjectID} diff --git a/modules/gcloud/bigtable.go b/modules/gcloud/bigtable.go index 664d01dcccd..8294ad26788 100644 --- a/modules/gcloud/bigtable.go +++ b/modules/gcloud/bigtable.go @@ -19,7 +19,10 @@ func RunBigTableContainer(ctx context.Context, opts ...testcontainers.ContainerC Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } req.Cmd = []string{ "/bin/sh", diff --git a/modules/gcloud/datastore.go b/modules/gcloud/datastore.go index b4f0c387357..72b487f12ba 100644 --- a/modules/gcloud/datastore.go +++ b/modules/gcloud/datastore.go @@ -19,7 +19,10 @@ func RunDatastoreContainer(ctx context.Context, opts ...testcontainers.Container Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } req.Cmd = []string{ "/bin/sh", diff --git a/modules/gcloud/firestore.go b/modules/gcloud/firestore.go index 413ce14a5c5..ee998a55b30 100644 --- a/modules/gcloud/firestore.go +++ b/modules/gcloud/firestore.go @@ -19,7 +19,10 @@ func RunFirestoreContainer(ctx context.Context, opts ...testcontainers.Container Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } req.Cmd = []string{ "/bin/sh", diff --git a/modules/gcloud/gcloud.go b/modules/gcloud/gcloud.go index 83ff0f08542..a5886dc7437 100644 --- a/modules/gcloud/gcloud.go +++ b/modules/gcloud/gcloud.go @@ -57,8 +57,9 @@ var _ testcontainers.ContainerCustomizer = (*Option)(nil) type Option func(*options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } // WithProjectID sets the project ID for the GCloud container. @@ -69,14 +70,16 @@ func WithProjectID(projectID string) Option { } // applyOptions applies the options to the container request and returns the settings. -func applyOptions(req *testcontainers.GenericContainerRequest, opts []testcontainers.ContainerCustomizer) options { +func applyOptions(req *testcontainers.GenericContainerRequest, opts []testcontainers.ContainerCustomizer) (options, error) { settings := defaultOptions() for _, opt := range opts { if apply, ok := opt.(Option); ok { apply(&settings) } - opt.Customize(req) + if err := opt.Customize(req); err != nil { + return options{}, err + } } - return settings + return settings, nil } diff --git a/modules/gcloud/pubsub.go b/modules/gcloud/pubsub.go index 65d87ae1ad5..bf83f3a2f54 100644 --- a/modules/gcloud/pubsub.go +++ b/modules/gcloud/pubsub.go @@ -19,7 +19,10 @@ func RunPubsubContainer(ctx context.Context, opts ...testcontainers.ContainerCus Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } req.Cmd = []string{ "/bin/sh", diff --git a/modules/gcloud/spanner.go b/modules/gcloud/spanner.go index 12420eae140..eb7d10ea8ca 100644 --- a/modules/gcloud/spanner.go +++ b/modules/gcloud/spanner.go @@ -18,7 +18,10 @@ func RunSpannerContainer(ctx context.Context, opts ...testcontainers.ContainerCu Started: true, } - settings := applyOptions(&req, opts) + settings, err := applyOptions(&req, opts) + if err != nil { + return nil, err + } container, err := testcontainers.GenericContainer(ctx, req) if err != nil { diff --git a/modules/inbucket/inbucket.go b/modules/inbucket/inbucket.go index d130c87bb0b..c446404df30 100644 --- a/modules/inbucket/inbucket.go +++ b/modules/inbucket/inbucket.go @@ -61,7 +61,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/k3s/go.mod b/modules/k3s/go.mod index deeb2e1ac70..9248830178d 100644 --- a/modules/k3s/go.mod +++ b/modules/k3s/go.mod @@ -1,6 +1,8 @@ module github.com/testcontainers/testcontainers-go/modules/k3s -go 1.20 +go 1.21 + +toolchain go1.22.0 require ( github.com/docker/docker v25.0.2+incompatible diff --git a/modules/k3s/go.sum b/modules/k3s/go.sum index ac26bd931f9..57eed07e7ab 100644 --- a/modules/k3s/go.sum +++ b/modules/k3s/go.sum @@ -1,6 +1,7 @@ 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= @@ -17,6 +18,7 @@ github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoY github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -46,6 +48,7 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -63,9 +66,11 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -78,6 +83,7 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -106,7 +112,9 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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= @@ -118,6 +126,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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= @@ -151,13 +160,17 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1: go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= 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.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= 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.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= 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= @@ -180,6 +193,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -215,6 +229,7 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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= @@ -236,6 +251,7 @@ gopkg.in/yaml.v3 v3.0.0/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= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= diff --git a/modules/k3s/k3s.go b/modules/k3s/k3s.go index 0f7ccb4d168..971829c0b9e 100644 --- a/modules/k3s/k3s.go +++ b/modules/k3s/k3s.go @@ -68,7 +68,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) @@ -83,7 +85,9 @@ func getContainerHost(ctx context.Context, opts ...testcontainers.ContainerCusto // Use a dummy request to get the provider from options. var req testcontainers.GenericContainerRequest for _, opt := range opts { - opt.Customize(&req) + if err := opt.Customize(&req); err != nil { + return "", err + } } logging := req.Logger diff --git a/modules/k6/k6.go b/modules/k6/k6.go index 82976b7ea34..7b31687e6db 100644 --- a/modules/k6/k6.go +++ b/modules/k6/k6.go @@ -21,7 +21,7 @@ type K6Container struct { // and passes it to k6 as the test to run. // The path to the script must be an absolute path func WithTestScript(scriptPath string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { script := filepath.Base(scriptPath) target := "/home/k6x/" + script req.Files = append( @@ -35,20 +35,26 @@ func WithTestScript(scriptPath string) testcontainers.CustomizeRequestOption { // add script to the k6 run command req.Cmd = append(req.Cmd, target) + + return nil } } // WithCmdOptions pass the given options to the k6 run command func WithCmdOptions(options ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Cmd = append(req.Cmd, options...) + + return nil } } // SetEnvVar adds a '--env' command-line flag to the k6 command in the container for setting an environment variable for the test script. func SetEnvVar(variable string, value string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Cmd = append(req.Cmd, "--env", fmt.Sprintf("%s=%s", variable, value)) + + return nil } } @@ -68,7 +74,7 @@ func WithCache() testcontainers.CustomizeRequestOption { } } - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { mount := testcontainers.ContainerMount{ Source: testcontainers.DockerVolumeMountSource{ Name: cacheVol, @@ -77,6 +83,8 @@ func WithCache() testcontainers.CustomizeRequestOption { Target: "/cache", } req.Mounts = append(req.Mounts, mount) + + return nil } } @@ -94,7 +102,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 399f17fb70d..f5d49e9db92 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -96,7 +96,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } err := validateKRaftVersion(genericContainerReq.Image) @@ -117,8 +119,10 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["CLUSTER_ID"] = clusterID + + return nil } } diff --git a/modules/localstack/localstack.go b/modules/localstack/localstack.go index c2e8d64d26c..ae4c7c8c01c 100644 --- a/modules/localstack/localstack.go +++ b/modules/localstack/localstack.go @@ -90,7 +90,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&localStackReq.GenericContainerRequest) + if err := opt.Customize(&localStackReq.GenericContainerRequest); err != nil { + return nil, err + } } if isLegacyMode(localStackReq.Image) { diff --git a/modules/localstack/types.go b/modules/localstack/types.go index a57ae1808f6..962975841af 100644 --- a/modules/localstack/types.go +++ b/modules/localstack/types.go @@ -26,8 +26,10 @@ var NoopOverrideContainerRequest = func(req testcontainers.ContainerRequest) tes return req } -func (opt OverrideContainerRequestOption) Customize(req *testcontainers.GenericContainerRequest) { +func (opt OverrideContainerRequestOption) Customize(req *testcontainers.GenericContainerRequest) error { req.ContainerRequest = opt(req.ContainerRequest) + + return nil } // OverrideContainerRequest returns a function that can be used to merge the passed container request with one that is created by the LocalStack container @@ -43,7 +45,9 @@ func OverrideContainerRequest(r testcontainers.ContainerRequest) func(req testco } opt := testcontainers.CustomizeRequest(destContainerReq) - opt.Customize(&srcContainerReq) + if err := opt.Customize(&srcContainerReq); err != nil { + panic(err) + } return srcContainerReq.ContainerRequest } diff --git a/modules/mariadb/mariadb.go b/modules/mariadb/mariadb.go index b84d82c2ec7..9ad5c6f6b80 100644 --- a/modules/mariadb/mariadb.go +++ b/modules/mariadb/mariadb.go @@ -33,7 +33,7 @@ type MariaDBContainer struct { // WithDefaultCredentials applies the default credentials to the container request. // It will look up for MARIADB environment variables. func WithDefaultCredentials() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { username := req.Env["MARIADB_USER"] password := req.Env["MARIADB_PASSWORD"] if strings.EqualFold(rootUser, username) { @@ -46,6 +46,8 @@ func WithDefaultCredentials() testcontainers.CustomizeRequestOption { req.Env["MARIADB_ALLOW_EMPTY_ROOT_PASSWORD"] = "yes" delete(req.Env, "MARIADB_PASSWORD") } + + return nil } } @@ -54,7 +56,7 @@ func WithDefaultCredentials() testcontainers.CustomizeRequestOption { // the MARIADB_* equivalent variables are provided. MARIADB_* variants will always be // used in preference to MYSQL_* variants. func withMySQLEnvVars() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { // look up for MARIADB environment variables and apply the same to MYSQL for k, v := range req.Env { if strings.HasPrefix(k, "MARIADB_") { @@ -63,40 +65,50 @@ func withMySQLEnvVars() testcontainers.CustomizeRequestOption { req.Env[mysqlEnvVar] = v } } + + return nil } } func WithUsername(username string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MARIADB_USER"] = username + + return nil } } func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MARIADB_PASSWORD"] = password + + return nil } } func WithDatabase(database string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MARIADB_DATABASE"] = database + + return nil } } func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: "/etc/mysql/conf.d/my.cnf", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { var initScripts []testcontainers.ContainerFile for _, script := range scripts { cf := testcontainers.ContainerFile{ @@ -107,6 +119,8 @@ func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { initScripts = append(initScripts, cf) } req.Files = append(req.Files, initScripts...) + + return nil } } @@ -131,13 +145,17 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize opts = append(opts, WithDefaultCredentials()) for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } // Apply MySQL environment variables after user customization // In future releases of MariaDB, they could remove the MYSQL_* environment variables // at all. Then we can remove this customization. - withMySQLEnvVars().Customize(&genericContainerReq) + if err := withMySQLEnvVars().Customize(&genericContainerReq); err != nil { + return nil, err + } username, ok := req.Env["MARIADB_USER"] if !ok { diff --git a/modules/milvus/milvus.go b/modules/milvus/milvus.go index 5f64021a93a..a33ae27935c 100644 --- a/modules/milvus/milvus.go +++ b/modules/milvus/milvus.go @@ -67,7 +67,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/minio/minio.go b/modules/minio/minio.go index e61d2a29130..b81586934aa 100644 --- a/modules/minio/minio.go +++ b/modules/minio/minio.go @@ -25,8 +25,10 @@ type MinioContainer struct { // It is used in conjunction with WithPassword to set a user and its password. // It will create the specified user. It must not be empty or undefined. func WithUsername(username string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MINIO_ROOT_USER"] = username + + return nil } } @@ -34,8 +36,10 @@ func WithUsername(username string) testcontainers.CustomizeRequestOption { // It is required for you to use the Minio image. It must not be empty or undefined. // This environment variable sets the root user password for Minio. func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MINIO_ROOT_PASSWORD"] = password + + return nil } } @@ -72,7 +76,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } username := req.Env["MINIO_ROOT_USER"] diff --git a/modules/mockserver/mockserver.go b/modules/mockserver/mockserver.go index 37ebc1b1b54..e3bb5fbd281 100644 --- a/modules/mockserver/mockserver.go +++ b/modules/mockserver/mockserver.go @@ -34,7 +34,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/mongodb/mongodb.go b/modules/mongodb/mongodb.go index 761aa99e54b..565e8bc466b 100644 --- a/modules/mongodb/mongodb.go +++ b/modules/mongodb/mongodb.go @@ -36,7 +36,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } username := req.Env["MONGO_INITDB_ROOT_USERNAME"] password := req.Env["MONGO_INITDB_ROOT_PASSWORD"] @@ -59,8 +61,10 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize // It is used in conjunction with WithPassword to set a username and its password. // It will create the specified user with superuser power. func WithUsername(username string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MONGO_INITDB_ROOT_USERNAME"] = username + + return nil } } @@ -68,8 +72,10 @@ func WithUsername(username string) testcontainers.CustomizeRequestOption { // It is used in conjunction with WithUsername to set a username and its password. // It will set the superuser password for MongoDB. func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MONGO_INITDB_ROOT_PASSWORD"] = password + + return nil } } diff --git a/modules/mssql/mssql.go b/modules/mssql/mssql.go index 386df760d41..78321beab3c 100644 --- a/modules/mssql/mssql.go +++ b/modules/mssql/mssql.go @@ -24,17 +24,21 @@ type MSSQLServerContainer struct { } func WithAcceptEULA() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["ACCEPT_EULA"] = "Y" + + return nil } } func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { if password == "" { password = defaultPassword } req.Env["MSSQL_SA_PASSWORD"] = password + + return nil } } @@ -55,7 +59,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/mysql/mysql.go b/modules/mysql/mysql.go index 2ebfce1710b..9c7552d0ea2 100644 --- a/modules/mysql/mysql.go +++ b/modules/mysql/mysql.go @@ -31,7 +31,7 @@ type MySQLContainer struct { } func WithDefaultCredentials() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { username := req.Env["MYSQL_USER"] password := req.Env["MYSQL_PASSWORD"] if strings.EqualFold(rootUser, username) { @@ -43,6 +43,8 @@ func WithDefaultCredentials() testcontainers.CustomizeRequestOption { req.Env["MYSQL_ALLOW_EMPTY_PASSWORD"] = "yes" delete(req.Env, "MYSQL_PASSWORD") } + + return nil } } @@ -67,7 +69,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize opts = append(opts, WithDefaultCredentials()) for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } username, ok := req.Env["MYSQL_USER"] @@ -114,36 +118,44 @@ func (c *MySQLContainer) ConnectionString(ctx context.Context, args ...string) ( } func WithUsername(username string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MYSQL_USER"] = username + + return nil } } func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MYSQL_PASSWORD"] = password + + return nil } } func WithDatabase(database string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["MYSQL_DATABASE"] = database + + return nil } } func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: "/etc/mysql/conf.d/my.cnf", FileMode: 0o755, } req.Files = append(req.Files, cf) + + return nil } } func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { var initScripts []testcontainers.ContainerFile for _, script := range scripts { cf := testcontainers.ContainerFile{ @@ -154,5 +166,7 @@ func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { initScripts = append(initScripts, cf) } req.Files = append(req.Files, initScripts...) + + return nil } } diff --git a/modules/nats/nats.go b/modules/nats/nats.go index 42cebc15909..aa0b501d16d 100644 --- a/modules/nats/nats.go +++ b/modules/nats/nats.go @@ -41,7 +41,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(CmdOption); ok { apply(&settings) } - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } // Include the command line arguments diff --git a/modules/nats/options.go b/modules/nats/options.go index f43cf9b9d55..38856d68a9c 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -23,8 +23,9 @@ var _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) type CmdOption func(opts *options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o CmdOption) Customize(req *testcontainers.GenericContainerRequest) { +func (o CmdOption) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } func WithUsername(username string) CmdOption { diff --git a/modules/neo4j/config.go b/modules/neo4j/config.go index 9d0bc708168..7b469e0bbec 100644 --- a/modules/neo4j/config.go +++ b/modules/neo4j/config.go @@ -32,20 +32,22 @@ func WithoutAuthentication() testcontainers.CustomizeRequestOption { // An empty string disables authentication. // The default password is "password". func WithAdminPassword(adminPassword string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { pwd := "none" if adminPassword != "" { pwd = fmt.Sprintf("neo4j/%s", adminPassword) } req.Env["NEO4J_AUTH"] = pwd + + return nil } } // WithLabsPlugin registers one or more Neo4jLabsPlugin for download and server startup. // There might be plugins not supported by your selected version of Neo4j. func WithLabsPlugin(plugins ...LabsPlugin) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { rawPluginValues := make([]string, len(plugins)) for i := 0; i < len(plugins); i++ { rawPluginValues[i] = string(plugins[i]) @@ -54,6 +56,8 @@ func WithLabsPlugin(plugins ...LabsPlugin) testcontainers.CustomizeRequestOption if len(plugins) > 0 { req.Env["NEO4JLABS_PLUGINS"] = fmt.Sprintf(`["%s"]`, strings.Join(rawPluginValues, `","`)) } + + return nil } } @@ -64,8 +68,8 @@ func WithLabsPlugin(plugins ...LabsPlugin) testcontainers.CustomizeRequestOption // See WithNeo4jSettings to add multiple settings at once // Note: credentials must be configured with WithAdminPassword func WithNeo4jSetting(key, value string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - addSetting(req, key, value) + return func(req *testcontainers.GenericContainerRequest) error { + return addSetting(req, key, value) } } @@ -76,33 +80,40 @@ func WithNeo4jSetting(key, value string) testcontainers.CustomizeRequestOption { // See WithNeo4jSetting to add a single setting // Note: credentials must be configured with WithAdminPassword func WithNeo4jSettings(settings map[string]string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { for key, value := range settings { - addSetting(req, key, value) + if err := addSetting(req, key, value); err != nil { + return err + } } + + return nil } } // WithLogger sets a custom logger to be used by the container // Consider calling this before other "With functions" as these may generate logs func WithLogger(logger testcontainers.Logging) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Logger = logger + + return nil } } -func addSetting(req *testcontainers.GenericContainerRequest, key string, newVal string) { +func addSetting(req *testcontainers.GenericContainerRequest, key string, newVal string) error { normalizedKey := formatNeo4jConfig(key) if oldVal, found := req.Env[normalizedKey]; found { // make sure AUTH is not overwritten by a setting if key == "AUTH" { - req.Logger.Printf("setting %q is not permitted, WithAdminPassword has already been set\n", normalizedKey) - return + return fmt.Errorf("setting %q is not permitted, WithAdminPassword has already been set", normalizedKey) } - req.Logger.Printf("setting %q with value %q is now overwritten with value %q\n", []any{key, oldVal, newVal}...) + return fmt.Errorf("setting %q with value %q is now overwritten with value %q", []any{key, oldVal, newVal}...) } req.Env[normalizedKey] = newVal + + return nil } func validate(req *testcontainers.GenericContainerRequest) error { @@ -123,8 +134,10 @@ func formatNeo4jConfig(name string) string { // the commercial licence agreement of Neo4j Enterprise Edition. The license // agreement is available at https://neo4j.com/terms/licensing/. func WithAcceptCommercialLicenseAgreement() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["NEO4J_ACCEPT_LICENSE_AGREEMENT"] = "yes" + + return nil } } @@ -134,7 +147,9 @@ func WithAcceptCommercialLicenseAgreement() testcontainers.CustomizeRequestOptio // agreement is available at https://neo4j.com/terms/enterprise_us/. Please // read the terms of the evaluation agreement before you accept. func WithAcceptEvaluationLicenseAgreement() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["NEO4J_ACCEPT_LICENSE_AGREEMENT"] = "eval" + + return nil } } diff --git a/modules/neo4j/neo4j.go b/modules/neo4j/neo4j.go index 1a7f3507bcb..70566aa0cd1 100644 --- a/modules/neo4j/neo4j.go +++ b/modules/neo4j/neo4j.go @@ -83,7 +83,9 @@ func RunContainer(ctx context.Context, options ...testcontainers.ContainerCustom } for _, option := range options { - option.Customize(&genericContainerReq) + if err := option.Customize(&genericContainerReq); err != nil { + return nil, err + } } err := validate(&genericContainerReq) diff --git a/modules/openldap/openldap.go b/modules/openldap/openldap.go index e29658c6399..8e0cc271de1 100644 --- a/modules/openldap/openldap.go +++ b/modules/openldap/openldap.go @@ -63,8 +63,10 @@ func (c *OpenLDAPContainer) LoadLdif(ctx context.Context, ldif []byte) error { // It is used in conjunction with WithAdminPassword to set a username and its password. // It will create the specified user with admin power. func WithAdminUsername(username string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["LDAP_ADMIN_USERNAME"] = username + + return nil } } @@ -72,21 +74,25 @@ func WithAdminUsername(username string) testcontainers.CustomizeRequestOption { // It is used in conjunction with WithAdminUsername to set a username and its password. // It will set the admin password for OpenLDAP. func WithAdminPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["LDAP_ADMIN_PASSWORD"] = password + + return nil } } // WithRoot sets the root of the OpenLDAP instance func WithRoot(root string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["LDAP_ROOT"] = root + + return nil } } // WithInitialLdif sets the initial ldif file to be loaded into the OpenLDAP container func WithInitialLdif(ldif string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Files = append(req.Files, testcontainers.ContainerFile{ HostFilePath: ldif, ContainerFilePath: "/initial_ldif.ldif", @@ -111,6 +117,8 @@ func WithInitialLdif(ldif string) testcontainers.CustomizeRequestOption { }, }, }) + + return nil } } @@ -141,7 +149,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/opensearch/opensearch.go b/modules/opensearch/opensearch.go index 019f68f2874..61290c9ed23 100644 --- a/modules/opensearch/opensearch.go +++ b/modules/opensearch/opensearch.go @@ -66,7 +66,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(Option); ok { apply(settings) } - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } // set credentials if they are provided, otherwise use the defaults diff --git a/modules/opensearch/options.go b/modules/opensearch/options.go index 3792d08c6fb..f1223762a32 100644 --- a/modules/opensearch/options.go +++ b/modules/opensearch/options.go @@ -22,8 +22,9 @@ var _ testcontainers.ContainerCustomizer = (*Option)(nil) type Option func(*Options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } // WithPassword sets the password for the OpenSearch container. diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index 111a2a0953d..43c5c811130 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -50,7 +50,7 @@ func (c *PostgresContainer) ConnectionString(ctx context.Context, args ...string // It will also set the "config_file" parameter to the path of the config file // as a command line argument to the container func WithConfigFile(cfg string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cfgFile := testcontainers.ContainerFile{ HostFilePath: cfg, ContainerFilePath: "/etc/postgresql.conf", @@ -59,6 +59,8 @@ func WithConfigFile(cfg string) testcontainers.CustomizeRequestOption { req.Files = append(req.Files, cfgFile) req.Cmd = append(req.Cmd, "-c", "config_file=/etc/postgresql.conf") + + return nil } } @@ -66,14 +68,16 @@ func WithConfigFile(cfg string) testcontainers.CustomizeRequestOption { // It can be used to define a different name for the default database that is created when the image is first started. // If it is not specified, then the value of WithUser will be used. func WithDatabase(dbName string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["POSTGRES_DB"] = dbName + + return nil } } // WithInitScripts sets the init scripts to be run when the container starts func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { initScripts := []testcontainers.ContainerFile{} for _, script := range scripts { cf := testcontainers.ContainerFile{ @@ -84,6 +88,8 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { initScripts = append(initScripts, cf) } req.Files = append(req.Files, initScripts...) + + return nil } } @@ -91,8 +97,10 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { // It is required for you to use the PostgreSQL image. It must not be empty or undefined. // This environment variable sets the superuser password for PostgreSQL. func WithPassword(password string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["POSTGRES_PASSWORD"] = password + + return nil } } @@ -101,12 +109,14 @@ func WithPassword(password string) testcontainers.CustomizeRequestOption { // It will create the specified user with superuser power and a database with the same name. // If it is not specified, then the default user of postgres will be used. func WithUsername(user string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { if user == "" { user = defaultUser } req.Env["POSTGRES_USER"] = user + + return nil } } @@ -129,7 +139,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/pulsar/pulsar.go b/modules/pulsar/pulsar.go index f8ad59dfd9f..bdb45ab69ee 100644 --- a/modules/pulsar/pulsar.go +++ b/modules/pulsar/pulsar.go @@ -71,7 +71,7 @@ func (c *Container) resolveURL(ctx context.Context, port nat.Port) (string, erro // WithFunctionsWorker enables the functions worker, which will override the default pulsar command // and add a waiting strategy for the functions worker func WithFunctionsWorker() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Cmd = []string{"/bin/bash", "-c", defaultPulsarCmd} ss := []wait.Strategy{ @@ -81,6 +81,8 @@ func WithFunctionsWorker() testcontainers.CustomizeRequestOption { ss = append(ss, defaultWaitStrategies.Strategies...) req.WaitingFor = wait.ForAll(ss...) + + return nil } } @@ -100,14 +102,18 @@ func (c *Container) WithLogConsumers(ctx context.Context, consumer ...testcontai // WithPulsarEnv allows to use the native APIs and set each variable with PULSAR_PREFIX_ as prefix. func WithPulsarEnv(configVar string, configValue string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["PULSAR_PREFIX_"+configVar] = configValue + + return nil } } func WithTransactions() testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - WithPulsarEnv("transactionCoordinatorEnabled", "true")(req) + return func(req *testcontainers.GenericContainerRequest) error { + if err := WithPulsarEnv("transactionCoordinatorEnabled", "true")(req); err != nil { + return err + } // clone defaultWaitStrategies ss := []wait.Strategy{ @@ -119,6 +125,8 @@ func WithTransactions() testcontainers.CustomizeRequestOption { ss = append(ss, defaultWaitStrategies.Strategies...) req.WaitingFor = wait.ForAll(ss...) + + return nil } } @@ -146,7 +154,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } c, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/qdrant/qdrant.go b/modules/qdrant/qdrant.go index 675a8ebfc8f..c5a4244707a 100644 --- a/modules/qdrant/qdrant.go +++ b/modules/qdrant/qdrant.go @@ -31,7 +31,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/modules/rabbitmq/options.go b/modules/rabbitmq/options.go index fd4266b0fc4..885768ab5e4 100644 --- a/modules/rabbitmq/options.go +++ b/modules/rabbitmq/options.go @@ -44,8 +44,9 @@ var _ testcontainers.ContainerCustomizer = (*Option)(nil) type Option func(*options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } // WithAdminPassword sets the password for the default admin user diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index 9fb28212e15..118b39aca4f 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -100,11 +100,15 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(Option); ok { apply(&settings) } - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } if settings.SSLSettings != nil { - applySSLSettings(settings.SSLSettings)(&genericContainerReq) + if err := applySSLSettings(settings.SSLSettings)(&genericContainerReq); err != nil { + return nil, err + } } nodeConfig, err := renderRabbitMQConfig(settings) @@ -118,7 +122,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize return nil, err } - withConfig(tmpConfigFile)(&genericContainerReq) + if err := withConfig(tmpConfigFile)(&genericContainerReq); err != nil { + return nil, err + } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) if err != nil { @@ -135,7 +141,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } func withConfig(hostPath string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["RABBITMQ_CONFIG_FILE"] = defaultCustomConfPath req.Files = append(req.Files, testcontainers.ContainerFile{ @@ -143,6 +149,8 @@ func withConfig(hostPath string) testcontainers.CustomizeRequestOption { ContainerFilePath: defaultCustomConfPath, FileMode: 0o644, }) + + return nil } } @@ -154,7 +162,7 @@ func applySSLSettings(sslSettings *SSLSettings) testcontainers.CustomizeRequestO const defaultPermission = 0o644 - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Files = append(req.Files, testcontainers.ContainerFile{ HostFilePath: sslSettings.CACertFile, ContainerFilePath: rabbitCaCertPath, @@ -174,6 +182,8 @@ func applySSLSettings(sslSettings *SSLSettings) testcontainers.CustomizeRequestO // To verify that TLS has been enabled on the node, container logs should contain an entry about a TLS listener being enabled // See https://www.rabbitmq.com/ssl.html#enabling-tls-verify-configuration req.WaitingFor = wait.ForAll(req.WaitingFor, wait.ForLog("started TLS (SSL) listener on [::]:5671")) + + return nil } } diff --git a/modules/redis/options_test.go b/modules/redis/options_test.go index 7150af6df2c..0ff8bd201ce 100644 --- a/modules/redis/options_test.go +++ b/modules/redis/options_test.go @@ -39,7 +39,8 @@ func TestWithConfigFile(t *testing.T) { }, } - WithConfigFile("redis.conf")(req) + err := WithConfigFile("redis.conf")(req) + require.NoError(t, err) require.Equal(t, tt.expectedCmds, req.Cmd) }) @@ -77,7 +78,8 @@ func TestWithLogLevel(t *testing.T) { }, } - WithLogLevel(LogLevelDebug)(req) + err := WithLogLevel(LogLevelDebug)(req) + require.NoError(t, err) require.Equal(t, tt.expectedCmds, req.Cmd) }) @@ -130,7 +132,8 @@ func TestWithSnapshotting(t *testing.T) { }, } - WithSnapshotting(tt.seconds, tt.changedKeys)(req) + err := WithSnapshotting(tt.seconds, tt.changedKeys)(req) + require.NoError(t, err) require.Equal(t, tt.expectedCmds, req.Cmd) }) diff --git a/modules/redis/redis.go b/modules/redis/redis.go index a828816d733..d24cfac676c 100644 --- a/modules/redis/redis.go +++ b/modules/redis/redis.go @@ -60,7 +60,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) @@ -76,7 +78,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { const defaultConfigFile = "/usr/local/redis.conf" - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ HostFilePath: configFile, ContainerFilePath: defaultConfigFile, @@ -86,7 +88,7 @@ func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { if len(req.Cmd) == 0 { req.Cmd = []string{redisServerProcess, defaultConfigFile} - return + return nil } // prepend the command to run the redis server with the config file, which must be the first argument of the redis server process @@ -97,14 +99,18 @@ func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { // prepend the redis server and the confif file, then the rest of the args req.Cmd = append([]string{redisServerProcess, defaultConfigFile}, req.Cmd...) } + + return nil } } // WithLogLevel sets the log level for the redis server process // See https://redis.io/docs/reference/modules/modules-api-ref/#redismodule_log for more information. func WithLogLevel(level LogLevel) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { processRedisServerArgs(req, []string{"--loglevel", string(level)}) + + return nil } } @@ -120,8 +126,9 @@ func WithSnapshotting(seconds int, changedKeys int) testcontainers.CustomizeRequ seconds = 1 } - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { processRedisServerArgs(req, []string{"--save", fmt.Sprintf("%d", seconds), fmt.Sprintf("%d", changedKeys)}) + return nil } } diff --git a/modules/redpanda/options.go b/modules/redpanda/options.go index 4340be30d85..f78766115b8 100644 --- a/modules/redpanda/options.go +++ b/modules/redpanda/options.go @@ -59,14 +59,15 @@ func defaultOptions() options { } // Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. -var _ testcontainers.ContainerCustomizer = (*Option)(nil) +var _ testcontainers.ContainerCustomizer = (Option)(nil) // Option is an option for the Redpanda container. type Option func(*options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) { +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. + return nil } func WithNewServiceAccount(username, password string) Option { diff --git a/modules/redpanda/redpanda.go b/modules/redpanda/redpanda.go index 6213809d4b4..b0a73dae801 100644 --- a/modules/redpanda/redpanda.go +++ b/modules/redpanda/redpanda.go @@ -91,7 +91,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize if apply, ok := opt.(Option); ok { apply(&settings) } - opt.Customize(&req) + if err := opt.Customize(&req); err != nil { + return nil, err + } } // 2.1. If the image is not at least v23.3, disable wasm transform @@ -198,7 +200,6 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize wait.ForListeningPort(defaultKafkaAPIPort), wait.ForLog("Successfully started Redpanda!").WithPollInterval(100*time.Millisecond)). WaitUntilReady(ctx, container) - if err != nil { return nil, fmt.Errorf("failed to wait for Redpanda readiness: %w", err) } diff --git a/modules/vault/vault.go b/modules/vault/vault.go index ca5ab4bb13e..5c6f47aa6eb 100644 --- a/modules/vault/vault.go +++ b/modules/vault/vault.go @@ -41,7 +41,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) @@ -54,15 +56,17 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize // WithToken is a container option function that sets the root token for the Vault func WithToken(token string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { req.Env["VAULT_DEV_ROOT_TOKEN_ID"] = token req.Env["VAULT_TOKEN"] = token + + return nil } } // WithInitCommand is an option function that adds a set of initialization commands to the Vault's configuration func WithInitCommand(commands ...string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { commandsList := make([]string, 0, len(commands)) for _, command := range commands { commandsList = append(commandsList, "vault "+command) @@ -70,6 +74,8 @@ func WithInitCommand(commands ...string) testcontainers.CustomizeRequestOption { cmd := []string{"/bin/sh", "-c", strings.Join(commandsList, " && ")} req.WaitingFor = wait.ForAll(req.WaitingFor, wait.ForExec(cmd)) + + return nil } } diff --git a/modules/weaviate/go.mod b/modules/weaviate/go.mod index 9e79b741eec..aa00690ebae 100644 --- a/modules/weaviate/go.mod +++ b/modules/weaviate/go.mod @@ -1,6 +1,8 @@ module github.com/testcontainers/testcontainers-go/modules/weaviate -go 1.20 +go 1.21 + +toolchain go1.22.0 require ( github.com/testcontainers/testcontainers-go v0.28.0 diff --git a/modules/weaviate/go.sum b/modules/weaviate/go.sum index 3b4033bb89d..9131bce56c3 100644 --- a/modules/weaviate/go.sum +++ b/modules/weaviate/go.sum @@ -1,6 +1,7 @@ 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -25,6 +26,7 @@ github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoY github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -114,6 +116,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -129,6 +132,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -179,6 +183,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 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= @@ -234,13 +239,17 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1: go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= 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.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= 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.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -273,6 +282,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -304,6 +314,7 @@ 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/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-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -322,6 +333,7 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= @@ -334,6 +346,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -346,3 +359,4 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C 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/weaviate/weaviate.go b/modules/weaviate/weaviate.go index 30a196c8a6d..8d34a7343b6 100644 --- a/modules/weaviate/weaviate.go +++ b/modules/weaviate/weaviate.go @@ -40,7 +40,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) diff --git a/network/network.go b/network/network.go index 646ed463a86..8a057d41102 100644 --- a/network/network.go +++ b/network/network.go @@ -2,6 +2,7 @@ package network import ( "context" + "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" @@ -24,7 +25,9 @@ func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.Docker } for _, opt := range opts { - opt.Customize(&nc) + if err := opt.Customize(&nc); err != nil { + return nil, err + } } //nolint:staticcheck @@ -54,76 +57,90 @@ func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.Docker // NetworkCustomizer is an interface that can be used to configure the network create request. type NetworkCustomizer interface { - Customize(req *types.NetworkCreate) + Customize(req *types.NetworkCreate) error } // CustomizeNetworkOption is a type that can be used to configure the network create request. -type CustomizeNetworkOption func(req *types.NetworkCreate) +type CustomizeNetworkOption func(req *types.NetworkCreate) error // Customize implements the NetworkCustomizer interface, // applying the option to the network create request. -func (opt CustomizeNetworkOption) Customize(req *types.NetworkCreate) { - opt(req) +func (opt CustomizeNetworkOption) Customize(req *types.NetworkCreate) error { + return opt(req) } // WithAttachable allows to set the network as attachable. func WithAttachable() CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { original.Attachable = true + + return nil } } // WithCheckDuplicate allows to check if a network with the same name already exists. func WithCheckDuplicate() CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { //nolint:staticcheck original.CheckDuplicate = true + + return nil } } // WithDriver allows to override the default network driver, which is "bridge". func WithDriver(driver string) CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { original.Driver = driver + + return nil } } // WithEnableIPv6 allows to set the network as IPv6 enabled. // Please use this option if and only if IPv6 is enabled on the Docker daemon. func WithEnableIPv6() CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { original.EnableIPv6 = true + + return nil } } // WithInternal allows to set the network as internal. func WithInternal() CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { original.Internal = true + + return nil } } // WithLabels allows to set the network labels, adding the new ones // to the default Testcontainers for Go labels. func WithLabels(labels map[string]string) CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { for k, v := range labels { original.Labels[k] = v } + + return nil } } // WithIPAM allows to change the default IPAM configuration. func WithIPAM(ipam *network.IPAM) CustomizeNetworkOption { - return func(original *types.NetworkCreate) { + return func(original *types.NetworkCreate) error { original.IPAM = ipam + + return nil } } // WithNetwork reuses an already existing network, attaching the container to it. // Finally it sets the network alias on that network to the given alias. func WithNetwork(aliases []string, nw *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { networkName := nw.Name // attaching to the network because it was created with success or it already existed. @@ -133,21 +150,18 @@ func WithNetwork(aliases []string, nw *testcontainers.DockerNetwork) testcontain req.NetworkAliases = make(map[string][]string) } req.NetworkAliases[networkName] = aliases + + return nil } } // WithNewNetwork creates a new network with random name and customizers, and attaches the container to it. // Finally it sets the network alias on that network to the given alias. func WithNewNetwork(ctx context.Context, aliases []string, opts ...NetworkCustomizer) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { + return func(req *testcontainers.GenericContainerRequest) error { newNetwork, err := New(ctx, opts...) if err != nil { - logger := req.Logger - if logger == nil { - logger = testcontainers.Logger - } - logger.Printf("failed to create network. Container won't be attached to it: %v", err) - return + return fmt.Errorf("new network: %w", err) } networkName := newNetwork.Name @@ -159,5 +173,7 @@ func WithNewNetwork(ctx context.Context, aliases []string, opts ...NetworkCustom req.NetworkAliases = make(map[string][]string) } req.NetworkAliases[networkName] = aliases + + return nil } } diff --git a/network/network_test.go b/network/network_test.go index 1756c7d5e6b..4d081638cd2 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -422,7 +422,8 @@ func TestWithNetwork(t *testing.T) { ContainerRequest: testcontainers.ContainerRequest{}, } - network.WithNetwork([]string{"alias"}, nw)(&req) + err := network.WithNetwork([]string{"alias"}, nw)(&req) + require.NoError(t, err) assert.Len(t, req.Networks, 1) assert.Equal(t, networkName, req.Networks[0]) @@ -468,7 +469,8 @@ func TestWithSyntheticNetwork(t *testing.T) { }, } - network.WithNetwork([]string{"alias"}, nw)(&req) + err := network.WithNetwork([]string{"alias"}, nw)(&req) + require.NoError(t, err) assert.Len(t, req.Networks, 1) assert.Equal(t, networkName, req.Networks[0]) @@ -502,11 +504,12 @@ func TestWithNewNetwork(t *testing.T) { ContainerRequest: testcontainers.ContainerRequest{}, } - network.WithNewNetwork(context.Background(), []string{"alias"}, + err := network.WithNewNetwork(context.Background(), []string{"alias"}, network.WithAttachable(), network.WithInternal(), network.WithLabels(map[string]string{"this-is-a-test": "value"}), )(&req) + require.NoError(t, err) assert.Len(t, req.Networks, 1) @@ -549,11 +552,12 @@ func TestWithNewNetworkContextTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) defer cancel() - network.WithNewNetwork(ctx, []string{"alias"}, + err := network.WithNewNetwork(ctx, []string{"alias"}, network.WithAttachable(), network.WithInternal(), network.WithLabels(map[string]string{"this-is-a-test": "value"}), )(&req) + require.Error(t, err) // we do not want to fail, just skip the network creation assert.Empty(t, req.Networks) diff --git a/options.go b/options.go index 391d3e1a82c..2860cd39a43 100644 --- a/options.go +++ b/options.go @@ -17,46 +17,51 @@ import ( // ContainerCustomizer is an interface that can be used to configure the Testcontainers container // request. The passed request will be merged with the default one. type ContainerCustomizer interface { - Customize(req *GenericContainerRequest) + Customize(req *GenericContainerRequest) error } // CustomizeRequestOption is a type that can be used to configure the Testcontainers container request. // The passed request will be merged with the default one. -type CustomizeRequestOption func(req *GenericContainerRequest) +type CustomizeRequestOption func(req *GenericContainerRequest) error -func (opt CustomizeRequestOption) Customize(req *GenericContainerRequest) { - opt(req) +func (opt CustomizeRequestOption) Customize(req *GenericContainerRequest) error { + return opt(req) } // CustomizeRequest returns a function that can be used to merge the passed container request with the one that is used by the container. // Slices and Maps will be appended. func CustomizeRequest(src GenericContainerRequest) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { if err := mergo.Merge(req, &src, mergo.WithOverride, mergo.WithAppendSlice); err != nil { - Logger.Printf("error merging container request, keeping the original one. Error: %v", err) - return + return fmt.Errorf("error merging container request, keeping the original one: %w", err) } + + return nil } } // WithConfigModifier allows to override the default container config func WithConfigModifier(modifier func(config *container.Config)) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.ConfigModifier = modifier + + return nil } } // WithEndpointSettingsModifier allows to override the default endpoint settings func WithEndpointSettingsModifier(modifier func(settings map[string]*network.EndpointSettings)) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.EnpointSettingsModifier = modifier + + return nil } } // WithEnv sets the environment variables for a container. // If the environment variable already exists, it will be overridden. func WithEnv(envs map[string]string) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { if req.Env == nil { req.Env = map[string]string{} } @@ -64,20 +69,26 @@ func WithEnv(envs map[string]string) CustomizeRequestOption { for key, val := range envs { req.Env[key] = val } + + return nil } } // WithHostConfigModifier allows to override the default host config func WithHostConfigModifier(modifier func(hostConfig *container.HostConfig)) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.HostConfigModifier = modifier + + return nil } } // WithImage sets the image for a container func WithImage(image string) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.Image = image + + return nil } } @@ -138,19 +149,22 @@ func (p prependHubRegistry) Substitute(image string) (string, error) { // WithImageSubstitutors sets the image substitutors for a container func WithImageSubstitutors(fn ...ImageSubstitutor) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.ImageSubstitutors = fn + + return nil } } // WithLogConsumers sets the log consumers for a container func WithLogConsumers(consumer ...LogConsumer) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { if req.LogConsumerCfg == nil { req.LogConsumerCfg = &LogConsumerConfig{} } req.LogConsumerCfg.Consumers = consumer + return nil } } @@ -198,7 +212,7 @@ func (r RawCommand) AsCommand() []string { // It will leverage the container lifecycle hooks to call the command right after the container // is started. func WithStartupCommand(execs ...Executable) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { startupCommandsHook := ContainerLifecycleHooks{ PostStarts: []ContainerHook{}, } @@ -213,6 +227,8 @@ func WithStartupCommand(execs ...Executable) CustomizeRequestOption { } req.LifecycleHooks = append(req.LifecycleHooks, startupCommandsHook) + + return nil } } @@ -220,7 +236,7 @@ func WithStartupCommand(execs ...Executable) CustomizeRequestOption { // It will leverage the container lifecycle hooks to call the command right after the container // is ready. func WithAfterReadyCommand(execs ...Executable) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { postReadiesHook := []ContainerHook{} for _, exec := range execs { @@ -235,6 +251,8 @@ func WithAfterReadyCommand(execs ...Executable) CustomizeRequestOption { req.LifecycleHooks = append(req.LifecycleHooks, ContainerLifecycleHooks{ PostReadies: postReadiesHook, }) + + return nil } } @@ -245,7 +263,9 @@ func WithWaitStrategy(strategies ...wait.Strategy) CustomizeRequestOption { // WithWaitStrategyAndDeadline sets the wait strategy for a container, including deadline func WithWaitStrategyAndDeadline(deadline time.Duration, strategies ...wait.Strategy) CustomizeRequestOption { - return func(req *GenericContainerRequest) { + return func(req *GenericContainerRequest) error { req.WaitingFor = wait.ForAll(strategies...).WithDeadline(deadline) + + return nil } } diff --git a/options_test.go b/options_test.go index bbd4a944d5c..1bf1c078265 100644 --- a/options_test.go +++ b/options_test.go @@ -49,7 +49,8 @@ func TestOverrideContainerRequest(t *testing.T) { } // the toBeMergedRequest should be merged into the req - testcontainers.CustomizeRequest(toBeMergedRequest)(&req) + err := testcontainers.CustomizeRequest(toBeMergedRequest)(&req) + require.NoError(t, err) // toBeMergedRequest should not be changed assert.Equal(t, "", toBeMergedRequest.Env["BAR"]) @@ -87,7 +88,8 @@ func TestWithLogConsumers(t *testing.T) { lc := &msgsLogConsumer{} - testcontainers.WithLogConsumers(lc)(&req) + err := testcontainers.WithLogConsumers(lc)(&req) + require.NoError(t, err) c, err := testcontainers.GenericContainer(context.Background(), req) // we expect an error because the MySQL environment variables are not set @@ -112,7 +114,8 @@ func TestWithStartupCommand(t *testing.T) { testExec := testcontainers.NewRawCommand([]string{"touch", "/tmp/.testcontainers"}) - testcontainers.WithStartupCommand(testExec)(&req) + err := testcontainers.WithStartupCommand(testExec)(&req) + require.NoError(t, err) assert.Len(t, req.LifecycleHooks, 1) assert.Len(t, req.LifecycleHooks[0].PostStarts, 1) @@ -143,7 +146,8 @@ func TestWithAfterReadyCommand(t *testing.T) { testExec := testcontainers.NewRawCommand([]string{"touch", "/tmp/.testcontainers"}) - testcontainers.WithAfterReadyCommand(testExec)(&req) + err := testcontainers.WithAfterReadyCommand(testExec)(&req) + require.NoError(t, err) assert.Len(t, req.LifecycleHooks, 1) assert.Len(t, req.LifecycleHooks[0].PostReadies, 1) @@ -205,7 +209,7 @@ func TestWithEnv(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { opt := testcontainers.WithEnv(tc.env) - opt.Customize(tc.req) + require.NoError(t, opt.Customize(tc.req)) require.Equal(t, tc.expect, tc.req.Env) }) } diff --git a/reaper.go b/reaper.go index 3dff6a7c9d1..859f8b76de3 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 } diff --git a/wait/testdata/main.go b/wait/testdata/main.go index 4e89caf9f8d..42a006887c1 100644 --- a/wait/testdata/main.go +++ b/wait/testdata/main.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/base64" + "errors" "io" "log" "net/http" @@ -58,7 +59,7 @@ func main() { server := http.Server{Addr: ":6443", Handler: mux} go func() { log.Println("serving...") - if err := server.ListenAndServeTLS("tls.pem", "tls-key.pem"); err != nil && err != http.ErrServerClosed { + if err := server.ListenAndServeTLS("tls.pem", "tls-key.pem"); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } }()