Browse code
feature: new stack description window
Edward Langley authored on 08/10/2019 05:51:33
Showing 9 changed files
Showing 9 changed files
- aws-access.asd
- src/aws-dispatcher.lisp
- src/capi-interface.lisp
- src/domain.lisp
- src/mfa-tool.lisp
- src/objc-utils.lisp
- src/stack-store.lisp
- src/stack.lisp
- src/store.lisp
... | ... |
@@ -2,25 +2,31 @@ |
2 | 2 |
(in-package :asdf-user) |
3 | 3 |
|
4 | 4 |
(defsystem :aws-access |
5 |
- :description "A simple tool for access to CJ's AWS accounts" |
|
6 |
- :author "Ed L <edward@elangley.org>" |
|
7 |
- :license "MIT" |
|
8 |
- :depends-on (:alexandria |
|
9 |
- :aws-sdk |
|
10 |
- :aws-sdk/services/sts |
|
11 |
- :cells |
|
12 |
- :cl-yaml |
|
13 |
- :fwoar-lisputils |
|
14 |
- :serapeum |
|
15 |
- :ubiquitous |
|
16 |
- :uiop |
|
17 |
- :yason |
|
18 |
- :cxml |
|
19 |
- :xpath) |
|
20 |
- :serial t |
|
21 |
- :components ((:module "src" |
|
22 |
- :serial t |
|
23 |
- :components ((:file "package") |
|
24 |
- (:file "domain") |
|
25 |
- (:file "mfa-tool") |
|
26 |
- (:file "capi-interface"))))) |
|
5 |
+ :description "A simple tool for access to CJ's AWS accounts" |
|
6 |
+ :author "Ed L <edward@elangley.org>" |
|
7 |
+ :license "MIT" |
|
8 |
+ :depends-on (:alexandria |
|
9 |
+ :aws-sdk |
|
10 |
+ :aws-sdk/services/sts |
|
11 |
+ :cells |
|
12 |
+ :cl-yaml |
|
13 |
+ :cxml |
|
14 |
+ :daydreamer |
|
15 |
+ :fwoar-lisputils |
|
16 |
+ :serapeum |
|
17 |
+ :ubiquitous |
|
18 |
+ :uiop |
|
19 |
+ :xpath |
|
20 |
+ :yason) |
|
21 |
+ :serial t |
|
22 |
+ :components ((:module "src" |
|
23 |
+ :serial t |
|
24 |
+ :components ((:file "package") |
|
25 |
+ (:file "store") |
|
26 |
+ (:file "aws-dispatcher") |
|
27 |
+ (:file "domain") |
|
28 |
+ (:file "objc-utils") |
|
29 |
+ (:file "mfa-tool") |
|
30 |
+ (:file "stack-store") |
|
31 |
+ (:file "stack") |
|
32 |
+ (:file "capi-interface"))))) |
27 | 33 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,52 @@ |
1 |
+(defpackage :mfa-tool.aws-dispatcher |
|
2 |
+ (:use :cl) |
|
3 |
+ (:export #:aws-dispatcher #:update-stacks #:select-stack #:stacks #:stack)) |
|
4 |
+(in-package :mfa-tool.aws-dispatcher) |
|
5 |
+ |
|
6 |
+(defclass aws-dispatcher () |
|
7 |
+ ((%region :reader region :accessor %region |
|
8 |
+ :initarg :region) |
|
9 |
+ (%credentials :reader credentials |
|
10 |
+ :initarg :credentials)) |
|
11 |
+ (:default-initargs |
|
12 |
+ :region "us-east-1" |
|
13 |
+ :credentials (error "AWS-DISPATCHER requires a :CREDENTIALS initarg"))) |
|
14 |
+ |
|
15 |
+(defclass update-stacks () |
|
16 |
+ ((%stacks :initarg :stacks :reader stacks))) |
|
17 |
+(defun update-stacks (stacks) |
|
18 |
+ (fw.lu:new 'update-stacks stacks)) |
|
19 |
+ |
|
20 |
+(defclass select-stack () |
|
21 |
+ ((%stacks :initarg :stack :reader stack))) |
|
22 |
+ |
|
23 |
+(defun select-stack (stack) |
|
24 |
+ (fw.lu:new 'select-stack stack)) |
|
25 |
+ |
|
26 |
+(defclass update-region () |
|
27 |
+ ((%new-region :initarg :region :reader region))) |
|
28 |
+(defun update-region (region) |
|
29 |
+ (fw.lu:new 'update-region region)) |
|
30 |
+ |
|
31 |
+(defmethod mfa-tool.store:dispatch :around ((store aws-dispatcher) action) |
|
32 |
+ (let ((aws-sdk:*session* (aws-sdk:make-session :credentials (credentials store) |
|
33 |
+ :region (region store)))) |
|
34 |
+ (call-next-method))) |
|
35 |
+ |
|
36 |
+(defmethod mfa-tool.store:execute ((store aws-dispatcher) (action update-region)) |
|
37 |
+ (setf (%region store) (region action))) |
|
38 |
+ |
|
39 |
+(defmethod mfa-tool.store:dispatch :after ((store aws-dispatcher) (action update-region)) |
|
40 |
+ (mfa-tool.store:dispatch store :|Get Stacks|)) |
|
41 |
+ |
|
42 |
+(defmethod mfa-tool.store:dispatch :after ((store aws-dispatcher) (action (eql :|Get Stacks|))) |
|
43 |
+ (bt:make-thread |
|
44 |
+ (lambda () |
|
45 |
+ (let ((aws-sdk:*session* (aws-sdk:make-session :credentials (credentials store) |
|
46 |
+ :region (region store)))) |
|
47 |
+ (mfa-tool.store:dispatch store |
|
48 |
+ (update-stacks (mapcar 'daydreamer.aws-result:extract-stack |
|
49 |
+ (daydreamer.aws-result:extract-list |
|
50 |
+ (cdar |
|
51 |
+ (aws/cloudformation:describe-stacks)))))))) |
|
52 |
+ :name "Stack Fetcher")) |
|
0 | 53 |
\ No newline at end of file |
... | ... |
@@ -1,7 +1,8 @@ |
1 | 1 |
(in-package :mfa-tool) |
2 | 2 |
|
3 | 3 |
(capi:define-interface mfa-tool () |
4 |
- ((%default-account :initarg :default-account :reader default-account) |
|
4 |
+ ((assumed-credentials :accessor assumed-credentials :initform (make-hash-table :test 'equal)) |
|
5 |
+ (%default-account :initarg :default-account :reader default-account) |
|
5 | 6 |
(%signin-url :accessor signin-url)) |
6 | 7 |
(:panes |
7 | 8 |
(output-pane capi:collector-pane :reader output |
... | ... |
@@ -31,7 +32,8 @@ |
31 | 32 |
:reader account-selector) |
32 | 33 |
(action-buttons capi:push-button-panel |
33 | 34 |
:items '(:|Open Web Console| |
34 |
- :|Authorize iTerm|) |
|
35 |
+ :|Authorize iTerm| |
|
36 |
+ :|Cloudformation Stacks|) |
|
35 | 37 |
:selection-callback 'execute-action |
36 | 38 |
:callback-type :data-interface) |
37 | 39 |
(listener-button capi:push-button |
... | ... |
@@ -71,6 +73,11 @@ |
71 | 73 |
(probe-file |
72 | 74 |
(merge-pathnames (make-pathname :name "AuthorizeShell" :type "scpt") |
73 | 75 |
(bundle-resource-root)))))) |
76 |
+ (:method ((action (eql :|Cloudformation Stacks|)) (interface mfa-tool)) |
|
77 |
+ (let ((stack-interface (make-instance 'mfa-tool.stack:stack-interface |
|
78 |
+ :credentials (current-credentials interface)))) |
|
79 |
+ (mfa-tool.store:dispatch stack-interface :|Get Stacks|) |
|
80 |
+ (capi:display stack-interface))) |
|
74 | 81 |
(:method ((action (eql :|Lisp REPL|)) (interface mfa-tool)) |
75 | 82 |
(capi:contain (make-instance 'capi:listener-pane) |
76 | 83 |
:best-width 1280 |
... | ... |
@@ -133,6 +140,14 @@ |
133 | 140 |
|
134 | 141 |
(:menu-bar edit-menu window-menu)) |
135 | 142 |
|
143 |
+(defun start-in-repl (&optional (accounts (asdf:system-relative-pathname :aws-access "accounts.json"))) |
|
144 |
+ (ubiquitous:restore :cj.mfa-tool) |
|
145 |
+ (setf aws:*session* (aws:make-session) |
|
146 |
+ *print-readably* nil |
|
147 |
+ *accounts* (reprocess-accounts (load-accounts accounts))) |
|
148 |
+ (interface :default-account |
|
149 |
+ (ubiquitous:value :default-account))) |
|
150 |
+ |
|
136 | 151 |
(defun main () |
137 | 152 |
(setf *debugger-hook* 'debugging |
138 | 153 |
*print-readably* nil |
... | ... |
@@ -1,7 +1,7 @@ |
1 | 1 |
(in-package :mfa-tool) |
2 | 2 |
|
3 | 3 |
(defvar *accounts* ()) |
4 |
- |
|
4 |
+(defvar *main-credentials* (aws-sdk:make-credentials)) |
|
5 | 5 |
(defun session-name () |
6 | 6 |
(format nil "bootstrap~d" (+ 5000 (random 5000)))) |
7 | 7 |
|
... | ... |
@@ -27,6 +27,7 @@ |
27 | 27 |
(mfa-serial-number *user_management_account_id* |
28 | 28 |
user)) |
29 | 29 |
(role-arn (role-arn account role))) |
30 |
+ (let ((aws-sdk:*session* (aws-sdk:make-session :credentials *main-credentials*)))) |
|
30 | 31 |
(loop |
31 | 32 |
(restart-case |
32 | 33 |
(return |
... | ... |
@@ -72,6 +73,13 @@ |
72 | 73 |
(url :reader url |
73 | 74 |
:initform (cells:c? (get-url (^url-params)))))) |
74 | 75 |
|
76 |
+(defgeneric session-credentials (source) |
|
77 |
+ (:method ((source sts-result-handler)) |
|
78 |
+ (aws-sdk:make-credentials |
|
79 |
+ :access-key-id (session-id source) |
|
80 |
+ :secret-access-key (session-key source) |
|
81 |
+ :session-token (session-token source)))) |
|
82 |
+ |
|
75 | 83 |
(defun url-from-signin-token (signin-token) |
76 | 84 |
(format nil "https://signin.aws.amazon.com/federation?Action=login&Destination=https%3A%2F%2Fconsole.aws.amazon.com&SigninToken=~a" |
77 | 85 |
signin-token)) |
... | ... |
@@ -7,25 +7,29 @@ |
7 | 7 |
|
8 | 8 |
(defparameter *developer-p* (equal "elangley" (uiop/os:getenv "USER"))) |
9 | 9 |
|
10 |
-(defun bundle-resource-root () |
|
11 |
- (make-pathname :directory |
|
12 |
- (pathname-directory |
|
13 |
- (objc:invoke-into 'string |
|
14 |
- (objc:invoke "NSBundle" "mainBundle") |
|
15 |
- "pathForResource:ofType:" "app" "icns")))) |
|
10 |
+(defgeneric assumed-credentials (store)) |
|
11 |
+(defgeneric (setf assumed-credentials) (value store)) |
|
16 | 12 |
|
17 |
-(defun clear-cookies () |
|
18 |
- (let ((cookie-storage (objc:invoke "NSHTTPCookieStorage" "sharedHTTPCookieStorage"))) |
|
19 |
- (map nil |
|
20 |
- (lambda (cookie) |
|
21 |
- (objc:invoke cookie-storage "deleteCookie:" cookie)) |
|
22 |
- (objc:invoke-into 'array cookie-storage "cookies")))) |
|
13 |
+(defun current-account (interface) |
|
14 |
+ (cdr (capi:choice-selected-item (account-selector interface)))) |
|
15 |
+ |
|
16 |
+(defun credentials-for-account (interface account) |
|
17 |
+ (gethash account |
|
18 |
+ (assumed-credentials interface))) |
|
19 |
+(defun (setf credentials-for-account) (new-credentials interface account) |
|
20 |
+ (setf (gethash account |
|
21 |
+ (assumed-credentials interface)) |
|
22 |
+ new-credentials)) |
|
23 |
+ |
|
24 |
+(defun current-credentials (interface) |
|
25 |
+ (credentials-for-account interface |
|
26 |
+ (current-account interface))) |
|
23 | 27 |
|
24 | 28 |
(defun go-on (_ interface) |
25 | 29 |
(declare (ignore _)) |
26 | 30 |
(let ((token (capi:text-input-pane-text (mfa-input interface))) |
27 | 31 |
(user-name (capi:text-input-pane-text (user-input interface))) |
28 |
- (account (cdr (capi:choice-selected-item (account-selector interface))))) |
|
32 |
+ (account (current-account interface))) |
|
29 | 33 |
(clear-cookies) |
30 | 34 |
(multiple-value-bind (signin-token creds) |
31 | 35 |
(handler-bind (((or dexador:http-request-forbidden |
... | ... |
@@ -58,8 +62,8 @@ |
58 | 62 |
(session-token creds))) |
59 | 63 |
(capi:set-button-panel-enabled-items (slot-value interface 'action-buttons) |
60 | 64 |
:set t) |
61 |
- (setf (signin-url interface) |
|
62 |
- (url-from-signin-token signin-token))))) |
|
65 |
+ (setf (credentials-for-account interface account) (session-credentials creds) |
|
66 |
+ (signin-url interface) (url-from-signin-token signin-token))))) |
|
63 | 67 |
|
64 | 68 |
(defun close-active-screen () |
65 | 69 |
(let ((active-interface |
66 | 70 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
1 |
+(in-package :mfa-tool) |
|
2 |
+ |
|
3 |
+(defun bundle-resource-root () |
|
4 |
+ (make-pathname :directory |
|
5 |
+ (pathname-directory |
|
6 |
+ (objc:invoke-into 'string |
|
7 |
+ (objc:invoke "NSBundle" "mainBundle") |
|
8 |
+ "pathForResource:ofType:" "app" "icns")))) |
|
9 |
+ |
|
10 |
+(defun clear-cookies () |
|
11 |
+ (let ((cookie-storage (objc:invoke "NSHTTPCookieStorage" "sharedHTTPCookieStorage"))) |
|
12 |
+ (map nil |
|
13 |
+ (lambda (cookie) |
|
14 |
+ (objc:invoke cookie-storage "deleteCookie:" cookie)) |
|
15 |
+ (objc:invoke-into 'array cookie-storage "cookies")))) |
0 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,43 @@ |
1 |
+(defpackage :mfa-tool.stack-store |
|
2 |
+ (:use :cl) |
|
3 |
+ (:export #:stack-store #:available-stacks selected-stack parameters outputs)) |
|
4 |
+(in-package :mfa-tool.stack-store) |
|
5 |
+ |
|
6 |
+(defun column (key) |
|
7 |
+ (serapeum:op (serapeum:assocadr key _ |
|
8 |
+ :test 'equal))) |
|
9 |
+ |
|
10 |
+(defclass stack-store (mfa-tool.store:store mfa-tool.aws-dispatcher:aws-dispatcher) |
|
11 |
+ ((%available-stacks :accessor available-stacks :initform nil) |
|
12 |
+ (%selected-stack :accessor selected-stack :initform nil) |
|
13 |
+ (%parameters :accessor parameters :initform nil) |
|
14 |
+ (%outputs :accessor outputs :initform nil))) |
|
15 |
+ |
|
16 |
+ |
|
17 |
+(defun output-columns (output) |
|
18 |
+ (funcall (data-lens:juxt (column "OutputKey") |
|
19 |
+ (column "OutputValue")) |
|
20 |
+ output)) |
|
21 |
+ |
|
22 |
+(defun parameter-columns (output) |
|
23 |
+ (funcall (data-lens:juxt (column "ParameterKey") |
|
24 |
+ (column "ParameterValue")) |
|
25 |
+ output)) |
|
26 |
+ |
|
27 |
+(defmethod mfa-tool.store:dispatch :after ((store stack-store) |
|
28 |
+ (action mfa-tool.aws-dispatcher:update-stacks)) |
|
29 |
+ (alexandria:when-let ((stack (car (mfa-tool.aws-dispatcher:stacks action)))) |
|
30 |
+ (mfa-tool.store:dispatch store (mfa-tool.aws-dispatcher:select-stack stack)))) |
|
31 |
+ |
|
32 |
+(defmethod mfa-tool.store:execute ((store stack-store) (action mfa-tool.aws-dispatcher:update-stacks)) |
|
33 |
+ (setf (available-stacks store) (sort (mfa-tool.aws-dispatcher:stacks action) |
|
34 |
+ 'string-lessp |
|
35 |
+ :key 'daydreamer.aws-result:stack-name))) |
|
36 |
+ |
|
37 |
+(defmethod mfa-tool.store:execute ((store stack-store) (action mfa-tool.aws-dispatcher:select-stack)) |
|
38 |
+ (let ((stack (mfa-tool.aws-dispatcher:stack action))) |
|
39 |
+ (setf (selected-stack store) stack |
|
40 |
+ |
|
41 |
+ (parameters store) (mapcar 'parameter-columns (daydreamer.aws-result:parameters stack)) |
|
42 |
+ |
|
43 |
+ (outputs store) (mapcar 'output-columns (daydreamer.aws-result:outputs stack))))) |
0 | 44 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,144 @@ |
1 |
+(in-package :mfa-tool) |
|
2 |
+(defpackage :mfa-tool.stack |
|
3 |
+ (:use :cl) |
|
4 |
+ (:export #:stack-interface |
|
5 |
+ #:format-stack-status)) |
|
6 |
+(in-package :mfa-tool.stack) |
|
7 |
+ |
|
8 |
+(defun dispatch-with-action-creator (action-creator) |
|
9 |
+ (lambda (store data) |
|
10 |
+ (mfa-tool.store:dispatch store |
|
11 |
+ (funcall action-creator data)))) |
|
12 |
+ |
|
13 |
+(defmacro with-pp ((pane) &body body) |
|
14 |
+ `(capi:apply-in-pane-process ,pane |
|
15 |
+ (lambda () |
|
16 |
+ ,@body))) |
|
17 |
+ |
|
18 |
+(defun human-readable-stack-status (stack) |
|
19 |
+ (nstring-capitalize |
|
20 |
+ (substitute #\space #\_ |
|
21 |
+ (string (daydreamer.aws-result:stack-status stack))))) |
|
22 |
+(defun format-stack-status (stream stack &optional colon-p at-sign-p) |
|
23 |
+ (declare (ignore colon-p at-sign-p)) |
|
24 |
+ (princ (human-readable-stack-status stack) |
|
25 |
+ stream)) |
|
26 |
+ |
|
27 |
+(defun get-output-columns (type col1 col2) |
|
28 |
+ `((:title ,(format nil "~a Name" type) |
|
29 |
+ :adjust :right |
|
30 |
+ :width (character ,(max (+ (length type) 5) col1))) |
|
31 |
+ (:title "Value" |
|
32 |
+ :adjust :left |
|
33 |
+ :width (character ,(max (+ (length type) 5) col2))))) |
|
34 |
+ |
|
35 |
+(capi:define-interface stack-interface (capi:interface mfa-tool.stack-store:stack-store) |
|
36 |
+ () |
|
37 |
+ (:panes |
|
38 |
+ (region-chooser capi:option-pane |
|
39 |
+ :reader region-chooser |
|
40 |
+ ;; :external-max-width '(character 35) |
|
41 |
+ :items (list "us-east-1" "us-east-2" |
|
42 |
+ "us-west-1" "us-west-2" |
|
43 |
+ "ca-central-1" |
|
44 |
+ "eu-central-1" |
|
45 |
+ "eu-west-1" "eu-west-2") |
|
46 |
+ :selection-callback (dispatch-with-action-creator 'mfa-tool.aws-dispatcher::update-region) |
|
47 |
+ :callback-type :interface-data) |
|
48 |
+ |
|
49 |
+ (stack-chooser capi:list-panel |
|
50 |
+ :reader stack-chooser |
|
51 |
+ ;; :external-max-width '(character 35) |
|
52 |
+ :items () |
|
53 |
+ :print-function 'daydreamer.aws-result:stack-name |
|
54 |
+ :selection-callback (dispatch-with-action-creator 'mfa-tool.aws-dispatcher:select-stack) |
|
55 |
+ :callback-type :interface-data) |
|
56 |
+ |
|
57 |
+ (status-display capi:display-pane |
|
58 |
+ :background :transparent |
|
59 |
+ :reader status-display |
|
60 |
+ :text "") |
|
61 |
+ |
|
62 |
+ (outputs-display capi:multi-column-list-panel |
|
63 |
+ :columns (get-output-columns "Output" 10 10) |
|
64 |
+ :header-args (list :selection-callback :sort) |
|
65 |
+ :sort-descriptions (list (capi:make-sorting-description |
|
66 |
+ :type "Output Name" |
|
67 |
+ :key 'car |
|
68 |
+ :sort 'string-lessp |
|
69 |
+ :reverse-sort 'string-greaterp) |
|
70 |
+ (capi:make-sorting-description |
|
71 |
+ :type "Value" |
|
72 |
+ :key 'cadr |
|
73 |
+ :sort 'string-lessp |
|
74 |
+ :reverse-sort 'string-greaterp)) |
|
75 |
+ :items nil |
|
76 |
+ :vertical-scroll t |
|
77 |
+ :reader outputs-display |
|
78 |
+ :visible-min-height '(character 10) |
|
79 |
+ :visible-min-width '(character 50)) |
|
80 |
+ (parameters-display capi:multi-column-list-panel |
|
81 |
+ :columns (get-output-columns "Parameter" 10 10) |
|
82 |
+ :header-args (list :selection-callback :sort) |
|
83 |
+ :sort-descriptions (list (capi:make-sorting-description |
|
84 |
+ :type "Parameter Name" |
|
85 |
+ :key 'car |
|
86 |
+ :sort 'string-lessp |
|
87 |
+ :reverse-sort 'string-greaterp) |
|
88 |
+ (capi:make-sorting-description |
|
89 |
+ :type "Value" |
|
90 |
+ :key 'cadr |
|
91 |
+ :sort 'string-lessp |
|
92 |
+ :reverse-sort 'string-greaterp)) |
|
93 |
+ :items nil |
|
94 |
+ :vertical-scroll t |
|
95 |
+ :reader parameters-display |
|
96 |
+ :visible-min-height '(character 10) |
|
97 |
+ :visible-min-width '(character 50))) |
|
98 |
+ (:layouts |
|
99 |
+ (key-layout capi:column-layout |
|
100 |
+ '(region-chooser |
|
101 |
+ stack-chooser) |
|
102 |
+ :visible-max-width '(character 35)) |
|
103 |
+ (attribute-layout capi:column-layout |
|
104 |
+ '(status-display |
|
105 |
+ parameters-display |
|
106 |
+ outputs-display)) |
|
107 |
+ (main-layout capi:row-layout |
|
108 |
+ '(key-layout |
|
109 |
+ attribute-layout))) |
|
110 |
+ (:default-initargs |
|
111 |
+ :layout 'main-layout |
|
112 |
+ :title "Stack Explorer" |
|
113 |
+ :visible-min-width 800)) |
|
114 |
+ |
|
115 |
+(defmethod mfa-tool.store:execute :after ((interface stack-interface) (_ mfa-tool.aws-dispatcher:update-stacks)) |
|
116 |
+ (with-pp (interface) |
|
117 |
+ (with-accessors ((stack-chooser stack-chooser)) interface |
|
118 |
+ (setf (capi:collection-items stack-chooser) |
|
119 |
+ (mfa-tool.stack-store:available-stacks interface))))) |
|
120 |
+ |
|
121 |
+(defun max-widths (cols) |
|
122 |
+ (loop for (col1 col2) in cols |
|
123 |
+ maximizing (length col1) into len1 |
|
124 |
+ maximizing (length col2) into len2 |
|
125 |
+ finally (return (list len1 len2)))) |
|
126 |
+ |
|
127 |
+(defmethod mfa-tool.store:execute :after ((interface stack-interface) (_ mfa-tool.aws-dispatcher:select-stack)) |
|
128 |
+ (with-pp (interface) |
|
129 |
+ (with-accessors ((status-display status-display) (selected-stack mfa-tool.stack-store:selected-stack) |
|
130 |
+ (outputs-display outputs-display) (outputs mfa-tool.stack-store:outputs) |
|
131 |
+ (parameters-display parameters-display) (parameters mfa-tool.stack-store:parameters)) |
|
132 |
+ interface |
|
133 |
+ (capi:modify-multi-column-list-panel-columns |
|
134 |
+ outputs-display :columns (apply 'get-output-columns "Output" (max-widths outputs))) |
|
135 |
+ (capi:modify-multi-column-list-panel-columns |
|
136 |
+ parameters-display :columns (apply 'get-output-columns "Parameter" (max-widths parameters))) |
|
137 |
+ |
|
138 |
+ (setf (capi:display-pane-text status-display) |
|
139 |
+ (format nil "~a: ~/mfa-tool.stack:format-stack-status/" |
|
140 |
+ (daydreamer.aws-result:stack-name selected-stack) |
|
141 |
+ selected-stack) |
|
142 |
+ (capi:collection-items parameters-display) parameters |
|
143 |
+ (capi:collection-items outputs-display) outputs)))) |
|
144 |
+ |
0 | 145 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+(defpackage :mfa-tool.store |
|
2 |
+ (:use :cl) |
|
3 |
+ (:export #:store #:execute #:dispatch)) |
|
4 |
+(in-package :mfa-tool.store) |
|
5 |
+ |
|
6 |
+(defclass store () |
|
7 |
+ ()) |
|
8 |
+ |
|
9 |
+(defgeneric execute (store action) |
|
10 |
+ (:argument-precedence-order action store) |
|
11 |
+ (:method :around (store action) |
|
12 |
+ (call-next-method) |
|
13 |
+ store) |
|
14 |
+ (:method (store action) |
|
15 |
+ store)) |
|
16 |
+ |
|
17 |
+(defgeneric dispatch (store action) |
|
18 |
+ (:argument-precedence-order action store) |
|
19 |
+ (:method ((store store) action) |
|
20 |
+ (execute store action))) |
|
0 | 21 |
\ No newline at end of file |