# Copyright (c) 2016-2022 Cristian Măgherușan-Stanciu
# Licensed under the Open Software License version 3.0

  AWSTemplateFormatVersion: "2010-09-09"
  Description: >
    "Implements support for triggering the main AutoSpotting Lambda function on
    regional events such as instance launches or imminent spot terminations that
    can only be detected from within a given region"
  Parameters:
    AutoSpottingLambdaARN:
      Description: "The ARN of the main AutoSpotting Lambda function"
      Type: "String"
    LambdaRegionalExecutionRoleARN:
      Description: "Execution Role ARN for Regional Lambda"
      Type: "String"
  Resources:
    EventHandler:
      Type: AWS::Lambda::Function
      Properties:
        Description: >
          "Regional Lambda function that invokes the main AutoSpotting Lambda
          function on events such as instance launches or imminent spot instance
          terminations"
        Handler: "index.handler"
        Runtime: "python3.8"
        Timeout: 300
        Environment:
          Variables:
            AUTOSPOTTING_LAMBDA_ARN:
              Ref: "AutoSpottingLambdaARN"
        Role:
          Ref: "LambdaRegionalExecutionRoleARN"
        Code:
          ZipFile: |
            from base64 import b64decode
            from boto3 import client
            from json import dumps
            from os import environ
            from sys import exc_info
            from traceback import print_exc

            lambda_arn = (environ['AUTOSPOTTING_LAMBDA_ARN'])

            def parse_region_from_arn(arn):
                return arn.split(':')[3]

            def handler(event, context):
                print("Running Lambda function", lambda_arn)
                try:
                    svc = client('lambda', region_name=parse_region_from_arn(lambda_arn))
                    response = svc.invoke(
                        FunctionName=lambda_arn,
                        LogType='Tail',
                        Payload=dumps(event),
                    )
                    print("Invoked funcion log tail:\n", b64decode(response["LogResult"]).decode('utf-8'))
                except:
                    print_exc()
                    print("Unexpected error:", exc_info()[0])
    SpotTerminationLambdaPermission:
      Type: "AWS::Lambda::Permission"
      Properties:
        Action: "lambda:InvokeFunction"
        FunctionName:
          Ref: "EventHandler"
        Principal: "events.amazonaws.com"
        SourceArn:
          Fn::GetAtt:
            - "SpotTerminationEventRule"
            - "Arn"
    SpotTerminationEventRule:
      Type: "AWS::Events::Rule"
      Properties:
        Description: >
          "This rule is triggered 2 minutes before AWS terminates a spot
          instance or when AWS send a Rebalance Recommendation"
        EventPattern:
          detail-type:
            - "EC2 Spot Instance Interruption Warning"
            - "EC2 Instance Rebalance Recommendation"
          source:
            - "aws.ec2"
        State: "ENABLED"
        Targets:
          -
            Id: "SpotTerminationEventGenerator"
            Arn:
              Fn::GetAtt:
                - "EventHandler"
                - "Arn"
    InstanceRunningLambdaPermission:
      Type: "AWS::Lambda::Permission"
      Properties:
        Action: "lambda:InvokeFunction"
        FunctionName:
          Ref: "EventHandler"
        Principal: "events.amazonaws.com"
        SourceArn:
          Fn::GetAtt:
            - "InstanceRunningEventRule"
            - "Arn"
    InstanceRunningEventRule:
      Type: "AWS::Events::Rule"
      Properties:
        Description: >
          "This rule is triggered after EC2 launched a new instance"
        EventPattern:
          detail-type:
            - "EC2 Instance State-change Notification"
          source:
            - "aws.ec2"
          detail:
            state:
              - "running"
        State: "ENABLED"
        Targets:
          -
            Id: "InstanceRunningEventGenerator"
            Arn:
              Fn::GetAtt:
                - "EventHandler"
                - "Arn"
    LifecycleHookLambdaPermission:
      Type: "AWS::Lambda::Permission"
      Properties:
        Action: "lambda:InvokeFunction"
        FunctionName:
          Ref: "EventHandler"
        Principal: "events.amazonaws.com"
        SourceArn:
          Fn::GetAtt:
            - "LifecycleHookEventRule"
            - "Arn"
    LifecycleHookEventRule:
      Type: "AWS::Events::Rule"
      Properties:
        Description: >
          "This rule is triggered after we failed to complete a lifecycle hook"
        EventPattern:
          detail-type:
            - "AWS API Call via CloudTrail"
          source:
            - "aws.autoscaling"
          detail:
            eventName:
              - "CompleteLifecycleAction"
            errorCode:
              - "ValidationException"
            requestParameters:
              lifecycleActionResult:
                - "CONTINUE"
        State: "ENABLED"
        Targets:
          -
            Id: "LifecycleHookEventGenerator"
            Arn:
              Fn::GetAtt:
                - "EventHandler"
                - "Arn"
