Skip to content

Commit 0139214

Browse files
mdelapenyaclaude
andauthored
chore(meilisearch|memcached|milvus|minio|mockserver|mssql): use Run function (#3415)
* chore(meilisearch): use Run function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore(memcached): use Run function * chore(milvus): use Run function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix(milvus): improve wait strategy to fix test flakiness Add wait.ForListeningPort for both HTTP and gRPC ports in addition to the HTTP health check. This ensures both services are fully ready before the container is marked as ready, eliminating the race condition where the health endpoint was ready but the gRPC proxy was not. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore(minio): use Run function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore(mockserver): use Run function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore(mssql): use Run function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * chore(mssql): use built-in options --------- Co-authored-by: Claude <[email protected]>
1 parent 2b2a7fd commit 0139214

File tree

6 files changed

+172
-166
lines changed

6 files changed

+172
-166
lines changed

modules/meilisearch/meilisearch.go

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"strings"
910
"time"
1011

1112
"github.com/testcontainers/testcontainers-go"
@@ -31,70 +32,73 @@ func (c *MeilisearchContainer) MasterKey() string {
3132

3233
// Run creates an instance of the Meilisearch container type
3334
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MeilisearchContainer, error) {
34-
req := testcontainers.ContainerRequest{
35-
Image: img,
36-
ExposedPorts: []string{defaultHTTPPort},
37-
Env: map[string]string{
38-
masterKeyEnvVar: defaultMasterKey,
39-
},
40-
}
41-
42-
genericContainerReq := testcontainers.GenericContainerRequest{
43-
ContainerRequest: req,
44-
Started: true,
45-
}
46-
4735
// Gather all config options (defaults and then apply provided options)
4836
settings := defaultOptions()
4937
for _, opt := range opts {
5038
if apply, ok := opt.(Option); ok {
5139
apply(settings)
5240
}
53-
if err := opt.Customize(&genericContainerReq); err != nil {
54-
return nil, fmt.Errorf("customize: %w", err)
55-
}
41+
}
42+
43+
moduleOpts := []testcontainers.ContainerCustomizer{
44+
testcontainers.WithExposedPorts(defaultHTTPPort),
45+
testcontainers.WithEnv(map[string]string{
46+
masterKeyEnvVar: defaultMasterKey,
47+
}),
48+
// the wait strategy does not support TLS at the moment,
49+
// so we need to disable it in the strategy for now.
50+
testcontainers.WithWaitStrategy(wait.ForHTTP("/health").
51+
WithPort(defaultHTTPPort).
52+
WithTLS(false).
53+
WithStartupTimeout(120 * time.Second).
54+
WithStatusCodeMatcher(func(status int) bool {
55+
return status == http.StatusOK
56+
}).
57+
WithResponseMatcher(func(body io.Reader) bool {
58+
decoder := json.NewDecoder(body)
59+
r := struct {
60+
Status string `json:"status"`
61+
}{}
62+
if err := decoder.Decode(&r); err != nil {
63+
return false
64+
}
65+
66+
return r.Status == "available"
67+
}),
68+
),
5669
}
5770

5871
if settings.DumpDataFilePath != "" {
59-
genericContainerReq.Files = []testcontainers.ContainerFile{
60-
{
72+
moduleOpts = append(moduleOpts,
73+
testcontainers.WithFiles(testcontainers.ContainerFile{
6174
HostFilePath: settings.DumpDataFilePath,
6275
ContainerFilePath: "/dumps/" + settings.DumpDataFileName,
6376
FileMode: 0o755,
64-
},
65-
}
66-
genericContainerReq.Cmd = []string{"meilisearch", "--import-dump", "/dumps/" + settings.DumpDataFileName}
77+
}),
78+
testcontainers.WithCmd("meilisearch", "--import-dump", "/dumps/"+settings.DumpDataFileName),
79+
)
6780
}
6881

69-
// the wait strategy does not support TLS at the moment,
70-
// so we need to disable it in the strategy for now.
71-
genericContainerReq.WaitingFor = wait.ForHTTP("/health").
72-
WithPort(defaultHTTPPort).
73-
WithTLS(false).
74-
WithStartupTimeout(120 * time.Second).
75-
WithStatusCodeMatcher(func(status int) bool {
76-
return status == http.StatusOK
77-
}).
78-
WithResponseMatcher(func(body io.Reader) bool {
79-
decoder := json.NewDecoder(body)
80-
r := struct {
81-
Status string `json:"status"`
82-
}{}
83-
if err := decoder.Decode(&r); err != nil {
84-
return false
85-
}
86-
87-
return r.Status == "available"
88-
})
89-
90-
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
82+
ctr, err := testcontainers.Run(ctx, img, append(moduleOpts, opts...)...)
9183
var c *MeilisearchContainer
92-
if container != nil {
93-
c = &MeilisearchContainer{Container: container, masterKey: req.Env[masterKeyEnvVar]}
84+
if ctr != nil {
85+
c = &MeilisearchContainer{Container: ctr}
9486
}
9587

9688
if err != nil {
97-
return c, fmt.Errorf("generic container: %w", err)
89+
return c, fmt.Errorf("run meilisearch: %w", err)
90+
}
91+
92+
inspect, err := ctr.Inspect(ctx)
93+
if err != nil {
94+
return c, fmt.Errorf("inspect meilisearch: %w", err)
95+
}
96+
97+
for _, env := range inspect.Config.Env {
98+
if v, ok := strings.CutPrefix(env, masterKeyEnvVar+"="); ok {
99+
c.masterKey = v
100+
break
101+
}
98102
}
99103

100104
return c, nil

modules/memcached/memcached.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,21 @@ type Container struct {
1919

2020
// Run creates an instance of the Memcached container type
2121
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
22-
req := testcontainers.ContainerRequest{
23-
Image: img,
24-
ExposedPorts: []string{defaultPort},
25-
WaitingFor: wait.ForListeningPort(defaultPort),
22+
moduleOpts := []testcontainers.ContainerCustomizer{
23+
testcontainers.WithExposedPorts(defaultPort),
24+
testcontainers.WithWaitStrategy(wait.ForListeningPort(defaultPort)),
2625
}
2726

28-
genericContainerReq := testcontainers.GenericContainerRequest{
29-
ContainerRequest: req,
30-
Started: true,
31-
}
32-
33-
for _, opt := range opts {
34-
if err := opt.Customize(&genericContainerReq); err != nil {
35-
return nil, fmt.Errorf("customize: %w", err)
36-
}
37-
}
27+
moduleOpts = append(moduleOpts, opts...)
3828

39-
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
29+
ctr, err := testcontainers.Run(ctx, img, moduleOpts...)
4030
var c *Container
41-
if container != nil {
42-
c = &Container{Container: container}
31+
if ctr != nil {
32+
c = &Container{Container: ctr}
4333
}
4434

4535
if err != nil {
46-
return c, fmt.Errorf("generic container: %w", err)
36+
return c, fmt.Errorf("run memcached: %w", err)
4737
}
4838

4939
return c, nil

modules/milvus/milvus.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ var embedEtcdConfigTpl string
1919
const (
2020
embedEtcdContainerPath = "/milvus/configs/embedEtcd.yaml"
2121
defaultClientPort = 2379
22+
etcdPort = "2379/tcp"
23+
httpPort = "9091/tcp"
2224
grpcPort = "19530/tcp"
2325
)
2426

@@ -47,45 +49,42 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
4749
}
4850

4951
// Adapted from https://github.com/milvus-io/milvus/blob/v2.6.3/scripts/standalone_embed.sh
50-
req := testcontainers.ContainerRequest{
51-
Image: img,
52-
ExposedPorts: []string{"19530/tcp", "9091/tcp", "2379/tcp"},
53-
Env: map[string]string{
52+
moduleOpts := []testcontainers.ContainerCustomizer{
53+
testcontainers.WithExposedPorts(grpcPort, httpPort, etcdPort),
54+
testcontainers.WithEnv(map[string]string{
5455
"ETCD_USE_EMBED": "true",
5556
"ETCD_DATA_DIR": "/var/lib/milvus/etcd",
5657
"ETCD_CONFIG_PATH": embedEtcdContainerPath,
5758
"COMMON_STORAGETYPE": "local",
5859
"DEPLOY_MODE": "STANDALONE",
59-
},
60-
Cmd: []string{"milvus", "run", "standalone"},
61-
WaitingFor: wait.ForHTTP("/healthz").
62-
WithPort("9091").
63-
WithStartupTimeout(time.Minute).
64-
WithPollInterval(time.Second),
65-
Files: []testcontainers.ContainerFile{
66-
{ContainerFilePath: embedEtcdContainerPath, Reader: config},
67-
},
60+
}),
61+
testcontainers.WithCmd("milvus", "run", "standalone"),
62+
testcontainers.WithWaitStrategy(wait.ForAll(
63+
wait.ForHTTP("/healthz").
64+
WithPort(httpPort).
65+
WithStartupTimeout(time.Minute).
66+
WithPollInterval(time.Second),
67+
wait.ForListeningPort(httpPort).
68+
WithStartupTimeout(time.Minute),
69+
wait.ForListeningPort(grpcPort).
70+
WithStartupTimeout(time.Minute),
71+
)),
72+
testcontainers.WithFiles(testcontainers.ContainerFile{
73+
ContainerFilePath: embedEtcdContainerPath,
74+
Reader: config,
75+
}),
6876
}
6977

70-
genericContainerReq := testcontainers.GenericContainerRequest{
71-
ContainerRequest: req,
72-
Started: true,
73-
}
74-
75-
for _, opt := range opts {
76-
if err := opt.Customize(&genericContainerReq); err != nil {
77-
return nil, err
78-
}
79-
}
78+
moduleOpts = append(moduleOpts, opts...)
8079

81-
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
80+
ctr, err := testcontainers.Run(ctx, img, moduleOpts...)
8281
var c *MilvusContainer
83-
if container != nil {
84-
c = &MilvusContainer{Container: container}
82+
if ctr != nil {
83+
c = &MilvusContainer{Container: ctr}
8584
}
8685

8786
if err != nil {
88-
return c, fmt.Errorf("generic container: %w", err)
87+
return c, fmt.Errorf("run milvus: %w", err)
8988
}
9089

9190
return c, nil

modules/minio/minio.go

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"github.com/testcontainers/testcontainers-go"
910
"github.com/testcontainers/testcontainers-go/wait"
@@ -57,42 +58,58 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize
5758

5859
// Run creates an instance of the Minio container type
5960
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) {
60-
req := testcontainers.ContainerRequest{
61-
Image: img,
62-
ExposedPorts: []string{"9000/tcp"},
63-
WaitingFor: wait.ForHTTP("/minio/health/live").WithPort("9000"),
64-
Env: map[string]string{
61+
moduleOpts := []testcontainers.ContainerCustomizer{
62+
testcontainers.WithExposedPorts("9000/tcp"),
63+
testcontainers.WithWaitStrategy(wait.ForHTTP("/minio/health/live").WithPort("9000")),
64+
testcontainers.WithEnv(map[string]string{
6565
"MINIO_ROOT_USER": defaultUser,
6666
"MINIO_ROOT_PASSWORD": defaultPassword,
67-
},
68-
Cmd: []string{"server", "/data"},
67+
}),
68+
testcontainers.WithCmd("server", "/data"),
6969
}
7070

71-
genericContainerReq := testcontainers.GenericContainerRequest{
72-
ContainerRequest: req,
73-
Started: true,
74-
}
71+
moduleOpts = append(moduleOpts, opts...)
7572

76-
for _, opt := range opts {
77-
if err := opt.Customize(&genericContainerReq); err != nil {
78-
return nil, err
73+
// Validate credentials after applying user options
74+
validateCreds := func(req *testcontainers.GenericContainerRequest) error {
75+
username := req.Env["MINIO_ROOT_USER"]
76+
password := req.Env["MINIO_ROOT_PASSWORD"]
77+
if username == "" || password == "" {
78+
return errors.New("username or password has not been set")
7979
}
80+
return nil
8081
}
8182

82-
username := req.Env["MINIO_ROOT_USER"]
83-
password := req.Env["MINIO_ROOT_PASSWORD"]
84-
if username == "" || password == "" {
85-
return nil, errors.New("username or password has not been set")
86-
}
83+
moduleOpts = append(moduleOpts, testcontainers.CustomizeRequestOption(validateCreds))
8784

88-
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
85+
ctr, err := testcontainers.Run(ctx, img, moduleOpts...)
8986
var c *MinioContainer
90-
if container != nil {
91-
c = &MinioContainer{Container: container, Username: username, Password: password}
87+
if ctr != nil {
88+
c = &MinioContainer{Container: ctr}
89+
}
90+
91+
if err != nil {
92+
return c, fmt.Errorf("run minio: %w", err)
9293
}
9394

95+
// Retrieve credentials from container environment
96+
inspect, err := ctr.Inspect(ctx)
9497
if err != nil {
95-
return c, fmt.Errorf("generic container: %w", err)
98+
return c, fmt.Errorf("inspect minio: %w", err)
99+
}
100+
101+
var foundUser, foundPass bool
102+
for _, env := range inspect.Config.Env {
103+
if v, ok := strings.CutPrefix(env, "MINIO_ROOT_USER="); ok {
104+
c.Username, foundUser = v, true
105+
}
106+
if v, ok := strings.CutPrefix(env, "MINIO_ROOT_PASSWORD="); ok {
107+
c.Password, foundPass = v, true
108+
}
109+
110+
if foundUser && foundPass {
111+
break
112+
}
96113
}
97114

98115
return c, nil

modules/mockserver/mockserver.go

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,24 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize
2121

2222
// Run creates an instance of the MockServer container type
2323
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MockServerContainer, error) {
24-
req := testcontainers.ContainerRequest{
25-
Image: img,
26-
ExposedPorts: []string{"1080/tcp"},
27-
WaitingFor: wait.ForAll(
24+
moduleOpts := []testcontainers.ContainerCustomizer{
25+
testcontainers.WithExposedPorts("1080/tcp"),
26+
testcontainers.WithWaitStrategy(wait.ForAll(
2827
wait.ForLog("started on port: 1080"),
2928
wait.ForListeningPort("1080/tcp").SkipInternalCheck(),
30-
),
31-
Env: map[string]string{},
29+
)),
3230
}
3331

34-
genericContainerReq := testcontainers.GenericContainerRequest{
35-
ContainerRequest: req,
36-
Started: true,
37-
}
38-
39-
for _, opt := range opts {
40-
if err := opt.Customize(&genericContainerReq); err != nil {
41-
return nil, err
42-
}
43-
}
32+
moduleOpts = append(moduleOpts, opts...)
4433

45-
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
34+
ctr, err := testcontainers.Run(ctx, img, moduleOpts...)
4635
var c *MockServerContainer
47-
if container != nil {
48-
c = &MockServerContainer{Container: container}
36+
if ctr != nil {
37+
c = &MockServerContainer{Container: ctr}
4938
}
39+
5040
if err != nil {
51-
return c, fmt.Errorf("generic container: %w", err)
41+
return c, fmt.Errorf("run mockserver: %w", err)
5242
}
5343

5444
return c, nil

0 commit comments

Comments
 (0)