Skip to content

Commit 04c578c

Browse files
committed
feat(gcloud): add option to run firestore in datastore mode
1 parent 438cefe commit 04c578c

File tree

5 files changed

+153
-7
lines changed

5 files changed

+153
-7
lines changed

docs/modules/gcloud.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ In example: `Run(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk
176176
177177
{% include "./gcloud-shared.md" %}
178178
179+
### Datastore mode
180+
181+
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.
182+
183+
Requires `cloud-sdk:465.0.0` or higher
184+
179185
### Examples
180186
181187
<!--codeinclude-->

modules/gcloud/firestore/examples_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log"
77

8+
"cloud.google.com/go/datastore"
89
"cloud.google.com/go/firestore"
910
"google.golang.org/api/option"
1011
"google.golang.org/grpc"
@@ -97,3 +98,69 @@ func ExampleRun() {
9798
// Output:
9899
// Ada Lovelace
99100
}
101+
102+
func ExampleRun_datastoreMode() {
103+
ctx := context.Background()
104+
105+
firestoreContainer, err := tcfirestore.Run(
106+
ctx,
107+
"gcr.io/google.com/cloudsdktool/cloud-sdk:513.0.0-emulators",
108+
tcfirestore.WithProjectID("firestore-project"),
109+
tcfirestore.WithDatastoreMode(),
110+
)
111+
defer func() {
112+
if err := testcontainers.TerminateContainer(firestoreContainer); err != nil {
113+
log.Printf("failed to terminate container: %s", err)
114+
}
115+
}()
116+
if err != nil {
117+
log.Printf("failed to run container: %v", err)
118+
return
119+
}
120+
121+
projectID := firestoreContainer.ProjectID()
122+
123+
conn, err := grpc.NewClient(firestoreContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{}))
124+
if err != nil {
125+
log.Printf("failed to dial: %v", err)
126+
return
127+
}
128+
129+
options := []option.ClientOption{option.WithGRPCConn(conn)}
130+
client, err := datastore.NewClient(ctx, projectID, options...)
131+
if err != nil {
132+
log.Printf("failed to create client: %v", err)
133+
return
134+
}
135+
defer client.Close()
136+
137+
userKey := datastore.NameKey("users", "alovelace", nil)
138+
139+
type Person struct {
140+
Firstname string `json:"firstname"`
141+
Lastname string `json:"lastname"`
142+
}
143+
144+
data := Person{
145+
Firstname: "Ada",
146+
Lastname: "Lovelace",
147+
}
148+
149+
_, err = client.Put(ctx, userKey, &data)
150+
if err != nil {
151+
log.Printf("failed to create entity: %v", err)
152+
return
153+
}
154+
155+
saved := Person{}
156+
err = client.Get(ctx, userKey, &saved)
157+
if err != nil {
158+
log.Printf("failed to get entity: %v", err)
159+
return
160+
}
161+
162+
fmt.Println(saved.Firstname, saved.Lastname)
163+
164+
// Output:
165+
// Ada Lovelace
166+
}

modules/gcloud/firestore/firestore.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,15 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
5656
}
5757
}
5858

59+
gcloudParameters := "--project=" + settings.ProjectID
60+
if settings.datastoreMode {
61+
gcloudParameters += " --database-mode=datastore-mode"
62+
}
63+
5964
req.Cmd = []string{
6065
"/bin/sh",
6166
"-c",
62-
"gcloud beta emulators firestore start --host-port 0.0.0.0:8080 --project=" + settings.ProjectID,
67+
"gcloud beta emulators firestore start --host-port 0.0.0.0:8080 " + gcloudParameters,
6368
}
6469

6570
container, err := testcontainers.GenericContainer(ctx, req)

modules/gcloud/firestore/firestore_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"testing"
66

7+
"cloud.google.com/go/datastore"
78
"cloud.google.com/go/firestore"
89
"github.com/stretchr/testify/require"
910
"google.golang.org/api/option"
@@ -59,3 +60,48 @@ func TestRun(t *testing.T) {
5960
require.Equal(t, "Ada", saved.Firstname)
6061
require.Equal(t, "Lovelace", saved.Lastname)
6162
}
63+
64+
func TestRunWithDatastore(t *testing.T) {
65+
ctx := context.Background()
66+
67+
firestoreContainer, err := tcfirestore.Run(
68+
ctx,
69+
"gcr.io/google.com/cloudsdktool/cloud-sdk:513.0.0-emulators",
70+
tcfirestore.WithProjectID("firestore-project"),
71+
tcfirestore.WithDatastoreMode(),
72+
)
73+
testcontainers.CleanupContainer(t, firestoreContainer)
74+
require.NoError(t, err)
75+
76+
projectID := firestoreContainer.ProjectID()
77+
78+
conn, err := grpc.NewClient(firestoreContainer.URI(), grpc.WithTransportCredentials(insecure.NewCredentials()))
79+
require.NoError(t, err)
80+
81+
options := []option.ClientOption{option.WithGRPCConn(conn)}
82+
client, err := datastore.NewClient(ctx, projectID, options...)
83+
require.NoError(t, err)
84+
defer client.Close()
85+
86+
userKey := datastore.NameKey("users", "alovelace", nil)
87+
88+
type Person struct {
89+
Firstname string `json:"firstname"`
90+
Lastname string `json:"lastname"`
91+
}
92+
93+
data := Person{
94+
Firstname: "Ada",
95+
Lastname: "Lovelace",
96+
}
97+
98+
_, err = client.Put(ctx, userKey, &data)
99+
require.NoError(t, err)
100+
101+
saved := Person{}
102+
err = client.Get(ctx, userKey, &saved)
103+
require.NoError(t, err)
104+
105+
require.Equal(t, "Ada", saved.Firstname)
106+
require.Equal(t, "Lovelace", saved.Lastname)
107+
}

modules/gcloud/firestore/options.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
package firestore
22

3-
import "github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
3+
import (
4+
"github.com/testcontainers/testcontainers-go"
5+
"github.com/testcontainers/testcontainers-go/modules/gcloud/internal/shared"
6+
)
47

5-
// Options aliases the common GCloud options
6-
type options = shared.Options
8+
// options embeds the common GCloud options
9+
type options struct {
10+
shared.Options
11+
datastoreMode bool
12+
}
13+
14+
type Option func(o *options) error
715

8-
// Option aliases the common GCloud option type
9-
type Option = shared.Option
16+
// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface.
17+
func (o Option) Customize(*testcontainers.GenericContainerRequest) error {
18+
// NOOP to satisfy interface.
19+
return nil
20+
}
1021

1122
// defaultOptions returns a new Options instance with the default project ID.
1223
func defaultOptions() options {
13-
return shared.DefaultOptions()
24+
return options{
25+
Options: shared.DefaultOptions(),
26+
}
27+
}
28+
29+
// WithDatastoreMode sets the firestore emulator to run in datastore mode.
30+
// Requires a cloud-sdk image with version 465.0.0 or higher
31+
func WithDatastoreMode() Option {
32+
return func(o *options) error {
33+
o.datastoreMode = true
34+
return nil
35+
}
1436
}
1537

1638
// WithProjectID re-exports the common GCloud WithProjectID option

0 commit comments

Comments
 (0)