├── .gitignore ├── Pipfile ├── zappa_settings.json ├── .env.example ├── README.md └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | zappa_settings.json 3 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | 5 | [dev-packages] 6 | awscli = "*" 7 | 8 | [packages] 9 | zappa = "*" 10 | flask = "*" 11 | requests = "*" 12 | dotenv = "*" 13 | pymysql = "*" 14 | -------------------------------------------------------------------------------- /zappa_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "app_function": "app.app", 4 | "aws_region": "us-west-2", 5 | "profile_name": "personal", 6 | "s3_bucket": "lambda-tutorial-01", 7 | "runtime": "python2.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_HOST=
2 | DB_USERNAME= 3 | DB_PASSWORD= 4 | DB_NAME= 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambda Tutorial 2 | 3 | I built this tutorial on lambda because of my original struggles getting lambda set up and working. this covers: 4 | 5 | * set up with zappa 6 | * vpc to connect to a mysql RDS instance on amazon 7 | * RDS persistence and querying through the lambda function 8 | * CORs setup 9 | * simple security 10 | 11 | The tutorial should be done by checking out these branches and going through the readmes in this order 12 | 13 | * hello-world 14 | * routing 15 | * rds-integration 16 | * database-requests 17 | 18 | If you just want to run the app, adjust your .env to have the values for the 19 | config vars in .env.example and run: 20 | 21 | ```bash 22 | pipenv install 23 | 24 | ``` 25 | 26 | then, 27 | 28 | ``` 29 | zappa deploy 30 | ``` 31 | 32 | if you get stuck here I'd recommend at least running through the readme in the [rds-integration](https://github.com/ipbrennan90/lambda-tutorial/tree/rds-integration) branch 33 | 34 | 35 | 36 | I have done my best to keep this simple and short, please open any issues if you have trouble with an explanation or get stuck on a step and I will work hard to solve those. 37 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from flask import Flask, Response, json, request 4 | from dotenv import Dotenv 5 | from uuid import uuid5, uuid1 6 | import pymysql 7 | # Of course, replace by your correct path 8 | dotenv = Dotenv(os.path.join(os.path.dirname(__file__), ".env")) 9 | os.environ.update(dotenv) 10 | 11 | app = Flask(__name__) 12 | RDS_HOST = os.environ.get("DB_HOST") 13 | RDS_PORT = int(os.environ.get("DB_PORT", 3306)) 14 | NAME = os.environ.get("DB_USERNAME") 15 | PASSWORD = os.environ.get("DB_PASSWORD") 16 | DB_NAME = os.environ.get("DB_NAME") 17 | 18 | logger = logging.getLogger() 19 | logger.setLevel(logging.INFO) 20 | 21 | def connect(): 22 | try: 23 | cursor = pymysql.cursors.DictCursor 24 | conn = pymysql.connect(RDS_HOST, user=NAME, passwd=PASSWORD, db=DB_NAME, port=RDS_PORT, cursorclass=cursor, connect_timeout=5) 25 | logger.info("SUCCESS: connection to RDS successful") 26 | return conn 27 | except Exception as e: 28 | logger.exception("Database Connection Error") 29 | 30 | def validate(data): 31 | error_fields = [] 32 | not_null = [ 33 | "first_name", 34 | "last_name", 35 | "email" 36 | ] 37 | 38 | for x in not_null: 39 | if x not in data or len(data[x]) == 0: 40 | error_fields.append(x) 41 | return (len(error_fields) == 0, error_fields) 42 | 43 | def insert(data): 44 | uniq_id = str(uuid5(uuid1(), str(uuid1()))) 45 | query = """insert into User (ID, FirstName, LastName, Email) 46 | values(%s, %s, %s, %s) 47 | """ 48 | return (query, (uniq_id, data["first_name"], data["last_name"], data["email"])) 49 | 50 | def build_response(resp_dict, status_code): 51 | response = Response(json.dumps(resp_dict), status_code) 52 | response.headers["Access-Control-Allow-Origin"] = "*" 53 | response.headers["Access-Control-Allow-Headers"] = "Content-Type" 54 | return response 55 | 56 | @app.route('/') 57 | def index(): 58 | return build_response({"message": "Welcome to my lambda app!"}, 200) 59 | 60 | @app.route('/user', methods=["GET", "POST"]) 61 | def user(): 62 | if request.method == "OPTIONS": 63 | return build_response({"status": "success"}, 200) 64 | 65 | conn = connect() 66 | if request.method == "GET": 67 | items = [] 68 | try: 69 | with conn.cursor() as cur: 70 | cur.execute("select * from User") 71 | for row in cur: 72 | items.append(row) 73 | conn.commit() 74 | except Exception as e: 75 | logger.info(e) 76 | response = build_response({"status": "error", "message": "error getting users"}, 500) 77 | return response 78 | finally: 79 | conn.close() 80 | response = build_response({"rows": items, "status": "success"}, 200) 81 | return response 82 | if request.method == "POST": 83 | data = { 84 | "first_name": request.form.get("first_name", ""), 85 | "last_name": request.form.get("last_name", ""), 86 | "email": request.form.get("email", "") 87 | } 88 | valid, fields = validate(data) 89 | if not valid: 90 | error_fields = ', '.join(fields) 91 | error_message = "Data missing from these fields: %s" %error_fields 92 | return build_response({"status": "error", "message": error_message}, 400) 93 | query, vals = insert(data) 94 | try: 95 | with conn.cursor() as cur: 96 | cur.execute(query, vals) 97 | conn.commit() 98 | except Exception as e: 99 | logger.exception("insert error") 100 | return build_response({"status": "error", "message": "insert error"}, 500) 101 | finally: 102 | conn.close() 103 | cur.close() 104 | return build_response({"status": "success"}, 200) 105 | 106 | if __name__ == '__main__': 107 | app.run() 108 | --------------------------------------------------------------------------------