--- AWSTemplateFormatVersion: 2010-09-09 Description: FIS ApiGateway Parameters: apiGatewayName: Type: String Default: fis-workshop apiGatewayStageName: Type: String AllowedPattern: "[a-z0-9]+" Default: v1 LambdaFunctionName: Type: String AllowedPattern: "[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+" Default: fis-workshop-api-errors-unavailable ErrorQueue: Type: String AllowedPattern: "[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+" Default: fis-workshop-api-queue-unavailable LatestAmiId: Type: 'AWS::SSM::Parameter::Value' Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' SubnetId: Description: Subnet in which to instantiate VM - needed if no Default VPC was configured # Can't use Type: AWS::EC2::Subnet::Id because it checks the default before the condition Type: String AllowedPattern: "subnet-[a-z0-9]+" Default: "subnet-000000" Conditions: UseDefaultVpc: !Equals - !Ref SubnetId - "subnet-000000" Resources: Instance: Type: 'AWS::EC2::Instance' Properties: ImageId: !Ref LatestAmiId InstanceType: t3.micro Tags: - Key: Name Value: FisAPIFailures SubnetId: !If [UseDefaultVpc, !Ref "AWS::NoValue", !Ref SubnetId] apiGateway: Type: AWS::ApiGateway::RestApi Properties: Description: FIS-Workshop-API-Failures EndpointConfiguration: Types: - REGIONAL Name: !Ref apiGatewayName async: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref apiGateway ParentId: !GetAtt - apiGateway - RootResourceId PathPart: terminate apiGatewayGetMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt apiFailureFunction.Arn ResourceId: !GetAtt apiGateway.RootResourceId RestApiId: !Ref apiGateway apiGatewayTerminateMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt apiFailureFunction.Arn ResourceId: !Ref async RestApiId: !Ref apiGateway apiGatewayPostMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: POST Integration: Credentials: !GetAtt apigwIAMRole.Arn IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 PassthroughBehavior: NEVER RequestParameters: integration.request.header.Content-Type: '''application/x-www-form-urlencoded''' RequestTemplates: application/json: Action=SendMessage&MessageBody="example" Type: AWS Uri: !Join - '' - - 'arn:aws:apigateway:' - !Ref 'AWS::Region' - :sqs:path/ - !Ref 'AWS::AccountId' - / - !Ref 'ErrorQueue' MethodResponses: - ResponseModels: application/json: Empty StatusCode: '200' ResourceId: !Ref async RestApiId: !Ref apiGateway apiGatewayDeploymentAsync: Type: AWS::ApiGateway::Deployment DependsOn: - apiGatewayGetMethod - apiGatewayPostMethod Properties: RestApiId: !Ref apiGateway StageName: !Ref apiGatewayStageName apigwIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - apigateway.amazonaws.com Policies: - PolicyDocument: Version: 2012-10-17 Statement: - Action: sqs:SendMessage Effect: Allow Resource: !GetAtt 'errorQueue.Arn' - Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: '*' PolicyName: fisWorkshopApiErrors apiFailureFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 from os import environ as env ec2 = boto3.client('ec2') instance_id = env.get('INSTANCE') if not instance_id: raise Exception("Workshop Instance Not Found") def describe_instances(): resp = ec2.describe_instances( Filters=[{ 'Name': 'instance-state-name', 'Values': ['running'] }] ) instance_ids = [ i['Instances'][0].get('InstanceId') for i in resp['Reservations']] return { "InstanceIds": instance_ids, "RetryAttempts": resp['ResponseMetadata'].get('RetryAttempts') } def terminate_instances(i_id): ec2.terminate_instances( InstanceIds=[i_id] ) def handler(event,context): if event.get('httpMethod') == 'GET' and event.get('path') == "/": return { "body": f"{describe_instances()}" + "\n", "headers": { "Content-Type": "text/plain" }, 'statusCode': 200 } elif event.get('httpMethod') == 'GET' and event.get('path') == "/terminate": describe_instances() return { "body": f"Deleting Instance: {instance_id}" + "\n", "headers": { "Content-Type": "text/plain" }, 'statusCode': 200 } elif event.get('Records'): for record in event.get('Records'): if record.get('eventSource') == 'aws:sqs':\ terminate_instances(instance_id) Description: FIS Workshop FunctionName: !Ref LambdaFunctionName Handler: index.handler MemorySize: 128 Timeout: 30 Role: !GetAtt lambdaIAMRole.Arn Runtime: python3.8 Environment: Variables: INSTANCE: !Ref Instance lambdaThrottleInvoke: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt apiFailureFunction.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/${apiGatewayStageName}/*/* LambdaFunctionEventSourceMapping: Type: AWS::Lambda::EventSourceMapping Properties: BatchSize: 10 Enabled: true EventSourceArn: !GetAtt errorQueue.Arn FunctionName: !GetAtt apiFailureFunction.Arn errorQueue: Type: AWS::SQS::Queue Properties: DelaySeconds: 0 MaximumMessageSize: 262144 MessageRetentionPeriod: 1209600 QueueName: !Ref ErrorQueue ReceiveMessageWaitTimeSeconds: 0 VisibilityTimeout: 30 lambdaIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - lambda.amazonaws.com Policies: - PolicyDocument: Version: 2012-10-17 Statement: - Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:* PolicyName: lambda - PolicyDocument: Version: 2012-10-17 Statement: - Action: - ec2:DescribeInstances Effect: Allow Resource: "*" PolicyName: DescribeInstances - PolicyDocument: Version: 2012-10-17 Statement: - Action: - ec2:TerminateInstances Effect: Allow Resource: !Sub arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${Instance} PolicyName: TerminateInstances - PolicyDocument: Version: 2012-10-17 Statement: - Action: - sqs:SendMessage - sqs:GetQueueUrl - sqs:ReceiveMessage - sqs:GetQueueAttributes - sqs:ChangeMessageVisibility - sqs:DeleteMessage Effect: Allow Resource: !GetAtt errorQueue.Arn PolicyName: SqsMessages lambdaLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${LambdaFunctionName} RetentionInDays: 90 Outputs: apiGatewayInvokeURL: Value: !Sub https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName} iamRole: Value: !Ref lambdaIAMRole instanceId: Value: !Ref Instance