Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions modules/rabbitmq/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
115 changes: 51 additions & 64 deletions modules/rabbitmq/rabbitmq.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand All @@ -133,38 +101,55 @@ 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
}

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)
}
}

Expand All @@ -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)
}
}

Expand Down
30 changes: 30 additions & 0 deletions modules/rabbitmq/rabbitmq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Loading