This repository was archived by the owner on Jun 13, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathboot_lambda.clj
More file actions
192 lines (179 loc) · 11 KB
/
boot_lambda.clj
File metadata and controls
192 lines (179 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
(ns provisdom.boot-lambda
{:boot/export-tasks true}
(:require
[clojure.string :as str]
[clojure.java.io :as io]
[clojure.java.shell :as java-shell]
[boot.core :as core]
[boot.util :as util]))
(defn- cmd-opts->string
[cmd-opts init]
(reduce (fn [opts-str [k v]]
(if (nil? v)
opts-str
(format (if (instance? Boolean v) "%s --%s" "%s --%s %s") opts-str (name k) v))) init cmd-opts))
(defn- shell
[command]
(util/dosh "bash" "-c" command))
(defn- task-opts->cmd-opts
[{:keys [local-file] :as task-opts} input-files]
(cond-> task-opts
local-file (assoc :zip-file (let [files (core/by-path [local-file] input-files)]
(cond
(empty? files)
(throw (Exception. ^String (str "No files found for path " local-file)))
(> 1 (count files))
(throw (Exception. ^String (str "Multiple files found matching path " local-file)))
:else
(str "fileb://" (-> files first core/tmp-file .getAbsolutePath))))
:local-file nil)))
(defn- lambda-cmd-string
[input-files opts command & args]
(-> opts (task-opts->cmd-opts input-files) (cmd-opts->string (format "aws lambda %s" command)) (str " " (str/join " " args))))
(defn- last-lambda-update-status
[function-name]
(let [cmd-string (format "aws lambda get-function-configuration --function-name %s --query \"LastUpdateStatus\" --output text" function-name)]
(util/info (str cmd-string "\n"))
(-> (apply java-shell/sh (str/split cmd-string #" "))
(:out)
(str/trim))))
(defn- await-configuration-update-success
[function-name]
(let [retry-after-seconds 10
timeout-seconds 90] ; AWS docs say it should complete within a minute; give it a little longer (https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-configuration.html)
(loop [elapsed-seconds 0]
(when (>= elapsed-seconds timeout-seconds)
(throw (Exception. "Function configuration update did not complete in time\n")))
(let [status (last-lambda-update-status function-name)]
(case status
"InProgress" (do (util/info "Waiting until lambda function configuration has completed successfully...\n")
(Thread/sleep (* retry-after-seconds 1000))
(recur (+ elapsed-seconds retry-after-seconds)))
"Failed" (throw (Exception. "Function configuration update failed\n"))
"Successful" (util/info "Function configuration update was successful\n")
(throw (Exception. (str "Unexpected function configuration update status: " status))))))))
(core/deftask create-function
[f function-name VAL str "The name you want to assign to the function you are uploading"
r runtime VAL str "The runtime environment for the Lambda function you are uploading"
a region VAL str "AWS region"
i role VAL str "The ARN of the IAM role that Lambda assumes when it executes your function"
e handler VAL sym "The function within your code that Lambda calls to begin execution"
c code VAL str "The code for the Lambda function"
d description VAL str "A short, user-defined function description"
t timeout VAL int "The function execution time at which Lambda should terminate the function"
m memory-size VAL int "The amount of memory, in MB, your Lambda function is given"
p publish bool "This boolean parameter can be used to request AWS Lambda to create the Lambda function and publish a version as an atomic operation"
v vpc-config VAL str "Identifies the list of security group IDs and subnet IDs"
l local-file VAL str "The path to the local file of the code you are uploading"
j cli-input-json VAL str "Performs service operation based on the JSON string provided"
g generate-cli-skeleton bool "Prints a sample input JSON to standard output"]
(when-not (and function-name runtime role handler)
(throw (Exception. "Required function-name, runtime, role, and handler to create function")))
(core/with-pre-wrap fileset
(let [input-files (core/input-files fileset)
cmd-string (lambda-cmd-string input-files *opts* "create-function")]
(util/info "Creating lambda function...\n")
(util/info (str cmd-string "\n"))
(shell cmd-string))
fileset))
(core/deftask update-function-configuration
[f function-name VAL str "The name you want to assign to the function you are uploading"
r runtime VAL str "The runtime environment for the Lambda function you are uploading"
i role VAL str "The ARN of the IAM role that Lambda assumes when it executes your function"
e handler VAL sym "The function within your code that Lambda calls to begin execution"
d description VAL str "A short, user-defined function description"
t timeout VAL int "The function execution time at which Lambda should terminate the function"
m memory-size VAL int "The amount of memory, in MB, your Lambda function is given"
v vpc-config VAL str "Identifies the list of security group IDs and subnet IDs"
j cli-input-json VAL str "Performs service operation based on the JSON string provided"
g generate-cli-skeleton bool "Prints a sample input JSON to standard output"
_ environment VAL str "The parent object that contains your environment's configuration settings"
_ dead-letter-config VAL str "The parent object that contains the target ARN (Amazon Resource Name) of an Amazon SQS queue or Amazon SNS topic"
_ kms-key-arn VAL str "The Amazon Resource Name (ARN) of the KMS key used to encrypt your function's environment variables"
_ tracing-config VAL str "The parent object that contains your function's tracing settings"
_ revision-id VAL str "Used to ensure you are updating the latest update of the function version or alias"
_ layers VAL str "A string containing zero or more function layers specified by their ARNs and separated by a space"]
(when-not function-name
(throw (Exception. "Required function-name to update function configuration")))
(core/with-pre-wrap fileset
(let [input-files (core/input-files fileset)
cmd-string (lambda-cmd-string input-files *opts* "update-function-configuration")]
(util/info "Updating lambda function configuration...\n")
(util/info (str cmd-string "\n"))
(shell cmd-string)
(await-configuration-update-success function-name))
fileset))
(core/deftask update-function
[f function-name VAL str "The name you want to assign to the function you are uploading"
l local-file VAL str "The path to the local file of the code you are uploading"
a region VAL str "AWS region"
b s3-bucket VAL str "Amazon S3 bucket name where the .zip file containing your deployment package is stored"
k s3-key VAL str "The Amazon S3 object (the deployment package) key name you want to upload"
v s3-object-version VAL str "The Amazon S3 object (the deployment package) version you want to upload"
p publish bool "This boolean parameter can be used to request AWS Lambda to update the Lambda function and publish a version as an atomic operation"
j cli-input-json VAL str "Performs service operation based on the JSON string provided"
g generate-cli-skeleton bool "Prints a sample input JSON to standard output"]
(when-not function-name
(throw (Exception. "Required function-name to update function")))
(core/with-pre-wrap fileset
(let [input-files (core/input-files fileset)
cmd-string (lambda-cmd-string input-files *opts* "update-function-code")]
(util/info "Updating lambda function...\n")
(util/info (str cmd-string "\n"))
(shell cmd-string))
fileset))
(core/deftask invoke
[f function-name VAL str "The name you want to assign to the function you are uploading"
i invocation-type VAL str "You can optionally request asynchronous execution by specifying Event as the invocation-type"
p payload VAL str "JSON that you want to provide to your Lambda function as input."
l log-type VAL str "You can set this optional parameter to Tail in the request only if you specify the invocation-type parameter with value RequestResponse"
c client-context VAL str "Using the ClientContext you can pass client-specific information to the Lambda function you are invoking"
q qualifier VAL str "You can use this optional parameter to specify a Lambda function version or alias name"
o out-file VAL str "Filename where the content will be saved"]
(when-not (and function-name out-file)
(throw (Exception. "Required function-name and out-file to invoke the function")))
(core/with-pre-wrap fileset
(let [input-files (core/input-files fileset)
cmd-string (lambda-cmd-string input-files (dissoc *opts* :out-file) "invoke" out-file)]
(util/info "Invoking lambda function...\n")
(util/info (str cmd-string "\n"))
(shell cmd-string))
fileset))
(defn select-task-keys
[task-var opts]
(let [task-keys (-> (map (comp :id (partial apply hash-map)) (:argspec (meta task-var)))
set
(disj :help))]
(select-keys opts task-keys)))
(core/deftask deploy
"Creates the Lambda function if it does not exist, otherwise updates the function."
[o opts VAL code "The options passed to [[create-function]] and [[update-function]]"]
(let [function-name (:function-name opts)]
(assert function-name ":function-name is required.")
(try
;; throws if function does not exist
(shell (format "aws lambda get-function --function-name %s" function-name))
(comp (apply update-function-configuration (mapcat identity (select-task-keys #'update-function-configuration opts)))
(apply update-function (mapcat identity (select-task-keys #'update-function opts))))
(catch Exception ex
(util/info (format "Function %s not found. Creating function..." function-name))
(apply create-function (mapcat identity (select-task-keys #'create-function opts)))))))
(core/deftask generate-cljs-lambda-index
[f handler VAL sym "The AWS Lambda function handler."
p path VAL str "The path to the main JS file. Defaults to './main.js'."
o out VAL str "The generated output file name. Defaults to 'index.js'."]
(assert handler "A :handler is required.")
(core/with-pre-wrap fileset
(let [input-files (core/input-files fileset)
js-file-path (or path "./main.js")
out-file-name (or out "index.js")
file-content (format "require(\"%s\");\nexports.handler = %s;"
js-file-path
(-> handler
str
(str/replace "-" "_")
(str/replace "/" ".")))
out-dir (core/tmp-dir!)
out-file (io/file out-dir out-file-name)]
(spit out-file file-content)
(core/commit! (core/add-resource fileset out-dir)))))