diff --git a/docs/modules/azure.md b/docs/modules/azure.md index 8260970369..695e0efeb8 100644 --- a/docs/modules/azure.md +++ b/docs/modules/azure.md @@ -61,6 +61,12 @@ In example: `Run(context.Background(), "mcr.microsoft.com/azure-storage/azurite: When starting the Azurite container, you can pass options in a variadic way to configure it. +#### WithEnabledServices + +- Not available until the next release :material-tag: main + +The default Azurite container entrypoint runs all three storage services: blob, queue, and table. Use this option to specify the required services for fewer exposed ports and slightly reduced container resources. E.g. `azurite.WithEnabledServices(azurite.BlobService)`. + #### WithInMemoryPersistence - Since :material-tag: v0.36.0 diff --git a/modules/azure/azurite/azurite.go b/modules/azure/azurite/azurite.go index 9eeabd5a54..83a4530310 100644 --- a/modules/azure/azurite/azurite.go +++ b/modules/azure/azurite/azurite.go @@ -33,74 +33,85 @@ type Container struct { opts options } -// Deprecated: Use [azure.BlobServiceURL], [azure.QueueServiceURL], or [azure.TableServiceURL] methods instead. // ServiceURL returns the URL of the given service func (c *Container) ServiceURL(ctx context.Context, srv Service) (string, error) { - return c.serviceURL(ctx, srv) + port, err := servicePort(srv) + if err != nil { + return "", err + } + + return c.PortEndpoint(ctx, port, "http") } // BlobServiceURL returns the URL of the Blob service func (c *Container) BlobServiceURL(ctx context.Context) (string, error) { - return c.serviceURL(ctx, blobService) + return c.ServiceURL(ctx, BlobService) } // QueueServiceURL returns the URL of the Queue service func (c *Container) QueueServiceURL(ctx context.Context) (string, error) { - return c.serviceURL(ctx, queueService) + return c.ServiceURL(ctx, QueueService) } // TableServiceURL returns the URL of the Table service func (c *Container) TableServiceURL(ctx context.Context) (string, error) { - return c.serviceURL(ctx, tableService) + return c.ServiceURL(ctx, TableService) } -func (c *Container) serviceURL(ctx context.Context, srv service) (string, error) { - var port nat.Port +func servicePort(srv Service) (nat.Port, error) { switch srv { - case blobService: - port = BlobPort - case queueService: - port = QueuePort - case tableService: - port = TablePort + case BlobService: + return BlobPort, nil + case QueueService: + return QueuePort, nil + case TableService: + return TablePort, nil default: return "", fmt.Errorf("unknown service: %s", srv) } - - return c.PortEndpoint(ctx, port, "http") } // Run creates an instance of the Azurite container type func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { - moduleCmd := []string{} - - moduleOpts := []testcontainers.ContainerCustomizer{ - testcontainers.WithEntrypoint("azurite"), - testcontainers.WithExposedPorts(BlobPort, QueuePort, TablePort), - } - // 1. Gather all config options (defaults and then apply provided options) settings := defaultOptions() + for _, opt := range opts { + if o, ok := opt.(Option); ok { + if err := o(&settings); err != nil { + return nil, fmt.Errorf("azurite option: %w", err) + } + } + } + + entrypoint := "azurite" + if len(settings.EnabledServices) == 1 && settings.EnabledServices[0] != TableService { + // Use azurite-table in future once it matures. Graceful shutdown is currently very slow. + entrypoint = fmt.Sprintf("%s-%s", entrypoint, settings.EnabledServices[0]) + } + moduleOpts := []testcontainers.ContainerCustomizer{testcontainers.WithEntrypoint(entrypoint)} // 2. evaluate the enabled services to apply the right wait strategy and Cmd options if len(settings.EnabledServices) > 0 { + cmd := make([]string, 0, len(settings.EnabledServices)) + exposedPorts := make([]string, 0, len(settings.EnabledServices)) waitingFor := make([]wait.Strategy, 0, len(settings.EnabledServices)) + for _, srv := range settings.EnabledServices { - switch srv { - case BlobService: - moduleCmd = append(moduleCmd, "--blobHost", "0.0.0.0") - waitingFor = append(waitingFor, wait.ForListeningPort(BlobPort)) - case QueueService: - moduleCmd = append(moduleCmd, "--queueHost", "0.0.0.0") - waitingFor = append(waitingFor, wait.ForListeningPort(QueuePort)) - case TableService: - moduleCmd = append(moduleCmd, "--tableHost", "0.0.0.0") - waitingFor = append(waitingFor, wait.ForListeningPort(TablePort)) + port, err := servicePort(srv) + if err != nil { + return nil, err } + + cmd = append(cmd, fmt.Sprintf("--%sHost", srv), "0.0.0.0", fmt.Sprintf("--%sPort", srv), port.Port()) + exposedPorts = append(exposedPorts, string(port)) + waitingFor = append(waitingFor, wait.ForListeningPort(port)) } - moduleOpts = append(moduleOpts, testcontainers.WithCmd(moduleCmd...)) - moduleOpts = append(moduleOpts, testcontainers.WithWaitStrategy(waitingFor...)) + moduleOpts = append(moduleOpts, + testcontainers.WithCmd(cmd...), + testcontainers.WithExposedPorts(exposedPorts...), + testcontainers.WithWaitStrategy(waitingFor...), + ) } moduleOpts = append(moduleOpts, opts...) diff --git a/modules/azure/azurite/azurite_test.go b/modules/azure/azurite/azurite_test.go index 1809ae767e..37e693bd34 100644 --- a/modules/azure/azurite/azurite_test.go +++ b/modules/azure/azurite/azurite_test.go @@ -36,6 +36,38 @@ func TestAzurite_inMemoryPersistence(t *testing.T) { }) } +func TestAzurite_enabledServices(t *testing.T) { + ctx := context.Background() + + services := []azurite.Service{azurite.BlobService, azurite.QueueService, azurite.TableService} + for _, service := range services { + t.Run(string(service), func(t *testing.T) { + ctr, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.33.0", azurite.WithEnabledServices(service)) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + for _, srv := range services { + _, err = ctr.ServiceURL(ctx, srv) + if srv == service { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } + }) + } + + t.Run("unknown", func(t *testing.T) { + _, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.33.0", azurite.WithEnabledServices("foo")) + require.EqualError(t, err, "azurite option: unknown service: foo") + }) + + t.Run("duplicate", func(t *testing.T) { + _, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.33.0", azurite.WithEnabledServices(azurite.BlobService, azurite.BlobService)) + require.EqualError(t, err, "azurite option: duplicate service: blob") + }) +} + func TestAzurite_serviceURL(t *testing.T) { ctx := context.Background() diff --git a/modules/azure/azurite/examples_test.go b/modules/azure/azurite/examples_test.go index 9fc8c91f21..842720dfd6 100644 --- a/modules/azure/azurite/examples_test.go +++ b/modules/azure/azurite/examples_test.go @@ -57,6 +57,7 @@ func ExampleRun_blobOperations() { ctx, "mcr.microsoft.com/azure-storage/azurite:3.33.0", azurite.WithInMemoryPersistence(64), + azurite.WithEnabledServices(azurite.BlobService), ) defer func() { if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { @@ -198,6 +199,7 @@ func ExampleRun_queueOperations() { ctx, "mcr.microsoft.com/azure-storage/azurite:3.28.0", azurite.WithInMemoryPersistence(64), + azurite.WithEnabledServices(azurite.QueueService), ) defer func() { if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { @@ -292,6 +294,7 @@ func ExampleRun_tableOperations() { ctx, "mcr.microsoft.com/azure-storage/azurite:3.28.0", azurite.WithInMemoryPersistence(64), + azurite.WithEnabledServices(azurite.TableService), ) defer func() { if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { diff --git a/modules/azure/azurite/options.go b/modules/azure/azurite/options.go index 7e8d2dc780..1666894c7c 100644 --- a/modules/azure/azurite/options.go +++ b/modules/azure/azurite/options.go @@ -8,12 +8,51 @@ import ( type options struct { // EnabledServices is a list of services that should be enabled - EnabledServices []service + EnabledServices []Service } func defaultOptions() options { return options{ - EnabledServices: []service{blobService, queueService, tableService}, + EnabledServices: []Service{BlobService, QueueService, TableService}, + } +} + +// Satisfy the testcontainers.ContainerCustomizer interface +var _ testcontainers.ContainerCustomizer = (Option)(nil) + +// Option is an option for the Azurite container. +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 { + // NOOP to satisfy interface. + return nil +} + +// WithEnabledServices is a custom option to specify which services should be enabled. +func WithEnabledServices(services ...Service) Option { + return func(o *options) error { + if len(services) == 0 { + services = []Service{BlobService, QueueService, TableService} + } else { + seen := make(map[Service]bool, len(services)) + for _, srv := range services { + if seen[srv] { + return fmt.Errorf("duplicate service: %s", srv) + } + seen[srv] = true + + switch srv { + case BlobService, QueueService, TableService: + // valid service, continue + default: + return fmt.Errorf("unknown service: %s", srv) + } + } + } + + o.EnabledServices = services + return nil } } diff --git a/modules/azure/azurite/services.go b/modules/azure/azurite/services.go index 3b1fb5a23d..69c9ade850 100644 --- a/modules/azure/azurite/services.go +++ b/modules/azure/azurite/services.go @@ -1,31 +1,15 @@ package azurite const ( - // blobService is the service name for the Blob service - blobService Service = "blob" - - // Deprecated: this constant is kept for backward compatibility, but it'll be removed in the next major version. // BlobService is the service name for the Blob service - BlobService Service = blobService - - // queueService is the service name for the Queue service - queueService Service = "queue" + BlobService Service = "blob" - // Deprecated: this constant is kept for backward compatibility, but it'll be removed in the next major version. // QueueService is the service name for the Queue service - QueueService Service = queueService + QueueService Service = "queue" - // tableService is the service name for the Table service - tableService Service = "table" - - // Deprecated: this constant is kept for backward compatibility, but it'll be removed in the next major version. // TableService is the service name for the Table service - TableService Service = tableService + TableService Service = "table" ) -// Deprecated: this type is kept for backward compatibility, but it'll be removed in the next major version. // Service is the type for the services that Azurite can provide -type Service = service - -// service is the type for the services that Azurite can provide -type service string +type Service string diff --git a/modules/azure/eventhubs/options.go b/modules/azure/eventhubs/options.go index 25f19ee0a4..e749996b6d 100644 --- a/modules/azure/eventhubs/options.go +++ b/modules/azure/eventhubs/options.go @@ -23,7 +23,7 @@ func defaultOptions() options { } } -// Satisfy the testcontainers.CustomizeRequestOption interface +// Satisfy the testcontainers.ContainerCustomizer interface var _ testcontainers.ContainerCustomizer = (Option)(nil) // Option is an option for the EventHubs container. diff --git a/modules/azure/servicebus/options.go b/modules/azure/servicebus/options.go index 24de1041fe..f9396515b6 100644 --- a/modules/azure/servicebus/options.go +++ b/modules/azure/servicebus/options.go @@ -23,7 +23,7 @@ func defaultOptions() options { } } -// Satisfy the testcontainers.CustomizeRequestOption interface +// Satisfy the testcontainers.ContainerCustomizer interface var _ testcontainers.ContainerCustomizer = (Option)(nil) // Option is an option for the ServiceBus container.