From 4f95fcf32199399aa7e8d54dd6293e870e8a5820 Mon Sep 17 00:00:00 2001 From: Hammy Date: Mon, 30 Aug 2021 16:35:39 +0100 Subject: [PATCH] Create API Gateway with Lambda Backend --- stacks/src/template.py | 202 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 3 deletions(-) diff --git a/stacks/src/template.py b/stacks/src/template.py index 9082e11..1f62648 100644 --- a/stacks/src/template.py +++ b/stacks/src/template.py @@ -1,18 +1,214 @@ -from troposphere import Template +from awacs.aws import Action, Statement, Principal, Policy +from troposphere import Template, GetAtt, Ref, Join, Output, iam, awslambda +from troposphere.apigateway import RestApi, Resource, Method, Integration, IntegrationResponse, MethodResponse, \ + Deployment, Stage +from troposphere.awslambda import Function, Code +from troposphere.s3 import Bucket +from troposphere.sns import Topic, Subscription from troposphere.sqs import Queue from util import Util PROJECT_NAME: str = "Tweeter" +REST_API_NAME: str = "TweeterAPI" +REST_API_STAGE_NAME: str = "v1" +API_LAMBDA_NAME: str = "ApiLambda" +API_LAMBDA_KEBAB_NAME: str = "api-lambda" +SHARED_CONFIG_BUCKET_NAME: str = "SharedConfig" + S3_QUEUE_NAME: str = "S3Queue" -TWEETER_QUEUE_NAME: str = "TwitterQueue" +TWITTER_QUEUE_NAME: str = "TwitterQueue" DYNAMO_DB_QUEUE_NAME: str = "DynamoDbQueue" +FANOUT_TOPIC_NAME: str = "FanoutTopic" template: Template = Template(PROJECT_NAME + "Workflow") templateUtil: Util = Util(template) +shared_config_bucket: Bucket = template.add_resource(Bucket(SHARED_CONFIG_BUCKET_NAME)) +rest_api: RestApi = template.add_resource(RestApi(REST_API_NAME, Name=REST_API_NAME)) + +api_lambda_execute_statements = [ + Statement( + Action=[ + Action("logs", "*"), + Action("cloudwatch", "*"), + Action("cloudformation", "DescribeStacks"), + Action("cloudformation", "DescribeStackEvents"), + Action("cloudformation", "DescribeStackResource"), + Action("cloudformation", "DescribeStackResources"), + Action("cloudformation", "GetTemplate"), + Action("cloudformation", "List*"), + ], + Effect="Allow", + Resource=["*"] + ), + Statement( + Action=[ + Action("sns", "Publish") + ], + Effect="Allow", + Resource=[Ref(FANOUT_TOPIC_NAME)] + ), +] + + +api_lambda_execute_role: iam.Role = template.add_resource( + iam.Role( + API_LAMBDA_NAME + "ExecuteRole", + AssumeRolePolicyDocument=Policy( + Statement=[ + Statement( + Effect="Allow", + Action=[Action("sts", "AssumeRole")], + Principal=Principal("Service", ["lambda.amazonaws.com", "apigateway.amazonaws.com"]) + ) + ] + ), + Policies=[ + iam.Policy( + PolicyName=API_LAMBDA_NAME + "ExecutePolicy", + PolicyDocument=Policy(Statement=api_lambda_execute_statements) + ) + ] + ) +) + +api_lambda_code: Code = Code( + S3Bucket=Ref(shared_config_bucket), + S3Key=Join("", [API_LAMBDA_KEBAB_NAME, "/code/", API_LAMBDA_KEBAB_NAME, "-", "1", ".zip"]) +) + +api_lambda: Function = template.add_resource( + Function( + API_LAMBDA_NAME + "Function", + Code=api_lambda_code, + Description=API_LAMBDA_NAME + " Function", + Handler="request_handler.event_handler", + Role=GetAtt(API_LAMBDA_NAME + "ExecuteRole", "Arn"), + Runtime="python3.9", + Timeout=300, + MemorySize=1024 + ) +) + +api_lambda_invoke_permission = template.add_resource(awslambda.Permission( + "APILambdaPermission", + Action="lambda:InvokeFunction", + FunctionName=Ref(api_lambda), + Principal="apigateway.amazonaws.com", + SourceArn=Join("", ["arn:aws:execute-api:", Ref("AWS::Region"), ":", Ref("AWS::AccountId"), ":", Ref(rest_api), "/*/*/*"]) +)) + +rest_api_resource: Resource = template.add_resource( + Resource( + API_LAMBDA_NAME + "Resource", + RestApiId=Ref(rest_api), + PathPart="tweet", + ParentId=GetAtt(REST_API_NAME, "RootResourceId") + ) +) + +api_lambda_method: Method = template.add_resource( + Method( + API_LAMBDA_NAME + "Method", + DependsOn=API_LAMBDA_NAME + "Function", + RestApiId=Ref(rest_api), + AuthorizationType="NONE", + ResourceId=Ref(rest_api_resource), + HttpMethod="POST", + Integration=Integration( + Type="AWS", + IntegrationHttpMethod="POST", + IntegrationResponses=[IntegrationResponse(StatusCode="200")], + Uri=Join( + "", + [ + "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/", + GetAtt(api_lambda, "Arn"), + "/invocations" + ] + ) + ), + MethodResponses=[MethodResponse("SuccessResponse", StatusCode="200")] + ) +) + +rest_api_deployment: Deployment = template.add_resource( + Deployment( + REST_API_STAGE_NAME + "Deployment", + DependsOn=API_LAMBDA_NAME + "Method", + RestApiId=Ref(rest_api) + ) +) + +rest_api_stage: Stage = template.add_resource( + Stage( + REST_API_STAGE_NAME + "Stage", + StageName=REST_API_STAGE_NAME, + RestApiId=Ref(rest_api), + DeploymentId=Ref(rest_api_deployment) + ) +) + +template.add_output( + [ + Output( + "RestApiEndpoint", + Value=Join( + "", + [ + "https://", + Ref(rest_api), + ".execute-api.eu-west-1.amazonaws.com/", + REST_API_STAGE_NAME, + ], + ), + Description="Endpoint for this stage of the api", + ) + ] +) + s3_queue: Queue = templateUtil.create_queue(S3_QUEUE_NAME) -tweeter_queue: Queue = templateUtil.create_queue(TWEETER_QUEUE_NAME) +twitter_queue: Queue = templateUtil.create_queue(TWITTER_QUEUE_NAME) dynamo_db_queue: Queue = templateUtil.create_queue(DYNAMO_DB_QUEUE_NAME) +fanout_topic: Topic = template.add_resource(Topic( + FANOUT_TOPIC_NAME, + Subscription=[ + Subscription( + Protocol="sqs", + Endpoint=GetAtt(s3_queue, "Arn") + ), + Subscription( + Protocol="sqs", + Endpoint=GetAtt(twitter_queue, "Arn") + ), + Subscription( + Protocol="sqs", + Endpoint=GetAtt(dynamo_db_queue, "Arn") + ) + ] +)) + +templateUtil.add_queue_policy_for_write_to_topic( + s3_queue, + "S3QueuePolicy", + "AllowS3QueueToWriteToFanoutTopic", + fanout_topic +) + +templateUtil.add_queue_policy_for_write_to_topic( + twitter_queue, + "TwitterQueuePolicy", + "AllowTwitterQueueToWriteToFanoutTopic", + fanout_topic +) + +templateUtil.add_queue_policy_for_write_to_topic( + dynamo_db_queue, + "DynamoDbQueuePolicy", + "AllowDynamoDbQueueToWriteToFanoutTopic", + fanout_topic +) + print(template.to_json())