Skip to content

Commit d10b7fa

Browse files
committed
Add termination-protection? opt to cfn stack
1 parent 7c3bae2 commit d10b7fa

File tree

3 files changed

+85
-4
lines changed

3 files changed

+85
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
They would previously return nil for these responses.
77
- Add `salmon.cleanup/full-delete-all-stacks!` for deleting
88
resources in test accounts
9+
- Add `:termination-protection?` option to `salmon.cloudformation/stack`.
10+
This can be used to enable or disable termination protection.
911

1012
## v0.20.0 (2024-12-12)
1113

src/salmon/cloudformation.clj

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,23 +198,35 @@
198198
(defn- cou-stack!
199199
"Create a new stack or update an existing one with the same name."
200200
[client {::ds/keys [config]} template-json]
201-
(let [{:keys [capabilities name parameters tags]} config
201+
(let [{:keys [capabilities name parameters tags termination-protection?]} config
202202
request {:Capabilities (seq capabilities)
203+
:EnableTerminationProtection (boolean termination-protection?)
203204
:Parameters (aws-parameters parameters)
204205
:StackName name
205206
:Tags (u/tags tags)
206207
:TemplateBody template-json}
207208
r (aws/invoke client {:op :DescribeStacks
208209
:request {:StackName name}})
209-
{:keys [StackId StackStatus]} (some-> r :Stacks first)]
210+
[{:keys [EnableTerminationProtection StackId StackStatus]}]
211+
#__ (:Stacks r)]
210212
(cond
211213
(= "ROLLBACK_COMPLETE" StackStatus)
212214
#__ (do
213215
(delete-stack! client StackId)
214216
(create-stack! client request))
215217
(= "ValidationError" (u/aws-error-code r)) (create-stack! client request)
216218
(u/anomaly? r) [r false]
217-
:else (update-stack! client request StackId))))
219+
220+
:else
221+
(do
222+
(when (and (not (nil? termination-protection?))
223+
(not= EnableTerminationProtection (boolean termination-protection?)))
224+
(u/invoke! client
225+
{:op :UpdateTerminationProtection
226+
:request
227+
{:EnableTerminationProtection (boolean termination-protection?)
228+
:StackName StackId}}))
229+
(update-stack! client request StackId)))))
218230

219231
(defn- outputs-map-raw [outputs-seq]
220232
(reduce
@@ -358,7 +370,12 @@
358370
359371
:template
360372
A map representing a CloudFormation template. The map
361-
may contain donut.system refs."
373+
may contain donut.system refs.
374+
375+
:termination-protection?
376+
Enables or disables termination protection on the stack.
377+
Ignored when nil.
378+
Default: nil."
362379
[& {:as config}]
363380
{::ds/config config
364381
::ds/start start-stack!

test/salmon/cloudformation_test.clj

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,68 @@
338338
"Outputs are correct")
339339
(ds/signal sys :salmon/delete))))
340340

341+
(deftest test-termination-protection
342+
(let [{:keys [regions]} (test/get-config)
343+
stack-name (test/rand-stack-name)]
344+
(doseq [region regions
345+
:let [stack-tp (stack-a :lint? true
346+
:name stack-name
347+
:region region
348+
:template template-a
349+
:termination-protection? true)
350+
stack-no-tp (stack-a :lint? true
351+
:name stack-name
352+
:region region
353+
:template template-a
354+
:termination-protection? false)
355+
stack-tp-unset (stack-a :lint? true
356+
:name stack-name
357+
:region region
358+
:template template-a
359+
:termination-protection? nil)
360+
system-def (system-a stack-tp)]]
361+
(test/with-system-delete [system system-def]
362+
(testing "termination-protection? prevents deletion"
363+
(is (thrown-with-msg?
364+
ExceptionInfo
365+
#"TerminationProtection is enabled"
366+
(cause (ds/signal @system :salmon/delete)))))
367+
(testing "nil termination-protection? leaves existing setting"
368+
(reset! system (ds/start (system-a stack-tp-unset)))
369+
(is (thrown-with-msg?
370+
ExceptionInfo
371+
#"TerminationProtection is enabled"
372+
(cause (ds/signal @system :salmon/delete)))))
373+
(testing "delete works after disabling termination-protection?"
374+
(reset! system (ds/start (system-a stack-no-tp)))
375+
(let [stack-id (-> @system ::ds/instances :services :stack-a :stack-id)]
376+
(reset! system (ds/signal @system :salmon/delete))
377+
(is (= {:name stack-name :stack-id stack-id}
378+
(-> @system ::ds/instances :services :stack-a)))))
379+
; Re-create stack with no termination protection
380+
(reset! system (ds/start (system-a stack-no-tp)))
381+
(testing "termination-protection? can be enabled for pre-existing stacks"
382+
(reset! system (ds/start (system-a stack-tp)))
383+
(is (thrown-with-msg?
384+
ExceptionInfo
385+
#"TerminationProtection is enabled"
386+
(cause (ds/signal @system :salmon/delete)))))
387+
(testing "termination-protection? can be disabled and enabled again"
388+
(reset! system (ds/start (system-a stack-no-tp)))
389+
(reset! system (ds/start (system-a stack-tp)))
390+
(is (thrown-with-msg?
391+
ExceptionInfo
392+
#"TerminationProtection is enabled"
393+
(cause (ds/signal @system :salmon/delete)))))
394+
(testing "delete works after disabling termination-protection?"
395+
(reset! system (ds/start (system-a stack-no-tp)))
396+
(testing "nil termination-protection? leaves existing setting"
397+
(reset! system (ds/start (system-a stack-tp-unset)))
398+
(let [stack-id (-> @system ::ds/instances :services :stack-a :stack-id)]
399+
(reset! system (ds/signal @system :salmon/delete))
400+
(is (= {:name stack-name :stack-id stack-id}
401+
(-> @system ::ds/instances :services :stack-a))))))))))
402+
341403
(deftest test-describe-stack-raw
342404
(let [stack-name (test/rand-stack-name)
343405
sys (ds/start (system-a (stack-a :capabilities #{"CAPABILITY_NAMED_IAM"} :name stack-name :template template-a)))]

0 commit comments

Comments
 (0)