Simulando a AWS no seu ambiente Local
Tags: javascript, aws, node
Published: 2023-08-17 22:41:38

Opa, espero que esteja tudo bem com você que está lendo esse artigo no momento.
Passei por situações interessantes, no período em que desenvolvia uma _feature_ na atual empresa em que me encontro. 

Tive a necessidade de utilizar recursos da AWS, seja para aprender o que eu iria manusear, como implementar no ambiente corporativo de forma correta.

Durante meus estudos, para evitar gastos, procurei soluções que me permitiriam usar as funções de armazenamento e envio de e-mail da AWS de forma gratuita. Até que finalmente encontro um recurso, que simula o ambiente _cloud_ da AWS dentro da sua máquina local.

O Localstack: `https://localstack.cloud/`, é um recurso que possibilita simular diversos recursos AWS (dynamoDB, s3, iam, cognito, ses), dentro da sua máquina, utilizando o docker. 

**Pré-requisitos**
- Docker & Docker-compose
- Node (Minimo na versão 16)
- Aws-cli instalada na máquina

Para ativar, basta criar um docker compose no seu projeto com os seguintes comandos: 

```
version: "3.8"

services:
  localstack:
    container_name: "localstack"
    image: localstack/localstack:1.0.4
    ports:
      - "4566:4566"            # LocalStack Gateway
    environment:
      - DOCKER_HOST=unix:///var/run/docker.sock
      - DEFAULT_REGION=us-east-1
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
```

Rode o comando `docker-compose up` para inicializar o funcionamento do localstack

Para validar se está realmente funcionando, abra o seu navegador na seguinte url => http://localhost:4566/health

**Iniciando um projeto Node**
Não iremos nos preocupar com padrão de projetos, apenas queremos criar uma aplicação, para: criar um arquivo num bucket S3, remover o arquivo, receber a url assinada, enviar um e-mail pelo SES

crie uma pasta e abra no editor de texto da sua preferencia

Inicie o seu projeto node com o seguinte comando: 

yarn init -y

**Instalando as dependencias**

yarn add aws-sdk@2.1438 axios@1.4

yarn add @types/node@20.5 @types/axios@0.14 localstack@1 ts-node-dev@2 typescript@5.1.6 -D

**Inicializando o Typescript**

yarn tsc --init

dentro do package.json, adicione o script para rodar o projeto 

```
"scripts": {
    "dev": "ts-node-dev --respawn --transpile-only index.ts"
},
```

crie um arquivo chamado index.ts

O projeto terá a seguinte estrutura

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi20yvekilb75fx1czlf.png)


**Codificando o projeto**
Dentro do index.ts, iremos realizar as configurações dos serviços S3 e SES

```
import AWS from 'aws-sdk'
import axios from 'axios'

const s3config = {
    s3ForcePathStyle: true
}

const sesConfig = {
    apiVersion: '2010-12-01'
}

const isLocal = true

if(isLocal) {
    AWS.config.update({
        credentials: {
            accessKeyId: 'test',
            secretAccessKey: 'test'
        },
        region: 'us-east-1'
    })
    
    const host = 'localhost'
    const awsEndpoint = new AWS.Endpoint(`http://${host}:4566`);

    //@ts-ignore
    s3config.endpoint = awsEndpoint
    //@ts-ignore
    sesConfig.endpoint = awsEndpoint
}

const s3 = new AWS.S3(s3config);
const ses = new AWS.SES(sesConfig)

const bucketName = 'local-data-files'
```


**OBSERVAÇÕES:** Dentro da constante isLocal, poderemos inserir esse valor em uma variável de ambiente, para que quando for enviado para ambientes de homologação e Produção, utilizemos os reais valores da AWS e não do Localstack

Para definir que o ambiente utilizado será o localstack, é necessário configurar as chaves da AWS e alterar o endpoint que as classes irão realizar as requisições

```
AWS.config.update({
        credentials: {
            accessKeyId: 'test',
            secretAccessKey: 'test'
        },
        region: 'us-east-1'
})

const host = 'localhost'
    const awsEndpoint = new AWS.Endpoint(`http://${host}:4566`);
```

Para realizar a codificação dos demais serviços, iremos criar duas classes

```
class StorageManagement {

}

class SendEmail {
    
}
```

Para as funções de gerenciamento no S3, adicionaremos os seguintes comandos:

```
class StorageManagement {
    public async execute() {
        const image = 'https://www.purina.co.uk/sites/default/files/2020-12/Dog_1098119012_Teaser.jpg'

        const buffer = await this.downloadImage(image);
        const upload = await this.upload(buffer);

        await this.remove(upload.Key);
    }

    private async downloadImage(imageUrl: string) {
        const response = await axios.get(imageUrl, {
            responseType: 'arraybuffer'
        })

        const buffer = Buffer.from(response.data, 'base64');
        return buffer
    }

    private async upload(buffer: Buffer) {
        const s3Upload = await s3.upload({
            Bucket: bucketName,
            Key: `FILE_${(new Date()).getTime()}`,
            Body: buffer
        }).promise();

        return s3Upload
    }

    private async remove(key: string) {
        await s3.deleteObject({
            Bucket: bucketName,
            Key: key
        })
    }
}
```

E para o envio de e-mail, temos o seguinte código:

```
class SendEmail {
    public async execute() {
        var params = {
            Destination: {
             ToAddresses: [
                'envio@teste.com', 
             ]
            }, 
            Message: {
             Body: {
              Html: {
               Charset: "UTF-8", 
               Data: "This message body contains HTML formatting. It can, for example, contain links like this one: <a class=\"ulink\" href=\"http://docs.aws.amazon.com/ses/latest/DeveloperGuide\" target=\"_blank\">Amazon SES Developer Guide</a>."
              }, 
              Text: {
               Charset: "UTF-8", 
               Data: "This is the message body in text format."
              }
             }, 
             Subject: {
              Charset: "UTF-8", 
              Data: "Test email"
             }
            },
            Source: "teste@mailinator.com", 
           };

        const emailSend = ses.sendEmail(params).promise()
    }
}
```

Antes de rodar nossa aplicação e ver o funcionamento, precisamos realizar alguns comandos via script para que o código funcione

crie um arquivo script.sh e adicione os seguintes comandos

```
# Nome do Bucket
BUCKET_NAME=local-data-files

# Comando AWS para criar um bucket no S3
# o Endpoint é para definir que iremos criar dentro do localstack
aws \
    s3 mb s3://$BUCKET_NAME \
    --endpoint http://localhost:4566

# Comando para validar o e-mail que iremos utilizar para teste no Localstack
aws ses verify-email-identity \
    --email-address teste@mailinator.com \
    --endpoint-url=http://localhost:4566
```

Para executar digite no terminal 
```
sh script.sh
```


**Testando**
Para Testar cada uma das funcionalidades, instancie as classes e chame a função _execute_

```
const test = new StorageManagement()
const testSES = new SendEmail()

testSES.execute()

test.execute()
```

**Validando**
Para Validar se o arquivo está presente no s3, deve-se executar o seguinte comando sh

```
BUCKET_NAME=local-data-files
aws \
    s3 ls s3://$BUCKET_NAME\
    --endpoint http://localhost:4566
```

Para visualizar se o email foi enviado, entre na seguinte url

http://localhost:4566/_localstack/ses

Espero que esse artigo tenha sido útil para você. Deixe seu comentário e sugestão de próximo tema.

Até mais! :heart:

**Repositório GitHub do artigo**
https://github.com/DEVinicius/use-s3-ses-localstack

#### Minhas Redes Sociais
[Instagram](https://www.instagram.com/viniciusfingerstyle/):green_book:
[LinkedIn](https://www.linkedin.com/in/vinicius-dev/):briefcase: