Localstack: Usando S3 con .Net Core

  • AWS
Photo by Frank Mckenna

En esta publicación, vimos cómo probar AWS localmente con Docker ejecutándose desde un archivo Docker Compose, pero hay escenarios en los que necesitamos iniciar un contenedor LocalStack usando DotNet, por ejemplo, estamos preparando un entorno para ejecutar pruebas de integración de una API web que use los servicios S3. Sin embargo, en esta publicación, utilizaremos S3 con la aplicación .Net Core Console para hacerlo.

En esta publicación, aprenderá a:

  • Ejecutar LocalStack en un contenedor Docker utilizando DotNet.
  • Acceder a la interfaz de usuario del panel de LocalStack.
  • Crear un bucket en S3.
  • Carguar y listar archivos contenidos en un bucket.

Prerrequisitos

Este tutorial asume que ejecutará LocalStack en una máquina con Windows y cumplirá estos requisitos:

  • Windows 10 Pro, Enterprise o Educational (Build 15063 o posterior).
  • Las características de Windows de Hyper-V y contenedores deben estar habilitadas.
  • Docker instalado.
  • Visual Studio 2019 Community Edition.

Puesta en marcha

Crear una solución DotNet

  1. En primer lugar, creamos una aplicación de consola .Net Core.
  2. Además, agregue los siguientes paquetes Nuget:
    • Install-Package Docker.DotNet -Version 3.125.2
    • Install-Package AWSSDK.S3 -Version 3.3.110.57
  3. A continuación, cree la clase LocalStackContainer.cs, esta clase tendrá toda la configuración para descargar la imagen LocalStack, los servicios y la asignación de puertos entre el host y el contenedor, estos son necesarios para acceder a S3.
using Docker.DotNet;
using Docker.DotNet.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class LocalStackContainer : IAsyncDisposable
    {
        private readonly DockerClient _dockerClient;
        private const string _imageName = "localstack/localstack:0.11.0";
        private string _containerId;
        private const string _containerName = "localstack-container";

        public LocalStackContainer() => _dockerClient = new DockerClientConfiguration(new Uri(GetDockerUri())).CreateClient();

        public async Task RunContainer()
        {
            try
            {
                await CleanUpContainer();

                await GetImage();

                await StartContainer();
            }
            catch (Exception ex)
            {
                await CleanUpContainer();
                throw new Exception(ex.Message);
            }
        }

        private async Task GetImage()
        {
            await _dockerClient.Images
                .CreateImageAsync(new ImagesCreateParameters
                {
                    FromImage = _imageName
                },
                new AuthConfig(),
                new Progress<JSONMessage>());
        }

        private async Task StartContainer()
        {
            var response = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters
            {
                Name = _containerName,
                Image = _imageName,
                ExposedPorts = new Dictionary<string, EmptyStruct>
                {
                    { "4572", default},
                    { "8081", default},
                },

                HostConfig = new HostConfig
                {
                    PortBindings = new Dictionary<string, IList<PortBinding>>
                    {
                        {"4572", new List<PortBinding> {new PortBinding { HostPort = "4572" } }},
                        {"8081", new List<PortBinding> {new PortBinding { HostPort = "8081" } }}
                    }
                },
                Env = new List<string> { "SERVICES=s3:4572", "PORT_WEB_UI=8081" }
            });

            _containerId = response.ID;
            Console.WriteLine("Starting up the localstack container");
            await _dockerClient.Containers.StartContainerAsync(_containerId, null);
            Console.WriteLine("LocalStack is running");
        }

        private async Task CleanUpContainer()
        {
            var containertToDelete = (await _dockerClient.Containers
                                        .ListContainersAsync(new ContainersListParameters { All = true }))
                                        .Where(x => x.Names.Any(n => n.Contains(_containerName)));

            if (containertToDelete.Any())
            {
                foreach (var item in containertToDelete)
                {
                    if (item.State.Equals("running"))
                        await _dockerClient.Containers.KillContainerAsync(item.ID, new ContainerKillParameters());
                    else
                        await _dockerClient.Containers.StopContainerAsync(item.ID, new ContainerStopParameters());

                    await _dockerClient.Containers.RemoveContainerAsync(item.ID, new ContainerRemoveParameters { Force = true });
                }
            }
        }

        private string GetDockerUri()
        {
            var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

            if (isWindows)
            {
                return "npipe://./pipe/docker_engine";
            }

            var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

            if (isLinux)
            {
                return "unix:/var/run/docker.sock";
            }

            throw new Exception("Unable to determine what OS this is running on");
        }

        public async ValueTask DisposeAsync()
        {
            if (_containerId != null)
            {
                Console.WriteLine("Shutdown localstack container");
                await CleanUpContainer();
            }
        }
    }
}
  1. Luego, en la clase Program.cs, agregue el código de inicio y ejecute algunas operaciones en el contenedor local y los servicios S3.
using Amazon.Runtime;
using Amazon.S3;
using System;
using System.IO;
using System.Threading.Tasks;

namespace ConsoleApp
{
    internal class Program
    {
        private static async Task Main(string[] args)
        {
            await RunS3Example();
        }

        private static async Task RunS3Example()
        {
            await using (var stacklocal = new LocalStackContainer())
            {
                await stacklocal.RunContainer();

                var config = new AmazonS3Config()
                {
                    ServiceURL = "http://localhost:4572/",
                    ForcePathStyle = true,
                    Timeout = TimeSpan.FromSeconds(5)
                };
                var credentials = new BasicAWSCredentials("tem", "temp");

                IAmazonS3 s3Client = new AmazonS3Client(credentials, config);

                var bucketName = "bucket-test";

                Console.WriteLine($"Creating bucket {bucketName}");
                await s3Client.PutBucketAsync(bucketName);

                var list = await s3Client.ListBucketsAsync();

                Console.WriteLine("Reading buckets...");
                list.Buckets.ForEach((bucket) => { Console.WriteLine(bucket.BucketName); });

                const string path = @"dummy-file.jpg";

                var file = File.Create(path);

                Console.WriteLine("Uploading a file..");
                await s3Client.UploadObjectFromStreamAsync(bucketName, path, file, null);

                Console.WriteLine($"List files in bucket {bucketName}");
                var filesResponse = await s3Client.ListObjectsAsync(bucketName);
                filesResponse.S3Objects.ForEach((s3Object) => Console.WriteLine(s3Object.Key));
            }

            Console.ReadLine();
        }
    }
}

Conclusiones

En resumen, usar S3 con .Net Core y LocalStack para prepararse para un entorno de desarrollo o ejecutar pruebas es fácil. Aunque es posible tambien ejecutar LocalStack usando un archivo docker-compose.yml como vimos en esta publicación.

Recursos adicionales

Ver o descargar código de muestra para esta publicación.


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *