- 
      
 - 
        
Save dogenpunk/0f291f45baee7ee34c6dbe9d21dedc5a to your computer and use it in GitHub Desktop.  
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | {:allow [arthur.core/hello-world-web | |
| arthur.core/echo-web] | |
| :lambdas {:hello-world-web | |
| {:fn arthur.core/hello-world-web | |
| :integration :api-gateway/proxy | |
| :api-gateway/resources [{:path-part "hello-world" | |
| :methods [{:http-method "ANY" | |
| :authorization-type "NONE" | |
| :integration {:timeout-in-millis 29000}}]}]} | |
| :echo-web | |
| {:fn arthur.core/echo-web | |
| :integration :api-gateway/proxy | |
| :api-gateway/resources [{:path-part "echo" | |
| :methods [{:http-method "ANY" | |
| :authorization-type "NONE" | |
| :integration {:timeout-in-millis 29000}}]}]}} | |
| :app-name "og-condense-sandbox"} | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | (ns user | |
| (:require [datomic.ion.dev :as ion-dev] | |
| [clojure.java.io :as io] | |
| [clojure.edn :as edn] | |
| [clojure.java.shell :as shell] | |
| [clojure.data.json :as json] | |
| [clojure.string :as string])) | |
| (def ion-config (edn/read-string (slurp "resources/datomic/ion-config.edn"))) | |
| (def stage-name "dev") | |
| (defn submap? | |
| ([a] (partial submap? a)) | |
| ([a b] (= a (select-keys b (keys a))))) | |
| (defn find-by [pred ms] (first (filter pred ms))) | |
| (defn aws [& args] | |
| (apply println "aws" args) | |
| (let [ret (apply shell/sh "aws" args)] | |
| (when (= 0 (:exit ret)) | |
| (json/read-str (:out ret) :key-fn keyword :eof-error? false)))) | |
| (defn stack-outputs [stack] | |
| (into {} (map (juxt (comp keyword :OutputKey) :OutputValue) (:Outputs stack)))) | |
| (defn get-rest-apis [] | |
| (:items (aws "apigateway" "get-rest-apis"))) | |
| (defn create-rest-api [] | |
| (aws "apigateway" "create-rest-api" | |
| "--name" (:app-name ion-config) | |
| "--binary-media-types" "*/*" | |
| "--endpoint-configuration" "{\"types\" : [\"REGIONAL\"]}")) | |
| (defn find-rest-api | |
| [] | |
| (find-by (submap? {:name (:app-name ion-config)}) (get-rest-apis))) | |
| (defn upsert-rest-api | |
| [] | |
| (let [rest-apis (get-rest-apis) | |
| name (:app-name ion-config) | |
| matches (filter (submap? {:name name}) rest-apis) | |
| [match & extra-matches] matches] | |
| (when extra-matches | |
| (throw (ex-info "Multiple matching rest-apis" {:name name :matches matches}))) | |
| (or match (create-rest-api)))) | |
| (defn get-resources | |
| [rest-api] | |
| (:items (aws "apigateway" "get-resources" "--rest-api-id" (:id rest-api)))) | |
| (defn sync-rest-api | |
| "Setup REST API based on ion-config via accreate-only changes. Reports if breaking changes are required." | |
| [] | |
| (let [stacks (aws "cloudformation" "describe-stacks") | |
| app-stack (find-by (submap? {:StackName (:app-name ion-config)}) (:Stacks stacks)) | |
| [_ _ _ region client-id & _] (string/split (:StackId app-stack) #":") | |
| {:keys [CodeDeployDeploymentGroup]} (stack-outputs app-stack) | |
| rest-api (upsert-rest-api) | |
| resources (get-resources rest-api) | |
| rest-api-id (:id rest-api) | |
| parent-id (:id (find-by (submap? {:path "/"}) resources))] | |
| (doseq [[lambda-name lambda-config] (:lambdas ion-config) | |
| :when (submap? {:integration :api-gateway/proxy} lambda-config) | |
| {:keys [path-part methods]} (:api-gateway/resources lambda-config)] | |
| (let [resource (or (find-by (submap? {:parentId parent-id :pathPart path-part}) resources) | |
| (aws "apigateway" "create-resource" | |
| "--rest-api-id" rest-api-id | |
| "--parent-id" parent-id | |
| "--path-part" path-part)) | |
| function-name (str CodeDeployDeploymentGroup "-" (name lambda-name)) | |
| function (aws "lambda" "get-function" "--function-name" function-name) | |
| FunctionArn (get-in function [:Configuration :FunctionArn]) | |
| ] | |
| (let [kvs {}] | |
| (when-not (submap? kvs resource) | |
| (throw (ex-info "resource incompatible with config" {:kvs kvs :resource resource})))) | |
| (doseq [{:keys [http-method authorization-type]} methods] | |
| (if-let [method (aws "apigateway" "get-method" | |
| "--rest-api-id" rest-api-id | |
| "--resource-id" (:id resource) | |
| "--http-method" http-method)] | |
| (let [kvs {:httpMethod http-method :authorizationType authorization-type}] | |
| (when-not (submap? kvs method) | |
| (throw (ex-info "method incompatible with config" {:kvs kvs :method method})))) | |
| (aws "apigateway" "put-method" | |
| "--rest-api-id" rest-api-id | |
| "--resource-id" (:id resource) | |
| "--http-method" http-method | |
| "--authorization-type" authorization-type)) | |
| (if-let [integration (aws "apigateway" "get-integration" | |
| "--rest-api-id" rest-api-id | |
| "--resource-id" (:id resource) | |
| "--http-method" http-method)] | |
| (let [kvs {}] | |
| (when-not (submap? kvs integration) | |
| (throw (ex-info "method incompatible with config" {:kvs kvs :integration integration})))) | |
| (let [service_api (str "2015-03-31/functions/" FunctionArn "/invocations") ; TODO: Okay to hardcode this date? | |
| uri (str "arn:aws:apigateway:" region ":lambda:path/" service_api)] | |
| (aws "apigateway" "put-integration" | |
| "--rest-api-id" rest-api-id | |
| "--resource-id" (:id resource) | |
| "--http-method" http-method | |
| "--type" "AWS_PROXY" | |
| "--integration-http-method" "POST" | |
| "--timeout-in-millis" "29000" | |
| "--uri" uri)))) | |
| (let [source-arn (str "arn:aws:execute-api:" region ":" client-id ":" rest-api-id "/*/*/" path-part) | |
| statement-id (str "ion-" (hash (str function-name source-arn))) | |
| {:keys [Policy]} (aws "lambda" "get-policy" "--function-name" function-name) | |
| policy-data (when Policy (json/read-str Policy :key-fn keyword)) | |
| statement (find-by (submap? {:Sid statement-id}) (:Statement policy-data))] | |
| (if statement | |
| (let [kvs {:Condition {:ArnLike source-arn} | |
| :Resource FunctionArn}] | |
| (when-not (submap? kvs statement) | |
| (throw (ex-info "statement incompatible with config" {:kvs kvs :statement statement})))) | |
| (aws "lambda" "add-permission" | |
| "--function-name" function-name | |
| "--action" "lambda:InvokeFunction" | |
| "--principal" "apigateway.amazonaws.com" | |
| "--statement-id" statement-id | |
| "--source-arn" source-arn))))))) | |
| (defn deploy-api [] | |
| (let [rest-api (find-rest-api)] | |
| (aws "apigateway" "create-deployment" "--rest-api-id" (:id rest-api) "--stage-name" stage-name))) | |
| (defn deploy-uri [] | |
| (let [stacks (aws "cloudformation" "describe-stacks") | |
| app-stack (find-by (submap? {:StackName (:app-name ion-config)}) (:Stacks stacks)) | |
| [_ _ _ region client-id & _] (string/split (:StackId app-stack) #":") | |
| {:keys [id]} (find-rest-api)] | |
| (str "https://" id ".execute-api." region ".amazonaws.com/" stage-name "/"))) | |
| (defn compute-group | |
| [] | |
| (let [stacks (aws "cloudformation" "describe-stacks") | |
| app-stack (find-by (submap? {:StackName (:app-name ion-config)}) (:Stacks stacks)) | |
| {:keys [CodeDeployDeploymentGroup]} (stack-outputs app-stack)] | |
| CodeDeployDeploymentGroup)) | |
| (defn release | |
| "Do push and deploy of app. Supports stable and unstable releases. Returns when deploy finishes running." | |
| [args] | |
| (try | |
| (let [group (compute-group) | |
| push-data (ion-dev/push args) | |
| deploy-args (merge (select-keys args [:creds-profile :region :uname]) | |
| (select-keys push-data [:rev]) | |
| {:group group})] | |
| (let [deploy-data (ion-dev/deploy deploy-args) | |
| deploy-status-args (merge (select-keys args [:creds-profile :region]) | |
| (select-keys deploy-data [:execution-arn]))] | |
| (loop [] | |
| (let [status-data (ion-dev/deploy-status deploy-status-args)] | |
| (if (= "RUNNING" (:code-deploy-status status-data)) | |
| (do (Thread/sleep 5000) (recur)) | |
| status-data))))) | |
| (catch Exception e | |
| {:deploy-status "ERROR" | |
| :message (.getMessage e)}))) | |
| (comment | |
| (release {}) | |
| (sync-rest-api) | |
| (deploy-api) | |
| (deploy-uri)) | |
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment