Skip to content

Instantly share code, notes, and snippets.

@ajvargo
Last active February 28, 2020 17:55
Show Gist options
  • Save ajvargo/b33c3003d6cf8dab0e900705f79bba34 to your computer and use it in GitHub Desktop.
Save ajvargo/b33c3003d6cf8dab0e900705f79bba34 to your computer and use it in GitHub Desktop.
First solid pass at getting code pipeline work flow updates in emacs.
;;; codepipeline-watch-mode.el --- Watch the progress of AWS Codepipeline Builds -*- lexical-binding: t; -*-
;;; Commentary:
;; Shells out to `aws` command line to get data for display.
;; My theme is dark. No idea how these colors look in a light theme.
;; M-x codepipeline-watch-get-status
;; `g` will refresh - it'll update on its own now and then.
;;; Code:
(require 'json)
(defgroup codepipeline-watch-mode nil
"Utility for seeing AWS Codepipeline progress"
:prefix "codepipeline-watch-"
:group 'convenience)
(defface codepipeline-watch-inprogress-face
'((t :inherit warn
:foreground "yellow"))
"Face for InProgress indicator"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-succeeded-face
'((t :inherit success))
"Face for Succeeded indicator"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-superseded-face
'((t :inherit default
:foreground "lightblue"))
"Face for Superseded indicator"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-failed-face
'((t :inherit error))
"Face for Failed indicator"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-waiting-face
'((t :inherit default
:foreground "lightblue"))
"Face for waiting indicator"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-header-face
'((t :inherit bold
:foreground "blue"
:height 1.3))
"Face for the header line"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-stage-face
'((t :inherit bold
:foreground "DarkViolet"
:height 1.1))
"Face for the stage line"
:group 'codepipeline-watch-mode)
(defface codepipeline-watch-action-face
'((t :inherit default
:foreground "DarkOrange3"))
"Face for the action line"
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-indent-depth 2
"Tab stop in spaces for formatting."
:type 'integer
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-pipeline-name-prefix "tf2-production-"
"Prefix that will be appended to any given name when fetching pipeline status."
:type 'string
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-staleness-threshold 300
"Length in seconds before allowing and automatic refresh."
:type 'integer
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-pipeline-names
'("airflow" "bastion" "caravan"
"chirp" "dewey" "hedwig"
"iris" "lello" "mycroft"
"nile" "pince" "plural"
"pulp" "taft" "templater"
"valve")
"Set of pipeline names that will be offered for selection."
:type 'list
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-header-time-format "%F %R"
"Format for the last updated time displayed in the header."
:type 'string
:group 'codepipeline-watch-mode)
(defcustom codepipeline-watch-time-ago-format "%dd %hh %mm ago%z"
"Format used to show time since action was updated.
See `format-seconds'."
:type 'string
:group 'codepipeline-watch-mode)
(defvar codepipeline-watch-status-face-mappings
'(("InProgress" . codepipeline-watch-inprogress-face)
("Succeeded" . codepipeline-watch-succeeded-face)
("Superseded" . codepipeline-watch-superseded-face)
("Failed" . codepipeline-watch-failed-face)
("Waiting" . codepipeline-watch-waiting-face))
"An alist of pipeline statuses to the faced used to display them.")
(defvar-local codepipeline-watch-pipeline-name nil "The AWS name for a given buffers pipeline.")
(defvar-local codepipeline-watch-last-updated-at nil "The time a given buffer was updated.")
(defun codepipeline-watch-status-face (status)
"Lookup face for `STATUS'."
(alist-get status codepipeline-watch-status-face-mappings nil nil #'string-equal))
(defun codepipeline-watch-draw-header ()
"Insert pipeline name and last fetched as a header."
(insert (propertize codepipeline-watch-pipeline-name 'face 'codepipeline-watch-header-face)
"\nLast fetched: "
(format-time-string codepipeline-watch-header-time-format codepipeline-watch-last-updated-at)
"\n\n"))
(defun codepipeline-watch-stages ()
"Assemble stage states from AWS network request."
(let ((cmd-out (shell-command-to-string (concat "aws codepipeline get-pipeline-state --output json --name " codepipeline-watch-pipeline-name))))
(alist-get 'stageStates
(condition-case err
(json-read-from-string cmd-out)
(error (message "%s" (error-message-string err))
(message cmd-out)
'())))))
(defun codepipeline-watch-actions (stage)
"Return action states of a given STAGE."
(alist-get 'actionStates stage))
(defun codepipeline-watch-draw-action (action)
"Insert ACTION name, status and last execution.
Last execution is in time-ago format."
(let ((status (alist-get 'status (alist-get 'latestExecution action '((status . "waiting"))))))
(insert (make-string (* 2 codepipeline-watch-indent-depth) ?\s)
(propertize (alist-get 'actionName action) 'face 'codepipeline-watch-action-face)
" "
(propertize status 'face (codepipeline-watch-status-face status))
" "
(format-seconds codepipeline-watch-time-ago-format (float-time (time-since (alist-get 'lastStatusChange (alist-get 'latestExecution action)))))
"\n")))
(defun codepipeline-watch-draw-stage (stage)
"Insert STAGE with is staus and last execution followed by any actions."
(let ((stage-name (alist-get 'stageName stage))
(status (alist-get 'status (alist-get 'latestExecution stage))))
(insert (make-string codepipeline-watch-indent-depth ?\s)
(propertize stage-name 'face 'codepipeline-watch-stage-face)
" "
(propertize status 'face (codepipeline-watch-status-face status))
"\n")
(mapc #'codepipeline-watch-draw-action (codepipeline-watch-actions stage))
(insert "\n")))
(defun codepipeline-watch-draw-summary (source-stage)
"Insert a summary of SOURCE-STAGE.
This includes a link to the commit and the last commit message."
(let ((action-state (aref (alist-get 'actionStates source-stage) 0)))
(insert-button "Commit" 'action (lambda (x) (browse-url (button-get x 'url))) 'url
(alist-get 'revisionUrl action-state)
'face '(:weight bold :underline "blue" :foreground "blue" :height 1.1))
(insert
"\n"
(replace-regexp-in-string "
" ""
(alist-get 'summary (alist-get 'latestExecution action-state)))
"\n\n")))
(defun codepipeline-watch-draw-status-buffer ()
"Draw entire status buffer for the project."
(setq codepipeline-watch-last-updated-at (current-time))
(let ((inhibit-read-only t))
(erase-buffer)
(codepipeline-watch-draw-header)
(let ((watch-stages (codepipeline-watch-stages)))
(codepipeline-watch-draw-summary (aref watch-stages 0))
(mapc #'codepipeline-watch-draw-stage watch-stages))
(goto-char (point-min))))
(defun codepipeline-watch-buffer-stale-p (&optional noconfirm)
(if noconfirm
(when codepipeline-watch-staleness-threshold
(> (float-time (time-subtract (current-time) codepipeline-watch-last-updated-at)) codepipeline-watch-staleness-threshold))
(message (concat (buffer-name) " is stale. You might want to refresh."))))
(defun codepipeline-watch-revert (&optional _ignore_auto _noconfirm)
(codepipeline-watch-draw-status-buffer))
(defun codepipeline-watch-completion-table (completions)
(lambda (string pred action)
(if (eq action 'metadata)
`(metadata (display-sort-function . ,#'identity))
(complete-with-action action completions string pred))))
(defun codepipeline-watch-get-status(pipeline)
(interactive (list
(let* ((ivy-sort-functions-alist nil))
(completing-read "Project: " (codepipeline-watch-completion-table codepipeline-watch-pipeline-names)))))
(setq codepipeline-watch-pipeline-names (cons pipeline (remove pipeline codepipeline-watch-pipeline-names)))
(let* ((buf (get-buffer-create (concat "*codepipeline-watch-" pipeline "*"))))
(with-current-buffer buf
(codepipeline-watch-mode)
(setq-local codepipeline-watch-pipeline-name (concat codepipeline-watch-pipeline-name-prefix pipeline))
(codepipeline-watch-draw-status-buffer))
(switch-to-buffer-other-window buf)))
(define-derived-mode codepipeline-watch-mode special-mode "Codepipeline-Watch"
"Mode for viewing the state of a codepipeline execution in AWS."
:group 'codepipeline-watch-mode
(setq-local revert-buffer-function #'codepipeline-watch-revert)
(setq-local buffer-stale-function #'codepipeline-watch-buffer-stale-p))
(provide 'codepipeline-watch-mode)
;;; codepipeline-watch-mode.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment