diff --git a/docs/modules/gcloud-shared.md b/docs/modules/gcloud-shared.md
new file mode 100644
index 0000000000..b845b95de3
--- /dev/null
+++ b/docs/modules/gcloud-shared.md
@@ -0,0 +1,7 @@
+{% include "../features/common_functional_options.md" %}
+
+#### WithProjectID
+
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The `WithProjectID` function sets the project ID for the Google Cloud container.
diff --git a/docs/modules/gcloud.md b/docs/modules/gcloud.md
index 79b23c2935..364c6f3ce6 100644
--- a/docs/modules/gcloud.md
+++ b/docs/modules/gcloud.md
@@ -16,21 +16,47 @@ go get github.com/testcontainers/testcontainers-go/modules/gcloud
## Usage example
+The Google Cloud module exposes the following Go packages:
+
+- [BigQuery](#bigquery): `github.com/testcontainers/testcontainers-go/modules/gcloud/bigquery`.
+- [BigTable](#bigtable): `github.com/testcontainers/testcontainers-go/modules/gcloud/bigtable`.
+- [Datastore](#datastore): `github.com/testcontainers/testcontainers-go/modules/gcloud/datastore`.
+- [Firestore](#firestore): `github.com/testcontainers/testcontainers-go/modules/gcloud/firestore`.
+- [Pubsub](#pubsub): `github.com/testcontainers/testcontainers-go/modules/gcloud/pubsub`.
+- [Spanner](#spanner): `github.com/testcontainers/testcontainers-go/modules/gcloud/spanner`.
!!!info
By default, the all the emulators use `gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators` as the default Docker image, except for the BigQuery emulator, which uses `ghcr.io/goccy/bigquery-emulator:0.6.1`, and Spanner, which uses `gcr.io/cloud-spanner-emulator/emulator:1.4.0`.
-### BigQuery
+## BigQuery
-
-[Creating a BigQuery container](../../modules/gcloud/bigquery_test.go) inside_block:runBigQueryContainer
-[Obtaining a BigQuery client](../../modules/gcloud/bigquery_test.go) inside_block:bigQueryClient
-
+### Run function
-It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the client example above.
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The BigQuery module exposes one entrypoint function to create the BigQuery container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+### Container Options
+
+When starting the BigQuery container, you can pass options in a variadic way to configure it.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "ghcr.io/goccy/bigquery-emulator:0.6.1")`.
+
+{% include "./gcloud-shared.md" %}
#### Data YAML (Seed File)
-- Since testcontainers-go :material-tag: v0.35.0
+- Not available until the next release of testcontainers-go :material-tag: main
If you would like to do additional initialization in the BigQuery container, add a `data.yaml` file represented by an `io.Reader` to the container request with the `WithDataYAML` function.
That file is copied after the container is created but before it's started. The startup command then used will look like `--project test --data-from-yaml /testcontainers-data.yaml`.
@@ -38,77 +64,173 @@ That file is copied after the container is created but before it's started. The
An example of a `data.yaml` file that seeds the BigQuery instance with datasets and tables is shown below:
-[Data Yaml content](../../modules/gcloud/testdata/data.yaml)
+[Data Yaml content](../../modules/gcloud/bigquery/testdata/data.yaml)
-!!!warning
- This feature is only available for the `BigQuery` container, and if you pass multiple `WithDataYAML` options, an error is returned.
+### Examples
+
+
+[Creating a BigQuery container](../../modules/gcloud/bigquery/examples_test.go) inside_block:runBigQueryContainer
+[Obtaining a BigQuery client](../../modules/gcloud/bigquery/examples_test.go) inside_block:bigQueryClient
+
+
+It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the client example above.
+
+## BigTable
+
+### Run function
+
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The BigTable module exposes one entrypoint function to create the BigTable container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+### Container Options
+
+When starting the BigTable container, you can pass options in a variadic way to configure it.
+
+#### Image
-### BigTable
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+
+{% include "./gcloud-shared.md" %}
+
+### Examples
-[Creating a BigTable container](../../modules/gcloud/bigtable_test.go) inside_block:runBigTableContainer
-[Obtaining a BigTable Admin client](../../modules/gcloud/bigtable_test.go) inside_block:bigTableAdminClient
-[Obtaining a BigTable client](../../modules/gcloud/bigtable_test.go) inside_block:bigTableClient
+[Creating a BigTable container](../../modules/gcloud/bigtable/examples_test.go) inside_block:runBigTableContainer
+[Obtaining a BigTable Admin client](../../modules/gcloud/bigtable/examples_test.go) inside_block:bigTableAdminClient
+[Obtaining a BigTable client](../../modules/gcloud/bigtable/examples_test.go) inside_block:bigTableClient
It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the Admin client example above.
-### Datastore
+## Datastore
+
+### Run function
+
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The Datastore module exposes one entrypoint function to create the Datastore container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+### Container Options
+
+When starting the Datastore container, you can pass options in a variadic way to configure it.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+
+{% include "./gcloud-shared.md" %}
+
+### Examples
-[Creating a Datastore container](../../modules/gcloud/datastore_test.go) inside_block:runDatastoreContainer
-[Obtaining a Datastore client](../../modules/gcloud/datastore_test.go) inside_block:datastoreClient
+[Creating a Datastore container](../../modules/gcloud/datastore/examples_test.go) inside_block:runDatastoreContainer
+[Obtaining a Datastore client](../../modules/gcloud/datastore/examples_test.go) inside_block:datastoreClient
It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the client example above.
-### Firestore
+## Firestore
-
-[Creating a Firestore container](../../modules/gcloud/firestore_test.go) inside_block:runFirestoreContainer
-[Obtaining a Firestore client](../../modules/gcloud/firestore_test.go) inside_block:firestoreClient
-
+### Run function
-It's important to set the target string of the `grpc.NewClient` method using the container's URI, as shown in the client example above.
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The Firestore module exposes one entrypoint function to create the Firestore container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+### Container Options
+
+When starting the Firestore container, you can pass options in a variadic way to configure it.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+
+{% include "./gcloud-shared.md" %}
-### Pubsub
+### Examples
-[Creating a Pubsub container](../../modules/gcloud/pubsub_test.go) inside_block:runPubsubContainer
-[Obtaining a Pubsub client](../../modules/gcloud/pubsub_test.go) inside_block:pubsubClient
+[Creating a Firestore container](../../modules/gcloud/firestore/examples_test.go) inside_block:runFirestoreContainer
+[Obtaining a Firestore client](../../modules/gcloud/firestore/examples_test.go) inside_block:firestoreClient
It's important to set the target string of the `grpc.NewClient` method using the container's URI, as shown in the client example above.
-### Spanner
+## Pubsub
+
+### Run function
+
+- Not available until the next release of testcontainers-go :material-tag: main
+
+The Pubsub module exposes one entrypoint function to create the Pubsub container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+### Container Options
+
+When starting the Pubsub container, you can pass options in a variadic way to configure it.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+
+{% include "./gcloud-shared.md" %}
+
+### Examples
-[Creating a Spanner container](../../modules/gcloud/spanner_test.go) inside_block:runSpannerContainer
-[Obtaining a Spanner Admin client](../../modules/gcloud/spanner_test.go) inside_block:spannerAdminClient
-[Obtaining a Spanner Database Admin client](../../modules/gcloud/spanner_test.go) inside_block:spannerDBAdminClient
+[Creating a Pubsub container](../../modules/gcloud/pubsub/examples_test.go) inside_block:runPubsubContainer
+[Obtaining a Pubsub client](../../modules/gcloud/pubsub/examples_test.go) inside_block:pubsubClient
-It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the Admin client example above.
+It's important to set the target string of the `grpc.NewClient` method using the container's URI, as shown in the client example above.
-## Module Reference
+## Spanner
### Run function
-- Since testcontainers-go :material-tag: v0.32.0
+- Not available until the next release of testcontainers-go :material-tag: main
-!!!info
- The `RunXXXContainer(ctx, opts...)` functions are deprecated and will be removed in the next major release of _Testcontainers for Go_.
-
-The GCloud module exposes one entrypoint function to create the different GCloud emulators, and each function receives three parameters:
+The Spanner module exposes one entrypoint function to create the Spanner container, and this function receives three parameters:
```golang
-func RunBigQuery(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*BigQueryContainer, error)
-func RunBigTable(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*BigTableContainer, error)
-func RunDatastore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatastoreContainer, error)
-func RunFirestore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*FirestoreContainer, error)
-func RunPubsub(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*PubsubContainer, error)
-func RunSpanner(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*SpannerContainer, error)
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
```
- `context.Context`, the Go context.
@@ -117,15 +239,21 @@ func RunSpanner(ctx context.Context, img string, opts ...testcontainers.Containe
### Container Options
-When starting any of the GCloud containers, you can pass options in a variadic way to configure it.
+When starting the Spanner container, you can pass options in a variadic way to configure it.
#### Image
-Use the second argument in the `RunXXX` function (`RunBigQuery, RunDatastore`, ...) to set a valid Docker image.
-In example: `RunXXX(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`.
+
+{% include "./gcloud-shared.md" %}
-{% include "../features/common_functional_options.md" %}
+### Examples
-### Container Methods
+
+[Creating a Spanner container](../../modules/gcloud/spanner/examples_test.go) inside_block:runSpannerContainer
+[Obtaining a Spanner Admin client](../../modules/gcloud/spanner/examples_test.go) inside_block:spannerAdminClient
+[Obtaining a Spanner Database Admin client](../../modules/gcloud/spanner/examples_test.go) inside_block:spannerDBAdminClient
+
-The GCloud container exposes the following methods:
+It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the Admin client example above.
diff --git a/modules/gcloud/bigquery.go b/modules/gcloud/bigquery.go
index 6e6d7627dc..1e99aafda9 100644
--- a/modules/gcloud/bigquery.go
+++ b/modules/gcloud/bigquery.go
@@ -8,14 +8,15 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunBigQuery instead
+// Deprecated: use [bigquery.Run] instead.
// RunBigQueryContainer creates an instance of the GCloud container type for BigQuery.
func RunBigQueryContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunBigQuery(ctx, "ghcr.io/goccy/bigquery-emulator:0.6.1", opts...)
}
+// Deprecated: use [bigquery.Run] instead.
// RunBigQuery creates an instance of the GCloud container type for BigQuery.
-// The URI will always use http:// as the protocol.
+// The URI uses http:// as the protocol.
func RunBigQuery(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
diff --git a/modules/gcloud/bigquery/bigquery.go b/modules/gcloud/bigquery/bigquery.go
new file mode 100644
index 0000000000..899d16e86e
--- /dev/null
+++ b/modules/gcloud/bigquery/bigquery.go
@@ -0,0 +1,84 @@
+package bigquery
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the BigQuery container.
+ DefaultProjectID = "test-project"
+
+ // bigQueryDataYamlPath is the path to the data yaml file in the container.
+ bigQueryDataYamlPath = "/testcontainers-data.yaml"
+)
+
+// Container represents the BigQuery container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the BigQuery container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the BigQuery container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the BigQuery GCloud container type.
+// The URI uses http:// as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"9050/tcp", "9060/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("9050/tcp"),
+ wait.ForHTTP("/discovery/v1/apis/bigquery/v2/rest").WithPort("9050/tcp").WithStatusCodeMatcher(func(status int) bool {
+ return status == 200
+ }).WithStartupTimeout(time.Second*5),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ req.Cmd = append(req.Cmd, "--project", settings.ProjectID)
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "9050/tcp", "http")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/bigquery/bigquery_test.go b/modules/gcloud/bigquery/bigquery_test.go
new file mode 100644
index 0000000000..5e395f6df3
--- /dev/null
+++ b/modules/gcloud/bigquery/bigquery_test.go
@@ -0,0 +1,100 @@
+package bigquery_test
+
+import (
+ "bytes"
+ "context"
+ _ "embed"
+ "errors"
+ "testing"
+
+ "cloud.google.com/go/bigquery"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/iterator"
+ "google.golang.org/api/option"
+ "google.golang.org/api/option/internaloption"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcbigquery "github.com/testcontainers/testcontainers-go/modules/gcloud/bigquery"
+)
+
+//go:embed testdata/data.yaml
+var dataYaml []byte
+
+func TestBigQueryWithDataYAML(t *testing.T) {
+ ctx := context.Background()
+
+ t.Run("valid", func(t *testing.T) {
+ bigQueryContainer, err := tcbigquery.Run(
+ ctx,
+ "ghcr.io/goccy/bigquery-emulator:0.6.1",
+ tcbigquery.WithProjectID("test"),
+ tcbigquery.WithDataYAML(bytes.NewReader(dataYaml)),
+ )
+ testcontainers.CleanupContainer(t, bigQueryContainer)
+ require.NoError(t, err)
+
+ projectID := bigQueryContainer.ProjectID()
+
+ opts := []option.ClientOption{
+ option.WithEndpoint(bigQueryContainer.URI()),
+ option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
+ option.WithoutAuthentication(),
+ internaloption.SkipDialSettingsValidation(),
+ }
+
+ client, err := bigquery.NewClient(ctx, projectID, opts...)
+ require.NoError(t, err)
+ defer client.Close()
+
+ selectQuery := client.Query("SELECT * FROM dataset1.table_a where name = @name")
+ selectQuery.QueryConfig.Parameters = []bigquery.QueryParameter{
+ {Name: "name", Value: "bob"},
+ }
+ it, err := selectQuery.Read(ctx)
+ require.NoError(t, err)
+
+ var val []bigquery.Value
+ for {
+ err := it.Next(&val)
+ if errors.Is(err, iterator.Done) {
+ break
+ }
+ require.NoError(t, err)
+ }
+
+ require.Equal(t, int64(30), val[0])
+ })
+
+ t.Run("multi-value-set", func(t *testing.T) {
+ bigQueryContainer, err := tcbigquery.Run(
+ ctx,
+ "ghcr.io/goccy/bigquery-emulator:0.6.1",
+ tcbigquery.WithProjectID("test"),
+ tcbigquery.WithDataYAML(bytes.NewReader(dataYaml)),
+ tcbigquery.WithDataYAML(bytes.NewReader(dataYaml)),
+ )
+ testcontainers.CleanupContainer(t, bigQueryContainer)
+ require.EqualError(t, err, `data yaml already exists`)
+ })
+
+ t.Run("multi-value-not-set", func(t *testing.T) {
+ noValueOption := func() testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ req.Cmd = append(req.Cmd, "--data-from-yaml")
+ return nil
+ }
+ }
+
+ bigQueryContainer, err := tcbigquery.Run(
+ ctx,
+ "ghcr.io/goccy/bigquery-emulator:0.6.1",
+ noValueOption(), // because --project is always added last, this option will receive `--project` as value, which results in an error
+ tcbigquery.WithProjectID("test"),
+ tcbigquery.WithDataYAML(bytes.NewReader(dataYaml)),
+ )
+ testcontainers.CleanupContainer(t, bigQueryContainer)
+ require.Error(t, err)
+ })
+}
diff --git a/modules/gcloud/bigquery/examples_test.go b/modules/gcloud/bigquery/examples_test.go
new file mode 100644
index 0000000000..4176db7af7
--- /dev/null
+++ b/modules/gcloud/bigquery/examples_test.go
@@ -0,0 +1,87 @@
+package bigquery_test
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "log"
+
+ "cloud.google.com/go/bigquery"
+ "google.golang.org/api/iterator"
+ "google.golang.org/api/option"
+ "google.golang.org/api/option/internaloption"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcbigquery "github.com/testcontainers/testcontainers-go/modules/gcloud/bigquery"
+)
+
+func ExampleRun() {
+ // runBigQueryContainer {
+ ctx := context.Background()
+
+ bigQueryContainer, err := tcbigquery.Run(
+ ctx,
+ "ghcr.io/goccy/bigquery-emulator:0.6.1",
+ tcbigquery.WithProjectID("bigquery-project"),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(bigQueryContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to run container: %v", err)
+ return
+ }
+ // }
+
+ // bigQueryClient {
+ projectID := bigQueryContainer.ProjectID()
+
+ opts := []option.ClientOption{
+ option.WithEndpoint(bigQueryContainer.URI()),
+ option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
+ option.WithoutAuthentication(),
+ internaloption.SkipDialSettingsValidation(),
+ }
+
+ client, err := bigquery.NewClient(ctx, projectID, opts...)
+ if err != nil {
+ log.Printf("failed to create bigquery client: %v", err)
+ return
+ }
+ defer client.Close()
+ // }
+
+ createFnQuery := client.Query("CREATE FUNCTION testr(arr ARRAY>) AS ((SELECT SUM(IF(elem.name = \"foo\",elem.val,null)) FROM UNNEST(arr) AS elem))")
+ _, err = createFnQuery.Read(ctx)
+ if err != nil {
+ log.Printf("failed to create function: %v", err)
+ return
+ }
+
+ selectQuery := client.Query("SELECT testr([STRUCT(\"foo\", 10), STRUCT(\"bar\", 40), STRUCT(\"foo\", 20)])")
+ it, err := selectQuery.Read(ctx)
+ if err != nil {
+ log.Printf("failed to read query: %v", err)
+ return
+ }
+
+ var val []bigquery.Value
+ for {
+ err := it.Next(&val)
+ if errors.Is(err, iterator.Done) {
+ break
+ }
+ if err != nil {
+ log.Printf("failed to iterate: %v", err)
+ return
+ }
+ }
+
+ fmt.Println(val)
+ // Output:
+ // [30]
+}
diff --git a/modules/gcloud/bigquery/options.go b/modules/gcloud/bigquery/options.go
new file mode 100644
index 0000000000..16e080bcee
--- /dev/null
+++ b/modules/gcloud/bigquery/options.go
@@ -0,0 +1,47 @@
+package bigquery
+
+import (
+ "errors"
+ "io"
+ "slices"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+)
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
+
+// WithDataYAML seeds the BigQuery project for the GCloud container with an [io.Reader] representing
+// the data yaml file, which is used to copy the file to the container, and then processed to seed
+// the BigQuery project.
+//
+// Other GCloud containers will ignore this option.
+func WithDataYAML(r io.Reader) testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ if slices.Contains(req.Cmd, "--data-from-yaml") {
+ return errors.New("data yaml already exists")
+ }
+
+ req.Cmd = append(req.Cmd, "--data-from-yaml", bigQueryDataYamlPath)
+
+ req.Files = append(req.Files, testcontainers.ContainerFile{
+ Reader: r,
+ ContainerFilePath: bigQueryDataYamlPath,
+ FileMode: 0o644,
+ })
+
+ return nil
+ }
+}
diff --git a/modules/gcloud/bigquery/options_test.go b/modules/gcloud/bigquery/options_test.go
new file mode 100644
index 0000000000..b24da996b2
--- /dev/null
+++ b/modules/gcloud/bigquery/options_test.go
@@ -0,0 +1,38 @@
+package bigquery
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+)
+
+func TestWithDataYAML(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ req := &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{},
+ }
+
+ err := WithDataYAML(bytes.NewReader([]byte("")))(req)
+ require.NoError(t, err)
+ require.Contains(t, req.Cmd, "--data-from-yaml")
+ require.Len(t, req.Files, 1)
+ })
+
+ t.Run("double-calls-errors", func(t *testing.T) {
+ req := &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{},
+ }
+
+ err := WithDataYAML(bytes.NewReader([]byte("")))(req)
+ require.NoError(t, err)
+ require.Contains(t, req.Cmd, "--data-from-yaml")
+ require.Len(t, req.Files, 1)
+
+ err = WithDataYAML(bytes.NewReader([]byte("")))(req)
+ require.Error(t, err)
+ require.Len(t, req.Files, 1)
+ })
+}
diff --git a/modules/gcloud/testdata/data.yaml b/modules/gcloud/bigquery/testdata/data.yaml
similarity index 100%
rename from modules/gcloud/testdata/data.yaml
rename to modules/gcloud/bigquery/testdata/data.yaml
diff --git a/modules/gcloud/bigquery_test.go b/modules/gcloud/bigquery_test.go
deleted file mode 100644
index 221a750c2d..0000000000
--- a/modules/gcloud/bigquery_test.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package gcloud_test
-
-import (
- "bytes"
- "context"
- _ "embed"
- "errors"
- "fmt"
- "log"
- "testing"
-
- "cloud.google.com/go/bigquery"
- "github.com/stretchr/testify/require"
- "google.golang.org/api/iterator"
- "google.golang.org/api/option"
- "google.golang.org/api/option/internaloption"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials/insecure"
-
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
-)
-
-//go:embed testdata/data.yaml
-var dataYaml []byte
-
-func ExampleRunBigQueryContainer() {
- // runBigQueryContainer {
- ctx := context.Background()
-
- bigQueryContainer, err := gcloud.RunBigQuery(
- ctx,
- "ghcr.io/goccy/bigquery-emulator:0.6.1",
- gcloud.WithProjectID("bigquery-project"),
- )
- defer func() {
- if err := testcontainers.TerminateContainer(bigQueryContainer); err != nil {
- log.Printf("failed to terminate container: %s", err)
- }
- }()
- if err != nil {
- log.Printf("failed to run container: %v", err)
- return
- }
- // }
-
- // bigQueryClient {
- projectID := bigQueryContainer.Settings.ProjectID
-
- opts := []option.ClientOption{
- option.WithEndpoint(bigQueryContainer.URI),
- option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
- option.WithoutAuthentication(),
- internaloption.SkipDialSettingsValidation(),
- }
-
- client, err := bigquery.NewClient(ctx, projectID, opts...)
- if err != nil {
- log.Printf("failed to create bigquery client: %v", err)
- return
- }
- defer client.Close()
- // }
-
- createFnQuery := client.Query("CREATE FUNCTION testr(arr ARRAY>) AS ((SELECT SUM(IF(elem.name = \"foo\",elem.val,null)) FROM UNNEST(arr) AS elem))")
- _, err = createFnQuery.Read(ctx)
- if err != nil {
- log.Printf("failed to create function: %v", err)
- return
- }
-
- selectQuery := client.Query("SELECT testr([STRUCT(\"foo\", 10), STRUCT(\"bar\", 40), STRUCT(\"foo\", 20)])")
- it, err := selectQuery.Read(ctx)
- if err != nil {
- log.Printf("failed to read query: %v", err)
- return
- }
-
- var val []bigquery.Value
- for {
- err := it.Next(&val)
- if errors.Is(err, iterator.Done) {
- break
- }
- if err != nil {
- log.Printf("failed to iterate: %v", err)
- return
- }
- }
-
- fmt.Println(val)
- // Output:
- // [30]
-}
-
-func TestBigQueryWithDataYAML(t *testing.T) {
- ctx := context.Background()
-
- t.Run("valid", func(t *testing.T) {
- bigQueryContainer, err := gcloud.RunBigQuery(
- ctx,
- "ghcr.io/goccy/bigquery-emulator:0.6.1",
- gcloud.WithProjectID("test"),
- gcloud.WithDataYAML(bytes.NewReader(dataYaml)),
- )
- testcontainers.CleanupContainer(t, bigQueryContainer)
- require.NoError(t, err)
-
- projectID := bigQueryContainer.Settings.ProjectID
-
- opts := []option.ClientOption{
- option.WithEndpoint(bigQueryContainer.URI),
- option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
- option.WithoutAuthentication(),
- internaloption.SkipDialSettingsValidation(),
- }
-
- client, err := bigquery.NewClient(ctx, projectID, opts...)
- require.NoError(t, err)
- defer client.Close()
-
- selectQuery := client.Query("SELECT * FROM dataset1.table_a where name = @name")
- selectQuery.QueryConfig.Parameters = []bigquery.QueryParameter{
- {Name: "name", Value: "bob"},
- }
- it, err := selectQuery.Read(ctx)
- require.NoError(t, err)
-
- var val []bigquery.Value
- for {
- err := it.Next(&val)
- if errors.Is(err, iterator.Done) {
- break
- }
- require.NoError(t, err)
- }
-
- require.Equal(t, int64(30), val[0])
- })
-
- t.Run("multi-value-set", func(t *testing.T) {
- bigQueryContainer, err := gcloud.RunBigQuery(
- ctx,
- "ghcr.io/goccy/bigquery-emulator:0.6.1",
- gcloud.WithProjectID("test"),
- gcloud.WithDataYAML(bytes.NewReader(dataYaml)),
- gcloud.WithDataYAML(bytes.NewReader(dataYaml)),
- )
- testcontainers.CleanupContainer(t, bigQueryContainer)
- require.EqualError(t, err, `data yaml already exists`)
- })
-
- t.Run("multi-value-not-set", func(t *testing.T) {
- noValueOption := func() testcontainers.CustomizeRequestOption {
- return func(req *testcontainers.GenericContainerRequest) error {
- req.Cmd = append(req.Cmd, "--data-from-yaml")
- return nil
- }
- }
-
- bigQueryContainer, err := gcloud.RunBigQuery(
- ctx,
- "ghcr.io/goccy/bigquery-emulator:0.6.1",
- noValueOption(), // because --project is always added last, this option will receive `--project` as value, which results in an error
- gcloud.WithProjectID("test"),
- gcloud.WithDataYAML(bytes.NewReader(dataYaml)),
- )
- testcontainers.CleanupContainer(t, bigQueryContainer)
- require.Error(t, err)
- })
-}
diff --git a/modules/gcloud/bigtable.go b/modules/gcloud/bigtable.go
index 134f14d1d6..e133b55e46 100644
--- a/modules/gcloud/bigtable.go
+++ b/modules/gcloud/bigtable.go
@@ -7,12 +7,13 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunBigTable instead
+// Deprecated: use [bigtable.Run] instead
// RunBigTableContainer creates an instance of the GCloud container type for BigTable.
func RunBigTableContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunBigQuery(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...)
}
+// Deprecated: use [bigtable.Run] instead
// RunBigTable creates an instance of the GCloud container type for BigTable.
func RunBigTable(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
diff --git a/modules/gcloud/bigtable/bigtable.go b/modules/gcloud/bigtable/bigtable.go
new file mode 100644
index 0000000000..5d6a7c98b5
--- /dev/null
+++ b/modules/gcloud/bigtable/bigtable.go
@@ -0,0 +1,82 @@
+package bigtable
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the BigTable container.
+ DefaultProjectID = "test-project"
+)
+
+// Container represents the BigTable container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the BigTable container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the BigTable container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the BigTable GCloud container type.
+// The URI uses the empty string as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"9000/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("9000/tcp"),
+ wait.ForLog("running"),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ req.Cmd = []string{
+ "/bin/sh",
+ "-c",
+ "gcloud beta emulators bigtable start --host-port 0.0.0.0:9000 --project=" + settings.ProjectID,
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "9000/tcp", "")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/bigtable/bigtable_test.go b/modules/gcloud/bigtable/bigtable_test.go
new file mode 100644
index 0000000000..129cefef7b
--- /dev/null
+++ b/modules/gcloud/bigtable/bigtable_test.go
@@ -0,0 +1,66 @@
+package bigtable_test
+
+import (
+ "context"
+ "testing"
+
+ "cloud.google.com/go/bigtable"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/option"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcbigtable "github.com/testcontainers/testcontainers-go/modules/gcloud/bigtable"
+)
+
+func TestRun(t *testing.T) {
+ ctx := context.Background()
+
+ bigTableContainer, err := tcbigtable.Run(
+ ctx,
+ "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
+ tcbigtable.WithProjectID("bigtable-project"),
+ )
+ testcontainers.CleanupContainer(t, bigTableContainer)
+ require.NoError(t, err)
+
+ projectId := bigTableContainer.ProjectID()
+
+ const (
+ instanceId = "test-instance"
+ tableName = "test-table"
+ )
+
+ options := []option.ClientOption{
+ option.WithEndpoint(bigTableContainer.URI()),
+ option.WithoutAuthentication(),
+ option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
+ }
+ adminClient, err := bigtable.NewAdminClient(ctx, projectId, instanceId, options...)
+ require.NoError(t, err)
+ defer adminClient.Close()
+
+ err = adminClient.CreateTable(ctx, tableName)
+ require.NoError(t, err)
+
+ err = adminClient.CreateColumnFamily(ctx, tableName, "name")
+ require.NoError(t, err)
+
+ client, err := bigtable.NewClient(ctx, projectId, instanceId, options...)
+ require.NoError(t, err)
+ defer client.Close()
+
+ tbl := client.Open(tableName)
+
+ mut := bigtable.NewMutation()
+ mut.Set("name", "firstName", bigtable.Now(), []byte("Gopher"))
+
+ err = tbl.Apply(ctx, "1", mut)
+ require.NoError(t, err)
+
+ row, err := tbl.ReadRow(ctx, "1", bigtable.RowFilter(bigtable.FamilyFilter("name")))
+ require.NoError(t, err)
+
+ require.Equal(t, "Gopher", string(row["name"][0].Value))
+}
diff --git a/modules/gcloud/bigtable_test.go b/modules/gcloud/bigtable/examples_test.go
similarity index 86%
rename from modules/gcloud/bigtable_test.go
rename to modules/gcloud/bigtable/examples_test.go
index 553581bcc4..a94419c502 100644
--- a/modules/gcloud/bigtable_test.go
+++ b/modules/gcloud/bigtable/examples_test.go
@@ -1,4 +1,4 @@
-package gcloud_test
+package bigtable_test
import (
"context"
@@ -11,17 +11,17 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
+ tcbigtable "github.com/testcontainers/testcontainers-go/modules/gcloud/bigtable"
)
-func ExampleRunBigTableContainer() {
+func ExampleRun() {
// runBigTableContainer {
ctx := context.Background()
- bigTableContainer, err := gcloud.RunBigTable(
+ bigTableContainer, err := tcbigtable.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
- gcloud.WithProjectID("bigtable-project"),
+ tcbigtable.WithProjectID("bigtable-project"),
)
defer func() {
if err := testcontainers.TerminateContainer(bigTableContainer); err != nil {
@@ -35,7 +35,7 @@ func ExampleRunBigTableContainer() {
// }
// bigTableAdminClient {
- projectId := bigTableContainer.Settings.ProjectID
+ projectId := bigTableContainer.ProjectID()
const (
instanceId = "test-instance"
@@ -43,7 +43,7 @@ func ExampleRunBigTableContainer() {
)
options := []option.ClientOption{
- option.WithEndpoint(bigTableContainer.URI),
+ option.WithEndpoint(bigTableContainer.URI()),
option.WithoutAuthentication(),
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
}
diff --git a/modules/gcloud/bigtable/options.go b/modules/gcloud/bigtable/options.go
new file mode 100644
index 0000000000..b8ae75b8f4
--- /dev/null
+++ b/modules/gcloud/bigtable/options.go
@@ -0,0 +1,17 @@
+package bigtable
+
+import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
diff --git a/modules/gcloud/datastore.go b/modules/gcloud/datastore.go
index caf53e9879..02fbd73f6a 100644
--- a/modules/gcloud/datastore.go
+++ b/modules/gcloud/datastore.go
@@ -7,12 +7,13 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunDatastore instead
+// Deprecated: use [datastore.Run] instead
// RunDatastoreContainer creates an instance of the GCloud container type for Datastore.
func RunDatastoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunDatastore(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...)
}
+// Deprecated: use [datastore.Run] instead
// RunDatastore creates an instance of the GCloud container type for Datastore.
func RunDatastore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
diff --git a/modules/gcloud/datastore/datastore.go b/modules/gcloud/datastore/datastore.go
new file mode 100644
index 0000000000..b421925079
--- /dev/null
+++ b/modules/gcloud/datastore/datastore.go
@@ -0,0 +1,82 @@
+package datastore
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the Datastore container.
+ DefaultProjectID = "test-project"
+)
+
+// Container represents the Datastore container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the Datastore container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the Datastore container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the Datastore GCloud container type.
+// The URI uses the empty string as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"8081/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("8081/tcp"),
+ wait.ForHTTP("/").WithPort("8081/tcp"),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ req.Cmd = []string{
+ "/bin/sh",
+ "-c",
+ "gcloud beta emulators datastore start --host-port 0.0.0.0:8081 --project=" + settings.ProjectID,
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "8081/tcp", "")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/datastore/datastore_test.go b/modules/gcloud/datastore/datastore_test.go
new file mode 100644
index 0000000000..e844ec0e99
--- /dev/null
+++ b/modules/gcloud/datastore/datastore_test.go
@@ -0,0 +1,60 @@
+package datastore_test
+
+import (
+ "context"
+ "log"
+ "testing"
+
+ "cloud.google.com/go/datastore"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/option"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcdatastore "github.com/testcontainers/testcontainers-go/modules/gcloud/datastore"
+)
+
+func TestRun(t *testing.T) {
+ ctx := context.Background()
+
+ datastoreContainer, err := tcdatastore.Run(
+ ctx,
+ "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
+ tcdatastore.WithProjectID("datastore-project"),
+ )
+ testcontainers.CleanupContainer(t, datastoreContainer)
+ require.NoError(t, err)
+
+ projectID := datastoreContainer.ProjectID()
+
+ options := []option.ClientOption{
+ option.WithEndpoint(datastoreContainer.URI()),
+ option.WithoutAuthentication(),
+ option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
+ }
+
+ dsClient, err := datastore.NewClient(ctx, projectID, options...)
+ if err != nil {
+ log.Printf("failed to create client: %v", err)
+ return
+ }
+ defer dsClient.Close()
+
+ type Task struct {
+ Description string
+ }
+
+ k := datastore.NameKey("Task", "sample", nil)
+ data := Task{
+ Description: "my description",
+ }
+ _, err = dsClient.Put(ctx, k, &data)
+ require.NoError(t, err)
+
+ saved := Task{}
+ err = dsClient.Get(ctx, k, &saved)
+ require.NoError(t, err)
+
+ require.Equal(t, "my description", saved.Description)
+}
diff --git a/modules/gcloud/datastore_test.go b/modules/gcloud/datastore/examples_test.go
similarity index 81%
rename from modules/gcloud/datastore_test.go
rename to modules/gcloud/datastore/examples_test.go
index fa056bbf63..7093f0e46b 100644
--- a/modules/gcloud/datastore_test.go
+++ b/modules/gcloud/datastore/examples_test.go
@@ -1,4 +1,4 @@
-package gcloud_test
+package datastore_test
import (
"context"
@@ -11,17 +11,17 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
+ tcdatastore "github.com/testcontainers/testcontainers-go/modules/gcloud/datastore"
)
-func ExampleRunDatastoreContainer() {
+func ExampleRun() {
// runDatastoreContainer {
ctx := context.Background()
- datastoreContainer, err := gcloud.RunDatastore(
+ datastoreContainer, err := tcdatastore.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
- gcloud.WithProjectID("datastore-project"),
+ tcdatastore.WithProjectID("datastore-project"),
)
defer func() {
if err := testcontainers.TerminateContainer(datastoreContainer); err != nil {
@@ -35,10 +35,10 @@ func ExampleRunDatastoreContainer() {
// }
// datastoreClient {
- projectID := datastoreContainer.Settings.ProjectID
+ projectID := datastoreContainer.ProjectID()
options := []option.ClientOption{
- option.WithEndpoint(datastoreContainer.URI),
+ option.WithEndpoint(datastoreContainer.URI()),
option.WithoutAuthentication(),
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
}
diff --git a/modules/gcloud/datastore/options.go b/modules/gcloud/datastore/options.go
new file mode 100644
index 0000000000..2adbd09340
--- /dev/null
+++ b/modules/gcloud/datastore/options.go
@@ -0,0 +1,17 @@
+package datastore
+
+import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
diff --git a/modules/gcloud/firestore.go b/modules/gcloud/firestore.go
index 297b47f80c..b333a64f44 100644
--- a/modules/gcloud/firestore.go
+++ b/modules/gcloud/firestore.go
@@ -7,12 +7,13 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunFirestore instead
+// Deprecated: use [firestore.Run] instead
// RunFirestoreContainer creates an instance of the GCloud container type for Firestore.
func RunFirestoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunFirestore(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...)
}
+// Deprecated: use [firestore.Run] instead
// RunFirestore creates an instance of the GCloud container type for Firestore.
func RunFirestore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
diff --git a/modules/gcloud/firestore_test.go b/modules/gcloud/firestore/examples_test.go
similarity index 81%
rename from modules/gcloud/firestore_test.go
rename to modules/gcloud/firestore/examples_test.go
index a457f2764e..d06abe3200 100644
--- a/modules/gcloud/firestore_test.go
+++ b/modules/gcloud/firestore/examples_test.go
@@ -1,4 +1,4 @@
-package gcloud_test
+package firestore_test
import (
"context"
@@ -11,7 +11,7 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
+ tcfirestore "github.com/testcontainers/testcontainers-go/modules/gcloud/firestore"
)
type emulatorCreds struct{}
@@ -24,14 +24,14 @@ func (ec emulatorCreds) RequireTransportSecurity() bool {
return false
}
-func ExampleRunFirestoreContainer() {
+func ExampleRun() {
// runFirestoreContainer {
ctx := context.Background()
- firestoreContainer, err := gcloud.RunFirestore(
+ firestoreContainer, err := tcfirestore.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
- gcloud.WithProjectID("firestore-project"),
+ tcfirestore.WithProjectID("firestore-project"),
)
defer func() {
if err := testcontainers.TerminateContainer(firestoreContainer); err != nil {
@@ -45,9 +45,9 @@ func ExampleRunFirestoreContainer() {
// }
// firestoreClient {
- projectID := firestoreContainer.Settings.ProjectID
+ projectID := firestoreContainer.ProjectID()
- conn, err := grpc.NewClient(firestoreContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{}))
+ conn, err := grpc.NewClient(firestoreContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{}))
if err != nil {
log.Printf("failed to dial: %v", err)
return
diff --git a/modules/gcloud/firestore/firestore.go b/modules/gcloud/firestore/firestore.go
new file mode 100644
index 0000000000..cade5dd48a
--- /dev/null
+++ b/modules/gcloud/firestore/firestore.go
@@ -0,0 +1,82 @@
+package firestore
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the Firestore container.
+ DefaultProjectID = "test-project"
+)
+
+// Container represents the Firestore container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the Firestore container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the Firestore container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the Firestore GCloud container type.
+// The URI uses the empty string as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"8080/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("8080/tcp"),
+ wait.ForLog("running"),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ req.Cmd = []string{
+ "/bin/sh",
+ "-c",
+ "gcloud beta emulators firestore start --host-port 0.0.0.0:8080 --project=" + settings.ProjectID,
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "8080/tcp", "")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/firestore/firestore_test.go b/modules/gcloud/firestore/firestore_test.go
new file mode 100644
index 0000000000..a98a52b2dc
--- /dev/null
+++ b/modules/gcloud/firestore/firestore_test.go
@@ -0,0 +1,61 @@
+package firestore_test
+
+import (
+ "context"
+ "testing"
+
+ "cloud.google.com/go/firestore"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/option"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcfirestore "github.com/testcontainers/testcontainers-go/modules/gcloud/firestore"
+)
+
+func TestRun(t *testing.T) {
+ ctx := context.Background()
+
+ firestoreContainer, err := tcfirestore.Run(
+ ctx,
+ "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
+ tcfirestore.WithProjectID("firestore-project"),
+ )
+ testcontainers.CleanupContainer(t, firestoreContainer)
+ require.NoError(t, err)
+
+ projectID := firestoreContainer.ProjectID()
+
+ conn, err := grpc.NewClient(firestoreContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+ require.NoError(t, err)
+
+ options := []option.ClientOption{option.WithGRPCConn(conn)}
+ client, err := firestore.NewClient(ctx, projectID, options...)
+ require.NoError(t, err)
+ defer client.Close()
+
+ users := client.Collection("users")
+ docRef := users.Doc("alovelace")
+
+ type Person struct {
+ Firstname string `json:"firstname"`
+ Lastname string `json:"lastname"`
+ }
+
+ data := Person{
+ Firstname: "Ada",
+ Lastname: "Lovelace",
+ }
+ _, err = docRef.Create(ctx, data)
+ require.NoError(t, err)
+
+ docsnap, err := docRef.Get(ctx)
+ require.NoError(t, err)
+
+ var saved Person
+ require.NoError(t, docsnap.DataTo(&saved))
+
+ require.Equal(t, "Ada", saved.Firstname)
+ require.Equal(t, "Lovelace", saved.Lastname)
+}
diff --git a/modules/gcloud/firestore/options.go b/modules/gcloud/firestore/options.go
new file mode 100644
index 0000000000..8c6f32a947
--- /dev/null
+++ b/modules/gcloud/firestore/options.go
@@ -0,0 +1,17 @@
+package firestore
+
+import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
diff --git a/modules/gcloud/gcloud.go b/modules/gcloud/gcloud.go
index 2b6a28ed5d..157bbf934f 100644
--- a/modules/gcloud/gcloud.go
+++ b/modules/gcloud/gcloud.go
@@ -13,6 +13,13 @@ import (
const defaultProjectID = "test-project"
+// Deprecated: use the specialized containers instead:
+// - [bigquery.Container]
+// - [bigtable.Container]
+// - [datastore.Container]
+// - [firestore.Container]
+// - [pubsub.Container]
+// - [spanner.Container]
type GCloudContainer struct {
testcontainers.Container
Settings options
@@ -76,6 +83,7 @@ func WithProjectID(projectID string) Option {
}
}
+// Deprecated: Use [bigquery.WithDataYAML] instead.
// WithDataYAML seeds the BigQuery project for the GCloud container with an [io.Reader] representing
// the data yaml file, which is used to copy the file to the container, and then processed to seed
// the BigQuery project.
diff --git a/modules/gcloud/internal/shared/shared.go b/modules/gcloud/internal/shared/shared.go
new file mode 100644
index 0000000000..0caf6a56df
--- /dev/null
+++ b/modules/gcloud/internal/shared/shared.go
@@ -0,0 +1,44 @@
+package shared
+
+import (
+ "github.com/testcontainers/testcontainers-go"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the Pubsub container.
+ DefaultProjectID = "test-project"
+)
+
+// Options represents the options for the different GCloud containers.
+// This type must contain all the options that are common to all the GCloud containers.
+type Options struct {
+ ProjectID string
+ URI string
+}
+
+// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface.
+var _ testcontainers.ContainerCustomizer = (*Option)(nil)
+
+// Option is an option for the GCloud container.
+type Option func(*Options) error
+
+// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface.
+func (o Option) Customize(*testcontainers.GenericContainerRequest) error {
+ // NOOP to satisfy interface.
+ return nil
+}
+
+// DefaultOptions returns a new Options instance with the default project ID.
+func DefaultOptions() Options {
+ return Options{
+ ProjectID: DefaultProjectID,
+ }
+}
+
+// WithProjectID sets the project ID for the GCloud container.
+func WithProjectID(projectID string) Option {
+ return func(o *Options) error {
+ o.ProjectID = projectID
+ return nil
+ }
+}
diff --git a/modules/gcloud/internal/shared/shared_test.go b/modules/gcloud/internal/shared/shared_test.go
new file mode 100644
index 0000000000..666203f9f7
--- /dev/null
+++ b/modules/gcloud/internal/shared/shared_test.go
@@ -0,0 +1,28 @@
+package shared_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+)
+
+func TestDefaultOptions(t *testing.T) {
+ opts := shared.DefaultOptions()
+ require.Equal(t, shared.DefaultProjectID, opts.ProjectID)
+}
+
+func TestWithProjectID(t *testing.T) {
+ opts := shared.DefaultOptions()
+
+ err := shared.WithProjectID("test-project")(&opts)
+ require.NoError(t, err)
+ require.Equal(t, "test-project", opts.ProjectID)
+}
+
+func TestCustomize(t *testing.T) {
+ err := shared.WithProjectID("test-project-2").Customize(&testcontainers.GenericContainerRequest{})
+ require.NoError(t, err)
+}
diff --git a/modules/gcloud/pubsub.go b/modules/gcloud/pubsub.go
index d57ea35c16..2d637563dc 100644
--- a/modules/gcloud/pubsub.go
+++ b/modules/gcloud/pubsub.go
@@ -7,12 +7,13 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunPubsub instead
+// Deprecated: use [pubsub.Run] instead
// RunPubsubContainer creates an instance of the GCloud container type for Pubsub.
func RunPubsubContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunPubsub(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...)
}
+// Deprecated: use [pubsub.Run] instead
// RunPubsub creates an instance of the GCloud container type for Pubsub.
func RunPubsub(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
diff --git a/modules/gcloud/pubsub_test.go b/modules/gcloud/pubsub/examples_test.go
similarity index 82%
rename from modules/gcloud/pubsub_test.go
rename to modules/gcloud/pubsub/examples_test.go
index 38cd539129..4707662c74 100644
--- a/modules/gcloud/pubsub_test.go
+++ b/modules/gcloud/pubsub/examples_test.go
@@ -1,4 +1,4 @@
-package gcloud_test
+package pubsub_test
import (
"context"
@@ -11,17 +11,17 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
+ tcpubsub "github.com/testcontainers/testcontainers-go/modules/gcloud/pubsub"
)
-func ExampleRunPubsubContainer() {
+func ExampleRun() {
// runPubsubContainer {
ctx := context.Background()
- pubsubContainer, err := gcloud.RunPubsub(
+ pubsubContainer, err := tcpubsub.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
- gcloud.WithProjectID("pubsub-project"),
+ tcpubsub.WithProjectID("pubsub-project"),
)
defer func() {
if err := testcontainers.TerminateContainer(pubsubContainer); err != nil {
@@ -35,9 +35,9 @@ func ExampleRunPubsubContainer() {
// }
// pubsubClient {
- projectID := pubsubContainer.Settings.ProjectID
+ projectID := pubsubContainer.ProjectID()
- conn, err := grpc.NewClient(pubsubContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials()))
+ conn, err := grpc.NewClient(pubsubContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("failed to dial: %v", err)
return
diff --git a/modules/gcloud/pubsub/options.go b/modules/gcloud/pubsub/options.go
new file mode 100644
index 0000000000..7b6049af7d
--- /dev/null
+++ b/modules/gcloud/pubsub/options.go
@@ -0,0 +1,17 @@
+package pubsub
+
+import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
diff --git a/modules/gcloud/pubsub/pubsub.go b/modules/gcloud/pubsub/pubsub.go
new file mode 100644
index 0000000000..d41e55d21a
--- /dev/null
+++ b/modules/gcloud/pubsub/pubsub.go
@@ -0,0 +1,82 @@
+package pubsub
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the Pubsub container.
+ DefaultProjectID = "test-project"
+)
+
+// Container represents the Pubsub container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the Pubsub container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the Pubsub container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the Pubsub GCloud container type.
+// The URI uses the empty string as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"8085/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("8085/tcp"),
+ wait.ForLog("started"),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ req.Cmd = []string{
+ "/bin/sh",
+ "-c",
+ "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085 --project=" + settings.ProjectID,
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "8085/tcp", "")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/pubsub/pubsub_test.go b/modules/gcloud/pubsub/pubsub_test.go
new file mode 100644
index 0000000000..f8979b05ff
--- /dev/null
+++ b/modules/gcloud/pubsub/pubsub_test.go
@@ -0,0 +1,63 @@
+package pubsub_test
+
+import (
+ "context"
+ "log"
+ "testing"
+
+ "cloud.google.com/go/pubsub"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/option"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcpubsub "github.com/testcontainers/testcontainers-go/modules/gcloud/pubsub"
+)
+
+func TestRun(t *testing.T) {
+ ctx := context.Background()
+
+ pubsubContainer, err := tcpubsub.Run(
+ ctx,
+ "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators",
+ tcpubsub.WithProjectID("pubsub-project"),
+ )
+ testcontainers.CleanupContainer(t, pubsubContainer)
+ require.NoError(t, err)
+
+ projectID := pubsubContainer.ProjectID()
+
+ conn, err := grpc.NewClient(pubsubContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ log.Printf("failed to dial: %v", err)
+ return
+ }
+
+ options := []option.ClientOption{option.WithGRPCConn(conn)}
+ client, err := pubsub.NewClient(ctx, projectID, options...)
+ require.NoError(t, err)
+ defer client.Close()
+
+ topic, err := client.CreateTopic(ctx, "greetings")
+ require.NoError(t, err)
+
+ subscription, err := client.CreateSubscription(ctx, "subscription",
+ pubsub.SubscriptionConfig{Topic: topic})
+ require.NoError(t, err)
+
+ result := topic.Publish(ctx, &pubsub.Message{Data: []byte("Hello World")})
+ _, err = result.Get(ctx)
+ require.NoError(t, err)
+
+ var data []byte
+ cctx, cancel := context.WithCancel(ctx)
+ err = subscription.Receive(cctx, func(_ context.Context, m *pubsub.Message) {
+ data = m.Data
+ m.Ack()
+ defer cancel()
+ })
+ require.NoError(t, err)
+
+ require.Equal(t, "Hello World", string(data))
+}
diff --git a/modules/gcloud/spanner.go b/modules/gcloud/spanner.go
index 8b306db4ce..b5e9bb57f3 100644
--- a/modules/gcloud/spanner.go
+++ b/modules/gcloud/spanner.go
@@ -7,12 +7,13 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)
-// Deprecated: use RunSpanner instead
+// Deprecated: use [spanner.Run] instead
// RunSpannerContainer creates an instance of the GCloud container type for Spanner.
func RunSpannerContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
return RunSpanner(ctx, "gcr.io/cloud-spanner-emulator/emulator:1.4.0", opts...)
}
+// Deprecated: use [spanner.Run] instead
// RunSpanner creates an instance of the GCloud container type for Spanner.
func RunSpanner(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) {
req := testcontainers.GenericContainerRequest{
diff --git a/modules/gcloud/spanner_test.go b/modules/gcloud/spanner/examples_test.go
similarity index 91%
rename from modules/gcloud/spanner_test.go
rename to modules/gcloud/spanner/examples_test.go
index 02e3b48b28..4237ba8c78 100644
--- a/modules/gcloud/spanner_test.go
+++ b/modules/gcloud/spanner/examples_test.go
@@ -1,4 +1,4 @@
-package gcloud_test
+package spanner_test
import (
"context"
@@ -16,17 +16,17 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/gcloud"
+ tcspanner "github.com/testcontainers/testcontainers-go/modules/gcloud/spanner"
)
-func ExampleRunSpannerContainer() {
+func ExampleRun() {
// runSpannerContainer {
ctx := context.Background()
- spannerContainer, err := gcloud.RunSpanner(
+ spannerContainer, err := tcspanner.Run(
ctx,
"gcr.io/cloud-spanner-emulator/emulator:1.4.0",
- gcloud.WithProjectID("spanner-project"),
+ tcspanner.WithProjectID("spanner-project"),
)
defer func() {
if err := testcontainers.TerminateContainer(spannerContainer); err != nil {
@@ -40,7 +40,7 @@ func ExampleRunSpannerContainer() {
// }
// spannerAdminClient {
- projectId := spannerContainer.Settings.ProjectID
+ projectId := spannerContainer.ProjectID()
const (
instanceId = "test-instance"
@@ -48,7 +48,7 @@ func ExampleRunSpannerContainer() {
)
options := []option.ClientOption{
- option.WithEndpoint(spannerContainer.URI),
+ option.WithEndpoint(spannerContainer.URI()),
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
option.WithoutAuthentication(),
internaloption.SkipDialSettingsValidation(),
diff --git a/modules/gcloud/spanner/options.go b/modules/gcloud/spanner/options.go
new file mode 100644
index 0000000000..2dabbcd722
--- /dev/null
+++ b/modules/gcloud/spanner/options.go
@@ -0,0 +1,17 @@
+package spanner
+
+import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
+
+// Options aliases the common GCloud options
+type options = shared.Options
+
+// Option aliases the common GCloud option type
+type Option = shared.Option
+
+// defaultOptions returns a new Options instance with the default project ID.
+func defaultOptions() options {
+ return shared.DefaultOptions()
+}
+
+// WithProjectID re-exports the common GCloud WithProjectID option
+var WithProjectID = shared.WithProjectID
diff --git a/modules/gcloud/spanner/spanner.go b/modules/gcloud/spanner/spanner.go
new file mode 100644
index 0000000000..388c6e5074
--- /dev/null
+++ b/modules/gcloud/spanner/spanner.go
@@ -0,0 +1,76 @@
+package spanner
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const (
+ // DefaultProjectID is the default project ID for the Pubsub container.
+ DefaultProjectID = "test-project"
+)
+
+// Container represents the Spanner container type used in the module
+type Container struct {
+ testcontainers.Container
+ settings options
+}
+
+// ProjectID returns the project ID of the Spanner container.
+func (c *Container) ProjectID() string {
+ return c.settings.ProjectID
+}
+
+// URI returns the URI of the Spanner container.
+func (c *Container) URI() string {
+ return c.settings.URI
+}
+
+// Run creates an instance of the Spanner GCloud container type.
+// The URI uses the empty string as the protocol.
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
+ req := testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{"9010/tcp"},
+ WaitingFor: wait.ForAll(
+ wait.ForListeningPort("9010/tcp"),
+ wait.ForLog("Cloud Spanner emulator running"),
+ ),
+ },
+ Started: true,
+ }
+
+ settings := defaultOptions()
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&settings); err != nil {
+ return nil, err
+ }
+ }
+ if err := opt.Customize(&req); err != nil {
+ return nil, err
+ }
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, req)
+ var c *Container
+ if container != nil {
+ c = &Container{Container: container, settings: settings}
+ }
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ portEndpoint, err := c.PortEndpoint(ctx, "9010/tcp", "")
+ if err != nil {
+ return c, fmt.Errorf("port endpoint: %w", err)
+ }
+
+ c.settings.URI = portEndpoint
+
+ return c, nil
+}
diff --git a/modules/gcloud/spanner/spanner_test.go b/modules/gcloud/spanner/spanner_test.go
new file mode 100644
index 0000000000..eafa75d729
--- /dev/null
+++ b/modules/gcloud/spanner/spanner_test.go
@@ -0,0 +1,105 @@
+package spanner_test
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "cloud.google.com/go/spanner"
+ database "cloud.google.com/go/spanner/admin/database/apiv1"
+ databasepb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
+ instance "cloud.google.com/go/spanner/admin/instance/apiv1"
+ instancepb "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/api/option"
+ "google.golang.org/api/option/internaloption"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/testcontainers/testcontainers-go"
+ tcspanner "github.com/testcontainers/testcontainers-go/modules/gcloud/spanner"
+)
+
+func TestRun(t *testing.T) {
+ ctx := context.Background()
+
+ spannerContainer, err := tcspanner.Run(
+ ctx,
+ "gcr.io/cloud-spanner-emulator/emulator:1.4.0",
+ tcspanner.WithProjectID("spanner-project"),
+ )
+ testcontainers.CleanupContainer(t, spannerContainer)
+ require.NoError(t, err)
+
+ projectId := spannerContainer.ProjectID()
+
+ const (
+ instanceId = "test-instance"
+ databaseName = "test-db"
+ )
+
+ options := []option.ClientOption{
+ option.WithEndpoint(spannerContainer.URI()),
+ option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
+ option.WithoutAuthentication(),
+ internaloption.SkipDialSettingsValidation(),
+ }
+
+ instanceAdmin, err := instance.NewInstanceAdminClient(ctx, options...)
+ if err != nil {
+ log.Printf("failed to create instance admin client: %v", err)
+ return
+ }
+ defer instanceAdmin.Close()
+
+ instanceOp, err := instanceAdmin.CreateInstance(ctx, &instancepb.CreateInstanceRequest{
+ Parent: "projects/" + projectId,
+ InstanceId: instanceId,
+ Instance: &instancepb.Instance{
+ DisplayName: instanceId,
+ },
+ })
+ require.NoError(t, err)
+
+ _, err = instanceOp.Wait(ctx)
+ require.NoError(t, err)
+
+ c, err := database.NewDatabaseAdminClient(ctx, options...)
+ require.NoError(t, err)
+ defer c.Close()
+
+ databaseOp, err := c.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{
+ Parent: fmt.Sprintf("projects/%s/instances/%s", projectId, instanceId),
+ CreateStatement: fmt.Sprintf("CREATE DATABASE `%s`", databaseName),
+ ExtraStatements: []string{
+ "CREATE TABLE Languages (Language STRING(MAX), Mascot STRING(MAX)) PRIMARY KEY (Language)",
+ },
+ })
+ require.NoError(t, err)
+
+ _, err = databaseOp.Wait(ctx)
+ require.NoError(t, err)
+
+ db := fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseName)
+ client, err := spanner.NewClient(ctx, db, options...)
+ require.NoError(t, err)
+ defer client.Close()
+
+ _, err = client.Apply(ctx, []*spanner.Mutation{
+ spanner.Insert("Languages",
+ []string{"language", "mascot"},
+ []any{"Go", "Gopher"}),
+ })
+ require.NoError(t, err)
+
+ row, err := client.Single().ReadRow(ctx, "Languages",
+ spanner.Key{"Go"}, []string{"mascot"})
+ require.NoError(t, err)
+
+ var mascot string
+ err = row.ColumnByName("Mascot", &mascot)
+ require.NoError(t, err)
+
+ require.Equal(t, "Gopher", mascot)
+}