|
(ns deploy.api-release |
|
(:require [clojure.data.json :as json] |
|
[clojure.string :as string] |
|
[deploy.aws-utils :refer [aws find-by submap? get-stack-context]]) |
|
(:import (java.util UUID))) |
|
|
|
(defn sync-permission |
|
"Setup permission based via accreate-only changes" |
|
[region client-id rest-api-id path-part function-name FunctionArn] |
|
(let [source-arn (str "arn:aws:execute-api:" region ":" client-id ":" rest-api-id "/*/*/" path-part) |
|
{:keys [Policy]} (aws "lambda" "get-policy" "--function-name" function-name) |
|
policy-data (when Policy (json/read-str Policy :key-fn keyword))] |
|
|
|
(when-not (find-by (submap? {:Condition {:ArnLike {:AWS:SourceArn source-arn}} |
|
:Resource FunctionArn}) |
|
(:Statement policy-data)) |
|
|
|
(aws "lambda" "add-permission" |
|
"--function-name" function-name |
|
"--action" "lambda:InvokeFunction" |
|
"--principal" "apigateway.amazonaws.com" |
|
"--statement-id" (.toString (UUID/randomUUID)) |
|
"--source-arn" source-arn)))) |
|
|
|
(defn sync-integration |
|
"Setup integration based via accreate-only changes" |
|
[region rest-api-id resource-id http-method function-arn timeout-in-millis] |
|
(when-not (aws "apigateway" "get-integration" |
|
"--rest-api-id" rest-api-id |
|
"--resource-id" resource-id |
|
"--http-method" http-method) |
|
|
|
(let [service_api (str "2015-03-31/functions/" function-arn "/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" resource-id |
|
"--http-method" http-method |
|
"--type" "AWS_PROXY" |
|
"--integration-http-method" "POST" |
|
"--timeout-in-millis" (str timeout-in-millis) |
|
"--uri" uri)))) |
|
|
|
(defn sync-method |
|
"Setup method based via accreate-only changes" |
|
[rest-api-id resource-id http-method authorization-type] |
|
(if-let [method (aws "apigateway" "get-method" |
|
"--rest-api-id" rest-api-id |
|
"--resource-id" resource-id |
|
"--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" resource-id |
|
"--http-method" http-method |
|
"--authorization-type" authorization-type))) |
|
|
|
(defn sync-resource |
|
"Setup resource based via accreate-only changes" |
|
[client-id region group rest-api-id api-resources parent-id path-part methods] |
|
(let [api-resource (or (find-by (submap? {:parentId parent-id :pathPart path-part}) api-resources) |
|
(aws "apigateway" "create-resource" |
|
"--rest-api-id" rest-api-id |
|
"--parent-id" parent-id |
|
"--path-part" path-part)) |
|
resource-id (:id api-resource)] |
|
|
|
(doseq [{:keys [http-method authorization-type integration]} methods] |
|
|
|
(let [{:keys [lambda-name timeout-in-millis]} integration |
|
function-name (str group "-" (name lambda-name)) |
|
function (aws "lambda" "get-function" "--function-name" function-name) |
|
function-arn (get-in function [:Configuration :FunctionArn])] |
|
|
|
(sync-method rest-api-id resource-id http-method authorization-type) |
|
(sync-integration region rest-api-id resource-id http-method function-arn timeout-in-millis) |
|
(sync-permission region client-id rest-api-id path-part function-name function-arn))))) |
|
|
|
(defn get-rest-api-id |
|
[app-name] |
|
(let [ids (aws "apigateway" "get-rest-apis" "--query" (str "items[?name==`" app-name "`].id"))] |
|
(when (next ids) |
|
(throw (ex-info "Multiple matching rest-apis" {:name app-name :rest-api-ids ids}))) |
|
(first ids))) |
|
|
|
(defn sync-rest-api |
|
"Setup REST API based via accreate-only changes" |
|
[api-config] |
|
(let [{:keys [app-name resources]} api-config |
|
{:keys [client-id region group]} (get-stack-context app-name) |
|
rest-api-id (or (get-rest-api-id app-name) |
|
(:id (aws "apigateway" "create-rest-api" |
|
"--name" app-name |
|
"--binary-media-types" "*/*" |
|
"--endpoint-configuration" "{\"types\" : [\"REGIONAL\"]}"))) |
|
api-resources (:items (aws "apigateway" "get-resources" "--rest-api-id" rest-api-id)) |
|
parent-id (:id (find-by (submap? {:path "/"}) api-resources))] |
|
|
|
(doseq [{:keys [path-part methods]} resources] |
|
(sync-resource client-id region group rest-api-id api-resources parent-id path-part methods)))) |
|
|
|
(defn deploy-uri |
|
[app-name stage-name] |
|
(let [stacks (aws "cloudformation" "describe-stacks") |
|
app-stack (find-by (submap? {:StackName app-name}) (:Stacks stacks)) |
|
[_ _ _ region & _] (string/split (:StackId app-stack) #":") |
|
rest-api-id (get-rest-api-id app-name)] |
|
(str "https://" rest-api-id ".execute-api." region ".amazonaws.com/" stage-name "/"))) |
|
|
|
(defn deploy-api |
|
[app-name stage-name] |
|
(let [rest-api-id (get-rest-api-id app-name)] |
|
(aws "apigateway" "create-deployment" "--rest-api-id" rest-api-id "--stage-name" stage-name))) |