from datetime import datetime,timezone
import sys
import boto3
import json


def pipeline_event(event, context):

    state = get_final_state(event)
    if state is None:
        return

    event_time = datetime.strptime(event['time'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc)
    metric_data = []

    if event['detail-type'] == "CodePipeline Pipeline Execution State Change":
        # Write green/red time based on last execution state
        prior_execution = get_prior_execution(event['detail']['pipeline'], event['detail']['execution-id'])
        if prior_execution is not None:
            last_execution_state = prior_execution['status']
            seconds_since_last_execution = (event_time - prior_execution['lastUpdateTime']).total_seconds()
            if last_execution_state == "Succeeded":
                append_metric(metric_data, "GreenTime", event, seconds=seconds_since_last_execution)
            elif last_execution_state == "Failed":
                append_metric(metric_data, "RedTime", event, seconds=seconds_since_last_execution)

        if state == "SUCCEEDED":
            append_metric(metric_data, "SuccessCount", event, count=1)

            current_execution = get_execution(event['detail']['pipeline'], event['detail']['execution-id'])
            if current_execution is not None:
                duration = (event_time - current_execution['startTime']).total_seconds()
                append_metric(metric_data, "LeadTime", event, seconds=duration)
        elif state == "FAILED":
            append_metric(metric_data, "FailureCount", event, count=1)

    elif event['detail-type'] == "CodePipeline Stage Execution State Change":
        if state == "SUCCEEDED":
            append_metric(metric_data, "SuccessCount", event, count=1)
            #append_metric(metric_data, "LeadTime", event, seconds=duration)
        elif state == "FAILED":
            append_metric(metric_data, "FailureCount", event, count=1)

    elif event['detail-type'] == "CodePipeline Action Execution State Change":
        if state == "SUCCEEDED":
            append_metric(metric_data, "SuccessCount", event, count=1)

        elif state == "FAILED":
            append_metric(metric_data, "FailureCount", event, count=1)

    if len(metric_data) > 0:
        client = boto3.client('cloudwatch')
        client.put_metric_data(
            Namespace='Pipeline',
            MetricData=metric_data
        )


# Return the state from the event iff it's one of SUCCEEDED or FAILED
def get_final_state(event):
    if 'detail' in event and 'state' in event['detail']:
        if any(event['detail']['state'] in s for s in ['SUCCEEDED', 'FAILED']):
            return event['detail']['state']
    return None


# Return the execution summary for a given execution id
def get_execution(pipeline_name, execution_id):
    client = boto3.client('codepipeline')
    response = client.list_pipeline_executions(pipelineName=pipeline_name)
    for e in response['pipelineExecutionSummaries']:
        if e['pipelineExecutionId'] == execution_id:
            return e

    return None


# Return the execution summary for the most prior final execution before a given execution id
def get_prior_execution(pipeline_name, execution_id):
    client = boto3.client('codepipeline')
    response = client.list_pipeline_executions(pipelineName=pipeline_name)
    found_current = False
    for e in response['pipelineExecutionSummaries']:
        if found_current and any(e['status'] in s for s in ['Succeeded', 'Failed']):
                return e
        elif e['pipelineExecutionId'] == execution_id:
            found_current = True

    return None


def append_metric(metric_list, metric_name, event, seconds=0, count=0):
    data = {
        'MetricName': metric_name,
        'Dimensions': [],
        'Timestamp': datetime.strptime(event['time'], '%Y-%m-%dT%H:%M:%SZ'),
    }

    resource_parts = []
    if 'pipeline' in event['detail']:
        data['Dimensions'].append({
            'Name': 'PipelineName',
            'Value': event['detail']['pipeline']
        })
        resource_parts.append(event['detail']['pipeline'])

    if 'stage' in event['detail']:
        data['Dimensions'].append({
            'Name': 'StageName',
            'Value': event['detail']['stage']
        })
        resource_parts.append(event['detail']['stage'])

    if 'action' in event['detail']:
        data['Dimensions'].append({
            'Name': 'ActionName',
            'Value': event['detail']['action']
        })
        resource_parts.append(event['detail']['action'])

    if seconds > 0:
        data['Value'] = seconds
        data['Unit'] = 'Seconds'
    elif count > 0:
        data['Value'] = count
        data['Unit'] = 'Count'
    else:
        # no metric to add
        return

    print("resource=%s metric=%s value=%s" % ('.'.join(resource_parts), metric_name, data['Value']))

    metric_list.append(data)


def generate_dashboard(client):
    paginator = client.get_paginator('list_metrics')

    response_iterator = paginator.paginate(
        Namespace='Pipeline'
    )

    pipeline_names = set()
    for response in response_iterator:
        for metric in response['Metrics']:
            for dim in metric['Dimensions']:
                if dim['Name'] == 'PipelineName':
                    pipeline_names.add(dim['Value'])


    widgets = []
    dashboard = {
        "widgets": widgets
    }
    y = 0
    for pipeline_name in sorted(pipeline_names):
        widgets.append({
            "type": "metric",
            "x": 0,
            "y": y,
            "width": 18,
            "height": 3,
            "properties": {
                "view": "singleValue",
                "metrics": [
                    [ "Pipeline", "SuccessCount", "PipelineName", pipeline_name, { "stat": "Sum", "period": 2592000 } ],
                    [ ".", "FailureCount", ".", ".", { "stat": "Sum", "period": 2592000 } ],
                    [ ".", "LeadTime", ".", ".", { "period": 2592000, "color": "#9467bd" } ],
                    [ ".", "RedTime", ".", ".", { "stat": "Sum", "period": 2592000, "yAxis": "left", "color": "#d62728" } ],
                    [ ".", "GreenTime", ".", ".", { "period": 2592000, "stat": "Sum", "color": "#2ca02c" } ]
                ],
                "region": "eu-central-1",
                "title": pipeline_name,
                "period": 300
            }
        })
        y += 3

    widgets.append({
        "type": "text",
        "x": 18,
        "y": 0,
        "width": 6,
        "height": 6,
        "properties": {
            "markdown": "\nAll metrics are calculated over the past 30 days\n\n* **SuccessCount** - count of all successful pipeline executions\n* **FailureCount** - count of all failed pipeline executions\n* **LeadTime** - average pipeline time for successful executions\n* **RedTime** - sum of all time spent with a red pipeline\n* **GreenTime** - sum of all time spent with a green pipeline\n"
        }
    })

    return dashboard


def dashboard_event(event, context):
    client = boto3.client('cloudwatch')
    dashboard = generate_dashboard(client)
    client.put_dashboard(
        DashboardName='Pipeline',
        DashboardBody=json.dumps(dashboard)
    )


if __name__ == '__main__':
    dashboard_event(None, None)

[((69, 13, 69, 41), 'boto3.client', 'boto3.client', ({(69, 26, 69, 40): '"""codepipeline"""'}, {}), "('codepipeline')", False, 'import boto3\n'), ((80, 13, 80, 41), 'boto3.client', 'boto3.client', ({(80, 26, 80, 40): '"""codepipeline"""'}, {}), "('codepipeline')", False, 'import boto3\n'), ((194, 13, 194, 39), 'boto3.client', 'boto3.client', ({(194, 26, 194, 38): '"""cloudwatch"""'}, {}), "('cloudwatch')", False, 'import boto3\n'), ((52, 17, 52, 43), 'boto3.client', 'boto3.client', ({(52, 30, 52, 42): '"""cloudwatch"""'}, {}), "('cloudwatch')", False, 'import boto3\n'), ((96, 21, 96, 75), 'datetime.datetime.strptime', 'datetime.strptime', ({(96, 39, 96, 52): "event['time']", (96, 54, 96, 74): '"""%Y-%m-%dT%H:%M:%SZ"""'}, {}), "(event['time'], '%Y-%m-%dT%H:%M:%SZ')", False, 'from datetime import datetime, timezone\n'), ((13, 17, 13, 71), 'datetime.datetime.strptime', 'datetime.strptime', ({(13, 35, 13, 48): "event['time']", (13, 50, 13, 70): '"""%Y-%m-%dT%H:%M:%SZ"""'}, {}), "(event['time'], '%Y-%m-%dT%H:%M:%SZ')", False, 'from datetime import datetime, timezone\n'), ((198, 22, 198, 43), 'json.dumps', 'json.dumps', ({(198, 33, 198, 42): 'dashboard'}, {}), '(dashboard)', False, 'import json\n')]