Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/modules/gcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk

{% include "./gcloud-shared.md" %}

### Datastore mode

Using the `WithDatastoreMode` option will run the Firestore emulator using `Firestore In Datastore` mode allowing you to use Datastore APIs and clients towards the Firestore emulator.

Requires `cloud-sdk:465.0.0` or higher

### Examples

<!--codeinclude-->
Expand Down
67 changes: 67 additions & 0 deletions modules/gcloud/firestore/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"

"cloud.google.com/go/datastore"
"cloud.google.com/go/firestore"
"google.golang.org/api/option"
"google.golang.org/grpc"
Expand Down Expand Up @@ -97,3 +98,69 @@ func ExampleRun() {
// Output:
// Ada Lovelace
}

func ExampleRun_datastoreMode() {
ctx := context.Background()

firestoreContainer, err := tcfirestore.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:513.0.0-emulators",
tcfirestore.WithProjectID("firestore-project"),
tcfirestore.WithDatastoreMode(),
)
defer func() {
if err := testcontainers.TerminateContainer(firestoreContainer); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()
if err != nil {
log.Printf("failed to run container: %v", err)
return
}

projectID := firestoreContainer.ProjectID()

conn, err := grpc.NewClient(firestoreContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{}))
if err != nil {
log.Printf("failed to dial: %v", err)
return
}

options := []option.ClientOption{option.WithGRPCConn(conn)}
client, err := datastore.NewClient(ctx, projectID, options...)
if err != nil {
log.Printf("failed to create client: %v", err)
return
}
defer client.Close()

userKey := datastore.NameKey("users", "alovelace", nil)

type Person struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}

data := Person{
Firstname: "Ada",
Lastname: "Lovelace",
}

_, err = client.Put(ctx, userKey, &data)
if err != nil {
log.Printf("failed to create entity: %v", err)
return
}

saved := Person{}
err = client.Get(ctx, userKey, &saved)
if err != nil {
log.Printf("failed to get entity: %v", err)
return
}

fmt.Println(saved.Firstname, saved.Lastname)

// Output:
// Ada Lovelace
}
7 changes: 6 additions & 1 deletion modules/gcloud/firestore/firestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
}
}

gcloudParameters := "--project=" + settings.ProjectID
if settings.datastoreMode {
gcloudParameters += " --database-mode=datastore-mode"
}

req.Cmd = []string{
"/bin/sh",
"-c",
"gcloud beta emulators firestore start --host-port 0.0.0.0:8080 --project=" + settings.ProjectID,
"gcloud beta emulators firestore start --host-port 0.0.0.0:8080 " + gcloudParameters,
}

container, err := testcontainers.GenericContainer(ctx, req)
Expand Down
46 changes: 46 additions & 0 deletions modules/gcloud/firestore/firestore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

"cloud.google.com/go/datastore"
"cloud.google.com/go/firestore"
"github.com/stretchr/testify/require"
"google.golang.org/api/option"
Expand Down Expand Up @@ -59,3 +60,48 @@ func TestRun(t *testing.T) {
require.Equal(t, "Ada", saved.Firstname)
require.Equal(t, "Lovelace", saved.Lastname)
}

func TestRunWithDatastore(t *testing.T) {
ctx := context.Background()

firestoreContainer, err := tcfirestore.Run(
ctx,
"gcr.io/google.com/cloudsdktool/cloud-sdk:513.0.0-emulators",
tcfirestore.WithProjectID("firestore-project"),
tcfirestore.WithDatastoreMode(),
)
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 := datastore.NewClient(ctx, projectID, options...)
require.NoError(t, err)
defer client.Close()

userKey := datastore.NameKey("users", "alovelace", nil)

type Person struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}

data := Person{
Firstname: "Ada",
Lastname: "Lovelace",
}

_, err = client.Put(ctx, userKey, &data)
require.NoError(t, err)

saved := Person{}
err = client.Get(ctx, userKey, &saved)
require.NoError(t, err)

require.Equal(t, "Ada", saved.Firstname)
require.Equal(t, "Lovelace", saved.Lastname)
}
34 changes: 28 additions & 6 deletions modules/gcloud/firestore/options.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
package firestore

import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
import (
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
)

// Options aliases the common GCloud options
type options = shared.Options
// options embeds the common GCloud options
type options struct {
shared.Options
datastoreMode bool
}

type Option func(o *options) error

// Option aliases the common GCloud option type
type Option = shared.Option
// 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 shared.DefaultOptions()
return options{
Options: shared.DefaultOptions(),
}
}

// WithDatastoreMode sets the firestore emulator to run in datastore mode.
// Requires a cloud-sdk image with version 465.0.0 or higher
func WithDatastoreMode() Option {
return func(o *options) error {
o.datastoreMode = true
return nil
}
}

// WithProjectID re-exports the common GCloud WithProjectID option
Expand Down