├── .gitignore ├── README.md ├── requirements.txt ├── sqs_messages_generator ├── __init__.py ├── app.py └── requirements.txt ├── template.yaml └── tests └── unit ├── __init__.py └── test_handler.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### OSX ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### PyCharm ### 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff: 51 | .idea/**/workspace.xml 52 | .idea/**/tasks.xml 53 | .idea/dictionaries 54 | 55 | # Sensitive or high-churn files: 56 | .idea/**/dataSources/ 57 | .idea/**/dataSources.ids 58 | .idea/**/dataSources.xml 59 | .idea/**/dataSources.local.xml 60 | .idea/**/sqlDataSources.xml 61 | .idea/**/dynamic.xml 62 | .idea/**/uiDesigner.xml 63 | 64 | # Gradle: 65 | .idea/**/gradle.xml 66 | .idea/**/libraries 67 | 68 | # CMake 69 | cmake-build-debug/ 70 | 71 | # Mongo Explorer plugin: 72 | .idea/**/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.iws 76 | 77 | ## Plugin-specific files: 78 | 79 | # IntelliJ 80 | /out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Ruby plugin and RubyMine 92 | /.rakeTasks 93 | 94 | # Crashlytics plugin (for Android Studio and IntelliJ) 95 | com_crashlytics_export_strings.xml 96 | crashlytics.properties 97 | crashlytics-build.properties 98 | fabric.properties 99 | 100 | ### PyCharm Patch ### 101 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 102 | 103 | # *.iml 104 | # modules.xml 105 | # .idea/misc.xml 106 | # *.ipr 107 | 108 | # Sonarlint plugin 109 | .idea/sonarlint 110 | 111 | ### Python ### 112 | # Byte-compiled / optimized / DLL files 113 | __pycache__/ 114 | *.py[cod] 115 | *$py.class 116 | 117 | # C extensions 118 | *.so 119 | 120 | # Distribution / packaging 121 | .Python 122 | build/ 123 | develop-eggs/ 124 | dist/ 125 | downloads/ 126 | eggs/ 127 | .eggs/ 128 | lib/ 129 | lib64/ 130 | parts/ 131 | sdist/ 132 | var/ 133 | wheels/ 134 | *.egg-info/ 135 | .installed.cfg 136 | *.egg 137 | 138 | # PyInstaller 139 | # Usually these files are written by a python script from a template 140 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 141 | *.manifest 142 | *.spec 143 | 144 | # Installer logs 145 | pip-log.txt 146 | pip-delete-this-directory.txt 147 | 148 | # Unit test / coverage reports 149 | htmlcov/ 150 | .tox/ 151 | .coverage 152 | .coverage.* 153 | .cache 154 | .pytest_cache/ 155 | nosetests.xml 156 | coverage.xml 157 | *.cover 158 | .hypothesis/ 159 | 160 | # Translations 161 | *.mo 162 | *.pot 163 | 164 | # Flask stuff: 165 | instance/ 166 | .webassets-cache 167 | 168 | # Scrapy stuff: 169 | .scrapy 170 | 171 | # Sphinx documentation 172 | docs/_build/ 173 | 174 | # PyBuilder 175 | target/ 176 | 177 | # Jupyter Notebook 178 | .ipynb_checkpoints 179 | 180 | # pyenv 181 | .python-version 182 | 183 | # celery beat schedule file 184 | celerybeat-schedule.* 185 | 186 | # SageMath parsed files 187 | *.sage.py 188 | 189 | # Environments 190 | .env 191 | .venv 192 | env/ 193 | venv/ 194 | ENV/ 195 | env.bak/ 196 | venv.bak/ 197 | 198 | # Spyder project settings 199 | .spyderproject 200 | .spyproject 201 | 202 | # Rope project settings 203 | .ropeproject 204 | 205 | # mkdocs documentation 206 | /site 207 | 208 | # mypy 209 | .mypy_cache/ 210 | 211 | ### VisualStudioCode ### 212 | .vscode/* 213 | !.vscode/settings.json 214 | !.vscode/tasks.json 215 | !.vscode/launch.json 216 | !.vscode/extensions.json 217 | .history 218 | 219 | ### Windows ### 220 | # Windows thumbnail cache files 221 | Thumbs.db 222 | ehthumbs.db 223 | ehthumbs_vista.db 224 | 225 | # Folder config file 226 | Desktop.ini 227 | 228 | # Recycle Bin used on file shares 229 | $RECYCLE.BIN/ 230 | 231 | # Windows Installer files 232 | *.cab 233 | *.msi 234 | *.msm 235 | *.msp 236 | 237 | # Windows shortcuts 238 | *.lnk 239 | 240 | # Build folder 241 | 242 | */build/* 243 | 244 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Serverless Application 2 | 3 | This is a sample template for AWS Serverless Application - Below is a brief explanation of what we have generated for you: 4 | 5 | ```bash 6 | . 7 | ├── README.md <-- This instructions file 8 | ├── hello_world <-- Source code for a lambda function 9 | │   ├── __init__.py 10 | │   ├── app.py <-- Lambda function code 11 | │   └── requirements.txt <-- Python dependencies 12 | ├── template.yaml <-- SAM Template 13 | └── tests <-- Unit tests 14 | └── unit 15 | ├── __init__.py 16 | └── test_handler.py 17 | ``` 18 | 19 | ## Requirements 20 | 21 | * AWS CLI already configured with at least PowerUser permission 22 | * [Python 2.7 installed](https://www.python.org/downloads/) 23 | * [Docker installed](https://www.docker.com/community-edition) 24 | * [Python Virtual Environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) 25 | 26 | ## Setup process 27 | 28 | ### Building the project 29 | 30 | [AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html) with the application as well as its dependencies. When you make changes to your source code or dependency manifest, 31 | run the following command to build your project local testing and deployment: 32 | 33 | ```bash 34 | sam build 35 | ``` 36 | 37 | If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: 38 | ```bash 39 | sam build --use-container 40 | ``` 41 | 42 | By default, this command writes built artifacts to `.aws-sam/build` folder. 43 | 44 | ### Local development 45 | 46 | **Invoking function locally through local API Gateway** 47 | 48 | ```bash 49 | sam local start-api 50 | ``` 51 | 52 | If the previous command ran successfully you should now be able to hit the following local endpoint to invoke your function `http://localhost:3000/hello` 53 | 54 | **SAM CLI** is used to emulate both Lambda and API Gateway locally and uses our `template.yaml` to understand how to bootstrap this environment (runtime, where the source code is, etc.) - The following excerpt is what the CLI will read in order to initialize an API and its routes: 55 | 56 | ```yaml 57 | ... 58 | Events: 59 | HelloWorld: 60 | Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api 61 | Properties: 62 | Path: /hello 63 | Method: get 64 | ``` 65 | 66 | ## Packaging and deployment 67 | 68 | AWS Lambda Python runtime requires a flat folder with all dependencies including the application. SAM will use `CodeUri` property to know where to look up for both application and dependencies: 69 | 70 | ```yaml 71 | ... 72 | HelloWorldFunction: 73 | Type: AWS::Serverless::Function 74 | Properties: 75 | CodeUri: hello_world/ 76 | ... 77 | ``` 78 | 79 | Firstly, we need a `S3 bucket` where we can upload our Lambda functions packaged as ZIP before we deploy anything - If you don't have a S3 bucket to store code artifacts then this is a good time to create one: 80 | 81 | ```bash 82 | aws s3 mb s3://BUCKET_NAME 83 | ``` 84 | 85 | Next, run the following command to package our Lambda function to S3: 86 | 87 | ```bash 88 | sam package \ 89 | --output-template-file packaged.yaml \ 90 | --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME 91 | ``` 92 | 93 | Next, the following command will create a Cloudformation Stack and deploy your SAM resources. 94 | 95 | ```bash 96 | sam deploy \ 97 | --template-file packaged.yaml \ 98 | --stack-name aws-serverless-application \ 99 | --capabilities CAPABILITY_IAM 100 | ``` 101 | 102 | > **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** 103 | 104 | After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: 105 | 106 | ```bash 107 | aws cloudformation describe-stacks \ 108 | --stack-name aws-serverless-application \ 109 | --query 'Stacks[].Outputs' 110 | ``` 111 | 112 | ## Testing 113 | 114 | We use **Pytest** and **pytest-mock** for testing our code and you can install it using pip: ``pip install pytest pytest-mock`` 115 | 116 | Next, we run `pytest` against our `tests` folder to run our initial unit tests: 117 | 118 | ```bash 119 | python -m pytest tests/ -v 120 | ``` 121 | 122 | **NOTE**: It is recommended to use a Python Virtual environment to separate your application development from your system Python installation. 123 | 124 | # Appendix 125 | 126 | ### Python Virtual environment 127 | **In case you're new to this**, python2 `virtualenv` module is not available in the standard library so we need to install it and then we can install our dependencies: 128 | 129 | 1. Create a new virtual environment 130 | 2. Install dependencies in the new virtual environment 131 | 132 | ```bash 133 | pip install virtualenv 134 | virtualenv .venv 135 | . .venv/bin/activate 136 | pip install -r requirements.txt 137 | ``` 138 | 139 | 140 | **NOTE:** You can find more information about Virtual Environment at [Python Official Docs here](https://docs.python.org/3/tutorial/venv.html). Alternatively, you may want to look at [Pipenv](https://github.com/pypa/pipenv) as the new way of setting up development workflows 141 | ## AWS CLI commands 142 | 143 | AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack: 144 | 145 | ```bash 146 | sam package \ 147 | --output-template-file packaged.yaml \ 148 | --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME 149 | 150 | sam deploy \ 151 | --template-file packaged.yaml \ 152 | --stack-name aws-serverless-application \ 153 | --capabilities CAPABILITY_IAM \ 154 | --parameter-overrides MyParameterSample=MySampleValue 155 | 156 | aws cloudformation describe-stacks \ 157 | --stack-name aws-serverless-application --query 'Stacks[].Outputs' 158 | ``` 159 | 160 | ## Bringing to the next level 161 | 162 | Here are a few ideas that you can use to get more acquainted as to how this overall process works: 163 | 164 | * Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path 165 | * Update unit test to capture that 166 | * Package & Deploy 167 | 168 | Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: 169 | 170 | * [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) 171 | * [Chalice Python Serverless framework](https://github.com/aws/chalice) 172 | * Sample Python with 3rd party dependencies, pipenv and Makefile: ``sam init --location https://github.com/aws-samples/cookiecutter-aws-sam-python`` 173 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Serverless-Projects/sqs-messages-generator/dba14976212fc70a4e17f603c7c3afb809efeee2/requirements.txt -------------------------------------------------------------------------------- /sqs_messages_generator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Serverless-Projects/sqs-messages-generator/dba14976212fc70a4e17f603c7c3afb809efeee2/sqs_messages_generator/__init__.py -------------------------------------------------------------------------------- /sqs_messages_generator/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | 4 | sqs = boto3.resource('sqs') 5 | QUEUE = os.environ['QUEUE'] 6 | MESSAGE_COUNT = os.environ['MESSAGE_COUNT'] 7 | 8 | def lambda_handler(event, context): 9 | # Retrieving a queue by its name 10 | queue = sqs.get_queue_by_name(QueueName=QUEUE) 11 | 12 | # Create a new message 13 | response = queue.send_message(MessageBody='test message with ' + MESSAGE_COUNT) 14 | 15 | # The response is not a resource, but gives you a message ID and MD5 16 | print("MessageId created: {0}".format(response.get('MessageId'))) 17 | print("MD5 created: {0}".format(response.get('MD5OfMessageBody'))) 18 | -------------------------------------------------------------------------------- /sqs_messages_generator/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Serverless-Projects/sqs-messages-generator/dba14976212fc70a4e17f603c7c3afb809efeee2/sqs_messages_generator/requirements.txt -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: >- 4 | An Amazon SQS message generator. Good for testing purposes. 5 | Parameters: 6 | QueueNameParameter: 7 | Type: String 8 | NumberOfMessagesParameter: 9 | Type: Number 10 | Resources: 11 | sqsmessagegenerator: 12 | Type: 'AWS::Serverless::Function' 13 | Properties: 14 | Handler: lambda_function.lambda_handler 15 | Runtime: python2.7 16 | CodeUri: . 17 | Description: >- 18 | An Amazon SQS message generator. 19 | MemorySize: 128 20 | Timeout: 3 21 | Policies: 22 | - Statement: 23 | - Effect: Allow 24 | Action: 25 | - 'sqs:SendMessage' 26 | Resource: 27 | - !Ref QueueNameParameter -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWS-Serverless-Projects/sqs-messages-generator/dba14976212fc70a4e17f603c7c3afb809efeee2/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_handler.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import json 3 | 4 | import pytest 5 | 6 | from hello_world import app 7 | 8 | 9 | @pytest.fixture() 10 | def apigw_event(): 11 | """ Generates API GW Event""" 12 | 13 | return { 14 | "body": '{ "test": "body"}', 15 | "resource": "/{proxy+}", 16 | "requestContext": { 17 | "resourceId": "123456", 18 | "apiId": "1234567890", 19 | "resourcePath": "/{proxy+}", 20 | "httpMethod": "POST", 21 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 22 | "accountId": "123456789012", 23 | "identity": { 24 | "apiKey": "", 25 | "userArn": "", 26 | "cognitoAuthenticationType": "", 27 | "caller": "", 28 | "userAgent": "Custom User Agent String", 29 | "user": "", 30 | "cognitoIdentityPoolId": "", 31 | "cognitoIdentityId": "", 32 | "cognitoAuthenticationProvider": "", 33 | "sourceIp": "127.0.0.1", 34 | "accountId": "", 35 | }, 36 | "stage": "prod", 37 | }, 38 | "queryStringParameters": {"foo": "bar"}, 39 | "headers": { 40 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 41 | "Accept-Language": "en-US,en;q=0.8", 42 | "CloudFront-Is-Desktop-Viewer": "true", 43 | "CloudFront-Is-SmartTV-Viewer": "false", 44 | "CloudFront-Is-Mobile-Viewer": "false", 45 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 46 | "CloudFront-Viewer-Country": "US", 47 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 48 | "Upgrade-Insecure-Requests": "1", 49 | "X-Forwarded-Port": "443", 50 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 51 | "X-Forwarded-Proto": "https", 52 | "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", 53 | "CloudFront-Is-Tablet-Viewer": "false", 54 | "Cache-Control": "max-age=0", 55 | "User-Agent": "Custom User Agent String", 56 | "CloudFront-Forwarded-Proto": "https", 57 | "Accept-Encoding": "gzip, deflate, sdch", 58 | }, 59 | "pathParameters": {"proxy": "/examplepath"}, 60 | "httpMethod": "POST", 61 | "stageVariables": {"baz": "qux"}, 62 | "path": "/examplepath", 63 | } 64 | 65 | 66 | def test_lambda_handler(apigw_event, mocker): 67 | 68 | requests_response_mock = namedtuple("response", ["text"]) 69 | requests_response_mock.text = "1.1.1.1\n" 70 | 71 | request_mock = mocker.patch.object( 72 | app.requests, 'get', side_effect=requests_response_mock) 73 | 74 | ret = app.lambda_handler(apigw_event, "") 75 | assert ret["statusCode"] == 200 76 | 77 | for key in ("message", "location"): 78 | assert key in ret["body"] 79 | 80 | data = json.loads(ret["body"]) 81 | assert data["message"] == "hello world" 82 | --------------------------------------------------------------------------------