# AWS Lambda Using Container PoC

This is a proof of concept for using containers as the code source for an AWS lambda function. The container simply calls the [cowsay](https://pypi.org/project/cowsay/) package and then returns metadata about the function execution. 

### Requirements
The following things are necessary to setup before getting started.
* [jq](https://formulae.brew.sh/formula/jq) - tested with v1.6
* [awscli](https://formulae.brew.sh/formula/awscli) - tested with v2.1.16
  * :warning: required features for `lambda update-function-code` operation are new ~December 2020
* [generated AWS credentials for dev](https://github.com/theorchard/collab/blob/7b610e3d44771ec4b6784d0820cc7d645e26a800/jcarrion/aws-creds-generator/README.md)
  * :warning: set `region = us-east-1` in `~/.aws/credentials` if not done already

### Develop Locally
Local development can be done using the base container `amazon/aws-lambda-python:3.8` instead of having to install python packages using a virtual environment.

1. Run docker-compose to build image
```
$ docker-compose up --build fibcow
```
2. Invoke function with input of `17`
```
$ curl --request POST \
  --url http://localhost:9000/2015-03-31/functions/function/invocations \
  --header 'Content-Type: application/json' \
  --data '{
	"n": 17
}'
```

* Response from cURL
```
{"result": 1597, "runtime": 0.0006046295166015625, "version": "dev", "character": "cow"}
```

* Output in STDOUT
```
fibcow_1  | time="2021-01-06T20:24:21.614" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"
fibcow_1  | time="2021-01-06T20:24:31.438" level=info msg="extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory"
fibcow_1  | time="2021-01-06T20:24:31.438" level=warning msg="Cannot list external agents" error="open /opt/extensions: no such file or directory"
fibcow_1  | START RequestId: 62fd4142-8780-46b9-a0d1-ac974c65e2a9 Version: $LATEST
fibcow_1  |   _____
fibcow_1  | < 1,597 >
fibcow_1  |   =====
fibcow_1  |           \
fibcow_1  |            \
fibcow_1  |              ^__^
fibcow_1  |              (oo)\_______
fibcow_1  |              (__)\       )\/\
fibcow_1  |                  ||----w |
fibcow_1  |                  ||     ||
fibcow_1  |
fibcow_1  |
fibcow_1  | END RequestId: 62fd4142-8780-46b9-a0d1-ac974c65e2a9
fibcow_1  | REPORT RequestId: 62fd4142-8780-46b9-a0d1-ac974c65e2a9	Init Duration: 0.82 ms	Duration: 161.07 ms	Billed Duration: 200 ms	Memory Size: 3008 MB	Max Memory Used: 3008 MB
```
:warning: Container will not automatically restart on code changes! This can be done via hooks in an editor or manually using `docker-compose restart fibcow` because the code in mounted in a volume in `docker-compose.yaml` the update will be very fast. Rebuilding the container will be slower, but still relatively fast due to docker layer caching.

### Push Image to ECR
1. Run the following script to create an ECR repo if it doesn't exist, build image, tag image with version, tag image with latest, upload images. Default ECR repo name is `dev-$USER-fibcow`
```
$ ./ecr_push.sh 1.0
```
:warning: In the real-world, this would be a pipeline that tags images based on commit hash.

### Create Lambda
1. Use an experimental branch of [terraform-lambda](https://github.com/pdumoulin/terraform-lambda/releases/tag/lambda-ecr-poc) to setup a lambda function using newly created image with tag `latest`. Default ECR repo name is `dev-$USER-fibcow`
```
$ cd terraform
$ terraform init
$ TF_VAR_USER=$USER terraform plan
$ TF_VAR_USER=$USER terraform apply
```
2. In the AWS console, send a test event to newly created lambda function, observe output.

### Update Lambda
Updating the tag `latest` to reference a new image **does not** automatically update the lambda to run a new version. In order to update the lambda, run the following script that sets the ECR image to the same value it already is to force a pull of an image.

1. Re-tag and push image with new version.
```
$ cd ..
$ ./ecr_push.sh 2.0
```
2. Force lambda function to use new image.
```
$ ./lambda_update.sh
```

### Results

#### The Good
* Develop lambdas locally using same flow as an API using your favourite REST client
* No worrying about Zappa modifying your lambda's configuration unexpectedly
* No need to install Zappa and all it's dependencies
* Easy to transport code to a fargate task

#### The Bad
*  The ECR repository **is not** created via terraform at the same time as the lambda function. The lambda module requires that `image_uri` point to a valid image. Terraform cannot create an image and upload to ECR. The sequence of events for setup must be...
	1. Create ECR repository
	2. Upload image to repository with `latest` tag
	3. Create lambda with `image_uri` pointing to image with `latest` tag
* Additional changes need to Jenkins lambda pipelines to support this flow

## Resources
* [Turotial from AWS](https://aws.amazon.com/blogs/aws/new-for-aws-lambda-container-image-support/)
* [Lambda Invoke API Reference](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html)
* [Terraform Lambda Docs](https://registry.terraform.io/providers/hashicorp/aws/3.19.0/docs/resources/lambda_function#image_uri)
