From ef27fed11394c506dec7a63e16a60b2a8942b7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 8 Oct 2025 07:59:27 +0200 Subject: [PATCH 1/2] chore(rabbitmq): use Run function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- modules/rabbitmq/rabbitmq.go | 111 +++++++++++++++-------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index b9e0264d76..086c1040c0 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -80,46 +80,12 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize // Run creates an instance of the RabbitMQ container type func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RabbitMQContainer, error) { - req := testcontainers.ContainerRequest{ - Image: img, - Env: map[string]string{ - "RABBITMQ_DEFAULT_USER": defaultUser, - "RABBITMQ_DEFAULT_PASS": defaultPassword, - }, - ExposedPorts: []string{ - DefaultAMQPPort, - DefaultAMQPSPort, - DefaultHTTPSPort, - DefaultHTTPPort, - }, - WaitingFor: wait.ForLog(".*Server startup complete.*").AsRegexp().WithStartupTimeout(60 * time.Second), - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ - { - PostStarts: []testcontainers.ContainerHook{}, - }, - }, - } - - genericContainerReq := testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - } - // Gather all config options (defaults and then apply provided options) settings := defaultOptions() for _, opt := range opts { if apply, ok := opt.(Option); ok { apply(&settings) } - if err := opt.Customize(&genericContainerReq); err != nil { - return nil, err - } - } - - if settings.SSLSettings != nil { - if err := applySSLSettings(settings.SSLSettings)(&genericContainerReq); err != nil { - return nil, err - } } nodeConfig, err := renderRabbitMQConfig(settings) @@ -133,22 +99,39 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom return nil, err } - if err := withConfig(tmpConfigFile)(&genericContainerReq); err != nil { - return nil, err + moduleOpts := []testcontainers.ContainerCustomizer{ + testcontainers.WithEnv(map[string]string{ + "RABBITMQ_DEFAULT_USER": defaultUser, + "RABBITMQ_DEFAULT_PASS": defaultPassword, + }), + testcontainers.WithExposedPorts( + DefaultAMQPPort, + DefaultAMQPSPort, + DefaultHTTPSPort, + DefaultHTTPPort, + ), + testcontainers.WithWaitStrategy(wait.ForLog(".*Server startup complete.*").AsRegexp().WithStartupTimeout(60 * time.Second)), + withConfig(tmpConfigFile), + } + + if settings.SSLSettings != nil { + moduleOpts = append(moduleOpts, applySSLSettings(settings.SSLSettings)) } - container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + moduleOpts = append(moduleOpts, opts...) + + ctr, err := testcontainers.Run(ctx, img, moduleOpts...) var c *RabbitMQContainer - if container != nil { + if ctr != nil { c = &RabbitMQContainer{ - Container: container, + Container: ctr, AdminUsername: settings.AdminUsername, AdminPassword: settings.AdminPassword, } } if err != nil { - return c, fmt.Errorf("generic container: %w", err) + return c, fmt.Errorf("run rabbitmq: %w", err) } return c, nil @@ -156,15 +139,15 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom func withConfig(hostPath string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { - req.Env["RABBITMQ_CONFIG_FILE"] = defaultCustomConfPath + if err := testcontainers.WithEnv(map[string]string{"RABBITMQ_CONFIG_FILE": defaultCustomConfPath})(req); err != nil { + return err + } - req.Files = append(req.Files, testcontainers.ContainerFile{ + return testcontainers.WithFiles(testcontainers.ContainerFile{ HostFilePath: hostPath, ContainerFilePath: defaultCustomConfPath, FileMode: 0o644, - }) - - return nil + })(req) } } @@ -177,27 +160,29 @@ func applySSLSettings(sslSettings *SSLSettings) testcontainers.CustomizeRequestO const defaultPermission = 0o644 return func(req *testcontainers.GenericContainerRequest) error { - req.Files = append(req.Files, testcontainers.ContainerFile{ - HostFilePath: sslSettings.CACertFile, - ContainerFilePath: rabbitCaCertPath, - FileMode: defaultPermission, - }) - req.Files = append(req.Files, testcontainers.ContainerFile{ - HostFilePath: sslSettings.CertFile, - ContainerFilePath: rabbitCertPath, - FileMode: defaultPermission, - }) - req.Files = append(req.Files, testcontainers.ContainerFile{ - HostFilePath: sslSettings.KeyFile, - ContainerFilePath: rabbitKeyPath, - FileMode: defaultPermission, - }) + if err := testcontainers.WithFiles( + testcontainers.ContainerFile{ + HostFilePath: sslSettings.CACertFile, + ContainerFilePath: rabbitCaCertPath, + FileMode: defaultPermission, + }, + testcontainers.ContainerFile{ + HostFilePath: sslSettings.CertFile, + ContainerFilePath: rabbitCertPath, + FileMode: defaultPermission, + }, + testcontainers.ContainerFile{ + HostFilePath: sslSettings.KeyFile, + ContainerFilePath: rabbitKeyPath, + FileMode: defaultPermission, + }, + )(req); err != nil { + return err + } // To verify that TLS has been enabled on the node, container logs should contain an entry about a TLS listener being enabled // See https://www.rabbitmq.com/ssl.html#enabling-tls-verify-configuration - req.WaitingFor = wait.ForAll(req.WaitingFor, wait.ForLog("started TLS (SSL) listener on [::]:5671")) - - return nil + return testcontainers.WithAdditionalWaitStrategy(wait.ForLog("started TLS (SSL) listener on [::]:5671"))(req) } } From dba32b6b40da4c82fd94fcbf91cd37822798da30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 8 Oct 2025 11:33:38 +0200 Subject: [PATCH 2/2] fix(rabbitmq): use custom credentials and return errors from options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use settings.AdminUsername and settings.AdminPassword instead of hardcoded defaults - Change Option type to return error for proper error handling - Update for-loop to check and return errors when applying options - Fixes credential handling bug identified in code review 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- modules/rabbitmq/options.go | 11 +++++++---- modules/rabbitmq/rabbitmq.go | 8 +++++--- modules/rabbitmq/rabbitmq_test.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/modules/rabbitmq/options.go b/modules/rabbitmq/options.go index 885768ab5e..15fa768891 100644 --- a/modules/rabbitmq/options.go +++ b/modules/rabbitmq/options.go @@ -41,7 +41,7 @@ type SSLSettings struct { var _ testcontainers.ContainerCustomizer = (*Option)(nil) // Option is an option for the RabbitMQ container. -type Option func(*options) +type Option func(*options) error // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. func (o Option) Customize(*testcontainers.GenericContainerRequest) error { @@ -51,21 +51,24 @@ func (o Option) Customize(*testcontainers.GenericContainerRequest) error { // WithAdminPassword sets the password for the default admin user func WithAdminPassword(password string) Option { - return func(o *options) { + return func(o *options) error { o.AdminPassword = password + return nil } } // WithAdminUsername sets the default admin username func WithAdminUsername(username string) Option { - return func(o *options) { + return func(o *options) error { o.AdminUsername = username + return nil } } // WithSSL enables SSL on the RabbitMQ container, configuring the Erlang config file with the provided settings. func WithSSL(settings SSLSettings) Option { - return func(o *options) { + return func(o *options) error { o.SSLSettings = &settings + return nil } } diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index 086c1040c0..db79a9f60a 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -84,7 +84,9 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom settings := defaultOptions() for _, opt := range opts { if apply, ok := opt.(Option); ok { - apply(&settings) + if err := apply(&settings); err != nil { + return nil, fmt.Errorf("apply option: %w", err) + } } } @@ -101,8 +103,8 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom moduleOpts := []testcontainers.ContainerCustomizer{ testcontainers.WithEnv(map[string]string{ - "RABBITMQ_DEFAULT_USER": defaultUser, - "RABBITMQ_DEFAULT_PASS": defaultPassword, + "RABBITMQ_DEFAULT_USER": settings.AdminUsername, + "RABBITMQ_DEFAULT_PASS": settings.AdminPassword, }), testcontainers.WithExposedPorts( DefaultAMQPPort, diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 0190cb97e5..799063358d 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -258,3 +258,33 @@ func requirePluginIsEnabled(t *testing.T, container testcontainers.Container, pl require.Contains(t, check, plugin+" is enabled") } } + +func TestRunContainer_withCustomCredentials(t *testing.T) { + ctx := context.Background() + + customUsername := "admin" + customPassword := "s3cr3t" + + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.12.11-management-alpine", + rabbitmq.WithAdminUsername(customUsername), + rabbitmq.WithAdminPassword(customPassword), + ) + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) + + // Verify the container reports the custom credentials + require.Equal(t, customUsername, rabbitmqContainer.AdminUsername) + require.Equal(t, customPassword, rabbitmqContainer.AdminPassword) + + // Get the AMQP URL - this will include the custom credentials + amqpURL, err := rabbitmqContainer.AmqpURL(ctx) + require.NoError(t, err) + require.Contains(t, amqpURL, customUsername) + require.Contains(t, amqpURL, customPassword) + + // Try to connect using the URL with custom credentials + amqpConnection, err := amqp.Dial(amqpURL) + require.NoError(t, err) + require.NoError(t, amqpConnection.Close()) +}