Using LocalStack with Microsoft Tye

7 minute read

LocalStack is a platform that facilitates the development of cloud-based applications by hosting locally a series of replicas of AWS services.

Since it uses containers behind the scenes, the documentation on their website explains how to use LocalStack with Docker, Docker Compose and Kubernetes.

In a previous post, I explained how to use Microsoft Tye to facilitate the local development of distributed .NET applications.

In this post we’ll see how to configure Tye to use LocalStack services and how to configure ASP.NET Core applications to consume those services when available.

Configuring LocalStack

The first step for using LocalStack in a Tye application is adding it to the tye.yaml file.

  - name: localstack
    image: localstack/localstack:1.0
    bindings:
      - protocol: http
        containerPort: 4566 
        port: 4566
    volumes:
      - source: /var/run/docker.sock
        target: /var/run/docker.sock

This is nothing special from the typical configuration of a Docker container but for the special volume mounting.

I will not delve into what the /var/run/docker.sock mount point is, but I suggest you go and read this blog post. Long story short, this mount point is used by containers to interact with the Docker API to manage other containers.
Since LocalStack uses this functionality to emulate services like Lambda, you can probably skip the volume mounting if you are not using any container-based service.

For this mount point to work in Windows, you need Docker to be using the WSL2 integration

Once we have configured LocalStack in the tye.yaml file, we can start the service

$ dotnet tye run

As seen previously, this command will take care of starting up the services composing the application: in our case, the LocalStack container.

LocalStack offers many configuration options. You can check the related documentation page to see all available options.

Using LocalStack services

Once Tye is done initializing the application, we can test the emulated services as if they were normal AWS services.

Before we start, make sure to add a fake profile in your ~/.aws/credentials file. I added a profile called LocalStack with fake values for access and secret key.

[LocalStack]
aws_access_key_id = AKIA1234567890
aws_secret_access_key = abcdefghijklmnopqrstuvwxyz

Furthermore, LocalStack acts as if the services were deployed on the us-east-1 region. For convenience, we will modify the ~/.aws/config file to add this preference.

[profile LocalStack]
region = us-east-1

AWS CLI

For this test, we will be using the S3 API.

I will assume the AWS CLI has been installed and is available via the PATH variable.

Let’s start by fetching a list of buckets.

$ aws --endpoint-url=http://localhost:4566 --profile LocalStack s3api list-buckets 
{
    "Buckets": [],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "bcaf1ffd86f41161ca5fb16fd081034f"
    }
}

Now, let’s create a bucket and test its existence

$ aws --endpoint-url=http://localhost:4566 --profile LocalStack s3api create-bucket --bucket test

$ aws --endpoint-url=http://localhost:4566 --profile LocalStack s3api list-buckets
{
    "Buckets": [
        {
            "Name": "test",
            "CreationDate": "2022-07-22T10:20:13+00:00"
        }
    ],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "bcaf1ffd86f41161ca5fb16fd081034f"
    }
}

Now that we have a bucket, let’s do some test operations with files.

In the snippet below, I create a file, I upload it to the LocalStack S3 and then I get a list of all the files available.

$ echo "Hello world" >> hello.txt

$ aws --endpoint-url=http://localhost:4566 --profile LocalStack s3 cp ./hello.txt s3://test/
upload: ./hello.txt to s3://test/hello.txt

$ aws --endpoint-url=http://localhost:4566 --profile LocalStack s3 ls s3://test/
2022-07-22 12:28:06         13 hello.txt

If you are tired of repeating the endpoint-url parameter, you can install the awslocal CLI application provided by LocalStack. More information here.

PowerShell toolkit

Like for AWS CLI, we can use the PowerShell toolkit to interact with the LocalStack services.

For this test, we will be using the SQS API.

First of all, we need to install the needed Powershell modules.

$ Install-Module -Name AWS.Tools.Installer
$ Install-AWSToolsModule SQS

Once the module is installed, we can use it to create a queue.

# Creates a new SQS queue
$ New-SQSQueue -QueueName test -EndpointUrl http://localhost:4566 -ProfileName LocalStack
http://localhost:4566/000000000000/test

# Lists all available SQS queues
$ Get-SQSQueue -EndpointUrl http://localhost:4566 -ProfileName LocalStack
http://localhost:4566/000000000000/test

# Gets the URL of the SQS queue we just created
$ Get-SQSQueueUrl -QueueName test -EndpointUrl http://localhost:4566 -ProfileName LocalStack
http://localhost:4566/000000000000/test

Next, we can send and receive a message.

# Sends a message to the SQS queue
$ Send-SQSMessage -QueueUrl http://localhost:4566/000000000000/test -EndpointUrl http://localhost:4566 -ProfileName LocalStack -MessageBody "Hello World"

MD5OfMessageAttributes       :
MD5OfMessageBody             : b10a8db164e0754105b7a99be72e3fe5
MD5OfMessageSystemAttributes :
MessageId                    : c9bf5e25-f16d-4767-b0f5-f4d9dfe1ce4a
SequenceNumber               :

# Receives a message from the SQS queue
$ Receive-SQSMessage -QueueUrl http://localhost:4566/000000000000/test -EndpointUrl http://localhost:4566 -ProfileName LocalStack

Attributes             : {}
Body                   : Hello World
MD5OfBody              : b10a8db164e0754105b7a99be72e3fe5
MD5OfMessageAttributes :
MessageAttributes      : {}
MessageId              : c9bf5e25-f16d-4767-b0f5-f4d9dfe1ce4a
ReceiptHandle          : a_very_long_string

This small example shows how easy is to use LocalStack with the PowerShell toolkit provided by AWS.

Did you know that PowerShell is crossplatform and you can use it on Linux and MacOS as well? More information here.

.NET SDK

Our next step is using LocalStack services in .NET applications using the AWS SDK.

I will assume that you have .NET SDK 6 installed and properly configured.

Let’s start creating a simple console application and add the SDK package to interact with AWS SQS.

$ dotnet new console
$ dotnet add package AWSSDK.SQS
$ dotnet run
Hello, World!

Now let’s replace the content of Program.cs with the following snippet.

using Amazon.Runtime;
using Amazon.SQS;

var credentials = new BasicAWSCredentials("FAKE", "FAKE");
var config = new AmazonSQSConfig { ServiceURL = "http://localhost:4566" };

var sqs = new AmazonSQSClient(credentials, config);

await sqs.CreateQueueAsync("test");

var queue = await sqs.GetQueueUrlAsync("test");

await sqs.SendMessageAsync(queue.QueueUrl, "Hello world");

var messages = await sqs.ReceiveMessageAsync(queue.QueueUrl);

foreach (var message in messages.Messages)
{
  Console.WriteLine(message.Body);
}

The small snippet above is doing the same steps as we did in the previous paragraph:

  • Create a queue whose name is test
  • Query the SQS service for the URL of the queue we just created
  • Send a message
  • Receive messages
  • Print the body of every message received

Integration with ASP.NET Core

In the examples above, we have simply used Tye as a way to start and configure the LocalStack application.
Truth be told, there are easier ways to achieve the same like using the LocalStack CLI.

The real advantage of coupling LocalStack with Microsoft Tye is to leverage the service discovery capabilities offered by Tye.

Like before, let’s start creating the application and the relevant additional packages.

$ dotnet new web -o web
$ dotnet add package Microsoft.Tye.Extensions.Configuration --version 0.10.0-alpha.21420.1
$ dotnet add package AWSSDK.Extensions.NETCore.Setup
$ dotnet add package AWSSDK.SQS

When the project is created and the packages added, let’s add it to the Tye application.

  - name: web
    project: web/web.csproj
    bindings:
      - protocol: http

Finally, let’s replace the content of Program.cs with the following snippet.

using Amazon.SQS;

var settings = new Dictionary<string, string>
{
  ["AWS:Profile"] = "LocalStack",
  ["AWS:Region"] = "us-east-1"
};

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddInMemoryCollection(settings);

var options = builder.Configuration.GetAWSOptions();
var localstack = builder.Configuration.GetServiceUri("localstack");

if (localstack is not null)
{
  options.DefaultClientConfig.ServiceURL = localstack.ToString();
}

builder.Services.AddAWSService<IAmazonSQS>(options);

var app = builder.Build();

app.MapGet("/", async (IAmazonSQS sqs) =>
{
  var queue = await sqs.GetQueueUrlAsync("test");
  var messages = await sqs.ReceiveMessageAsync(queue.QueueUrl);
  return Results.Ok(messages.Messages.Select(m => m.Body));
});

app.Run();

The snippet uses the new Minimal API to define the endpoint of the application.

The relevant part of the snippet is the following section

var options = builder.Configuration.GetAWSOptions();
var localstack = builder.Configuration.GetServiceUri("localstack");

if (localstack is not null)
{
  options.DefaultClientConfig.ServiceURL = localstack.ToString();
}

builder.Services.AddAWSService<IAmazonSQS>(options);

In the snippet above, we’re getting the AWS setup from the configuration subsystem. Then, we check if the process was served the configuration of a service called localstack: if so, we customize the AWS configuration so that it points to the URI we found.

Finally, we register the client for SQS with the customized configuration.

This approach allows us to use LocalStack when using Tye and the default AWS services when not.

When we start the application using the command dotnet tye run, both the LocalStack container and our ASP.NET Core application will be started. Furthermore, Tye will take care of passing the address of the localstack service using two environment variables: SERVICE__localstack__HOST and SERVICE__localstack__PORT. The GetServiceUri function used earlier will take care of fetching the value of these variables and use them to compose a URI pointing to the localstack service.

To test the service, create a SQS queue named test and send messages to it by either using the PowerShell toolkit like shown earlier. Finally, open the browser to the Tye dashboard (typically http://127.0.0.1:8000) and from there navigate to the web application. Each visit will return the body of a message from the SQS queue. If no message is available, an empty JSON array is returned.

Alternatively, you can use command line utilities like curl or Invoke-WebRequest.

Recap

In this post we saw how we can configure Tye to spin a LocalStack container and how to use it from AWS CLI, the PowerShell toolkit and even a .NET console application. Finally, we leveraged the service discovery mechanisms built into Tye to create a simple ASP.NET Core application able to use LocalStack when launched by Tye.