{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "This CloudFormation Template deploys the AWS resouces needed to ingest alarms to Incident Detection and Response from an SNS topic. Prerequisite AWS resources include an already created SNS topic. The AWS resources deployed are below. - Custom Event Bus - Lambda subscription to existing SNS topic - Lambda execution role - Lambda transform function - Lambda resource-based policy permission",
    "Parameters": {
        "APMNameParameter": {
            "Type": "String",
            "Description": "Enter the name of the Application Performance Monitoring (APM) tool without any spaces. e.g. Prometheus, Grafana.",
            "AllowedPattern": "^[a-zA-Z0-9]*$",
            "ConstraintDescription": "The pattern allows only lowercase and uppercase alphabetical characters and numerals (^[a-zA-Z0-9]*$)."
        },
        "TriggerSNSParameter": {
            "Type": "String",
            "Description": "Enter the SNS topic of your APM. Example arn:aws:sns:eu-west-1:012345678912:grafana-sns"
        }
    },
    "Resources": {
        "CustomEventBus": {
            "Type": "AWS::Events::EventBus",
            "Properties": {
                "Name": {
                    "Fn::Sub": "${APMNameParameter}-AWSIncidentDetectionResponse-EventBus"
                }
            }
        },
        "TransformLambdaExecutionRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "lambda.amazonaws.com"
                                ]
                            },
                            "Action": [
                                "sts:AssumeRole"
                            ]
                        }
                    ]
                },
                "Description": "IAM Role for Transform Lambda function to create logs and put events for customer Event Bus",
                "Policies": [
                    {
                        "PolicyName": {
                            "Fn::Sub": "IDR-TransformLambdaExecutionRolePolicy"
                        },
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": "logs:CreateLogGroup",
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "logs:CreateLogStream",
                                        "logs:PutLogEvents"
                                    ],
                                    "Resource": {
                                        "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${APMNameParameter}-AWSIncidentDetectionResponse-Lambda-Transform:*"
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": "events:PutEvents",
                                    "Resource": {
                                        "Fn::GetAtt": "CustomEventBus.Arn"
                                    }
                                }
                            ]
                        }
                    }
                ],
                "RoleName": {
                    "Fn::Sub": "IDR-TransformLambdaExecutionRole-${AWS::Region}"
                }
            },
            "DependsOn": [
                "CustomEventBus"
            ]
        },
        "TransformLambdaFunction": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "FunctionName": {
                    "Fn::Sub": "${APMNameParameter}-AWSIncidentDetectionResponse-Lambda-Transform"
                },
                "Description": "This function adds the necessary key:value pairs to the APM payload so that events can be ingested by AWS Incident Detection and Response.",
                "Runtime": "python3.10",
                "Timeout": 8,
                "Role": {
                    "Fn::GetAtt": "TransformLambdaExecutionRole.Arn"
                },
                "Environment": {
                    "Variables": {
                        "EnvEventBusName": {
                            "Ref": "CustomEventBus"
                        }
                    }
                },
                "Handler": "index.lambda_handler",
                "Code": {
                    "ZipFile": "import logging\nimport json\nimport boto3\nimport os\n\n\nlogger = logging.getLogger()\nlogger.setLevel(logging.INFO)\nEventBusName = os.environ['EnvEventBusName']\n\n\ndef lambda_handler(event, context):\n\tlogger.info(event)\n\t# Set the event[\"detail\"][\"incident-detection-response-identifier\"] value to the name of your alert that is coming from your APM.\n\n\tfor record in event [\"Records\"]: \n\t\ttry:\n\t\t\tmessage = record[\"Sns\"][\"Message\"]     \n\t\t\tif isinstance(message, str):\n\t\t\t\talert_data = json.loads(message)\n\t\t\telse:\n\t\t\t\talert_data = message\n\n\t\t\tif \"alerts\" not in alert_data:\n\t\t\t\tlogger.error(\"No 'alerts' field found in the message\")\n\t\t\t\tcontinue   \n\n\t\t\tfor alert in alert_data [\"alerts\"]:\n\t\t\t\ttry:\n\t\t\t\t# Check if required fields exist\n\t\t\t\t\tif \"labels\" not in alert:\n\t\t\t\t\t\tlogger.error(\"No 'labels' field found in alert\")\n\t\t\t\t\t\tcontinue\n\t\t\t\n\t\t\t\t\tif \"alertname\" not in alert[\"labels\"]:\n\t\t\t\t\t\tlogger.error(\"No 'alertname' field found in labels\")\n\t\t\t\t\t\tcontinue\n\n\t\t\t\t\tidentifier = alert[\"labels\"][\"alertname\"]\n\n\t\t\t\t\tdetail = {\n\t\t\t\t\t\t\"incident-detection-response-identifier\": identifier,\n\t\t\t\t\t\t\"original_message\": message\n\t\t\t\t\t\t\t}\n\t\t\t\t\tclient = boto3.client('events')\n\t\t\t\t\tresponse = client.put_events(\n\t\t\t\t\t\tEntries=[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t'Detail': json.dumps(detail),\n\t\t\t\t\t\t\t\t'DetailType': 'ams.monitoring/generic-apm', # Do not modify. This DetailType value is required.\n\t\t\t\t\t\t\t\t'Source': 'GenericAPMEvent', # Do not modify. This Source value is required.\n\t\t\t\t\t\t\t\t'EventBusName': EventBusName # Do not modify. This variable is set at the top of this code as a global variable. Change the variable value for your eventbus name at the top of this code.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t)\n\n\t\t\t\t\t#logger.info(f\"Sending payload to EventBridge: {json.dumps(detail, indent=2)}\")\n\t\t\t\t\tlogger.info(\"EventBridge event payload: %s\", json.dumps({\n\t\t\t\t\t\t\t\t'Detail': json.dumps(detail),\n\t\t\t\t\t\t\t\t'DetailType': 'ams.monitoring/generic-apm',\n\t\t\t\t\t\t\t\t'Source': 'GenericAPMEvent',\n\t\t\t\t\t\t\t\t'EventBusName': EventBusName\n\t\t\t\t\t\t\t\t}, indent=2))\n\n\t\t\t\texcept KeyError as e:\n\t\t\t\t\tlogger.error(f\"Key error while processing alert: {e}\")\n\t\t\t\t\tcontinue\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlogger.error(f\"Unexpected error while processing alert: {e}\")\n\t\t\t\t\tcontinue\n\n\t\texcept json.JSONDecodeError as e:\n\t\t\tlogger.error(f\"Failed to parse SNS message as JSON: {e}\")\n\t\t\tcontinue\n\t\texcept KeyError as e:\n\t\t\tlogger.error(f\"Missing required field in record: {e}\")\n\t\t\tcontinue\n\t\texcept Exception as e:\n\t\t\tlogger.error(f\"Unexpected error processing record: {e}\")\n\t\t\tcontinue"
                }
            },
            "DependsOn": [
                "TransformLambdaExecutionRole"
            ]
        },
        "SNSSubscription": {
            "Type": "AWS::SNS::Subscription",
            "Properties": {
                "Protocol": "lambda",
                "Endpoint": {
                    "Fn::GetAtt": "TransformLambdaFunction.Arn"
                },
                "TopicArn": {
                    "Ref": "TriggerSNSParameter"
                }
            },
            "DependsOn": [
                "TransformLambdaFunction"
            ]
        },
        "TransformLambdaPermission": {
            "Type": "AWS::Lambda::Permission",
            "Properties": {
                "Action": "lambda:InvokeFunction",
                "FunctionName": {
                    "Ref": "TransformLambdaFunction"
                },
                "Principal": "sns.amazonaws.com",
                "SourceArn": {
                    "Ref": "TriggerSNSParameter"
                },
                "SourceAccount": {
                    "Ref": "AWS::AccountId"
                }
            },
            "DependsOn": [
                "TransformLambdaFunction",
                "SNSSubscription"
            ]
        }
    }
}