summaryrefslogtreecommitdiff
path: root/lisp/aws
diff options
context:
space:
mode:
authorBret Koppel <bret@olo.com>2025-10-07 09:00:37 -0400
committerGitHub <noreply@github.com>2025-10-07 09:00:37 -0400
commit910d7fd656b9e957181732b36761449de5a970d1 (patch)
tree0f8ee46af3e93aa489785d64d54c5e155ef0057b /lisp/aws
parent9adba239b937df2830a5110adaa1dae17b7dd7d7 (diff)
parent0ad86ba37be273ee1fe4d9bdd5f7bb15c5701abf (diff)
Merge pull request #1 from ololabs/initial-commit
initial commit
Diffstat (limited to 'lisp/aws')
-rw-r--r--lisp/aws/aws.lisp221
1 files changed, 221 insertions, 0 deletions
diff --git a/lisp/aws/aws.lisp b/lisp/aws/aws.lisp
new file mode 100644
index 0000000..f5d6b93
--- /dev/null
+++ b/lisp/aws/aws.lisp
@@ -0,0 +1,221 @@
+;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+(declaim (optimize (speed 0) (safety 3) (debug 3)))
+
+(in-package :snow)
+
+(defclass aws ()
+ ((profile :initarg :profile
+ :initform nil
+ :accessor profile)
+ (region :initarg :region
+ :initform (region *webapp*)
+ :accessor region))
+ (:documentation ""))
+
+(defclass aws-tag ()
+ ((key :initarg :key
+ :initform nil
+ :accessor key)
+ (value :initarg :value
+ :initform nil
+ :accessor value))
+ (:documentation ""))
+
+(defclass aws-asg (aws)
+ ((name :initarg :name
+ :initform nil
+ :accessor name)
+ (arn :initarg :arn
+ :initform nil
+ :accessor arn)
+ (count-min :initarg :count-min
+ :initform nil
+ :accessor count-min)
+ (count-max :initarg :count-max
+ :initform nil
+ :accessor count-max)
+ (count-desired :initarg :count-desired
+ :initform nil
+ :accessor count-desired)
+ (instances :initarg :instances
+ :initform nil
+ :accessor instances)
+ (howmany :initarg :howmany
+ :initform nil
+ :accessor howmany))
+ (:documentation ""))
+
+(defclass aws-certificate (aws)
+ ((arn :initarg :arn
+ :initform nil
+ :accessor arn)
+ (domain-name :initarg :domain-name
+ :initform nil
+ :accessor domain-name)
+ (status :initarg :status
+ :initform nil
+ :accessor status)
+ (renewal-eligibility :initarg :renewal-eligibility
+ :initform nil
+ :accessor renewal-eligibility)
+ (not-before :initarg :not-before
+ :initform nil
+ :accessor not-before)
+ (not-after :initarg :not-after
+ :initform nil
+ :accessor not-after)
+ (tags :initarg :tags
+ :initform nil
+ :accessor tags)
+ (cert :initarg :cert
+ :initform nil
+ :accessor cert)
+ (chain :initarg :chain
+ :initform nil
+ :accessor chain)
+ (key :initarg :key
+ :initform nil
+ :accessor key)
+ (passphrase :initarg :passphrase
+ :initform nil
+ :accessor passphrase))
+ (:documentation ""))
+
+(defclass aws-ssm-parameter (aws)
+ ((name :initarg :name
+ :initform nil
+ :accessor name)
+ (value :initarg :value
+ :initform nil
+ :accessor value))
+ (:documentation ""))
+
+(defmethod sanitize-json ((aws-asg aws-asg))
+ (make-instance 'aws-asg
+ :profile nil
+ :region nil
+ :name (name aws-asg)
+ :count-min (count-min aws-asg)
+ :count-max (count-max aws-asg)
+ :count-desired (count-desired aws-asg)
+ :instances (instances aws-asg)
+ :howmany nil))
+
+(defmethod run-aws-cli ((aws aws) command)
+ "Runs an awscli command and returns the results as CL-JSON object."
+ (let ((output (make-array '(0) :element-type 'character :fill-pointer 0 :adjustable t)))
+ (with-output-to-string (stream output)
+ (uffi:run-shell-command (format nil
+ "~a --profile ~a --region ~a"
+ command
+ (profile aws)
+ (region aws))
+ :output stream))
+ (if (org-ckons-core::null-or-empty-p output)
+ output
+ (cl-json:decode-json-from-string output))))
+
+(defmethod get-asgs ((aws aws) filter)
+ (let ((command "aws autoscaling describe-auto-scaling-groups"))
+ (loop for asg in (cdar (run-aws-cli aws command))
+ when (org-ckons-core::match-it filter (cdr (assoc :*auto-scaling-group-name asg)))
+ collect (make-instance 'aws-asg
+ :profile (profile aws)
+ :region (region aws)
+ :name (cdr (assoc :*auto-scaling-group-name asg))
+ :arn (cdr (assoc :*auto-scaling-group-+arn+ asg))
+ :count-min (cdr (assoc :*min-size asg))
+ :count-max (cdr (assoc :*max-size asg))
+ :count-desired (cdr (assoc :*desired-capacity asg))
+ :instances (loop for instance in (cdr (assoc :*instances asg))
+ collect (cdr (assoc :*instance-id instance)))))))
+
+(defmethod get-certificates ((aws aws) &optional filter)
+ (sort (loop for cert in (cdr (assoc :*certificate-summary-list (run-aws-cli aws "aws acm list-certificates")))
+ when (or (and (null filter)
+ (string= (cdr (assoc :*status cert)) "ISSUED"))
+ (string= filter (cdr (assoc :*domain-name cert))))
+ collect (make-instance 'aws-certificate
+ :profile (profile aws)
+ :region (region aws)
+ :arn (cdr (assoc :*certificate-arn cert))
+ :domain-name (cdr (assoc :*domain-name cert))
+ :status (cdr (assoc :*status cert))
+ :renewal-eligibility (cdr (assoc :*renewal-eligibility cert))
+ :not-before (cdr (assoc :*not-before cert))
+ :not-after (cdr (assoc :*not-after cert))))
+ (lambda (x y) (string< (domain-name x) (domain-name y)))))
+
+(defmethod get-certificate-tags ((aws-certificate aws-certificate))
+ (setf (tags aws-certificate) (sort (loop for tag in (cdr (assoc :*tags (run-aws-cli aws-certificate (format nil "aws acm list-tags-for-certificate --certificate-arn ~a" (arn aws-certificate)))))
+ collect (make-instance 'aws-tag
+ :key (cdr (assoc :*key tag))
+ :value (cdr (assoc :*value tag))))
+ (lambda (x y) (string< (key x) (key y))))))
+
+(defun tag-exists-p (cert key value)
+ (find-if (lambda (x)
+ (and (string= (key x) key)
+ (string= (value x) value)))
+ (tags cert)))
+
+(defun get-certificates-to-renew (certs environment)
+ (loop for cert in certs
+ do (get-certificate-tags cert))
+ (loop for cert in certs
+ when (and (or (tag-exists-p cert "Purpose" "KafkaAuth")
+ (tag-exists-p cert "Purpose" "OloAuthSigningKey"))
+ (tag-exists-p cert "Environment" environment))
+ collect cert))
+
+(defmethod bg-perform ((aws-asg aws-asg))
+ (labels ((do-scale (count-desired)
+ (let ((command (format nil
+ "aws autoscaling set-desired-capacity --auto-scaling-group-name ~a --desired-capacity ~a"
+ (name aws-asg)
+ count-desired)))
+ (run-aws-cli aws-asg command)))
+ (instances-ready-p ()
+ (let ((updated-asg (car (get-asgs aws-asg (name aws-asg))))
+ (in-service-p t))
+ (loop for id in (instances updated-asg)
+ do (let* ((command (format nil "aws autoscaling describe-auto-scaling-instances --instance-id ~a" id))
+ (instance (cadar (run-aws-cli updated-asg command))))
+ (when (not (string= (cdr (assoc :*lifecycle-state instance)) "InService"))
+ (setf in-service-p nil))))
+ in-service-p)))
+ (let ((count (cond ((string= (howmany aws-asg) "all") (count-desired aws-asg))
+ ((string= (howmany aws-asg) "one") 1)
+ (t 0))))
+ (loop for index from 1 to count
+ do (progn
+ (do-scale (+ (count-desired aws-asg) 1))
+ (sleep 15)
+ (do-scale (count-desired aws-asg))
+ (sleep 15)
+ (loop while (not (instances-ready-p))
+ do (sleep 30)))))))
+
+(defmethod recycle-asg ((aws-asg aws-asg))
+ (enqueue *queue-aws* aws-asg))
+
+(defmethod bg-perform ((aws-certificate aws-certificate))
+ )
+
+(defmethod renew-certificates ((aws-certificate aws-certificate) environment filter)
+ (loop for cert in (get-certificates-to-renew (get-certificates aws-certificate filter) environment)
+ do (let* ((passphrase (cl-base64:string-to-base64-string (org-ckons-session::generate-sessionid)))
+ (exported-cert (run-aws-cli aws-certificate (format nil "aws acm export-certificate --certificate-arn ~a --passphrase ~a --output json" (arn cert) passphrase))))
+ (setf (cert cert) (cdr (assoc :*certificate exported-cert)))
+ (setf (chain cert) (cdr (assoc :*certificate-chain exported-cert)))
+ (setf (key cert) (cdr (assoc :*private-key exported-cert)))
+ (setf (passphrase cert) passphrase))))
+ ;;do (enqueue *queue-aws* cert)))
+
+(defun get-aws-ssm-parameter (profile key)
+ (let* ((aws-ssm-parameter (make-instance 'aws-ssm-parameter :profile profile :region "us-east-1"))
+ (command (format nil "aws ssm get-parameter --with-decryption --name \"~a\"" key))
+ (results (run-aws-cli aws-ssm-parameter command)))
+ (setf (name aws-ssm-parameter) (cdr (assoc :*name (cdar results))))
+ (setf (value aws-ssm-parameter) (cdr (assoc :*value (cdar results))))
+ aws-ssm-parameter))