AWS Lambda com Localstack
Tags: lambda, localstack, serverless, python
Published: 2020-05-26 01:43:19

for english, [click here](https://dev.to/maironmscosta/aws-lambda-with-localstack-32hc).

Hoje em dia é muito comum ver as pessoas fazendo uso de plataformas de serviços online como a [AWS](https://aws.amazon.com/), [Azure](https://azure.microsoft.com/), [GoogleCloud](https://cloud.google.com/). Estes são as mais famosas ["Cloud"](https://www.oracle.com/cloud/what-is-Cloud-computing/). E, como qualquer outro desenvolvedor, é muito importante testar seus códigos antes de colocá-lo em produção, e neste caso, algumas questões surgem, "Como é possível testar o código que está em produção?", "Como eu posso atualizar o código que já funciona sem quebrá-lo?" ou algo do tipo.  Então, para estes casos, este trabalho foi pensado. A meta principal é explicar como é possível testar um [AWS Lambda](https://aws.amazon.com/lambda/?nc1=h_ls) na sua máquina usando PyCharm com Python e Localstack.

Então... vamos lá!!!!!

## Setup

- Ubuntu 20.04 LTS;
- IDE: PyCharm Professional;
- Python 3.8;
- Docker;
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html):  é necessário para IDE funcionar com AWS Services;
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html): você pode usar suas credenciais verdadeiras (como descrito [aqui](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)), ou pode fazer uma ficticia. O Localstack verifica se as credenciais estão presentes e não se elas são válidas;
- [Serverless](https://serverless.com/) framework;

## Configuração

### AWS CLI

Sobre o [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html), é possível checar o material para instalar no site da AWS, [clique aqui](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html). 

### AWS SAM CLI

Para instalar o [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) é informado nos documentos da AWS que é para instalá-lo pelo Homebrew, mas em alguns casos é difícil instalar o Homebrew e pensando nisto, aqui está um outro jeito de instalar o [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html), é possíve pegar o código fonte no Github [clicando aqui](https://github.com/awslabs/aws-sam-cli).

```
$ git clone https://github.com/awslabs/aws-sam-cli.git
$ cd aws-sam-cli
$ sudo python3.8 ./setup.py build install
$ sam --version
```

A saída final:

```
SAM CLI, version 0.47.0
```

### Localstack Docker

Neste momento o "docker-compose.yaml" deve ser criado na raíz do projeto,  com isto será possível rodar o localstack de um docker container.

```
$ touch docker-compose.yml
```

**docker-compose.yml**

```
version: '2.1'
services:    
  localstack:  
    image: localstack/localstack  
    ports:  
      - "4567-4597:4567-4597"  
      - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"  
    environment:  
      - SERVICES=${SERVICES- }  
      - DEBUG=${DEBUG- }  
      - DATA_DIR=${DATA_DIR- }  
      - PORT_WEB_UI=${PORT_WEB_UI- }  
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }  
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }  
      - DOCKER_HOST=unix:///var/run/docker.sock  
    volumes:  
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"  
      - "/var/run/docker.sock:/var/run/docker.sock"
```

E agora vamos rodar o "docker-compose" com o seguinte comando:

```
$ docker-compose up
```

A partir deste momento é possível acessar diferentes serviços da AWS atráves das diferentes portas no servidor local. Por enquanto vamos testar o serviço do AWS S3.

```
$ curl -v http://localhost:4572
```

Parte da saída:

```
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
	<Owner><ID>bcaf1ffd86f41161ca5fb16fd081034f</ID>				
	<DisplayName>webfile</DisplayName></Owner><Buckets></Buckets>
</ListAllMyBucketsResult>
```

Este teste pode ser realizado também no browser. Apenas copie o endereço e cole na url do browser ou apenas [click aqui](http://localhost:4572).

**Algumas portas do AWS Service com Localstack**

- **S3**: 4572
- **DynamoDB**: 4570
- **CloudFormation**: 4581
- **Elasticsearch**: 4571
- **ES**: 4578
- **SNS**: 4575
- **SQS**: 4576
- **Lambda**: 4574
- **Kinesis**: 4568

### PyCharm com AWS Lambda

Uma vez o "docker-compose" estiver funcionando é hora de criar o projeto. Neste momento vamos criar um projeto novo no PyCharm:

- Criando um no projeto: 

![image 01](https://dev-to-uploads.s3.amazonaws.com/i/v9af03m99x8pnpb6p0mp.png)

No passo número 3 é muito importante checar se o "SAM CLI" foi automáticamente reconhecido.

![image 02](https://dev-to-uploads.s3.amazonaws.com/i/xm89qi465px2q1p4fruy.png)

- Criando o arquivo principal, o ```lambda_function.py```, na raíz do projeto.

```
import urllib.parse
import boto3
import json

# print('Loading function')

HOST = "http://[YOUR_IP]"
# Get the service resource
# To production it's not necessary inform the "endpoint_url" and "region_name"
s3 = boto3.client('s3',
                  endpoint_url= HOST + ":4572",
                  region_name="us-east-1")
sqs = boto3.client('sqs',
                  endpoint_url= HOST + ":4576",
                  region_name="us-east-1")

def lambda_handler(event, context):
    # print("Received event: " + json.dumps(event, indent=2))

    # Get the object from the event and show its content type
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    url_queue = HOST + ":4576/queue/lambda-tutorial"

    try:

        response = s3.get_object(Bucket=bucket, Key=key)

        deb = {
            "request_id": response['ResponseMetadata']['RequestId'],
            "queue_url": url_queue,
            "key": key,
            "bucket": bucket,
            "message": "aws lambda with localstack..."
        }

        print("#########################################################")
        print("Send Message")
        #Send message to SQS queue
        response = sqs.send_message(
                QueueUrl=deb["queue_url"],
                MessageBody=json.dumps(deb)
        )

        print("response: {}".format(response))

        print("#########################################################")
        print("Receive 10 Messages From SQS Queue")
        response = sqs.receive_message(
            QueueUrl=deb["queue_url"],
            MaxNumberOfMessages=10,
            VisibilityTimeout=0,
            WaitTimeSeconds=0
        )

        print("#########################################################")
        print("Read All Messages From Response")
        messages = response['Messages']
        for message in messages:
            print("Message: {}".format(message))

        print("Final Output: {}".format(json.dumps(response)))
        return json.dumps(response)
        
    except Exception as e:
        print(e)
        raise e
```

_Se o "boto3" não estiver instalado a IDE irá notificar, assim sendo, abra o terminal e execute o seguinte comando:_ 

```$ pip3.8 install boto3```

- Criando os arquivos que darão suporte ao projeto.

Neste momento algumas pastas e arquivos serão criados, as pastas conterão os arquivos utilizados no teste. Vamos criar as pastas "test/files" na raíz do projeto e então criar os arquivo para rodar o projeto.

```
aws-lambda-localstack
|test/
|- file/
|- - test_file.log
|- - input-event-test.json
|requirements.txt
```

**test_file.log**: Este arquivo será usado como exemplo vindo do bucket. Pode ser um arquivo vazio.

**requirements.txt**: Este arquivo é exigido pelo "SAM CLI".

**input-event-test.json**: Este arquivo será executado para rodar o projeto. Ele contém as informações do bucket.

```
{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "tutorial",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "lambda/test_file.log",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}
```

- Uma vez que os passos anteriores estivem terminados, o próximo passo será configurar o "run configuration".

![image 03-pycharm-run-configuration](https://dev-to-uploads.s3.amazonaws.com/i/lcb6dynn23csq1v8np0m.png)

![image-03-pycharm-run-configuration-aws-connection](https://dev-to-uploads.s3.amazonaws.com/i/d2sys59j4t20d07rk72t.png)

Agora a configuração de IDE está feita, hora de configurar as entradas. Para configurar as entradas é obrigatório rodar alguns comando para criar os arquivos no S3 e criar a fila no SQS.

1 - Criando o bucket no S3: o bucket será nomeado como "tutorial".

```$ aws --endpoint-url=http://localhost:4572 s3 mb s3://tutorial```

2 -  Criando a pasta no S3: será criada uma pasta chamada "lambda".

```$ aws --endpoint-url=http://localhost:4572 s3api put-object --bucket tutorial --key lambda```

O Response será alguma como:

```
{
    "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\""
}
```

3 - Copiando arquivos para o bucket: copiando os arquivos de "test/files" para "s3://tutorial/lambda/".

```
$ aws --endpoint-url=http://localhost:4572 s3 cp ./test/files/ s3://tutorial/lambda/ --recursive
```

Deve-se checar se o bucket foi criado. Copie o valor de "endpoint-url" e execute um "curl" ou cole no browser, por exemplo: 

```
$ curl -v http://localhost:4572/tutorial
```

A saída será algo como:

```
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
   <Name>tutorial</Name>
   <MaxKeys>1000</MaxKeys>
   <Delimiter>None</Delimiter>
   <IsTruncated>false</IsTruncated>
   <Contents>
      <Key>lambda</Key>
      <LastModified>2020-04-28T01:36:04.128Z</LastModified>
      <ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag>
      <Size>0</Size>
      <StorageClass>STANDARD</StorageClass>
      <Owner>
         <ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
         <DisplayName>webfile</DisplayName>
      </Owner>
   </Contents>
   <Contents>
      <Key>lambda/input-event-test.json</Key>
      <LastModified>2020-04-28T01:40:27.882Z</LastModified>
      <ETag>"4e114da7aa17878f62bf4485a90a97a2"</ETag>
      <Size>1011</Size>
      <StorageClass>STANDARD</StorageClass>
      <Owner>
         <ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
         <DisplayName>webfile</DisplayName>
      </Owner>
   </Contents>
   <Contents>
      <Key>lambda/test_file.log</Key>
      <LastModified>2020-04-28T01:40:27.883Z</LastModified>
      <ETag>"4ac646c9537443757aff7ebd0df4f448"</ETag>
      <Size>29</Size>
      <StorageClass>STANDARD</StorageClass>
      <Owner>
         <ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
         <DisplayName>webfile</DisplayName>
      </Owner>
   </Contents>
</ListBucketResult>
```

4 - Criando um fila no SQS.

```
$ aws --endpoint-url=http://localhost:4576 sqs create-queue --queue-name lambda-tutorial
```

O retorno será algo como:

```
{
    "QueueUrl": "http://localhost:4576/queue/lambda-tutorial"
}
```

Uma vez que tudo estiver feito e todas as saídas estão okays, é hora de rodar o projeto. Note que, no código foi inscrito a entrada e a saída pra este texto. Assim sendo, para rodar o projeto clique no botão "Run '[Local] lambda_function.lambda_handler'".

![image-04-pycharm-run-project](https://dev-to-uploads.s3.amazonaws.com/i/vxpjii26t2fzx0o8sv6x.png)
  
Depois disto, é possível ver a saída do processo no console. É exibido as entradas e ao final dez mensagens serão recuperadas da fila e exibidas no console.

BOA!

## Concluído!

Este pode ser somente o ponto inicial e eu espero que este texto ajude-os a testar seus serviços na AWS... Eu espero que todos vocês alcancem suas metas.
  
Repositório: [aws-lambda-localstack](https://bitbucket.org/MaironCosta/aws-lambda-localstack/src/master/).

Obrigado por ter lido até o fim!

### Referências

Algumas referências que foram utilizadas para concluir este texto.

- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html);
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html);
- [Serverless](https://serverless.com/);
- [Localstack](https://github.com/localstack/localstack);
- [Using Serverless Framework & Localstack to test your AWS applications locally](https://medium.com/manomano-tech/using-serverless-framework-localstack-to-test-your-aws-applications-locally-17748ffe6755);

### Algumas Cloud Computing

- [AWS](https://aws.amazon.com/); 
- [Azure](https://azure.microsoft.com/);
- [GoogleCloud](https://cloud.google.com/);
- [Openshift](https://www.openshift.com/);
- [Oracle](https://www.oracle.com/cloud/);