Skip to content

Azure

Since v0.36.0

Introduction

The Testcontainers module for Azure.

Adding this module to your project dependencies

Please run the following command to add the Azure module to your Go dependencies:

go get github.com/testcontainers/testcontainers-go/modules/azure

Usage example

The Azure module exposes the following Go packages:

  • Azurite: github.com/testcontainers/testcontainers-go/modules/azure/azurite.
  • EventHubs: github.com/testcontainers/testcontainers-go/modules/azure/eventhubs.
  • ServiceBus: github.com/testcontainers/testcontainers-go/modules/azure/servicebus.

EULA Acceptance

Due to licensing restrictions you are required to explicitly accept an End User License Agreement (EULA) for the EventHubs container image. This is facilitated through the WithAcceptEULA function.

ctx := context.Background()

azuriteContainer, err := azurite.Run(
    ctx,
    "mcr.microsoft.com/azure-storage/azurite:3.28.0",
)
defer func() {
    if err := testcontainers.TerminateContainer(azuriteContainer); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}

Azurite

Run function

The Azurite module exposes one entrypoint function to create the Azurite container, and this function receives three parameters:

func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
  • context.Context, the Go context.
  • string, the Docker image to use.
  • testcontainers.ContainerCustomizer, a variadic argument for passing options.

Default Credentials

The Azurite container uses the following default credentials:

// AccountName is the default testing account name used by Azurite
AccountName string = "devstoreaccount1"

// AccountKey is the default testing account key used by Azurite
AccountKey string = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="

Image

Use the second argument in the Run function to set a valid Docker image. In example: Run(context.Background(), "mcr.microsoft.com/azure-storage/azurite:3.28.0").

Container Options

When starting the Azurite container, you can pass options in a variadic way to configure it.

WithInMemoryPersistence

If you want to use in-memory persistence, you can use WithInMemoryPersistence(megabytes float64). E.g. azurite.WithInMemoryPersistence(64.0).

Please read the Azurite documentation for more information.

Warning

This option is only available in Azurite versions 3.28.0 and later.

The following options are exposed by the testcontainers package.

Basic Options

Lifecycle Options

Files & Mounts Options

Build Options

Logging Options

Image Options

Networking Options

Advanced Options

Experimental Options

Container Methods

The Azurite container exposes the following methods:

BlobServiceURL

Returns the service URL to connect to the Blob service of the Azurite container and an error, passing the Go context as parameter.

QueueServiceURL

Returns the service URL to connect to the Queue service of the Azurite container and an error, passing the Go context as parameter.

TableServiceURL

Returns the service URL to connect to the Table service of the Azurite container and an error, passing the Go context as parameter.

Examples

Blob Operations

In the following example, we will create a container with Azurite and perform some blob operations. For that, using the default credentials, we will create an Azurite container, upload a blob to it, list the blobs, and download the blob. Finally, we will remove the created blob and container.

ctx := context.Background()

azuriteContainer, err := azurite.Run(
    ctx,
    "mcr.microsoft.com/azure-storage/azurite:3.33.0",
    azurite.WithInMemoryPersistence(64),
)
defer func() {
    if err := testcontainers.TerminateContainer(azuriteContainer); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}
    cred, err := azblob.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey)
    if err != nil {
        log.Printf("failed to create shared key credential: %s", err)
        return
    }

 ⋯

    cred, err := azqueue.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey)
    if err != nil {
        log.Printf("failed to create shared key credential: %s", err)
        return
    }

 ⋯

    cred, err := aztables.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey)
    if err != nil {
        log.Printf("failed to create shared key credential: %s", err)
        return
    }
    serviceURL, err := azuriteContainer.BlobServiceURL(ctx)
    if err != nil {
        log.Printf("failed to get service URL: %s", err)
        return
    }

    blobServiceURL := serviceURL + "/" + azurite.AccountName

    client, err := azblob.NewClientWithSharedKeyCredential(blobServiceURL, cred, nil)
    if err != nil {
        log.Printf("failed to create client: %s", err)
        return
    }

 ⋯

    serviceURL, err := azuriteContainer.QueueServiceURL(ctx)
    if err != nil {
        log.Printf("failed to get service URL: %s", err)
        return
    }
    queueServiceURL := serviceURL + "/" + azurite.AccountName

    client, err := azqueue.NewServiceClientWithSharedKeyCredential(queueServiceURL, cred, nil)
    if err != nil {
        log.Printf("failed to create client: %s", err)
        return
    }

 ⋯

    serviceURL, err := azuriteContainer.TableServiceURL(ctx)
    if err != nil {
        log.Printf("failed to get service URL: %s", err)
        return
    }
    tablesServiceURL := serviceURL + "/" + azurite.AccountName

    client, err := aztables.NewServiceClientWithSharedKey(tablesServiceURL, cred, nil)
    if err != nil {
        log.Printf("failed to create client: %s", err)
        return
    }
containerName := "testcontainer"
_, err = client.CreateContainer(context.TODO(), containerName, nil)
if err != nil {
    log.Printf("failed to create container: %s", err)
    return
}
blobData := "Hello world!"
blobName := "HelloWorld.txt"

_, err = client.UploadStream(context.TODO(),
    containerName,
    blobName,
    strings.NewReader(blobData),
    &azblob.UploadStreamOptions{
        Metadata: map[string]*string{"Foo": to.Ptr("Bar")},
        Tags:     map[string]string{"Year": "2022"},
    })
if err != nil {
    log.Printf("failed to upload blob: %s", err)
    return
}

// Download the blob's contents and ensure that the download worked properly
blobDownloadResponse, err := client.DownloadStream(context.TODO(), containerName, blobName, nil)
if err != nil {
    log.Printf("failed to download blob: %s", err)
    return
}

// Use the bytes.Buffer object to read the downloaded data.
// RetryReaderOptions has a lot of in-depth tuning abilities, but for the sake of simplicity, we'll omit those here.
reader := blobDownloadResponse.Body
downloadData, err := io.ReadAll(reader)
if err != nil {
    log.Printf("failed to read downloaded data: %s", err)
    return
}
// List methods returns a pager object which can be used to iterate over the results of a paging operation.
// To iterate over a page use the NextPage(context.Context) to fetch the next page of results.
// PageResponse() can be used to iterate over the results of the specific page.
pager := client.NewListBlobsFlatPager(containerName, nil)
for pager.More() {
    resp, err := pager.NextPage(context.TODO())
    if err != nil {
        log.Printf("failed to list blobs: %s", err)
        return
    }

    fmt.Println(len(resp.Segment.BlobItems))
}
_, err = client.DeleteBlob(context.TODO(), containerName, blobName, nil)
if err != nil {
    log.Printf("failed to delete blob: %s", err)
    return
}
_, err = client.DeleteContainer(context.TODO(), containerName, nil)
if err != nil {
    log.Printf("failed to delete container: %s", err)
    return
}

Queue Operations

In the following example, we will create an Azurite container and perform some queue operations. For that, using the default credentials, we will create a queue, list the queues, and finally we will remove the created queue.

ctx := context.Background()

azuriteContainer, err := azurite.Run(
    ctx,
    "mcr.microsoft.com/azure-storage/azurite:3.28.0",
    azurite.WithInMemoryPersistence(64),
)
defer func() {
    if err := testcontainers.TerminateContainer(azuriteContainer); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}
cred, err := azqueue.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey)
if err != nil {
    log.Printf("failed to create shared key credential: %s", err)
    return
}
serviceURL, err := azuriteContainer.QueueServiceURL(ctx)
if err != nil {
    log.Printf("failed to get service URL: %s", err)
    return
}
queueServiceURL := serviceURL + "/" + azurite.AccountName

client, err := azqueue.NewServiceClientWithSharedKeyCredential(queueServiceURL, cred, nil)
if err != nil {
    log.Printf("failed to create client: %s", err)
    return
}
queueName := "testqueue"

_, err = client.CreateQueue(context.TODO(), queueName, &azqueue.CreateOptions{
    Metadata: map[string]*string{"hello": to.Ptr("world")},
})
if err != nil {
    log.Printf("failed to create queue: %s", err)
    return
}
pager := client.NewListQueuesPager(&azqueue.ListQueuesOptions{
    Include: azqueue.ListQueuesInclude{Metadata: true},
})

// list pre-existing queues
for pager.More() {
    resp, err := pager.NextPage(context.Background())
    if err != nil {
        log.Printf("failed to list queues: %s", err)
        return
    }

    fmt.Println(len(resp.Queues))
    fmt.Println(*resp.Queues[0].Name)
}
_, err = client.DeleteQueue(context.TODO(), queueName, &azqueue.DeleteOptions{})
if err != nil {
    log.Printf("failed to delete queue: %s", err)
    return
}

Table Operations

In the following example, we will create an Azurite container and perform some table operations. For that, using the default credentials, we will create a table, list the tables, and finally we will remove the created table.

ctx := context.Background()

azuriteContainer, err := azurite.Run(
    ctx,
    "mcr.microsoft.com/azure-storage/azurite:3.28.0",
    azurite.WithInMemoryPersistence(64),
)
defer func() {
    if err := testcontainers.TerminateContainer(azuriteContainer); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}
cred, err := aztables.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey)
if err != nil {
    log.Printf("failed to create shared key credential: %s", err)
    return
}
serviceURL, err := azuriteContainer.TableServiceURL(ctx)
if err != nil {
    log.Printf("failed to get service URL: %s", err)
    return
}
tablesServiceURL := serviceURL + "/" + azurite.AccountName

client, err := aztables.NewServiceClientWithSharedKey(tablesServiceURL, cred, nil)
if err != nil {
    log.Printf("failed to create client: %s", err)
    return
}
tableName := "fromServiceClient"
_, err = client.CreateTable(context.TODO(), tableName, nil)
if err != nil {
    log.Printf("failed to create table: %s", err)
    return
}
pager := client.NewListTablesPager(nil)
for pager.More() {
    resp, err := pager.NextPage(context.Background())
    if err != nil {
        log.Printf("failed to list tables: %s", err)
        return
    }

    fmt.Println(len(resp.Tables))
    fmt.Println(*resp.Tables[0].Name)
}
_, err = client.DeleteTable(context.TODO(), tableName, nil)
if err != nil {
    fmt.Println(err)
    return
}

EventHubs

Run function

The EventHubs module exposes one entrypoint function to create the EventHubs container, and this function receives three parameters:

func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
  • context.Context, the Go context.
  • string, the Docker image to use.
  • testcontainers.ContainerCustomizer, a variadic argument for passing options.

The EventHubs container needs an Azurite container to be running, for that reason Testcontainers for Go automatically creates a Docker network and an Azurite container for EventHubs to work. When terminating the EventHubs container, the Azurite container and the Docker network are also terminated.

Image

Use the second argument in the Run function to set a valid Docker image. In example: Run(context.Background(), "mcr.microsoft.com/azure-messaging/eventhubs-emulator:2.0.1").

Container Options

When starting the EventHubs container, you can pass options in a variadic way to configure it.

WithAzurite

This option allows you to set a different Azurite Docker image, instead of the default one, and also pass options to the Azurite container, in the form of a variadic argument of testcontainers.ContainerCustomizer.

WithAcceptEULA

This option allows you to accept the EULA for the EventHubs container.

WithConfig

This option allows you to set a custom EventHubs config file for the EventHubs container.

The config file must be a valid EventHubs config file, and it must be a valid JSON object.

{
    "UserConfig": {
        "NamespaceConfig": [
            {
                "Type": "EventHub",
                "Name": "emulatorNs1",
                "Entities": [
                    {
                        "Name": "eh1",
                        "PartitionCount": "1",
                        "ConsumerGroups": [
                            {
                                "Name": "cg1"
                            }
                        ]
                    }
                ]
            }
        ],
        "LoggingConfig": {
            "Type": "File"
        }
    }
}

The following options are exposed by the testcontainers package.

Basic Options

Lifecycle Options

Files & Mounts Options

Build Options

Logging Options

Image Options

Networking Options

Advanced Options

Experimental Options

Container Methods

The EventHubs container exposes the following methods:

ConnectionString

Returns the connection string to connect to the EventHubs container and an error, passing the Go context as parameter.

Examples

Send events to EventHubs

In the following example, inspired by the Azure Event Hubs Go SDK, we are creating an EventHubs container and sending events to it.

    cfg := `{
    "UserConfig": {
        "NamespaceConfig": [
            {
                "Type": "EventHub",
                "Name": "emulatorNs1",
                "Entities": [
                    {
                        "Name": "eh1",
                        "PartitionCount": "1",
                        "ConsumerGroups": [
                            {
                                "Name": "cg1"
                            }
                        ]
                    }
                ]
            }
        ],
        "LoggingConfig": {
            "Type": "File"
        }
    }
}
`
eventHubsCtr, err := eventhubs.Run(ctx, "mcr.microsoft.com/azure-messaging/eventhubs-emulator:2.1.0", eventhubs.WithAcceptEULA(), eventhubs.WithConfig(strings.NewReader(cfg)))
defer func() {
    if err := testcontainers.TerminateContainer(eventHubsCtr); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}
connectionString, err := eventHubsCtr.ConnectionString(ctx)
if err != nil {
    log.Printf("failed to get connection string: %s", err)
    return
}

producerClient, err := azeventhubs.NewProducerClientFromConnectionString(connectionString, "eh1", nil)
if err != nil {
    log.Printf("failed to create producer client: %s", err)
    return
}
defer producerClient.Close(context.TODO())
events := []*azeventhubs.EventData{
    {
        Body: []byte("hello"),
    },
    {
        Body: []byte("world"),
    },
}
newBatchOptions := &azeventhubs.EventDataBatchOptions{}

var batch *azeventhubs.EventDataBatch
maxRetries := 3
// Retry creating the event data batch 3 times, because the event hub is created from the configuration
// and Testcontainers cannot add a wait strategy for the event hub to be created.
for retries := 0; retries < maxRetries; retries++ {
    batch, err = producerClient.NewEventDataBatch(context.TODO(), newBatchOptions)
    if err == nil {
        break
    }

    if retries == maxRetries-1 {
        log.Printf("failed to create event data batch after %d attempts: %s", maxRetries, err)
        return
    }
}

for i := range events {
    err = batch.AddEventData(events[i], nil)
    if err != nil {
        log.Printf("failed to add event data to batch: %s", err)
        return
    }
}
err = producerClient.SendEventDataBatch(context.TODO(), batch, nil)
if err != nil {
    log.Printf("failed to send event data batch: %s", err)
    return
}

ServiceBus

Run function

The ServiceBus module exposes one entrypoint function to create the ServiceBus container, and this function receives three parameters:

func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
  • context.Context, the Go context.
  • string, the Docker image to use.
  • testcontainers.ContainerCustomizer, a variadic argument for passing options.

The ServiceBus container needs a MSSQL Server container to be running, for that reason Testcontainers for Go automatically creates a Docker network and an MSSQL Server container for ServiceBus to work. When terminating the ServiceBus container, the MSSQL Server container and the Docker network are also terminated.

Info

Since version 1.1.2 of the ServiceBus emulator, it's possible to set the SQL_WAIT_INTERVAL environment variable to the given seconds. This module sets it to 0 by default, because the MSSQL Server container is started first.

Image

Use the second argument in the Run function to set a valid Docker image. In example: Run(context.Background(), "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2").

Container Options

When starting the ServiceBus container, you can pass options in a variadic way to configure it.

WithMSSQL

This option allows you to set a different MSSQL Server Docker image, instead of the default one, and also pass options to the MSSQL container, in the form of a variadic argument of testcontainers.ContainerCustomizer.

WithAcceptEULA

This option allows you to accept the EULA for the ServiceBus container.

WithConfig

This option allows you to set a custom ServiceBus config file for the ServiceBus container.

The config file must be a valid ServiceBus config file, and it must be a valid JSON object.

{
    "UserConfig": {
        "Namespaces": [
            {
                "Name": "sbemulatorns",
                "Queues": [
                    {
                        "Name": "queue.1",
                        "Properties": {
                            "DeadLetteringOnMessageExpiration": false,
                            "DefaultMessageTimeToLive": "PT1H",
                            "DuplicateDetectionHistoryTimeWindow": "PT20S",
                            "ForwardDeadLetteredMessagesTo": "",
                            "ForwardTo": "",
                            "LockDuration": "PT1M",
                            "MaxDeliveryCount": 10,
                            "RequiresDuplicateDetection": false,
                            "RequiresSession": false
                        }
                    }
                ]
            }
        ],
        "Logging": {
            "Type": "File"
        }
    }
}

The following options are exposed by the testcontainers package.

Basic Options

Lifecycle Options

Files & Mounts Options

Build Options

Logging Options

Image Options

Networking Options

Advanced Options

Experimental Options

Container Methods

The ServiceBus container exposes the following methods:

ConnectionString

Returns the connection string to connect to the ServiceBus container and an error, passing the Go context as parameter.

Examples

Send events to ServiceBus

In the following example, inspired by the Azure Event Hubs Go SDK, we are creating an EventHubs container and sending events to it.

    cfg := `{
    "UserConfig": {
        "Namespaces": [
            {
                "Name": "sbemulatorns",
                "Queues": [
                    {
                        "Name": "queue.1",
                        "Properties": {
                            "DeadLetteringOnMessageExpiration": false,
                            "DefaultMessageTimeToLive": "PT1H",
                            "DuplicateDetectionHistoryTimeWindow": "PT20S",
                            "ForwardDeadLetteredMessagesTo": "",
                            "ForwardTo": "",
                            "LockDuration": "PT1M",
                            "MaxDeliveryCount": 10,
                            "RequiresDuplicateDetection": false,
                            "RequiresSession": false
                        }
                    }
                ]
            }
        ],
        "Logging": {
            "Type": "File"
        }
    }
}`
ctx := context.Background()

serviceBusContainer, err := servicebus.Run(
    ctx,
    "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2",
    servicebus.WithAcceptEULA(),
    servicebus.WithConfig(strings.NewReader(cfg)),
)
defer func() {
    if err := testcontainers.TerminateContainer(serviceBusContainer); err != nil {
        log.Printf("failed to terminate container: %s", err)
    }
}()
if err != nil {
    log.Printf("failed to start container: %s", err)
    return
}
connectionString, err := serviceBusContainer.ConnectionString(ctx)
if err != nil {
    log.Printf("failed to get connection string: %s", err)
    return
}

client, err := azservicebus.NewClientFromConnectionString(connectionString, nil)
if err != nil {
    log.Printf("failed to create client: %s", err)
    return
}
message := "Hello, Testcontainers!"

sender, err := client.NewSender("queue.1", nil)
if err != nil {
    log.Printf("failed to create sender: %s", err)
    return
}
defer sender.Close(context.TODO())

sbMessage := &azservicebus.Message{
    Body: []byte(message),
}
maxRetries := 3
// Retry sending the message 3 times, because the queue is created from the configuration
// and Testcontainers cannot add a wait strategy for the queue to be created.
for retries := 0; retries < maxRetries; retries++ {
    err = sender.SendMessage(context.TODO(), sbMessage, nil)
    if err == nil {
        break
    }

    if retries == maxRetries-1 {
        fmt.Printf("failed to send message after %d attempts: %s", maxRetries, err)
        return
    }
}
receiver, err := client.NewReceiverForQueue("queue.1", nil)
if err != nil {
    fmt.Printf("failed to create receiver: %s", err)
    return
}
defer receiver.Close(context.TODO())

// Receive 1 message from the queue
messagesCount := 1

messages, err := receiver.ReceiveMessages(context.TODO(), messagesCount, nil)
if err != nil {
    fmt.Printf("failed to receive messages: %s", err)
    return
}

fmt.Printf("received %d messages\n", len(messages))

for _, message := range messages {
    body := message.Body
    fmt.Printf("%s\n", string(body))

    err = receiver.CompleteMessage(context.TODO(), message, nil)
    if err != nil {
        fmt.Printf("failed to complete message: %s", err)
        return
    }
}