-
Notifications
You must be signed in to change notification settings - Fork 0
/
a_cloudformation_wrapper.sh
470 lines (423 loc) · 12.4 KB
/
a_cloudformation_wrapper.sh
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
#!/bin/bash
## Script Version ####
SCRIPT_VERSION="1.1" #
######################
set -e
PROFILE=${PROFILE:-'profile-name'}
PROJECT=${PROJECT:-'project-name'}
ENV=${ENV:-'environment'}
REGION=${REGION:-'eu-west-1'}
SERVICE=${SERVICE:-'common'}
PARAMETERS_FOLDER=${PARAMETERS_FOLDER:-'parameters'} # '.' if the same folder
TEMPLATE_FOLDER=${TEMPLATE_FOLDER:-'.'}
TEMPLATE_EXTENSION=${TEMPLATE_EXTENSION:-'yml'} #or yml. Depends on your preference
ENVIRONMENT_PARAMETER_NAME=${ENVIRONMENT_PARAMETER_NAME:-'EnvironmentVersion'}
ALLOWED_ENVS="dev test int prd" # space separated list of allowed environment names
RESOURCE=()
OPERATION=''
DRY_RUN=false
INSANEMODE=false # Set to true if you're not using git
print_help()
{
cat << EOF
usage: $0 [-h] -o [create,update,delete,changeset,validate,status] -e [Environment] -d resourcename1 resourcename2 ...
This script create or update cloudformation script
OPTIONS:
-h Show this message
-e Environment name
-o Operation to do. Allowed values [create, update, changeset, delete, validate, status]
-d Dry run. Only print aws commands
-f Force the execution of CFN even if local repo is behind the default remote one. Ignored if -d
-s Service name to use
CONVENTIONS:
This script assumes that the template and parameters file name have a specific format:
Template: <resourcename>.<extension>
Parameters: <resourcename>-parameters-<environment>.json
This script create stack with this name:
<project>-<environment>-<resourcename>
Example:
I have a template that create an rds for production environment for a project called github.
Template name: rds.yaml
Parameters name: rds-parameters-production.json
After run this command: ./$0 -o create -e production rds
the stack 'github-production-rds' will be created
$0: Version: $SCRIPT_VERSION. For more details see the project page https://github.com/claranet-coast/cloudformationWrapper
EOF
}
print_version()
{
echo $SCRIPT_VERSION
}
create_stack(){
for res in "${RESOURCE[@]}"
do
command="aws cloudformation \
create-stack \
--profile $PROFILE \
--stack-name $(get_stack_name $res) \
--template-body file://$TEMPLATE_FOLDER/$res.$TEMPLATE_EXTENSION \
--parameters file://$PARAMETERS_FOLDER/${SERVICE}-$ENV-$res.json \
--region $REGION \
--enable-termination-protection \
--capabilities CAPABILITY_NAMED_IAM"
echo $command
if ! $DRY_RUN
then
$command
aws cloudformation \
wait stack-create-complete \
--profile $PROFILE \
--stack-name $(get_stack_name $res) \
--region $REGION
exit 0
fi
done
}
update_stack()
{
for res in "${RESOURCE[@]}"
do
command="aws cloudformation \
update-stack \
--profile $PROFILE \
--stack-name $(get_stack_name $res) \
--template-body file://$TEMPLATE_FOLDER/$res.$TEMPLATE_EXTENSION \
--parameters file://$PARAMETERS_FOLDER/${SERVICE}-$ENV-$res.json \
--region $REGION \
--capabilities CAPABILITY_NAMED_IAM"
echo $command
if ! $DRY_RUN
then
$command
aws cloudformation \
wait stack-update-complete \
--profile $PROFILE \
--stack-name $(get_stack_name $res) \
--region $REGION
exit 0
fi
done
}
delete_stack()
{
echo "Are you sure to delete stack? (Type deleteme to continue)"
read confirmation_string
if [[ $confirmation_string != 'deleteme' ]]
then
exit 1
fi
for res in "${RESOURCE[@]}"
do
stack_name=$(get_stack_name $res)
delete_command="aws cloudformation \
delete-stack \
--profile $PROFILE \
--stack-name $stack_name \
--region $REGION"
if ! $DRY_RUN
then
check_termination_protection_command="aws cloudformation \
describe-stacks \
--profile $PROFILE \
--stack-name $stack_name \
--query Stacks[0].EnableTerminationProtection \
--output json \
--region $REGION"
echo $check_termination_protection_command
echo "Checking termination protection:"
if [[ $($check_termination_protection_command) == 'true' ]]
then
echo "The stack has termination protection enabled, do you still want to continue? (Type yes to continue)"
read tp_confirmation_string
if [[ $tp_confirmation_string == 'yes' ]]
then
disable_termination_protection_command="aws cloudformation \
update-termination-protection \
--no-enable-termination-protection \
--profile $PROFILE \
--stack-name $stack_name \
--region $REGION"
echo $disable_termination_protection_command
echo "Disabling termination protection:"
$disable_termination_protection_command
else
exit 1
fi
fi
echo $delete_command
echo "Starting stack delete:"
$delete_command
aws cloudformation \
wait stack-delete-complete \
--profile $PROFILE \
--stack-name $stack_name \
--region $REGION
exit 0
else
echo $delete_command
fi
done
}
create_changeset_stack()
{
for res in "${RESOURCE[@]}"
do
command="aws cloudformation \
create-change-set \
--profile ${PROFILE} \
--stack-name $(get_stack_name $res) \
--template-body file://$TEMPLATE_FOLDER/$res.$TEMPLATE_EXTENSION \
--parameters file://$PARAMETERS_FOLDER/${SERVICE}-$ENV-$res.json \
--region $REGION \
--capabilities CAPABILITY_NAMED_IAM \
--change-set-name $res-changeset"
echo $command
if ! $DRY_RUN
then
echo -e "\nCreating Changeset:\n"
$command
# Wait until the changeset creation completes
aws cloudformation wait change-set-create-complete \
--change-set-name $res-changeset \
--profile ${PROFILE} \
--region $REGION \
--stack-name $(get_stack_name $res)
# Describe the changeset
echo -e "\nDescribe Changeset:\n"
describe=$(aws cloudformation describe-change-set \
--change-set-name $res-changeset \
--profile ${PROFILE} \
--region $REGION \
--output json \
--stack-name $(get_stack_name $res) \
--query '{Status: Status, StatusReason: StatusReason, Changes: Changes}')
# pretty print the changeset
echo $describe | python -m json.tool
if ! [[ $describe == *"FAILED"* ]];
then
echo
read -p "Do you want to execute the changeset?[yY|nN]" -n 2 -r
echo # move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
execute_changeset
else
echo "Ok, I'm NOT executing the changeset but I'm NOT deleting it either"
echo "You can delete the changeset with the following command:"
DRY_RUN=true #THIS prevents the changeset to be actually deleted
delete_changeset
fi
else
delete_changeset
echo "Deleted useless changeset for you"
fi
fi
done
}
execute_changeset()
{
command="aws cloudformation execute-change-set \
--change-set-name $res-changeset \
--profile ${PROFILE} \
--region $REGION \
--stack-name $(get_stack_name $res)"
echo $command
if ! $DRY_RUN
then
$command
fi
}
delete_changeset()
{
command="aws cloudformation delete-change-set \
--change-set-name $res-changeset \
--profile ${PROFILE} \
--region $REGION \
--stack-name $(get_stack_name $res)"
echo $command
if ! $DRY_RUN
then
$command
fi
}
validate_stack()
{
for res in "${RESOURCE[@]}"
do
command="aws cloudformation \
validate-template \
--profile ${PROFILE} \
--region $REGION \
--template-body file://$TEMPLATE_FOLDER/$res.$TEMPLATE_EXTENSION"
echo $command
if ! $DRY_RUN
then
$command
fi
done
}
get_stack_status()
{
for res in "${RESOURCE[@]}"
do
aws cloudformation \
describe-stacks \
--profile ${PROFILE} \
--region $REGION \
--stack-name $(get_stack_name $res) \
--query 'Stacks[*].StackStatus'
done
}
check_if_aligned_with_gitdefaultremote()
{
git remote update origin
UPSTREAM='@{u}'
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")
printf "Checking if the local repo is aligned with the Default Remote.. "
RES=0
if [ $LOCAL = $REMOTE ]; then
echo "Up-to-date"
elif [ $LOCAL = $BASE ]; then
echo "Need to pull"
RES=1
elif [ $REMOTE = $BASE ]; then
echo "Need to push"
else
echo "Diverged"
RES=1
fi
return $RES
}
git_check()
{
check_if_aligned_with_gitdefaultremote
GITALIGNED=$?
if [[ $GITALIGNED != 0 && $INSANEMODE == false ]]
then
echo "WARNING: There are changes on the default remote that are not present locally"
echo "WARNING: Do a GIT PULL first"
if [[ $DRYRUN != true ]]
then
exit -1
else
echo "*Ignoring warning since it is a dry run*"
fi
elif [[ $INSANEMODE == true && $DRYRUN == false ]]
then
echo "WARNING: USING INSANE MODE. I'm running cloudformation even if git remote is ahead"
fi
}
has_version()
{
version=`cat $1 | jq --arg ENVIRONMENT_PARAMETER_NAME "$ENVIRONMENT_PARAMETER_NAME" '(.[] | select(.ParameterKey == $ENVIRONMENT_PARAMETER_NAME) | .ParameterValue)'|sed s/'"'//g`
echo $version
}
get_stack_name()
{
resource=$1
version=$(has_version $PARAMETERS_FOLDER/${SERVICE}-$ENV-$res.json)
if [ $version ]
then
name=${SERVICE}-$ENV-$resource-$version
else
name=${SERVICE}-$ENV-$resource
fi
echo $name
}
############
### MAIN ###
############
while getopts "vhe:o:s:df" opt
do
case $opt in
h)
print_help
exit -1
;;
e)
ENV=$OPTARG
;;
o)
OPERATION=$OPTARG
;;
d)
DRY_RUN=true
;;
f)
INSANEMODE=true
;;
v)
print_version
exit 1
;;
s)
SERVICE=$OPTARG
;;
?)
echo "Option/s error/s"
print_help
exit -1
;;
esac
done
shift $(( OPTIND - 1 ))
if [[ -z $@ ]]
then
echo
echo "No resource provided, please add the resource you want to work with (e.g. pvc, rds...)"
echo
print_help
exit -1
fi
for var in "$@"
do
RESOURCE+=($var)
done
# Check if the environment passed as argument is allowed
if ! [[ $ALLOWED_ENVS =~ (^|[[:space:]])$ENV($|[[:space:]]) ]]
then
echo "Invalid environment: Allowed values are [${ALLOWED_ENVS}]"
print_help
exit -1
fi
if [[ $OPERATION == 'create' ]]
then
git_check
create_stack
elif [[ $OPERATION == 'update' ]]
then
git_check
echo
read -p "You are about to do an update stack and usually is a STUPID IDEA. You should consider doing a changeset. Do you want to do a changeset instead?[yY|nN]" -n 1 -r
echo # move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
create_changeset_stack
elif [[ $REPLY =~ ^[Nn]$ ]]
then
echo
echo "Ok lets proceed with the update. If you break something you can only blame yourself."
echo
update_stack
else
echo "Unknown answer, just quitting."
fi
elif [[ $OPERATION == 'changeset' ]]
then
git_check
create_changeset_stack
elif [[ $OPERATION == 'validate' ]]
then
validate_stack
elif [[ $OPERATION == 'status' ]]
then
get_stack_status
elif [[ $OPERATION == 'delete' ]]
then
delete_stack
else
echo "Invalid operation $OPERATION: Allowed values are [create, update, changeset, delete, validate, status]"
print_help
fi