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 b9e0264d76..db79a9f60a 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -80,45 +80,13 @@ 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 + if err := apply(&settings); err != nil { + return nil, fmt.Errorf("apply option: %w", err) + } } } @@ -133,22 +101,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": settings.AdminUsername, + "RABBITMQ_DEFAULT_PASS": settings.AdminPassword, + }), + testcontainers.WithExposedPorts( + DefaultAMQPPort, + DefaultAMQPSPort, + DefaultHTTPSPort, + DefaultHTTPPort, + ), + testcontainers.WithWaitStrategy(wait.ForLog(".*Server startup complete.*").AsRegexp().WithStartupTimeout(60 * time.Second)), + withConfig(tmpConfigFile), } - container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + if settings.SSLSettings != nil { + moduleOpts = append(moduleOpts, applySSLSettings(settings.SSLSettings)) + } + + 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 +141,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 +162,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) } } 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()) +}