<> = moto = A python library that allows you to easily mock out tests based on AWS infrastructure. * [[https://docs.getmoto.org/en/latest/| get moto]] * [[https://docs.getmoto.org/en/latest/docs/getting_started.html|getting started with moto]] == Install AWS CLI == {{{#!highlight shell # Install aws cli cd ~/Downloads curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install aws --version }}} == Create S3 bucket == {{{#!highlight shell export AWS_ACCESS_KEY_ID='testing' export AWS_SECRET_ACCESS_KEY='testing' export AWS_SECURITY_TOKEN='testing' export AWS_SESSION_TOKEN='testing' export AWS_DEFAULT_REGION='us-east-1' export AWS_ENDPOINT_URL="http://localhost:4000" docker run --rm -p 4000:4000 --name moto motoserver/moto:latest }}} compose.yaml {{{#!highlight yaml services: motoserver: image: motoserver/moto:latest ports: - "4000:4000" environment: - MOTO_PORT=4000 # set moto listener port with env var - MOTO_DOCKER_NETWORK_NAME=motoserver_default # docker network ls volumes: - "/var/run/docker.sock:/var/run/docker.sock" }}} {{{#!highlight shell docker compose up docker compose exec motoserver sh # in the container cat /etc/os-release # NAME="Alpine Linux" exit aws s3 ls aws s3api create-bucket --bucket my-bucket # https://docs.aws.amazon.com/cli/latest/reference/s3api/ echo "test" > test.txt aws s3api put-object --bucket my-bucket --key dir-1/test.txt --body test.txt aws s3api get-object --bucket my-bucket --key dir-1/test.txt test2.txt cat test2.txt aws s3 ls }}} == Create lambda function == === start-moto.sh === {{{#!highlight sh #!/bin/sh docker compose up }}} === build.sh === {{{#!highlight sh #!/bin/bash # sudo apt install python3-pip PACKAGE_NAME="py-my-function.zip" BUILD_DIR="dist" HANDLER_FILE="lambda_function.py" TEST_FILE="test_lambda.py" echo "Cleaning up old builds" rm -rf $BUILD_DIR rm -f $PACKAGE_NAME mkdir $BUILD_DIR echo "Installing dependencies in $BUILD_DIR ..." pip3 install -r requirements.txt -t $BUILD_DIR/ echo "Running tests..." export PYTHONPATH=$PYTHONPATH:$(pwd)/$BUILD_DIR python3 -m unittest $TEST_FILE if [ $? -eq 0 ]; then echo "Tests passed! Proceeding to build..." else echo "Tests failed. Build aborted." exit 1 fi echo "Clean up pycache" find $BUILD_DIR -type d -name "__pycache__" -exec rm -rf {} + echo "Copy source code" cp $HANDLER_FILE $BUILD_DIR/ echo "Creating the ZIP $PACKAGE_NAME" cd $BUILD_DIR zip -r ../$PACKAGE_NAME . cd .. echo "Deployment package ready: $PACKAGE_NAME" }}} === test.sh === {{{#!highlight sh #!/bin/bash # sudo apt install python3-pip PACKAGE_NAME="py-my-function.zip" BUILD_DIR="dist" HANDLER_FILE="lambda_function.py" TEST_FILE="test_lambda.py" echo "Cleaning up old builds" rm -rf $BUILD_DIR rm -f $PACKAGE_NAME mkdir $BUILD_DIR echo "Installing dependencies in $BUILD_DIR ..." pip3 install -r requirements.txt -t $BUILD_DIR/ echo "Running tests..." export PYTHONPATH=$PYTHONPATH:$(pwd)/$BUILD_DIR python3 -m unittest $TEST_FILE if [ $? -eq 0 ]; then echo "Tests passed! Proceeding to build..." else echo "Tests failed. Build aborted." exit 1 fi }}} === compose.yaml === {{{#!highlight yaml services: motoserver: image: motoserver/moto:latest ports: - "4000:4000" environment: - MOTO_PORT=4000 # set moto listener port with env var - MOTO_DOCKER_NETWORK_NAME=motoserver_default # docker network ls volumes: - "/var/run/docker.sock:/var/run/docker.sock" }}} === test_lambda.py === {{{#!highlight python import os import unittest from unittest.mock import patch, MagicMock from lambda_function import lambda_handler class TestLambda(unittest.TestCase): @patch.dict(os.environ, {'AWS_ACCESS_KEY_ID':'xxx1'}) @patch.dict(os.environ, {'AWS_SECRET_ACCESS_KEY':'xxx2'}) @patch.dict(os.environ, {'AWS_DEFAULT_REGION':'xxx3'}) @patch.dict(os.environ, {'AWS_ENDPOINT_URL':'xxx4'}) @patch('lambda_function.get_s3_client') @patch('lambda_function.get_buckets') def test_handler_success(self, mock_get_buckets, mock_get_s3client): # mock function get_buckets return value mock_get_buckets.return_value=['aaaa','bbb'] # the return value for get_s3_client is a MagicMock mock_get_s3client.return_value=MagicMock() # when s3_client.create_bucket is called it returns dictionary cow chicken mock_get_s3client.return_value.create_bucket.return_value = {'cow':'chicken'} # Execute lambda handler event = {'first_name':'aaa','last_name':'bbb'} context = None response = lambda_handler(event, context) self.assertEqual(response['message'],"Hello aaa bbb! {\"cow\": \"chicken\"}") if __name__ == '__main__': unittest.main() }}} === requirements.txt === {{{ boto3 moto }}} === lambda_function.py === {{{#!highlight python import boto3 import os import json import datetime def get_s3_client(): session = boto3.session.Session() s3_client = session.client( service_name='s3', endpoint_url='http://motoserver:4000' ) return s3_client def get_buckets(s3_client): buckets=[] for bucket in s3_client.list_buckets()['Buckets']: buckets.append(bucket['Name']) return buckets def lambda_handler(event, context): message = 'Hello {} {}!'.format(event['first_name'], event['last_name']) s3_client = get_s3_client() buckets=get_buckets(s3_client) response = s3_client.create_bucket(Bucket='examplebucket') assert isinstance(response,dict) body = { 'message' : "%s %s %s"%(message , str(type(response)) , json.dumps(response) ), 'buckets' : buckets, 'currdatetime': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'AWS_ACCESS_KEY_ID' : os.environ["AWS_ACCESS_KEY_ID"], 'AWS_SECRET_ACCESS_KEY' : os.environ["AWS_SECRET_ACCESS_KEY"], 'AWS_DEFAULT_REGION' : os.environ["AWS_DEFAULT_REGION"], 'AWS_ENDPOINT_URL': os.environ['AWS_ENDPOINT_URL'] } s3_client.put_object(Body=str(body), Bucket='examplebucket', Key='examplebucket/response.txt') return body """ deploy.sh aws s3 ls aws s3api get-object --bucket examplebucket --key examplebucket/response.txt r1.txt cat r1.txt """ }}} === deploy.sh === {{{#!highlight sh #!/bin/sh export AWS_ACCESS_KEY_ID='testing' export AWS_SECRET_ACCESS_KEY='testing' export AWS_SECURITY_TOKEN='testing' export AWS_SESSION_TOKEN='testing' export AWS_DEFAULT_REGION='us-east-1' export AWS_ENDPOINT_URL="http://localhost:4000" # export DOCKER_HOST=unix:///var/run/docker.sock ROLE_NAME=lambda-ex LAMBDA_FUNCTION_NAME=py-my-function ZIP_NAME=py-my-function.zip MODULE_NAME=lambda_function zip $ZIP_NAME $MODULE_NAME.py aws lambda delete-function --function-name $LAMBDA_FUNCTION_NAME aws iam delete-role --role-name $ROLE_NAME aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' ROLE_ARN=$(aws iam list-roles | jq ".Roles[0].Arn" | sed 's/\"//g') echo "Role Arn $ROLE_ARN" aws lambda create-function --function-name $LAMBDA_FUNCTION_NAME --zip-file fileb://$ZIP_NAME --handler $MODULE_NAME.lambda_handler --runtime python3.14 --role $ROLE_ARN --timeout 120 PAYLOAD=$( echo "{ \"first_name\": \"Bob\",\"last_name\":\"Squarepants\" }" | base64 ) echo "Invoke lambda" aws lambda invoke --function-name $LAMBDA_FUNCTION_NAME --payload $PAYLOAD response.json echo "Show response" cat response.json | jq . }}}