Skip to content

Commit 9562594

Browse files
authored
fix(redpanda): wait for (testcontainers#2794)
Wait for the admin interface to response to HTTP to avoid failures in configuring the instance when its not fully ready. Clean up error wrapping.
1 parent 4dc3662 commit 9562594

File tree

1 file changed

+41
-30
lines changed

1 file changed

+41
-30
lines changed

modules/redpanda/redpanda.go

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize
6262
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
6363
tmpDir, err := os.MkdirTemp("", "redpanda")
6464
if err != nil {
65-
return nil, fmt.Errorf("failed to create directory: %w", err)
65+
return nil, fmt.Errorf("create temporary directory: %w", err)
6666
}
6767
defer os.RemoveAll(tmpDir)
6868

@@ -121,24 +121,24 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
121121
// the Kafka API.
122122
entrypointPath := filepath.Join(tmpDir, entrypointFile)
123123
if err := os.WriteFile(entrypointPath, entrypoint, 0o700); err != nil {
124-
return nil, fmt.Errorf("failed to create entrypoint file: %w", err)
124+
return nil, fmt.Errorf("write entrypoint file: %w", err)
125125
}
126126

127127
// 4. Register extra kafka listeners if provided, network aliases will be
128128
// set
129129
if err := registerListeners(settings, req); err != nil {
130-
return nil, fmt.Errorf("failed to register listeners: %w", err)
130+
return nil, fmt.Errorf("register listeners: %w", err)
131131
}
132132

133133
// Bootstrap config file contains cluster configurations which will only be considered
134134
// the very first time you start a cluster.
135135
bootstrapConfigPath := filepath.Join(tmpDir, bootstrapConfigFile)
136136
bootstrapConfig, err := renderBootstrapConfig(settings)
137137
if err != nil {
138-
return nil, fmt.Errorf("failed to create bootstrap config file: %w", err)
138+
return nil, err
139139
}
140140
if err := os.WriteFile(bootstrapConfigPath, bootstrapConfig, 0o600); err != nil {
141-
return nil, fmt.Errorf("failed to create bootstrap config file: %w", err)
141+
return nil, fmt.Errorf("write bootstrap config: %w", err)
142142
}
143143

144144
req.Files = append(req.Files,
@@ -158,11 +158,11 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
158158
if settings.EnableTLS {
159159
certPath := filepath.Join(tmpDir, certFile)
160160
if err := os.WriteFile(certPath, settings.cert, 0o600); err != nil {
161-
return nil, fmt.Errorf("failed to create certificate file: %w", err)
161+
return nil, fmt.Errorf("write certificate file: %w", err)
162162
}
163163
keyPath := filepath.Join(tmpDir, keyFile)
164164
if err := os.WriteFile(keyPath, settings.key, 0o600); err != nil {
165-
return nil, fmt.Errorf("failed to create key file: %w", err)
165+
return nil, fmt.Errorf("write key file: %w", err)
166166
}
167167

168168
req.Files = append(req.Files,
@@ -192,34 +192,54 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
192192
// the Redpanda config with the advertised Kafka address.
193193
hostIP, err := ctr.Host(ctx)
194194
if err != nil {
195-
return c, fmt.Errorf("failed to get container host: %w", err)
195+
return c, fmt.Errorf("host: %w", err)
196196
}
197197

198198
kafkaPort, err := ctr.MappedPort(ctx, nat.Port(defaultKafkaAPIPort))
199199
if err != nil {
200-
return c, fmt.Errorf("failed to get mapped Kafka port: %w", err)
200+
return c, fmt.Errorf("mapped kafka port: %w", err)
201201
}
202202

203203
// 7. Render redpanda.yaml config and mount it.
204204
nodeConfig, err := renderNodeConfig(settings, hostIP, kafkaPort.Int())
205205
if err != nil {
206-
return c, fmt.Errorf("failed to render node config: %w", err)
206+
return c, err
207207
}
208208

209-
err = ctr.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 600)
209+
err = ctr.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 0o600)
210210
if err != nil {
211-
return c, fmt.Errorf("failed to copy redpanda.yaml into container: %w", err)
211+
return c, fmt.Errorf("copy to container: %w", err)
212212
}
213213

214214
// 8. Wait until Redpanda is ready to serve requests.
215+
waitHTTP := wait.ForHTTP(defaultAdminAPIPort).
216+
WithStatusCodeMatcher(func(status int) bool {
217+
// Redpanda's admin API returns 404 for requests to "/".
218+
return status == http.StatusNotFound
219+
})
220+
221+
var tlsConfig *tls.Config
222+
if settings.EnableTLS {
223+
cert, err := tls.X509KeyPair(settings.cert, settings.key)
224+
if err != nil {
225+
return c, fmt.Errorf("create admin cert: %w", err)
226+
}
227+
caCertPool := x509.NewCertPool()
228+
caCertPool.AppendCertsFromPEM(settings.cert)
229+
tlsConfig = &tls.Config{
230+
Certificates: []tls.Certificate{cert},
231+
RootCAs: caCertPool,
232+
}
233+
waitHTTP = waitHTTP.WithTLS(true, tlsConfig)
234+
}
215235
err = wait.ForAll(
216236
wait.ForListeningPort(defaultKafkaAPIPort),
217-
wait.ForListeningPort(defaultAdminAPIPort),
237+
waitHTTP,
218238
wait.ForListeningPort(defaultSchemaRegistryPort),
219239
wait.ForLog("Successfully started Redpanda!"),
220240
).WaitUntilReady(ctx, ctr)
221241
if err != nil {
222-
return c, fmt.Errorf("failed to wait for Redpanda readiness: %w", err)
242+
return c, fmt.Errorf("wait for readiness: %w", err)
223243
}
224244

225245
c.urlScheme = "http"
@@ -231,34 +251,25 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
231251
if len(settings.ServiceAccounts) > 0 {
232252
adminAPIPort, err := ctr.MappedPort(ctx, nat.Port(defaultAdminAPIPort))
233253
if err != nil {
234-
return c, fmt.Errorf("failed to get mapped Admin API port: %w", err)
254+
return c, fmt.Errorf("mapped admin port: %w", err)
235255
}
236256

237257
adminAPIUrl := fmt.Sprintf("%s://%v:%d", c.urlScheme, hostIP, adminAPIPort.Int())
238258
adminCl := NewAdminAPIClient(adminAPIUrl)
239259
if settings.EnableTLS {
240-
cert, err := tls.X509KeyPair(settings.cert, settings.key)
241-
if err != nil {
242-
return c, fmt.Errorf("failed to create admin client with cert: %w", err)
243-
}
244-
caCertPool := x509.NewCertPool()
245-
caCertPool.AppendCertsFromPEM(settings.cert)
246260
adminCl = adminCl.WithHTTPClient(&http.Client{
247261
Timeout: 5 * time.Second,
248262
Transport: &http.Transport{
249263
ForceAttemptHTTP2: true,
250264
TLSHandshakeTimeout: 10 * time.Second,
251-
TLSClientConfig: &tls.Config{
252-
Certificates: []tls.Certificate{cert},
253-
RootCAs: caCertPool,
254-
},
265+
TLSClientConfig: tlsConfig,
255266
},
256267
})
257268
}
258269

259270
for username, password := range settings.ServiceAccounts {
260271
if err := adminCl.CreateUser(ctx, username, password); err != nil {
261-
return c, fmt.Errorf("failed to create service account with username %q: %w", username, err)
272+
return c, fmt.Errorf("create user %q: %w", username, err)
262273
}
263274
}
264275
}
@@ -299,12 +310,12 @@ func renderBootstrapConfig(settings options) ([]byte, error) {
299310

300311
tpl, err := template.New("bootstrap.yaml").Parse(bootstrapConfigTpl)
301312
if err != nil {
302-
return nil, fmt.Errorf("failed to parse redpanda config file template: %w", err)
313+
return nil, fmt.Errorf("parse bootstrap template: %w", err)
303314
}
304315

305316
var bootstrapConfig bytes.Buffer
306317
if err := tpl.Execute(&bootstrapConfig, bootstrapTplParams); err != nil {
307-
return nil, fmt.Errorf("failed to render redpanda bootstrap config template: %w", err)
318+
return nil, fmt.Errorf("render bootstrap template: %w", err)
308319
}
309320

310321
return bootstrapConfig.Bytes(), nil
@@ -353,12 +364,12 @@ func renderNodeConfig(settings options, hostIP string, advertisedKafkaPort int)
353364

354365
ncTpl, err := template.New("redpanda.yaml").Parse(nodeConfigTpl)
355366
if err != nil {
356-
return nil, fmt.Errorf("failed to parse redpanda config file template: %w", err)
367+
return nil, fmt.Errorf("parse node config template: %w", err)
357368
}
358369

359370
var redpandaYaml bytes.Buffer
360371
if err := ncTpl.Execute(&redpandaYaml, tplParams); err != nil {
361-
return nil, fmt.Errorf("failed to render redpanda node config template: %w", err)
372+
return nil, fmt.Errorf("render node config template: %w", err)
362373
}
363374

364375
return redpandaYaml.Bytes(), nil

0 commit comments

Comments
 (0)