Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
29b1bba
feat: added firebase module
xytis May 23, 2024
6a88123
chore: bump default container version
xytis Jan 30, 2025
4105b0a
fix: rewrote the module based on comments
xytis Jan 31, 2025
cc2bfa4
fix: another pass at comments
xytis Feb 2, 2025
7c24032
fix: ports are uint
xytis Feb 3, 2025
bc68bb3
Merge branch 'main' into u-health/main
mdelapenya Apr 14, 2025
bc33438
chore: bump testcontainers-go to latest
mdelapenya Apr 14, 2025
1a72008
chore: move to testdata
mdelapenya Apr 14, 2025
c2b8e2b
chore: use custom error when root is needed
mdelapenya Apr 14, 2025
ba0bdc0
fix: handle error in test
mdelapenya Apr 14, 2025
80298e5
chore: use new functional options
mdelapenya Apr 14, 2025
4e271db
fix: no need to pass labels, as they are added in the core
mdelapenya Apr 14, 2025
34409eb
chore: proper error message
mdelapenya Apr 14, 2025
fb2c0aa
chore: move options to its file
mdelapenya Apr 14, 2025
88cb16d
chore: simplify concat
mdelapenya Apr 14, 2025
aa4c29c
chore: add whilelines to separate concerns
mdelapenya Apr 14, 2025
458cc7c
docs: document the new options
mdelapenya Apr 14, 2025
ae27c74
merge: pull request #1 from mdelapenya/firebase-module
xytis Apr 14, 2025
ae49941
fix: lint pass
xytis Apr 23, 2025
cff37d9
fix: another lint pass
xytis Apr 23, 2025
9fe4988
Update modules/firebase/ports.go
xytis Apr 25, 2025
3ed1839
Update modules/firebase/ports.go
xytis Apr 25, 2025
d578817
Merge branch 'main' into main
xytis Apr 25, 2025
72c1f45
chore: reverted example test to failing state, so we could fix the ro…
xytis Apr 25, 2025
fd6035e
Update modules/firebase/ports.go
xytis Apr 25, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
matrix:
go-version: [1.22.x, 1.x]
platform: [ubuntu-latest]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, dynamodb, elasticsearch, etcd, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, meilisearch, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate, yugabytedb]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, dynamodb, elasticsearch, etcd, firebase, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, meilisearch, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate, yugabytedb]
uses: ./.github/workflows/ci-test-go.yml
with:
go-version: ${{ matrix.go-version }}
Expand Down
6 changes: 5 additions & 1 deletion .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
"name": "module / etcd",
"path": "../modules/etcd"
},
{
"name": "module / firebase",
"path": "../modules/firebase"
},
{
"name": "module / gcloud",
"path": "../modules/gcloud"
Expand Down Expand Up @@ -214,4 +218,4 @@
"path": "../modulegen"
}
]
}
}
47 changes: 47 additions & 0 deletions docs/modules/firebase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Firebase

Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

## Introduction

The Testcontainers module for Firebase.

## Adding this module to your project dependencies

Please run the following command to add the Firebase module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/firebase
```

## Usage example

<!--codeinclude-->
[Creating a Firebase container](../../modules/firebase/examples_test.go) inside_block:runFirebaseContainer
<!--/codeinclude-->

## Module reference

The Firebase module exposes one entrypoint function to create the Firebase container, and this function receives two parameters:

```golang
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*FirebaseContainer, error)
```

- `context.Context`, the Go context.
- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.

### Container Options

When starting the Firebase container, you can pass options in a variadic way to configure it.

#### Image

If you need to set a different Firebase Docker image, you can use `testcontainers.WithImage` with a valid Docker image
for Firebase. E.g. `testcontainers.WithImage("ghcr.io/thoughtgears/docker-firebase-emulator:13.6.0")`.

{% include "../features/common_functional_options.md" %}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docsd: we need to add here all the functional options for the module.

Copy link
Member

@mdelapenya mdelapenya Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xytis let's not forget adding the WithData option here 🙏

### Container Methods

The Firebase container exposes the following methods:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs: we probably need to append here the new methods the container expose

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xytis let's not forget adding here ConnectionString 🙏

1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ nav:
- modules/dynamodb.md
- modules/elasticsearch.md
- modules/etcd.md
- modules/firebase.md
- modules/gcloud.md
- modules/grafana-lgtm.md
- modules/inbucket.md
Expand Down
5 changes: 5 additions & 0 deletions modules/firebase/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include ../../commons-test.mk

.PHONY: test
test:
$(MAKE) test-firebase
13 changes: 13 additions & 0 deletions modules/firebase/anchor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package firebase_test

import (
"path/filepath"
"runtime"
)

// BasePath returns the catalog of the module this function resides in
func BasePath() string {
_, b, _, _ := runtime.Caller(0)
base := filepath.Dir(b)
return base
}
41 changes: 41 additions & 0 deletions modules/firebase/examples_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package firebase_test

import (
"context"
"fmt"
"log"
"path/filepath"

"github.com/testcontainers/testcontainers-go/modules/firebase"
)

func ExampleRunContainer() {
// runFirebaseContainer {
ctx := context.Background()

firebaseContainer, err := firebase.RunContainer(
ctx,
firebase.WithRoot(filepath.Join(BasePath(), "firebase")),
)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}

// Clean up the container
defer func() {
if err := firebaseContainer.Terminate(ctx); err != nil {
log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
}
}()
// }

state, err := firebaseContainer.State(ctx)
if err != nil {
log.Fatalf("failed to get container state: %s", err) // nolint:gocritic
}

fmt.Println(state.Running)

// Output:
// true
}
109 changes: 109 additions & 0 deletions modules/firebase/firebase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package firebase

import (
"context"
"fmt"
"strings"
"time"

"github.com/docker/docker/api/types/mount"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

// FirebaseContainer represents the Firebase container type used in the module
type FirebaseContainer struct {
testcontainers.Container
}

const defaultImageName = "ghcr.io/u-health/docker-firebase-emulator:13.29.2"

// WithRoot sets the directory which is copied to the destination container as firebase root
func WithRoot(rootPath string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
if !strings.HasSuffix(rootPath, "/firebase") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Could you clarify why this requirement is needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been an issue a year ago. Mounting did not correctly work at that time, and if the source directory did not match the destination directory, mounting would nest the directory instead of mapping it directly.

I see that the issue has been fixed, removing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad. The issue still exits. I wrote a failing test to exhibit the issue.

Is there something incorrect with the way I copy over the configuration?

Also, I don't think that volume mount would work in this case, because firebase emulator creates a lot of trash data in the root catalog, which in some cases can even make testing flaky (especially if using DATA_DIRECTORY as a fixture).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to find some time to look. I had a some in progress work to fix up the copy behaviour which was odd.

If you have a test to exercise this that, would help alongside a description of the desired behaviour and what you're seeing instead.

return fmt.Errorf("root path must end with '/firebase': %s", rootPath)
}
req.Files = append(req.Files, testcontainers.ContainerFile{
HostFilePath: rootPath,
ContainerFilePath: "/srv/firebase",
})

return nil
}
}

// WithData names the data directory in firebase root
func WithData(dataPath string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
req.Env["DATA_DIRECTORY"] = dataPath
return nil
}
}

func cache(volumeName string, volumeOptions *mount.VolumeOptions) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
m := testcontainers.ContainerMount{
Source: testcontainers.DockerVolumeMountSource{
Name: volumeName,
VolumeOptions: volumeOptions,
},
Target: "/root/.cache/firebase",
}
req.Mounts = append(req.Mounts, m)
return nil
}
}

// WithCache enables firebase binary cache based on session (meaningful only when multiple tests are used)
func WithCache() testcontainers.CustomizeRequestOption {
volumeName := fmt.Sprintf("firestore-cache-%s", testcontainers.SessionID())
volumeOptions := &mount.VolumeOptions{
Labels: testcontainers.GenericLabels(),
}

return cache(volumeName, volumeOptions)
}

// RunContainer creates an instance of the Firebase container type
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*FirebaseContainer, error) {
req := testcontainers.ContainerRequest{
Image: defaultImageName,
ExposedPorts: []string{
UiPort,
HubPort,
LoggingPort,
FunctionsPort,
FirestorePort,
PubsubPort,
DatabasePort,
AuthPort,
StoragePort,
HostingPort,
},

Env: map[string]string{},
WaitingFor: wait.ForAll(
wait.ForHTTP("/").WithPort(UiPort).WithStartupTimeout(3 * time.Minute),
),
}

genericContainerReq := testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
}

for _, opt := range opts {
if err := opt.Customize(&genericContainerReq); err != nil {
return nil, fmt.Errorf("customize: %w", err)
}
}

container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
if err != nil {
return nil, err
}

return &FirebaseContainer{Container: container}, nil
}
5 changes: 5 additions & 0 deletions modules/firebase/firebase/.firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "test"
}
}
37 changes: 37 additions & 0 deletions modules/firebase/firebase/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"emulators": {
"ui": {
"enabled": true,
"port": 4000,
"host": "0.0.0.0"
},
"hub": {
"port": 4400,
"host": "0.0.0.0"
},
"logging": {
"port": 4600,
"host": "0.0.0.0"
},
"firestore": {
"port": 8080,
"host": "0.0.0.0"
},
"auth": {
"port": 9099,
"host": "0.0.0.0"
},
"storage": {
"port": 9199,
"host": "0.0.0.0"
},
"singleProjectMode": true
},
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"storage": {
"rules": "storage.rules"
}
}
4 changes: 4 additions & 0 deletions modules/firebase/firebase/firestore.indexes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"indexes": [],
"fieldOverrides": []
}
9 changes: 9 additions & 0 deletions modules/firebase/firebase/firestore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}

8 changes: 8 additions & 0 deletions modules/firebase/firebase/storage.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
47 changes: 47 additions & 0 deletions modules/firebase/firebase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package firebase_test

import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/testcontainers/testcontainers-go/modules/firebase"
)

func TestFirebase(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Minute))

thing := fmt.Sprintf("%s/test", os.Getenv("PWD"))
fmt.Println(thing)

container, err := firebase.RunContainer(
ctx,
firebase.WithRoot(filepath.Join(BasePath(), "firebase")),
firebase.WithCache(),
)
if err != nil {
t.Fatal(err)
}

// Clean up the container after the test is complete
t.Cleanup(func() {
if err := container.Terminate(ctx); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
cancel()
})

// perform assertions
firestoreUrl, err := container.FirestoreConnectionString(ctx)
assert.NoError(t, err)
assert.NotEmpty(t, firestoreUrl)

authUrl, err := container.AuthConnectionString(ctx)
assert.NoError(t, err)
assert.NotEmpty(t, authUrl)
}
Loading