Skip to main content
""" AWS CloudWatch Alarms to Slack Notification Lambda Function Sends formatted CloudWatch alarm notifications to Slack """
import json
import os
import urllib3
from datetime import datetime

http = urllib3.PoolManager()

def get_alarm_color(state):
    """Return color based on alarm state"""
    colors = {
        'ALARM': '#FF0000',      # Red
        'OK': '#36A64F',         # Green
        'INSUFFICIENT_DATA': '#FFA500'  # Orange
    }
    return colors.get(state, '#808080')  # Gray for unknown

def get_alarm_emoji(state):
    """Return emoji based on alarm state"""
    emojis = {
        'ALARM': '🔴',
        'OK': '✅',
        'INSUFFICIENT_DATA': '⚠️'
    }
    return emojis.get(state, '❓')

def format_metric_name(metric_name):
    """Format metric names to be more readable"""
    readable_names = {
        'CPUUtilization': 'CPU Utilization',
        'FreeableMemory': 'Available Memory',
        'FreeStorageSpace': 'Free Storage Space',
        'DatabaseConnections': 'Database Connections',
        'TargetResponseTime': 'Target Response Time',
        'HTTPCode_Target_4XX_Count': 'HTTP 4XX Errors',
        'HTTPCode_Target_5XX_Count': 'HTTP 5XX Errors',
        'RejectedConnectionCount': 'Rejected Connections',
        'HealthyHostCount': 'Healthy Host Count'
    }
    return readable_names.get(metric_name, metric_name)

def parse_alarm_name(alarm_name):
    """Parse alarm name to extract resource type and name"""
    # Format: CW-RDS-<db-name>-<metric> or CW-ALB-<alb-name>-<metric> or CW-TG-<tg-name>-<metric>
    parts = alarm_name.split('-')
    if len(parts) >= 3:
        resource_type = parts[1]  # RDS, ALB, or TG
        return resource_type
    return 'Unknown'

def lambda_handler(event, context):
    """Main Lambda handler function"""
    
    # Get Slack webhook URL from environment variable
    slack_webhook_url = os.environ.get('SLACK_WEBHOOK_URL')
    
    if not slack_webhook_url:
        print("ERROR: SLACK_WEBHOOK_URL environment variable not set")
        return {
            'statusCode': 500,
            'body': json.dumps('SLACK_WEBHOOK_URL not configured')
        }
    
    # Parse SNS message
    try:
        sns_message = json.loads(event['Records'][0]['Sns']['Message'])
        print(f"Received SNS message: {json.dumps(sns_message, indent=2)}")
    except Exception as e:
        print(f"ERROR parsing SNS message: {str(e)}")
        return {
            'statusCode': 400,
            'body': json.dumps(f'Error parsing SNS message: {str(e)}')
        }
    
    # Extract alarm details
    alarm_name = sns_message.get('AlarmName', 'Unknown Alarm')
    new_state = sns_message.get('NewStateValue', 'UNKNOWN')
    old_state = sns_message.get('OldStateValue', 'UNKNOWN')
    reason = sns_message.get('NewStateReason', 'No reason provided')
    region = sns_message.get('Region', 'ap-south-1')
    timestamp = sns_message.get('StateChangeTime', datetime.utcnow().isoformat())
    
    # Get trigger details
    trigger = sns_message.get('Trigger', {})
    metric_name = trigger.get('MetricName', 'Unknown')
    namespace = trigger.get('Namespace', 'Unknown')
    threshold = trigger.get('Threshold', 'N/A')
    comparison_operator = trigger.get('ComparisonOperator', 'N/A')
    
    # Parse dimensions to get resource name
    dimensions = trigger.get('Dimensions', [])
    resource_name = 'Unknown'
    for dim in dimensions:
        if dim.get('name') in ['DBInstanceIdentifier', 'LoadBalancer', 'TargetGroup']:
            resource_name = dim.get('value', 'Unknown')
            break
    
    # Get resource type from alarm name
    resource_type = parse_alarm_name(alarm_name)
    
    # Format metric name
    readable_metric = format_metric_name(metric_name)
    
    # Get color and emoji
    color = get_alarm_color(new_state)
    emoji = get_alarm_emoji(new_state)
    
    # Build resource icon
    resource_icons = {
        'RDS': '🗄️',
        'ALB': '⚖️',
        'TG': '🎯'
    }
    resource_icon = resource_icons.get(resource_type, '📊')
    
    # Create Slack message
    slack_message = {
        "attachments": [
            {
                "color": color,
                "blocks": [
                    {
                        "type": "header",
                        "text": {
                            "type": "plain_text",
                            "text": f"{emoji} CloudWatch Alarm: {new_state}",
                            "emoji": True
                        }
                    },
                    {
                        "type": "section",
                        "fields": [
                            {
                                "type": "mrkdwn",
                                "text": f"*{resource_icon} Resource Type:*\n{resource_type}"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*📛 Alarm Name:*\n`{alarm_name}`"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*📊 Metric:*\n{readable_metric}"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*🎯 Resource:*\n`{resource_name}`"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*State Transition:*\n`{old_state}` → `{new_state}`"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*🚨 Threshold:*\n`{comparison_operator} {threshold}`"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*🌍 Region:*\n{region}"
                            },
                            {
                                "type": "mrkdwn",
                                "text": f"*🕐 Time:*\n{timestamp}"
                            }
                        ]
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"*📝 Reason:*\n```{reason}```"
                        }
                    },
                    {
                        "type": "divider"
                    },
                    {
                        "type": "context",
                        "elements": [
                            {
                                "type": "mrkdwn",
                                "text": f"🔗 <https://console.aws.amazon.com/cloudwatch/home?region={region}#alarmsV2:alarm/{alarm_name}|Click to View in CloudWatch Console>"
                            }
                        ]
                    }
                ]
            }
        ]
    }
    
    # Send to Slack
    try:
        encoded_data = json.dumps(slack_message).encode('utf-8')
        response = http.request(
            'POST',
            slack_webhook_url,
            body=encoded_data,
            headers={'Content-Type': 'application/json'}
        )
        
        print(f"Slack response status: {response.status}")
        print(f"Slack response data: {response.data.decode('utf-8')}")
        
        if response.status == 200:
            return {
                'statusCode': 200,
                'body': json.dumps('Successfully sent alarm to Slack')
            }
        else:
            return {
                'statusCode': response.status,
                'body': json.dumps(f'Failed to send to Slack: {response.data.decode("utf-8")}')
            }
            
    except Exception as e:
        print(f"ERROR sending to Slack: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f'Error sending to Slack: {str(e)}')
        }