CodePipeline Status

Monitor the status of your CodePipeline

Image preview of CodePipeline Status plugin.

pipeline-status.1m.py

Edit
Open on GitHub
#!/usr/bin/env PYTHONIOENCODING=UTF-8 /usr/bin/python3
# -*- coding: UTF-8 -*-

# <xbar.title>CodePipeline Status</xbar.title>
# <xbar.version>v1.0</xbar.version>
# <xbar.author>Sebastian Kruschwitz</xbar.author>
# <xbar.author.github>sebk</xbar.author.github>
# <xbar.desc>Monitor the status of your CodePipeline</xbar.desc>
# <xbar.dependencies>python,boto3</xbar.dependencies>
# <xbar.image>https://raw.githubusercontent.com/sebk/my-xbar-plugins/main/aws/codepipeline-status/pipeline-status.png</xbar.image>
# <xbar.abouturl>https://github.com/sebk/my-xbar-plugins/blob/main/aws/codepipeline-status/README.md</xbar.abouturl>

# <xbar.var>string(VAR_PIPELINE_NAME="Pipeline-Name"): Name of the pipeline</xbar.var>

import json
import os
from enum import Enum

import boto3


class Status(Enum):
    IN_PROGRESS = "InProgress"
    FAILED = "Failed"
    SUCCEEDED = "Succeeded"


class PipelineStatus:

    def __init__(self) -> None:
        self.pipeline_name = os.environ.get("VAR_PIPELINE_NAME")
        self.execution_data_cache = {}
        self.cp_client = boto3.client("codepipeline")
        pipeline_result = self.cp_client.get_pipeline_state(name=self.pipeline_name)
        self.pipeline_data = self.get_data(result=pipeline_result)

    def request_execution(self, execution_id):
        if not execution_id:
            return ""
        if execution_id in self.execution_data_cache:
            return self.execution_data_cache[execution_id]

        result = self.cp_client.get_pipeline_execution(pipelineName=self.pipeline_name,
                                                       pipelineExecutionId=execution_id)
        revisions = result["pipelineExecution"]["artifactRevisions"]
        if len(revisions) > 0:
            message = json.loads(revisions[0]["revisionSummary"])["CommitMessage"].replace("\n", " ")
            self.execution_data_cache[execution_id] = message
            return message
        return ""

    def get_data(self, result):
        stages = []
        for stage in result["stageStates"]:
            status = stage["latestExecution"]["status"] if "latestExecution" in stage else "unknown"

            execution_id = stage["latestExecution"]["pipelineExecutionId"] if "latestExecution" in stage else ""
            message = self.request_execution(execution_id=execution_id)

            actions = []
            for action in stage["actionStates"]:
                action_status = action["latestExecution"]["status"] if "latestExecution" in action else "unknown"
                actions.append({'name': action["actionName"], 'status': action_status})
            stages.append({'name': stage["stageName"], 'status': status, 'actions': actions, 'message': message})

        return stages

    def get_symbol(self, status) -> str:
        if status == Status.IN_PROGRESS: return ':hourglass_flowing_sand:'
        if status == Status.FAILED: return ':x:'
        if status == Status.SUCCEEDED: return ':white_check_mark:'

    def get_stage_status_symbol(self, data) -> str:
        any_progress = any(entry["status"] == Status.IN_PROGRESS.value for entry in data)
        if any_progress: return self.get_symbol(Status.IN_PROGRESS)

        any_error = any(entry["status"] == Status.FAILED.value for entry in data)
        if any_error: return self.get_symbol(Status.FAILED)

        all_succeeded = all(entry["status"] == Status.SUCCEEDED.value for entry in data)
        if all_succeeded: return self.get_symbol(Status.SUCCEEDED)

        return ':warning:'

    def get_action_status_symbol(self, action) -> str:
        # Python >= 3.10.x:
        # match action['status']:
        #     case 'Succeeded': return ':white_check_mark:'
        #     case 'InProgress': return ':hourglass_flowing_sand:'
        #     case 'Failed': return ':x:'
        #     case _: return ':grey_question:'
        # Python 3.9.x:
        if action["status"] == Status.SUCCEEDED.value: return self.get_symbol(Status.SUCCEEDED)
        if action["status"] == Status.IN_PROGRESS.value: return self.get_symbol(Status.IN_PROGRESS)
        if action["status"] == Status.FAILED.value: return self.get_symbol(Status.FAILED)
        return ':grey_question:'

    def display_status(self):
        emoji = self.get_stage_status_symbol(data=self.pipeline_data)
        status_string = f"{emoji} {self.pipeline_name}"
        print(f"{status_string}")

    def display_detailed_status(self):
        for stage in self.pipeline_data:
            stage_emoji = self.get_stage_status_symbol(stage["actions"])
            print(f"{stage_emoji} {stage['name']}")
            if stage["status"] != Status.SUCCEEDED.value:
                print(f"-- {stage['message']}")
            for action in stage["actions"]:
                action_emoji = self.get_action_status_symbol(action)
                print(f"-- {action_emoji} {action['name']} ")


if __name__ == '__main__':
    try:
        pipe_status = PipelineStatus()
        pipe_status.display_status()
        print("---")
        pipe_status.display_detailed_status()
    except Exception as ex:
        print(f":warning: Exception executing script. Exception: {ex}")
        raise ex