git.fiddlerwoaroof.com
Browse code

Refactor into files

Ed Langley authored on 11/04/2018 22:45:24
Showing 6 changed files
... ...
@@ -1,3 +1,5 @@
1 1
 *.fasl
2 2
 *~
3 3
 /cloud-watcher
4
+/gp.run
5
+nohup.out
4 6
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+* TODO remove things
0 2
new file mode 100644
... ...
@@ -0,0 +1,158 @@
1
+(defpackage :cloud-watcher.aws-result
2
+  (:use :cl :fw.lu :alexandria :st)
3
+  (:export
4
+   #:stack
5
+   #:outputs
6
+   #:reader
7
+   #:creation-time
8
+   #:notification-arns
9
+   #:stack-id
10
+   #:stack-name
11
+   #:description
12
+   #:stack-status
13
+   #:disable-rollback
14
+   #:tags
15
+   #:deletion-time
16
+   #:rollback-configuration
17
+   #:drift-information
18
+   #:enable-termination-protection
19
+   #:parameters
20
+   #:start-date-time
21
+   #:end-date-time
22
+   #:creation-date-time
23
+   #:extract-timeline
24
+   #:extract-list
25
+   #:extract-stack
26
+   #:timeline))
27
+(in-package :cloud-watcher.aws-result)
28
+
29
+(defun find-all-indices (pred str &optional accum (start (or (car accum) 0)))
30
+  (check-type pred function)
31
+  (check-type str string)
32
+  (check-type start fixnum)
33
+
34
+  (tagbody start-loop
35
+     (if (= start (length str))
36
+         (return-from find-all-indices (nreverse accum))
37
+         (let ((pos (position-if pred str :start start)))
38
+           (if pos
39
+               (progn (psetf accum (cons pos accum)
40
+                             start (1+ pos))
41
+                      (go start-loop))
42
+               (return-from find-all-indices (nreverse accum)))))))
43
+
44
+(defun transform-by-function-map (function-map key value &optional (default-transform 'identity))
45
+  (funcall (gethash key
46
+                    function-map
47
+                    default-transform)
48
+           value))
49
+
50
+(defun decamelize (v)
51
+  (let* ((indices (or (find-all-indices #'upper-case-p v) '(0)))
52
+         (parts (mapcar (serapeum:op (string-downcase (subseq v _ _)))
53
+                        (if (= (car indices) 0) indices (cons 0 indices))
54
+                        (if (/= (car indices) 0) indices (append (cdr indices)
55
+                                                                 (list (length v)))))))
56
+    (serapeum:string-join parts "-")))
57
+
58
+(deftest decamelize ()
59
+  (should be equal "a" (decamelize "A"))
60
+  (should be equal "a" (decamelize "a"))
61
+  (should be equal "outputs" (decamelize "Outputs"))
62
+  (should be equal "outputs-outputs" (decamelize "OutputsOutputs"))
63
+  (should be equal "a-b-c" (decamelize "ABC")))
64
+
65
+
66
+(defmacro tagged-kv-formatter (tag)
67
+  `(formatter ,(format nil "~a ~~a ~~a~~%" tag)))
68
+
69
+(defun alist-to-initargs (alist value-map)
70
+  (mapcan (destructuring-lambda ((key . value))
71
+            (let* ((key (string-case:string-case (key :default key)
72
+                          ("NotificationARNs" "NotificationArns")))
73
+                   (initarg (make-keyword (string-upcase (decamelize key)))))
74
+              (list initarg
75
+                    (transform-by-function-map value-map
76
+                                               initarg
77
+                                               value))))
78
+          alist))
79
+
80
+
81
+(defclass stack ()
82
+  ((%outputs :initarg :outputs :reader outputs)
83
+   (%capabilities :initarg :capabilities :reader capabilities)
84
+   (%creation-time :initarg :creation-time :reader creation-time)
85
+   (%notification-arns :initarg :notification-arns :reader notification-arns)
86
+   (%stack-id :initarg :stack-id :reader stack-id)
87
+   (%stack-name :initarg :stack-name :reader stack-name)
88
+   (%description :initarg :description :reader description)
89
+   (%stack-status :initarg :stack-status :reader stack-status)
90
+   (%disable-rollback :initarg :disable-rollback :reader disable-rollback)
91
+   (%tags :initarg :tags :reader tags)
92
+   (%deletion-time :initarg :deletion-time :reader deletion-time)
93
+   (%rollback-configuration :initarg :rollback-configuration :reader rollback-configuration)
94
+   (%drift-information :initarg :drift-information :reader drift-information)
95
+   (%enable-termination-protection :initarg :enable-termination-protection :reader enable-termination-protection)
96
+   (%parameters :initarg :parameters :reader parameters)))
97
+
98
+(defclass timeline ()
99
+  ((%start-date-time :initarg :start-date-time :reader start-date-time)
100
+   (%end-date-time :initarg :end-date-time :reader end-date-time)
101
+   (%creation-date-time :initarg :creation-date-time :reader creation-date-time)))
102
+
103
+(defun extract-timeline (aws-result)
104
+  (labels ((cdr-assoc (key list) (cdr (assoc key list :test #'equal)))
105
+           (get-in (keys alist)
106
+             #+null
107
+             (reduce (lambda (accum next)
108
+                       ())
109
+                     )
110
+             (loop
111
+                for key in keys
112
+                for cur-alist = alist then accum
113
+                for accum = (cdr-assoc key cur-alist)
114
+                finally (return-from get-in accum))))
115
+    (apply #'make-instance 'timeline
116
+           (alist-to-initargs (get-in '("Status" "Timeline")
117
+                                      aws-result)
118
+                              (make-hash-table)))))
119
+
120
+(defun extract-list (aws-result)
121
+  (mapcar (destructuring-lambda ((list-item-marker . items))
122
+            (if (string= list-item-marker "member")
123
+                items
124
+                (error 'invalid-result)))
125
+          aws-result))
126
+
127
+(defun extract-stack (aws-result)
128
+  (flet ((as-keyword (v) (alexandria:make-keyword (car v))))
129
+    (apply #'make-instance 'stack
130
+           (alist-to-initargs aws-result
131
+                              (fw.lu:alist-string-hash-table
132
+                               `((:outputs . extract-list)
133
+                                 (:capabilities . extract-list)
134
+                                 (:creation-time . car)
135
+                                 (:notification-arns . car)
136
+                                 (:stack-id . car)
137
+                                 (:stack-name . car)
138
+                                 (:description . car)
139
+                                 (:stack-status . ,#'as-keyword)
140
+                                 (:disable-rollback . car)
141
+                                 (:tags . extract-list)
142
+                                 (:rollback-configuration . car)
143
+                                 (:drift-information . car)
144
+                                 (:enable-termination-protection . car)
145
+                                 (:parameters . extract-list)))))))
146
+
147
+
148
+(defgeneric initialize-date (value)
149
+  (:method ((value cons)) (local-time:parse-timestring (car value)))
150
+  (:method (value) value))
151
+
152
+(defmethod initialize-instance :after ((instance timeline) &key)
153
+  (with-slots ((s-sdt %start-date-time)
154
+               (s-edt %end-date-time)
155
+               (s-cdt %creation-date-time)) instance
156
+    (setf s-sdt (initialize-date s-sdt)
157
+          s-edt (initialize-date s-edt)
158
+          s-cdt (initialize-date s-cdt))))
0 159
new file mode 100644
... ...
@@ -0,0 +1,46 @@
1
+(defpackage :cloud-watcher.cli
2
+  (:import-from :cloud-watcher.main :stack-parameters :stack-outputs :stack-for-name)
3
+  (:import-from :cloud-watcher.aws-result :start-date-time :end-date-time)
4
+  (:import-from :serapeum :op)
5
+  (:import-from :clon :defsynopsis :group :flag :stropt)
6
+  (:use :cl)
7
+  (:export options
8
+           #:*cloud-watcher-synopsis*
9
+           #:dump))
10
+
11
+(in-package :cloud-watcher.cli)
12
+
13
+(defparameter *cloud-watcher-synopsis*
14
+  (defsynopsis (:postfix "ARGS...")
15
+    (group (:header "actions")
16
+           (flag :short-name "p" :long-name "parameters" :description "show stack parameters")
17
+           (flag :short-name "o" :long-name "outputs" :description "show stack outputs")
18
+           (flag :short-name "w" :long-name "watch" :description "watch a cloudformation stack until it's done processing")
19
+           (flag :short-name "s" :long-name "start")
20
+           (stropt :long-name "aws-region" :default-value "us-west-2")
21
+           (flag :short-name "u" :long-name "update"))
22
+    (group (:header "misc")
23
+           (flag :long-name "help"))))
24
+
25
+(defun stack-parameters-main (name)
26
+  (stack-parameters (stack-for-name name)))
27
+
28
+(defun stack-outputs-main (name)
29
+  (stack-outputs (stack-for-name name)))
30
+
31
+(defun main ()
32
+  (let* ((context (net.didierverna.clon:make-context :synopsis *cloud-watcher-synopsis*))
33
+         (files (clon:remainder :context context))
34
+         (region (clon:getopt :long-name "aws-region"))
35
+         (aws-sdk/api:*session* (aws-sdk/session:make-session :region region)))
36
+
37
+    (format *error-output* "~&IN REGION: ~a~%" region)
38
+
39
+    (cond ((clon:getopt :long-name "help") (clon:help))
40
+          ((clon:getopt :long-name "watch") (cloud-watcher.main:watch-stack (car files)))
41
+          ((clon:getopt :long-name "outputs") (stack-outputs-main (car files)))
42
+          ((clon:getopt :long-name "parameters") (stack-parameters-main (car files))))))
43
+
44
+(defun dump ()
45
+  "Create an executable with the command-line interface defined above."
46
+  (clon:dump "cloud-watcher" main))
... ...
@@ -14,7 +14,12 @@
14 14
                  #:fwoar.lisputils
15 15
                  #:net.didierverna.clon
16 16
                  #:cl-base64
17
+                 #:local-time
18
+                 #:local-time-duration
17 19
                  #:aws-sdk/services/cloudformation
20
+                 #:aws-sdk/services/elasticmapreduce
18 21
                  #:should-test)
19 22
     :serial t
20
-    :components ((:file "main")))
23
+    :components ((:file "aws-result")
24
+                 (:file "main")
25
+                 (:file "cli")))
... ...
@@ -1,126 +1,21 @@
1 1
 ;; MIT license Edward Langley (c) 2018
2 2
 
3
-(defpackage :cloud-watcher.cli
4
-  (:use :cl :net.didierverna.clon)
5
-  (:export options
6
-           #:*cloud-watcher-synopsis*))
3
+(eval-when (:execute :load-toplevel :compile-toplevel)
4
+  (net.didierverna.clon:nickname-package))
7 5
 
8 6
 (defpackage :cloud-watcher.main
9
-  (:import-from :cloud-watcher.cli #:*cloud-watcher-synopsis*)
10
-  (:use :cl :fw.lu :alexandria :st)
11
-  (:export main dump))
12
-
13
-(in-package :cloud-watcher.cli)
14
-
15
-(defparameter *cloud-watcher-synopsis*
16
-  (defsynopsis (:postfix "ARGS...")
17
-    (group (:header "actions")
18
-           (flag :short-name "p" :long-name "parameters" :description "show stack parameters")
19
-           (flag :short-name "o" :long-name "outputs" :description "show stack outputs")
20
-           (flag :short-name "w" :long-name "watch" :description "watch a cloudformation stack until it's done processing")
21
-           (flag :short-name "s" :long-name "start")
22
-           (flag :short-name "u" :long-name "update"))
23
-    (group (:header "misc")
24
-           (flag :long-name "help"))))
7
+  (:use :cl :fw.lu :alexandria :st :cloud-watcher.aws-result)
8
+  (:export main dump
9
+           #:stack-parameters
10
+           #:stack-outputs
11
+           #:stack-for-name
12
+           #:watch-stack))
25 13
 
26 14
 (in-package :cloud-watcher.main)
27 15
 
28
-(eval-when (:execute :load-toplevel :compile-toplevel)
29
-  (net.didierverna.clon:nickname-package))
30
-
31 16
 (define-condition invalid-result (error)
32 17
   ())
33 18
 
34
-(defun extract-list (aws-result)
35
-  (mapcar (destructuring-lambda ((list-item-marker . items))
36
-            (if (string= list-item-marker "member")
37
-                items
38
-                (error 'invalid-result)))
39
-          aws-result))
40
-
41
-(defclass stack ()
42
-  ((%outputs :initarg :outputs :reader outputs)
43
-   (%capabilities :initarg :capabilities :reader capabilities)
44
-   (%creation-time :initarg :creation-time :reader creation-time)
45
-   (%notification-arns :initarg :notification-arns :reader notification-arns)
46
-   (%stack-id :initarg :stack-id :reader stack-id)
47
-   (%stack-name :initarg :stack-name :reader stack-name)
48
-   (%description :initarg :description :reader description)
49
-   (%stack-status :initarg :stack-status :reader stack-status)
50
-   (%disable-rollback :initarg :disable-rollback :reader disable-rollback)
51
-   (%tags :initarg :tags :reader tags)
52
-   (%rollback-configuration :initarg :rollback-configuration :reader rollback-configuration)
53
-   (%drift-information :initarg :drift-information :reader drift-information)
54
-   (%enable-termination-protection :initarg :enable-termination-protection :reader enable-termination-protection)
55
-   (%parameters :initarg :parameters :reader parameters)))
56
-
57
-(defun find-all-indices (pred str &optional accum (start (or (car accum) 0)))
58
-  (check-type pred function)
59
-  (check-type str string)
60
-  (check-type start fixnum)
61
-
62
-  (tagbody start-loop
63
-     (if (= start (length str))
64
-         (return-from find-all-indices (nreverse accum))
65
-         (let ((pos (position-if pred str :start start)))
66
-           (if pos
67
-               (progn (psetf accum (cons pos accum)
68
-                             start (1+ pos))
69
-                      (go start-loop))
70
-               (return-from find-all-indices (nreverse accum)))))))
71
-
72
-(defun decamelize (v)
73
-  (let* ((indices (or (find-all-indices #'upper-case-p v) '(0)))
74
-         (parts (mapcar (serapeum:op (string-downcase (subseq v _ _)))
75
-                        (if (= (car indices) 0) indices (cons 0 indices))
76
-                        (if (/= (car indices) 0) indices (append (cdr indices)
77
-                                                                 (list (length v)))))))
78
-    (serapeum:string-join parts "-")))
79
-
80
-(deftest decamelize ()
81
-  (should be equal "a" (decamelize "A"))
82
-  (should be equal "a" (decamelize "a"))
83
-  (should be equal "outputs" (decamelize "Outputs"))
84
-  (should be equal "outputs-outputs" (decamelize "OutputsOutputs"))
85
-  (should be equal "a-b-c" (decamelize "ABC")))
86
-
87
-(defun transform-by-function-map (function-map key value &optional (default-transform 'identity))
88
-  (funcall (gethash key
89
-                    function-map
90
-                    default-transform)
91
-           value))
92
-
93
-(defun alist-to-initargs (alist value-map)
94
-  (mapcan (destructuring-lambda ((key . value))
95
-            (let* ((key (string-case:string-case (key :default key)
96
-                          ("NotificationARNs" "NotificationArns")))
97
-                   (initarg (make-keyword (string-upcase (decamelize key)))))
98
-              (list initarg
99
-                    (transform-by-function-map value-map
100
-                                               initarg
101
-                                               value))))
102
-          alist))
103
-
104
-(defun extract-stack (aws-result)
105
-  (flet ((as-keyword (v) (alexandria:make-keyword (car v))))
106
-    (apply #'make-instance 'stack
107
-           (alist-to-initargs aws-result
108
-                              (fw.lu:alist-string-hash-table
109
-                               `((:outputs . extract-listq)
110
-                                 (:capabilities . extract-list)
111
-                                 (:creation-time . car)
112
-                                 (:notification-arns . car)
113
-                                 (:stack-id . car)
114
-                                 (:stack-name . car)
115
-                                 (:description . car)
116
-                                 (:stack-status . ,#'as-keyword)
117
-                                 (:disable-rollback . car)
118
-                                 (:tags . extract-list)
119
-                                 (:rollback-configuration . car)
120
-                                 (:drift-information . car)
121
-                                 (:enable-termination-protection . car)
122
-                                 (:parameters . extract-list)))))))
123
-
124 19
 (defun stack-for-name (name)
125 20
   (let* ((aws-result (car (extract-list (cdar (aws/cloudformation:describe-stacks :stack-name name)))))
126 21
          (the-stack (extract-stack aws-result)))
... ...
@@ -132,22 +27,21 @@
132 27
             (funcall formatter stream k v))
133 28
           data))
134 29
 
135
-(defmacro tagged-kv-formatter (tag)
136
-  `(formatter ,(format nil "~a ~~a ~~a~~%" tag)))
137
-
138 30
 (defun stack-outputs (the-stack)
139
-  (print-kvs (tagged-kv-formatter "OUTPUT")
31
+  (print-kvs (tagged-kv-formatter "OUTPUT") t
140 32
              (outputs the-stack)))
141 33
 
142 34
 (defun stack-parameters (the-stack)
143
-  (print-kvs (tagged-kv-formatter "PARAMETERS")
35
+  (print-kvs (tagged-kv-formatter "PARAMETERS") t
144 36
              (parameters the-stack)))
145 37
 
146
-(defun stack-parameters-main (name)
147
-  (stack-parameters (stack-for-name name)))
38
+(defun lt-format (a b &key &allow-other-keys)
39
+  (local-time:format-timestring a b))
148 40
 
149
-(defun stack-outputs-main (name)
150
-  (stack-outputs (stack-for-name name)))
41
+(defgeneric duration-of (timeline)
42
+  (:method ((timeline timeline))
43
+    (local-time-duration:timestamp-difference (end-date-time timeline)
44
+                                              (start-date-time timeline))))
151 45
 
152 46
 (defun watch-stack (name)
153 47
   (format t "~&Watching ~s~2%" name)
... ...
@@ -173,15 +67,3 @@
173 67
                (return)))
174 68
          (sleep 5)))
175 69
   (fresh-line))
176
-
177
-(defun main ()
178
-  (let* ((context (net.didierverna.clon:make-context :synopsis *cloud-watcher-synopsis*))
179
-         (files (clon:remainder :context context)))
180
-    (cond ((clon:getopt :long-name "help") (clon:help))
181
-          ((clon:getopt :long-name "watch") (watch-stack (car files)))
182
-          ((clon:getopt :long-name "outputs") (stack-outputs-main (car files)))
183
-          ((clon:getopt :long-name "parameters") (stack-parameters-main (car files))))))
184
-
185
-(defun dump ()
186
-  "Create an executable with the command-line interface defined above."
187
-  (clon:dump "cloud-watcher" main))