git.fiddlerwoaroof.com
Raw Blame History
(defpackage :daydreamer.aws-result
  (:use :cl :fw.lu :alexandria :st)
  (:export
   #:stack
   #:outputs
   #:reader
   #:creation-time
   #:notification-arns
   #:stack-id
   #:stack-name
   #:description
   #:stack-status
   #:disable-rollback
   #:tags
   #:deletion-time
   #:rollback-configuration
   #:drift-information
   #:enable-termination-protection
   #:parameters
   #:start-date-time
   #:end-date-time
   #:creation-date-time
   #:extract-timeline
   #:extract-list
   #:extract-stack
   #:timeline
   #:tagged-kv-formatter))
(in-package :daydreamer.aws-result)

(defun find-all-indices (pred str &optional accum (start (or (car accum) 0)))
  (check-type pred function)
  (check-type str string)
  (check-type start fixnum)

  (tagbody start-loop
     (if (= start (length str))
         (return-from find-all-indices (nreverse accum))
         (let ((pos (position-if pred str :start start)))
           (if pos
               (progn (psetf accum (cons pos accum)
                             start (1+ pos))
                      (go start-loop))
               (return-from find-all-indices (nreverse accum)))))))

(defun transform-by-function-map (function-map key value &optional (default-transform 'identity))
  (funcall (gethash key
                    function-map
                    default-transform)
           value))

(defun decamelize (v)
  (let* ((indices (or (find-all-indices #'upper-case-p v) '(0)))
         (parts (mapcar (serapeum:op (string-downcase (subseq v _ _)))
                        (if (= (car indices) 0) indices (cons 0 indices))
                        (if (/= (car indices) 0) indices (append (cdr indices)
                                                                 (list (length v)))))))
    (serapeum:string-join parts "-")))


(defmacro tagged-kv-formatter (tag)
  `(formatter ,(format nil "~a ~~a ~~a~~%" tag)))

(defun alist-to-initargs (alist value-map)
  (mapcan (destructuring-lambda ((key . value))
            (let* ((key (string-case:string-case (key :default key)
                          ("NotificationARNs" "NotificationArns")))
                   (initarg (make-keyword (string-upcase (decamelize key)))))
              (list initarg
                    (transform-by-function-map value-map
                                               initarg
                                               value))))
          alist))


(defclass stack ()
  ((%outputs :initarg :outputs :reader outputs :initform (list))
   (%capabilities :initarg :capabilities :reader capabilities)
   (%last-updated-time :initarg :last-updated-time :reader last-updated-time)
   (%creation-time :initarg :creation-time :reader creation-time)
   (%notification-arns :initarg :notification-arns :reader notification-arns)
   (%stack-id :initarg :stack-id :reader stack-id)
   (%stack-name :initarg :stack-name :reader stack-name)
   (%description :initarg :description :reader description)
   (%stack-status :initarg :stack-status :reader stack-status)
   (%disable-rollback :initarg :disable-rollback :reader disable-rollback)
   (%tags :initarg :tags :reader tags)
   (%deletion-time :initarg :deletion-time :reader deletion-time)
   (%rollback-configuration :initarg :rollback-configuration :reader rollback-configuration)
   (%drift-information :initarg :drift-information :reader drift-information)
   (%enable-termination-protection :initarg :enable-termination-protection :reader enable-termination-protection)
   (%template-description :initarg :template-description :reader template-description)
   (%parameters :initarg :parameters :reader parameters :initform (list))))

(defclass timeline ()
  ((%start-date-time :initarg :start-date-time :reader start-date-time)
   (%end-date-time :initarg :end-date-time :reader end-date-time)
   (%creation-date-time :initarg :creation-date-time :reader creation-date-time)))

(defun extract-timeline (aws-result)
  (labels ((cdr-assoc (key list) (cdr (assoc key list :test #'equal)))
           (get-in (keys alist)
             #+null
             (reduce (lambda (accum next)
                       ())
                     )
             (loop
                for key in keys
                for cur-alist = alist then accum
                for accum = (cdr-assoc key cur-alist)
                finally (return-from get-in accum))))
    (apply #'make-instance 'timeline
           (alist-to-initargs (get-in '("Status" "Timeline")
                                      aws-result)
                              (make-hash-table)))))

(defun extract-list (aws-result)
  (mapcar (destructuring-lambda ((list-item-marker . items))
            (if (string= list-item-marker "member")
                items
                (error 'invalid-result)))
          aws-result))

(defun extract-stack (aws-result)
  (flet ((as-keyword (v) (alexandria:make-keyword (car v))))
    (apply #'make-instance 'stack
           (alist-to-initargs aws-result
                              (fw.lu:alist-string-hash-table
                               `((:outputs . extract-list)
                                 (:capabilities . extract-list)
                                 (:creation-time . car)
                                 (:notification-arns . car)
                                 (:stack-id . car)
                                 (:stack-name . car)
                                 (:description . car)
                                 (:stack-status . ,#'as-keyword)
                                 (:disable-rollback . car)
                                 (:tags . extract-list)
                                 (:rollback-configuration . car)
                                 (:drift-information . car)
                                 (:enable-termination-protection . car)
                                 (:template-description . car)
                                 (:parameters . extract-list)))))))

(defparameter *stack-statuses*
  '("CREATE_COMPLETE" "CREATE_IN_PROGRESS" "CREATE_FAILED"
    "DELETE_COMPLETE" "DELETE_FAILED" "DELETE_IN_PROGRESS"
    "REVIEW_IN_PROGRESS"
    "ROLLBACK_COMPLETE" "ROLLBACK_FAILED" "ROLLBACK_IN_PROGRESS"
    "UPDATE_COMPLETE" "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS" "UPDATE_IN_PROGRESS"
    "UPDATE_ROLLBACK_COMPLETE" "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" "UPDATE_ROLLBACK_FAILED" "UPDATE_ROLLBACK_IN_PROGRESS"))

#+nil
(aws-sdk/services/cloudformation:list-stacks 
 :stack-status-filter '("UPDATE_COMPLETE" "UPDATE_IN_PROGRESS"
                        "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS" "UPDATE_ROLLBACK_COMPLETE"
                        "UPDATE_ROLLBACK_IN_PROGRESS" "UPDATE_ROLLBACK_FAILED" 
                        "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" "CREATE_FAILED"
                        "CREATE_COMPLETE" "CREATE_IN_PROGRESS"))

(defgeneric initialize-date (value)
  (:method ((value cons)) (local-time:parse-timestring (car value)))
  (:method (value) value))

(defmethod initialize-instance :after ((instance timeline) &key)
  (with-slots ((s-sdt %start-date-time)
               (s-edt %end-date-time)
               (s-cdt %creation-date-time)) instance
    (setf s-sdt (initialize-date s-sdt)
          s-edt (initialize-date s-edt)
          s-cdt (initialize-date s-cdt))))


(deftest decamelize ()
  (should be equal "a" (decamelize "A"))
  (should be equal "a" (decamelize "a"))
  (should be equal "outputs" (decamelize "Outputs"))
  (should be equal "outputs-outputs" (decamelize "OutputsOutputs"))
  (should be equal "a-b-c" (decamelize "ABC")))