personal-lifecycle-tracker / on-call-engineer / cloud-simulator / lambda / dispatcher.py
dispatcher.py
Raw
#!/usr/bin/env python3
"""
AWS Lambda Function Simulation: On-Call Incident Dispatcher.

In AWS Production:
- Triggered by Amazon SNS topic subscription or EventBridge rule.
- Parses the alert payload.
- Invokes AWS ECS / Fargate run_task API to launch an ephemeral container.
"""

import json
import logging
import os
import subprocess
import sys
import time

logging.basicConfig(level=logging.INFO, format="%(asctime)s [LAMBDA] %(message)s")
logger = logging.getLogger("lambda-dispatcher")


def lambda_handler(event, context=None):
    logger.info("Lambda invocation started with event payload.")
    
    # 1. Parse Event from SNS or direct EventBridge invocation
    alert_data = {}
    if "Records" in event and len(event["Records"]) > 0:
        sns_record = event["Records"][0].get("Sns", {})
        raw_msg = sns_record.get("Message", "{}")
        if isinstance(raw_msg, str):
            try:
                alert_data = json.loads(raw_msg)
            except json.JSONDecodeError:
                alert_data = {"summary": raw_msg}
        else:
            alert_data = raw_msg
    elif "detail" in event:
        alert_data = event["detail"]
    else:
        alert_data = event

    alert_name = alert_data.get("alert_name", alert_data.get("alertname", "HighHttpErrorRate"))
    severity = alert_data.get("severity", "critical")
    service_name = alert_data.get("service_name", alert_data.get("service", "api-backend"))
    
    ts = int(time.time())
    incident_id = f"INC-{ts}-{alert_name}"
    logger.info(f"Processed alert: '{alert_name}' [{severity}] for service '{service_name}' -> Incident ID: {incident_id}")

    # 2. Mimic ECS run_task API
    # In cloud: ecs_client.run_task(cluster='on-call-cluster', taskDefinition='on-call-agent:1', launchType='FARGATE', overrides={...})
    # In local simulation: runs an ephemeral Docker container using amazon/aws-cli:latest or docker runner
    s3_endpoint = os.environ.get("S3_ENDPOINT_URL", "http://personal-lifecycle-cloud-emulator:4566")
    s3_bucket = os.environ.get("S3_BUCKET", "lifecycle-on-call-incidents")
    
    script_dir = os.path.dirname(os.path.abspath(__file__))
    sim_dir = os.path.dirname(script_dir)
    fargate_script = os.path.join(sim_dir, "fargate-task", "task-runner.sh")
    
    logger.info("Dispatching AWS ECS / Fargate ephemeral container task...")
    
    docker_cmd = [
        "docker", "run", "--rm",
        "--name", f"fargate-task-{incident_id.lower()}",
        "--network", "personal-lifecycle-observability-net",
        "-e", f"INCIDENT_ID={incident_id}",
        "-e", f"ALERT_NAME={alert_name}",
        "-e", f"SEVERITY={severity}",
        "-e", f"SERVICE_NAME={service_name}",
        "-e", f"S3_ENDPOINT_URL={s3_endpoint}",
        "-e", f"S3_BUCKET={s3_bucket}",
        "-e", "AWS_DEFAULT_REGION=us-east-1",
        "-e", "AWS_ACCESS_KEY_ID=test",
        "-e", "AWS_SECRET_ACCESS_KEY=test",
        "-v", f"{fargate_script}:/task-runner.sh:ro",
        "--entrypoint", "/task-runner.sh",
        "amazon/aws-cli:latest"
    ]
    
    proc = subprocess.run(docker_cmd, capture_output=True, text=True)
    if proc.stdout:
        for line in proc.stdout.strip().split("\n"):
            logger.info(f"[FARGATE-STDOUT] {line}")
    if proc.stderr:
        for line in proc.stderr.strip().split("\n"):
            logger.warning(f"[FARGATE-STDERR] {line}")

    if proc.returncode != 0:
        logger.error(f"Fargate task exited with non-zero code {proc.returncode}")
        return {
            "statusCode": 500,
            "incidentId": incident_id,
            "error": proc.stderr
        }

    task_arn = f"arn:aws:ecs:us-east-1:000000000000:task/{incident_id}"
    logger.info(f"Fargate container task completed and terminated immediately. Task ARN: {task_arn}")

    return {
        "statusCode": 200,
        "incidentId": incident_id,
        "taskArn": task_arn,
        "s3AuditLocation": f"s3://{s3_bucket}/{incident_id}/"
    }


if __name__ == "__main__":
    sample_sns_event = {
        "Records": [
            {
                "Sns": {
                    "Message": json.dumps({
                        "alert_name": "HighHttpErrorRate",
                        "severity": "critical",
                        "service_name": "api-backend",
                        "summary": "Elevated HTTP 5xx error rate",
                        "observed_value": 100.0,
                        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
                    })
                }
            }
        ]
    }
    result = lambda_handler(sample_sns_event)
    print(json.dumps(result, indent=2))