Skip to content

Commit d7a114b

Browse files
heiytormdelapenya
andauthored
feat(mongodb): add replica set support via opts (testcontainers#2469)
* feat(mongodb): add WithReplicaSet option The `WithReplicaSet` option configures the MongoDB container to run a single-node replica set named "rs". The container will wait until the replica set is ready. * docs: document the new option * fix: proper indent --------- Co-authored-by: Manuel de la Peña <mdelapenya@gmail.com>
1 parent 68b7839 commit d7a114b

File tree

2 files changed

+72
-17
lines changed

2 files changed

+72
-17
lines changed

mongodb.go

+35
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,29 @@ func WithPassword(password string) testcontainers.CustomizeRequestOption {
7979
}
8080
}
8181

82+
// WithReplicaSet configures the container to run a single-node MongoDB replica set named "rs".
83+
// It will wait until the replica set is ready.
84+
func WithReplicaSet() testcontainers.CustomizeRequestOption {
85+
return func(req *testcontainers.GenericContainerRequest) error {
86+
req.Cmd = append(req.Cmd, "--replSet", "rs")
87+
req.LifecycleHooks = append(req.LifecycleHooks, testcontainers.ContainerLifecycleHooks{
88+
PostStarts: []testcontainers.ContainerHook{
89+
func(ctx context.Context, c testcontainers.Container) error {
90+
ip, err := c.ContainerIP(ctx)
91+
if err != nil {
92+
return err
93+
}
94+
95+
cmd := eval("rs.initiate({ _id: 'rs', members: [ { _id: 0, host: '%s:27017' } ] })", ip)
96+
return wait.ForExec(cmd).WaitUntilReady(ctx, c)
97+
},
98+
},
99+
})
100+
101+
return nil
102+
}
103+
}
104+
82105
// ConnectionString returns the connection string for the MongoDB container.
83106
// If you provide a username and a password, the connection string will also include them.
84107
func (c *MongoDBContainer) ConnectionString(ctx context.Context) (string, error) {
@@ -95,3 +118,15 @@ func (c *MongoDBContainer) ConnectionString(ctx context.Context) (string, error)
95118
}
96119
return c.Endpoint(ctx, "mongodb")
97120
}
121+
122+
// eval builds an mongosh|mongo eval command.
123+
func eval(command string, args ...any) []string {
124+
command = "\"" + fmt.Sprintf(command, args...) + "\""
125+
126+
return []string{
127+
"sh",
128+
"-c",
129+
// In previous versions, the binary "mongosh" was named "mongo".
130+
"mongosh --quiet --eval " + command + " || mongo --quiet --eval " + command,
131+
}
132+
}

mongodb_test.go

+37-17
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,70 @@ import (
1414

1515
func TestMongoDB(t *testing.T) {
1616
type tests struct {
17-
name string
18-
image string
17+
name string
18+
opts []testcontainers.ContainerCustomizer
1919
}
2020
testCases := []tests{
2121
{
22-
name: "From Docker Hub",
23-
image: "mongo:6",
22+
name: "From Docker Hub",
23+
opts: []testcontainers.ContainerCustomizer{
24+
testcontainers.WithImage("mongo:6"),
25+
},
2426
},
2527
{
26-
name: "Community Server",
27-
image: "mongodb/mongodb-community-server:7.0.2-ubi8",
28+
name: "Community Server",
29+
opts: []testcontainers.ContainerCustomizer{
30+
testcontainers.WithImage("mongodb/mongodb-community-server:7.0.2-ubi8"),
31+
},
2832
},
2933
{
30-
name: "Enterprise Server",
31-
image: "mongodb/mongodb-enterprise-server:7.0.0-ubi8",
34+
name: "Enterprise Server",
35+
opts: []testcontainers.ContainerCustomizer{
36+
testcontainers.WithImage("mongodb/mongodb-enterprise-server:7.0.0-ubi8"),
37+
},
38+
},
39+
{
40+
name: "With Replica set and mongo:4",
41+
opts: []testcontainers.ContainerCustomizer{
42+
testcontainers.WithImage("mongo:4"),
43+
mongodb.WithReplicaSet(),
44+
},
45+
},
46+
{
47+
name: "With Replica set and mongo:6",
48+
opts: []testcontainers.ContainerCustomizer{
49+
testcontainers.WithImage("mongo:6"),
50+
mongodb.WithReplicaSet(),
51+
},
3252
},
3353
}
3454

3555
for _, tc := range testCases {
36-
image := tc.image
37-
t.Run(image, func(t *testing.T) {
38-
t.Parallel()
56+
tc := tc
57+
t.Run(tc.name, func(tt *testing.T) {
58+
tt.Parallel()
3959

4060
ctx := context.Background()
4161

42-
mongodbContainer, err := mongodb.RunContainer(ctx, testcontainers.WithImage(image))
62+
mongodbContainer, err := mongodb.RunContainer(ctx, tc.opts...)
4363
if err != nil {
44-
t.Fatalf("failed to start container: %s", err)
64+
tt.Fatalf("failed to start container: %s", err)
4565
}
4666

4767
defer func() {
4868
if err := mongodbContainer.Terminate(ctx); err != nil {
49-
t.Fatalf("failed to terminate container: %s", err)
69+
tt.Fatalf("failed to terminate container: %s", err)
5070
}
5171
}()
5272

5373
endpoint, err := mongodbContainer.ConnectionString(ctx)
5474
if err != nil {
55-
t.Fatalf("failed to get connection string: %s", err)
75+
tt.Fatalf("failed to get connection string: %s", err)
5676
}
5777

5878
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint))
5979
if err != nil {
60-
t.Fatalf("failed to connect to MongoDB: %s", err)
80+
tt.Fatalf("failed to connect to MongoDB: %s", err)
6181
}
6282

6383
err = mongoClient.Ping(ctx, nil)
@@ -66,7 +86,7 @@ func TestMongoDB(t *testing.T) {
6686
}
6787

6888
if mongoClient.Database("test").Name() != "test" {
69-
t.Fatalf("failed to connect to the correct database")
89+
tt.Fatalf("failed to connect to the correct database")
7090
}
7191
})
7292
}

0 commit comments

Comments
 (0)