diff --git a/.gitignore b/.gitignore index e43b0f9..8ba446f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .DS_Store +terraform.tfstate +terraform.tfstate.backup +.terraform \ No newline at end of file diff --git a/lamda.py b/lambda.py similarity index 100% rename from lamda.py rename to lambda.py diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..2aca2fe --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,50 @@ +# howto +Deploy application with terraform + +### Table of Contents +**[Pre-deployment](#pre-deployment)**
+**[Deployment](#deployment)**
+**[Tests](#tests)**
+ + +Pre-deployment +--- + +Zip your python code: + +`zip python.zip lambda.py` + +Create a aws s3 bucket with location constraint: + +`aws s3api create-bucket --bucket=terraform-serverless-python --create-bucket-configuration LocationConstraint=eu-central-1` + +Upload your build artifact into newly created bucket: + +`aws s3 cp python.zip s3://terraform-serverless-python/v1.0.0/python.zip` + + +Deployment +--- + +Deploy your application with your artifact already available in the s3 bucket + +`terraform plan -var="app_version=1.0.0"` + +`terraform apply -var="app_version=1.0.0"` + +Use `terraform output` to get an output of your API Gateway **base_url**: + +```python +terraform output +base_url = https://95q1xx0fol.execute-api.eu-central-1.amazonaws.com/v1 +``` + +Tests +--- +Test your lambda function via API Gateway with **curl**: + +```python +curl -H "Content-Type: application/json" -X POST -d '{"answer":"def sum(x,y):\n return x-y"}' $(terraform output base_url)/pyexecute +``` + +Update your `assets/main.js` file with new **base_url**. diff --git a/terraform/api_gateway.tf b/terraform/api_gateway.tf new file mode 100644 index 0000000..7c617d2 --- /dev/null +++ b/terraform/api_gateway.tf @@ -0,0 +1,83 @@ +## API Gateway config +resource "aws_api_gateway_rest_api" "PyAPI" { + name = "TfServerlessPythonAPI" + description = "API for Serverless Python Application Example" + } + +resource "aws_api_gateway_resource" "ExecutePyResource" { + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + parent_id = "${aws_api_gateway_rest_api.PyAPI.root_resource_id}" + path_part = "pyexecute" + } + +resource "aws_api_gateway_method" "ExecutePyMethod" { + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + resource_id = "${aws_api_gateway_resource.ExecutePyResource.id}" + http_method = "POST" + authorization = "NONE" + } + +## Lambda Integration +resource "aws_api_gateway_integration" "LambdaIntegration" { + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + resource_id = "${aws_api_gateway_resource.ExecutePyResource.id}" + http_method = "${aws_api_gateway_method.ExecutePyMethod.http_method}" + + integration_http_method = "POST" + type = "AWS" + uri = "${aws_lambda_function.PyLambdaFunction.invoke_arn}" + } + +## Response mapping +resource "aws_api_gateway_method_response" "200" { + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + resource_id = "${aws_api_gateway_resource.ExecutePyResource.id}" + http_method = "${aws_api_gateway_method.ExecutePyMethod.http_method}" + status_code = "200" + } + +resource "aws_api_gateway_integration_response" "LambdaIntegrationResponse" { + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + resource_id = "${aws_api_gateway_resource.ExecutePyResource.id}" + http_method = "${aws_api_gateway_method.ExecutePyMethod.http_method}" + status_code = "${aws_api_gateway_method_response.200.status_code}" + depends_on = ["aws_api_gateway_integration.LambdaIntegration"] + } + +## API DEPLOYMENT +resource "aws_api_gateway_deployment" "PyAPIDeployment" { + depends_on = [ + "aws_api_gateway_integration.LambdaIntegration" + ] + + rest_api_id = "${aws_api_gateway_rest_api.PyAPI.id}" + stage_name = "v1" + } + +## Permission for API Gateway DEPLOYMENT to access Lambda +resource "aws_lambda_permission" "apigw" { + statement_id = "AllowAPIGatewayInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.PyLambdaFunction.arn}" + principal = "apigateway.amazonaws.com" + + # The /*/* portion grants access from any method on any resource + # within the API Gateway "REST API". + source_arn = "${aws_api_gateway_deployment.PyAPIDeployment.execution_arn}/*/*" + } + +## Permission for API Gateway REST API to access Lambda +resource "aws_lambda_permission" "lambda_permission" { + statement_id = "AllowTfServerlessPythonAPIInvoke" + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.PyLambdaFunction.arn}" + principal = "apigateway.amazonaws.com" + + # The /*/*/* part allows invocation from any stage, method and resource path + # within API Gateway REST API. + source_arn = "${aws_api_gateway_rest_api.PyAPI.execution_arn}/*/*/*" + } + +output "base_url" { + value = "${aws_api_gateway_deployment.PyAPIDeployment.invoke_url}" + } diff --git a/terraform/lambda.tf b/terraform/lambda.tf new file mode 100644 index 0000000..d5ac57e --- /dev/null +++ b/terraform/lambda.tf @@ -0,0 +1,51 @@ +## +## based on an excellent terrform for lambda example: +## https://www.terraform.io/docs/providers/aws/guides/serverless-with-aws-lambda-and-api-gateway.html +## + + +provider "aws" { + region = "eu-central-1" + } + +variable "app_version" { +} + +resource "aws_lambda_function" "PyLambdaFunction" { + function_name = "PythonEvalLambda" + + # The bucket name as created earlier with "aws s3api create-bucket" + s3_bucket = "terraform-serverless-python" + s3_key = "v${var.app_version}/python.zip" + + # "main" is the filename within the zip file (main.py) and "handler" + # is the name of the property under which the handler function was + # exported in that file. + handler = "lambda.lambda_handler" + runtime = "python3.6" + + role = "${aws_iam_role.lambda_exec.arn}" +} + + +# IAM role which dictates what other AWS services the Lambda function +# may access. +resource "aws_iam_role" "lambda_exec" { + name = "serverless_lambda_python" + + assume_role_policy = <