From bcb066d9748f0706f1f662ad9200f2a2234a7156 Mon Sep 17 00:00:00 2001 From: Levon Becker Date: Tue, 21 Mar 2017 14:42:02 -0700 Subject: [PATCH] 1.6.0 - Read Changelog --- CHANGELOG.md | 5 + README.md | 359 +++++++++++++++++++------------------ cfn-launcher.sh => cfnl.sh | 268 +++++++++++++++------------ 3 files changed, 346 insertions(+), 286 deletions(-) rename cfn-launcher.sh => cfnl.sh (64%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8162476..ce846f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ##CHANGE LOG --- +##1.6.0 - 03/21/2017 - Levon Becker +* Added Stack Status Option +* Added Error Handling for multiple Action Flag Options +* Renamed Script from cfn-launcher.sh to cfnl.sh + ##1.5.1 - 11/15/2016 - Levon Becker * Removed Task Type from header since Action replaces it. * Move Display Runtime to Function so can be used during loops diff --git a/README.md b/README.md index c982667..cc4a0af 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ It is designed to be reusable without having to make changes to the script. Simple use a default create YAML config or create you own custom YAML config file. ## Tested Environment -* macOS El Capitan 10.11.6 -* AWS CLI v1.10.66 +* macOS Sierra 10.12.3 +* AWS CLI v1.11.55 -# Prerequisites +## Prerequisites This has been test * BASH Shell * **tee** command installed @@ -18,57 +18,74 @@ This has been test * Proxy configured for Shell if needed * AWS CLI installed and configured -## Usage -### Download Repo Locally +## Setup 1. Open Terminal 2. Pull down this git repository locally 1. ```git clone https://github.com/bonusbits/cfn_launcher.git /path/to/clone/``` +3. Create symlink & aliases the the *cfnl.sh* as desired. Examples below. -### CFNL Config -1. Create new YAML file with the same content of one of the example configs -2. Replace values with custom values as desired - **Example** +## Help +The following can be displayed with the *-h* switch. - ```yaml +``` +CloudFormation Launcher v1.6.0 + +Usage: /usr/local/bin/cfnl [-u | -d | -s] -f ./config_file.yml + +Options: + -f File Path : (Required) YAML Script Config File Full Path + -u Update Stack : (Action Flag) Sets Action to Update Stack + -d Delete Stack : (Action Flag) Sets Action to Delete Stack + -s Stack Status : (Action Flag) Sets Action to Get Stack Status + -b Debug Output : Display Additional Output for Debugging + -h Help : Displays Help Information + -v Version : Displays Script Version + +Action Flags: + Only one action flag can be used. The default Action is 'Create'. + The three override Action Flags are -u, -d and -s. + +Description: + This script uses the AWS CLI and BASH to create, update, delete or get + status of a CloudFormation Stack. It uses the AWS CLI to push the + CloudFormation Template to AWS. Then loops over and over checking + status of the stack. + +YAML Config File Format Example: stackname: stack1 profilename: awsaccount - templateurl: https://s3.amazonaws.com/bucket/stack1.yml - templatelocal: /path/to/cfn/templates/stack1.yml # Not used if uses3template: true - parametersfilepath: /path/to/cfn/template/parameters/awsaccount-stack1-dev-uswest2.json + templateurl: https://s3.amazonaws.com/bucket/webapp1.yml # Or .json + templatelocal: /path/to/cfn/templates/webapp1.yml # Unless using URL + parametersfilepath: /Users/levon/.cfnl/uswest2/client1/account1/dev/webapp1.json capabilityiam: false capabilitynamediam: false deletecreatefailures: true uses3template: true nolog: false - logfile: /path/to/where/you/want/logs/awsaccount-stack1-dev-uswest2.log + logfile: /Users/levon/.cfnl/logs/uswest2/client1/account1/dev/webapp1.log verbose: true waittime: 5 maxwaits: 180 - noheader: false - ``` -## Script Symlink and Aliases (Optional) -Here are some examples that can be used to allow access to the cfn-launcher script without needing to be in the repo as a working directory. -I generally like to create a symbolic link to the shell script in a standard accessable path that is already setup in the ```PATH``` environment variable. -Then I create various aliases in my bash profile or aliases script to call the script with different configurations to make it quick and easy to call. -Yes these alias examples are long, but you can tab auto fill. -Of course you can name the aliases whatever you want as long as there is not another command with the same name on the system. -It could be as simple as cfc1, cfu1, cfd1, cfc2... or c-s1-uw2, u-s2-uw2, d-s2-uw2, c-s2-ue1... or cs1uw2, ds1uw2... etc. Whatever makes since to you... -Then the only challenge is if they are really short is remembering what each means. +Examples: + Create Stack + /usr/local/bin/cfnl -f /Users/levon/.cfnl/uswest2/client1/account1/dev/webapp1.yml -### Symlink + Update Stack + /usr/local/bin/cfnl -u -f /Users/levon/.cfnl/uswest2/client1/account1/dev/webapp1.yml -```bash - if [ ! -h "/usr/local/bin/cfnl" ]; then - ln -s "/path/to/clone/cfn_launcher/cfn-launcher.sh" /usr/local/bin/cfnl - fi -``` + Delete Stack + /usr/local/bin/cfnl -d -f /Users/levon/.cfnl/uswest2/client1/account1/dev/webapp1.yml + + Stack Status + /usr/local/bin/cfnl -s -f /Users/levon/.cfnl/uswest2/client1/account1/dev/webapp1.yml +``` -### Aliases -Examples BASH Profile scripts to simplify use. +## Example BASH Profile Functions and Aliases +Examples BASH Profile scripts to simplify use, but are not required of course. -#### .bash_profile +### .bash_profile ```bash # CloudFormation Launcher Aliases if [ -f $HOME/.bash_cfnl ]; then @@ -76,7 +93,7 @@ if [ -f $HOME/.bash_cfnl ]; then fi ``` -#### .bash_cfnl +### .bash_cfnl ```bash #!/usr/bin/env bash @@ -84,7 +101,7 @@ fi # Symlink if [ ! -h "/usr/local/bin/cfnl" ]; then - ln -s "$HOME/Development/github/bonusbits/cfn_launcher/cfn-launcher.sh" /usr/local/bin/cfnl + ln -s "$HOME/Development/github/bonusbits/cfn_launcher/cfnl.sh" /usr/local/bin/cfnl fi function cfnl-show() { @@ -102,22 +119,28 @@ function cfnl-set-path() { cfnl-set-path $HOME/.cfnl/uswest2/client1/account01/prd/project01 function cfc() { - echo "Running Create Stack (/usr/local/bin/cfnl -f ${CFNL_PATH}/${1}.yml)" + #echo "Running Create Stack (/usr/local/bin/cfnl -f ${CFNL_PATH}/${1}.yml)" /usr/local/bin/cfnl -f "$CFNL_PATH/$1.yml" } function cfu() { - echo "Running Update Stack (/usr/local/bin/cfnl -u -f ${CFNL_PATH}/${1}.yml)" + #echo "Running Update Stack (/usr/local/bin/cfnl -u -f ${CFNL_PATH}/${1}.yml)" /usr/local/bin/cfnl -u -f "$CFNL_PATH/$1.yml" } function cfd() { - echo "Running Delete Stack (/usr/local/bin/cfnl -d -f ${CFNL_PATH}/${1}.yml)" + #echo "Running Delete Stack (/usr/local/bin/cfnl -d -f ${CFNL_PATH}/${1}.yml)" /usr/local/bin/cfnl -d -f "$CFNL_PATH/$1.yml" } + +function cfs() { + # echo "Running Stack Status (/usr/local/bin/cfnl -s -f ${CFNL_PATH}/${1}.yml)" + /usr/local/bin/cfnl -s -f "$CFNL_PATH/$1.yml" +} ``` -#### Folder Structure +### Example Folder Structure +You can use whatever folder structure you like. This is just an example. ``` $HOME/.cfnl/ @@ -151,10 +174,10 @@ function cfd() { └── web-green.yml ``` -#### Example Commands +### Example Aliased Commands ```bash # Set CFNL Path -cfnl-set-vars uswest2 client01 account01 prd project01 +cfnl-set-path $HOME/.cfnl/uswest2/client01/account01/dev/project01 # CFNL is now set to use $HOME/.cfnl/uswest2/client01/account01/dev/project01/*.yml # Create Stack @@ -163,159 +186,151 @@ cfc web cfd web # Update Stack cfu web -``` - -Also, if you need to switch between AWS Accounts or update STS. That can be dropped in front of the CFNL call with ```&&``` separator. - -Example: -**aws-set** is function from somewhere that we wrote that sets environment variables for different AWS accounts. -**gentoken** is function from somewhere that we wrote that runs a script to update our AWS Secure Tokens. - -```bash -alias cf-c="aws-set awsaccount-dev-uswest2 && gentoken && cfnl -f " +# Stack Status +cfs web ``` -### Run CFNL Without Aliases -1. Open Terminal -2. Change to this directory - - ```bash - cd /path/to/clone/cfn-launcher/ - ``` -3. Run script and point to a config yaml file - - ```bash - # Create Stack - /path/to/cfn_launcher/cfn-launcher.sh -f /path/to/cfnl_configs/my-launcher-config.yml - # If created alias or symlink - cfnl -f /path/to/cfnl_configs/my-launcher-config.yml - - # Update Stack - /path/to/cfn_launcher/cfn-launcher.sh -u -f /path/to/cfnl_configs/my-launcher-config.yml - # If created alias or symlink - cfnl -u -f /path/to/cfnl_configs/my-launcher-config.yml - - # Delete Stack - /path/to/cfn_launcher/cfn-launcher.sh -u -f /path/to/cfnl_configs/my-launcher-config.yml - # If created alias or symlink - cfnl -d -f /path/to/cfnl_configs/my-launcher-config.yml - ``` - -### Output Examples -#### Success - ----------------------------------------------------------------------------------------------------------------------- - ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| - ----------------------------------------------------------------------------------------------------------------------- - CloudFormation Launcher - 1.1.1_10-20-2016 - ----------------------------------------------------------------------------------------------------------------------- - PARAMETERS - ----------------------------------------------------------------------------------------------------------------------- - STACK NAME: bonusbits-prd-bastion - PROFILE: bonusbits - TEMPLATE: https://s3.amazonaws.com/bonusbits-public/cloudformation-templates/github/bastion.template - PARAMETERS FILE: ./bonusbits-prd-bastion.json - ENABLE IAM: true - TASK TYPE: create-stack - LOG FILE: /var/log/cfn-launcher/cfn-launcher.log - VERBOSE: true - ----------------------------------------------------------------------------------------------------------------------- - +## Output Examples +### Create + [2017-03-21_13:44:35] ** Start CloudFormation Launcher v1.6.0 ** + [2017-03-21_13:44:35] ACTION: CREATE { - "StackId": "arn:aws:cloudformation:us-west-2:00000000000:stack/bonusbits-prd-bastion/37f98fa0-96e7-11e6-b5c9-50d6cd0dfcc6" + "StackId": "arn:aws:cloudformation:us-west-2:000000000000:stack/webapp1/32046180-0e78-11e7-86c8-513ac9841b19" } - [2016-10-20_10:12:35] REPORT: Success Started Stack Command - [2016-10-20_10:12:36] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:12:36] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:12:36] REPORT: CREATE stack is not complete! - [2016-10-20_10:12:36] REPORT: Attempt 1 of 180. - [2016-10-20_10:12:36] REPORT: Polling again in 5 seconds... + [2017-03-21_13:44:36] REPORT: Successfully Executed CREATE Stack Command + + [2017-03-21_13:44:37] REPORT: Successfully Executing Status Check + [2017-03-21_13:44:37] REPORT: Status (CREATE_IN_PROGRESS) + [2017-03-21_13:44:37] REPORT: CREATE stack is not complete! + [2017-03-21_13:44:37] REPORT: Attempt 1 of 360. + [2017-03-21_13:44:37] RUNTIME: 00 minutes 02 seconds + [2017-03-21_13:44:37] REPORT: Polling again in 5 seconds... - [2016-10-20_10:12:43] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:12:43] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:12:43] REPORT: CREATE stack is not complete! - [2016-10-20_10:12:43] REPORT: Attempt 2 of 180. - [2016-10-20_10:12:43] REPORT: Polling again in 5 seconds... + [2017-03-21_13:44:42] REPORT: Successfully Executing Status Check + [2017-03-21_13:44:42] REPORT: Status (CREATE_IN_PROGRESS) + [2017-03-21_13:44:42] REPORT: CREATE stack is not complete! + [2017-03-21_13:44:42] REPORT: Attempt 2 of 360. + [2017-03-21_13:44:42] RUNTIME: 00 minutes 07 seconds + [2017-03-21_13:44:42] REPORT: Polling again in 5 seconds... - ... + [2017-03-21_13:44:48] REPORT: Successfully Executing Status Check + [2017-03-21_13:44:48] REPORT: Status (CREATE_IN_PROGRESS) + [2017-03-21_13:44:48] REPORT: CREATE stack is not complete! + [2017-03-21_13:44:48] REPORT: Attempt 3 of 360. + [2017-03-21_13:44:48] RUNTIME: 00 minutes 13 seconds + [2017-03-21_13:44:48] REPORT: Polling again in 5 seconds... - [2016-10-20_10:16:25] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:16:25] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:16:25] REPORT: CREATE stack is not complete! - [2016-10-20_10:16:25] REPORT: Attempt 37 of 180. - [2016-10-20_10:16:25] REPORT: Polling again in 5 seconds... + ... Fast Forward ... - [2016-10-20_10:16:32] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:16:32] REPORT: Status (CREATE_COMPLETE) - [2016-10-20_10:16:32] REPORT: CREATE Completed! + [2017-03-21_14:00:48] REPORT: Successfully Executing Status Check + [2017-03-21_14:00:48] REPORT: Status (CREATE_IN_PROGRESS) + [2017-03-21_14:00:48] REPORT: CREATE stack is not complete! + [2017-03-21_14:00:48] REPORT: Attempt 172 of 360. + [2017-03-21_14:00:48] RUNTIME: 16 minutes 13 seconds + [2017-03-21_14:00:48] REPORT: Polling again in 5 seconds... - [2016-10-20_10:16:32] ENDTIME: (Thu Oct 20 10:16:32 PDT 2016) - [2016-10-20_10:16:32] RUNTIME: 3 minutes + [2017-03-21_14:00:54] REPORT: Successfully Executing Status Check + [2017-03-21_14:00:54] REPORT: Status (CREATE_COMPLETE) + [2017-03-21_14:00:54] REPORT: CREATE Completed! - [2016-10-20_10:16:32] REPORT: SUCCESS! - -#### Failure - ----------------------------------------------------------------------------------------------------------------------- - ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| - ----------------------------------------------------------------------------------------------------------------------- - CloudFormation Launcher - 1.1.1_10-20-2016 - ----------------------------------------------------------------------------------------------------------------------- - PARAMETERS - ----------------------------------------------------------------------------------------------------------------------- - STACK NAME: bonusbits-prd-bastion - PROFILE: bonusbits - TEMPLATE: https://s3.amazonaws.com/bonusbits-public/cloudformation-templates/github/bastion.template - PARAMETERS FILE: ./bonusbits-prd-bastion.json - ENABLE IAM: true - TASK TYPE: create-stack - LOG FILE: /var/log/cfn-launcher/cfn-launcher.log - VERBOSE: true - ----------------------------------------------------------------------------------------------------------------------- + [2017-03-21_14:00:54] ENDTIME: (Tue Mar 21 14:00:54 PDT 2017) + [2017-03-21_14:00:54] RUNTIME: 16 minutes 19 seconds + [2017-03-21_14:00:54] REPORT: CREATE SUCCESS! + +### Update + [2017-03-21_14:11:10] ** Start CloudFormation Launcher v1.6.0 ** + [2017-03-21_14:11:10] ACTION: UPDATE { - "StackId": "arn:aws:cloudformation:us-east-1:000000000000:stack/bonusbits-prd-bastion/3c98fcd0-96e7-17e6-9c84-80d5cd265c36" + "StackId": "arn:aws:cloudformation:us-west-2:000000000000:stack/webapp1/32046180-0e78-11e7-86c8-513ac9841b19" } - [2016-10-20_10:05:33] REPORT: Success Started Stack Command - [2016-10-20_10:05:34] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:05:34] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:05:34] REPORT: CREATE stack is not complete! - [2016-10-20_10:05:34] REPORT: Attempt 1 of 180. - [2016-10-20_10:05:34] REPORT: Polling again in 5 seconds... + [2017-03-21_14:11:11] REPORT: Successfully Executed UPDATE Stack Command + + [2017-03-21_14:11:12] REPORT: Successfully Executing Status Check + [2017-03-21_14:11:12] REPORT: Status (UPDATE_IN_PROGRESS) + [2017-03-21_14:11:12] REPORT: UPDATE stack is not complete! + [2017-03-21_14:11:12] REPORT: Attempt 1 of 360. + [2017-03-21_14:11:12] RUNTIME: 00 minutes 02 seconds + [2017-03-21_14:11:12] REPORT: Polling again in 5 seconds... + + [2017-03-21_14:11:17] REPORT: Successfully Executing Status Check + [2017-03-21_14:11:17] REPORT: Status (UPDATE_IN_PROGRESS) + [2017-03-21_14:11:17] REPORT: UPDATE stack is not complete! + [2017-03-21_14:11:17] REPORT: Attempt 2 of 360. + [2017-03-21_14:11:17] RUNTIME: 00 minutes 07 seconds + [2017-03-21_14:11:17] REPORT: Polling again in 5 seconds... - [2016-10-20_10:05:41] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:05:41] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:05:41] REPORT: CREATE stack is not complete! - [2016-10-20_10:05:41] REPORT: Attempt 2 of 180. - [2016-10-20_10:05:41] REPORT: Polling again in 5 seconds... + ... Fast Forward ... - ... + [2017-03-21_14:11:46] REPORT: Successfully Executing Status Check + [2017-03-21_14:11:46] REPORT: Status (UPDATE_IN_PROGRESS) + [2017-03-21_14:11:46] REPORT: UPDATE stack is not complete! + [2017-03-21_14:11:46] REPORT: Attempt 7 of 360. + [2017-03-21_14:11:46] RUNTIME: 00 minutes 36 seconds + [2017-03-21_14:11:46] REPORT: Polling again in 5 seconds... - [2016-10-20_10:07:57] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:07:57] REPORT: Status (CREATE_IN_PROGRESS) - [2016-10-20_10:07:57] REPORT: CREATE stack is not complete! - [2016-10-20_10:07:57] REPORT: Attempt 23 of 180. - [2016-10-20_10:07:57] REPORT: Polling again in 5 seconds... + [2017-03-21_14:11:52] REPORT: Successfully Executing Status Check + [2017-03-21_14:11:52] REPORT: Status (UPDATE_COMPLETE) + [2017-03-21_14:11:52] REPORT: UPDATE Completed! - [2016-10-20_10:08:03] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:08:03] REPORT: Status (ROLLBACK_IN_PROGRESS) - [2016-10-20_10:08:03] ERROR: Failed and Rolling Back! + [2017-03-21_14:11:52] ENDTIME: (Tue Mar 21 14:11:52 PDT 2017) + [2017-03-21_14:11:52] RUNTIME: 00 minutes 42 seconds - # TODO: Add example + [2017-03-21_14:11:52] REPORT: UPDATE SUCCESS! + +### Status + /Users/username/.cfnl/uswest2/client1/account01/prd/webapp1.yml + Stack Status: (UPDATE_COMPLETE) + +### Delete + [2017-03-21_14:17:20] ** Start CloudFormation Launcher v1.6.0 ** + [2017-03-21_14:17:20] ACTION: DELETE + [2017-03-21_14:17:20] ACTION: Deleting Stack + [2017-03-21_14:17:21] REPORT: Successfully Executed Delete Stack Command + [2017-03-21_14:17:21] REPORT: Successfully Executing Status Check + [2017-03-21_14:17:21] REPORT: Status (DELETE_IN_PROGRESS) + [2017-03-21_14:17:21] REPORT: Delete not complete! + [2017-03-21_14:17:21] REPORT: Attempt 1 of 360. + [2017-03-21_14:17:21] RUNTIME: 00 minutes 01 seconds + [2017-03-21_14:17:21] Polling again in 5 seconds... + + [2017-03-21_14:17:27] REPORT: Successfully Executing Status Check + [2017-03-21_14:17:27] REPORT: Status (DELETE_IN_PROGRESS) + [2017-03-21_14:17:27] REPORT: Delete not complete! + [2017-03-21_14:17:27] REPORT: Attempt 2 of 360. + [2017-03-21_14:17:27] RUNTIME: 00 minutes 07 seconds + [2017-03-21_14:17:27] Polling again in 5 seconds... + + ... Fast Forward ... - [2016-10-20_10:08:07] ACTION: Deleting Stack - [2016-10-20_10:08:09] REPORT: Success Deleting Stack Command - [2016-10-20_10:08:10] REPORT: Success Loaded Status Check into Variable - [2016-10-20_10:08:10] REPORT: Status (DELETE_IN_PROGRESS) - [2016-10-20_10:08:10] REPORT: Delete not complete! - [2016-10-20_10:08:10] REPORT: Attempt 24 of 180. - [2016-10-20_10:08:10] Polling again in 5 seconds... + [2017-03-21_14:18:54] REPORT: Successfully Executing Status Check + [2017-03-21_14:18:54] REPORT: Status (DELETE_IN_PROGRESS) + [2017-03-21_14:18:54] REPORT: Delete not complete! + [2017-03-21_14:18:54] REPORT: Attempt 17 of 360. + [2017-03-21_14:18:54] RUNTIME: 01 minutes 34 seconds + [2017-03-21_14:18:54] Polling again in 5 seconds... + [2017-03-21_14:19:00] REPORT: Successfully Executing Status Check + [2017-03-21_14:19:00] REPORT: Status (DELETE_IN_PROGRESS) + [2017-03-21_14:19:00] REPORT: Delete not complete! + [2017-03-21_14:19:00] REPORT: Attempt 18 of 360. + [2017-03-21_14:19:00] RUNTIME: 01 minutes 40 seconds + [2017-03-21_14:19:00] Polling again in 5 seconds... - [2016-10-20_10:08:15] ENDTIME: (Thu Oct 20 10:08:15 PDT 2016) - [2016-10-20_10:08:15] RUNTIME: 2 minutes - [2016-10-20_10:08:16] ERROR: FAILED! + An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id webapp1 does not exist + [2017-03-21_14:19:06] REPORT: Successfully Executing Status Check + [2017-03-21_14:19:06] REPORT: Status (DOES NOT EXIST) + [2017-03-21_14:19:06] REPORT: Stack Deleted (webapp1) + + [2017-03-21_14:19:06] ENDTIME: (Tue Mar 21 14:19:06 PDT 2017) + [2017-03-21_14:19:06] RUNTIME: 01 minutes 46 seconds + + [2017-03-21_14:19:06] REPORT: DELETE SUCCESS! +### Status + /Users/username/.cfnl/uswest2/client1/account01/prd/webapp1.yml + Stack Status: (DOES NOT EXIST) + ## Troubleshooting * Can not use underscores of hyphens in yaml properties file key names. diff --git a/cfn-launcher.sh b/cfnl.sh similarity index 64% rename from cfn-launcher.sh rename to cfnl.sh index e10fe5a..57f3acc 100755 --- a/cfn-launcher.sh +++ b/cfnl.sh @@ -4,82 +4,111 @@ successful=false delete_successful=false triggered_delete=false -script_version=1.5.1 +script_version=1.6.0 # unset stack_name # read -p "Enter Stack Name: " stack_name function help_message () { -helpmessage=" ------------------------------------------------------------------------------------------------------------------------ -||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ------------------------------------------------------------------------------------------------------------------------ -AUTHOR: Levon Becker -PURPOSE: Create, Update or Delete CloudFormation Stack. -VERSION: $script_version -DESCRIPTION: This script uses the AWS CLI and BASH to create, update or delete a CloudFormation Stack. - It uses the AWS CLI to push the CloudFormation Template to AWS. - Then loops over and over checking status of the stack. ------------------------------------------------------------------------------------------------------------------------ -YAML FILE FORMAT EXAMPLE ------------------------------------------------------------------------------------------------------------------------ -stackname: stack1 -profilename: awsaccount -templateurl: https://s3.amazonaws.com/bucket/stack1.yml -templatelocal: /path/to/cfn/templates/stack1.yml # Not used if uses3template: true -parametersfilepath: /path/to/cfn/template/parameters/awsaccount-stack1-dev-uswest2.json -capabilityiam: false -capabilitynamediam: false -deletecreatefailures: true -uses3template: true -nolog: false -logfile: /path/to/where/you/want/logs/awsaccount-stack1-dev-uswest2.log -verbose: true -waittime: 5 -maxwaits: 180 -noheader: false ------------------------------------------------------------------------------------------------------------------------ -EXAMPLES ------------------------------------------------------------------------------------------------------------------------ -Create Stack -$0 -f /path/to/cfnl/configs/awsaccount-stack-env-region.yml - -Update Stack -$0 -u -f /path/to/cfnl/configs/awsaccount-stack-env-region.yml - -Delete Stack -$0 -d -f /path/to/cfnl/configs/awsaccount-stack-env-region.yml +helpmessage="Description: + This script uses the AWS CLI and BASH to create, update, delete or get + status of a CloudFormation Stack. It uses the AWS CLI to push the + CloudFormation Template to AWS. Then loops over and over checking + status of the stack. + +YAML Config File Format Example: + stackname: stack1 + profilename: awsaccount + templateurl: https://s3.amazonaws.com/bucket/webapp1.yml # Or .json + templatelocal: /path/to/cfn/templates/webapp1.yml # Unless using URL + parametersfilepath: $HOME/.cfnl/uswest2/client1/account1/dev/webapp1.json + capabilityiam: false + capabilitynamediam: false + deletecreatefailures: true + uses3template: true + nolog: false + logfile: $HOME/.cfnl/logs/uswest2/client1/account1/dev/webapp1.log + verbose: true + waittime: 5 + maxwaits: 180 + +Examples: + Create Stack + $0 -f $HOME/.cfnl/uswest2/client1/account1/dev/webapp1.yml + + Update Stack + $0 -u -f $HOME/.cfnl/uswest2/client1/account1/dev/webapp1.yml + + Delete Stack + $0 -d -f $HOME/.cfnl/uswest2/client1/account1/dev/webapp1.yml + + Stack Status + $0 -s -f $HOME/.cfnl/uswest2/client1/account1/dev/webapp1.yml + +Author: + Levon Becker + https://github.com/LevonBecker + https://www.bonusbits.com " + usage echo "$helpmessage"; } function version_message() { -versionmessage="CloudFormation Launcher Version: $script_version" +versionmessage="CloudFormation Launcher v$script_version" echo "$versionmessage"; } function usage() { -usagemessage=" -usage: $0 [-u] -f ./config_file.yml - --f File Path : YAML Script Config File Full Path (Required) --u Update Stack : Triggers Update Operation (Default is Create Stack) --d Delete Stack : Triggers Deletion of Stack --h Help : Displays Help Information +usagemessage="Usage: $0 [-u | -d | -s] -f ./config_file.yml + +Options: + -f File Path : (Required) YAML Script Config File Full Path + -u Update Stack : (Action Flag) Sets Action to Update Stack + -d Delete Stack : (Action Flag) Sets Action to Delete Stack + -s Stack Status : (Action Flag) Sets Action to Get Stack Status + -b Debug Output : Display Additional Output for Debugging + -h Help : Displays Help Information + -v Version : Displays Script Version + +Action Flags: + Only one action flag can be used. The default Action is 'Create'. + The three override Action Flags are -u, -d and -s. " + version_message + echo '' echo "$usagemessage"; } -while getopts "f:udvh" opts; do +while getopts "f:bdhsuv" opts; do case $opts in + b ) debug=true;; + d ) delete=true;; f ) config_file_path=$OPTARG;; + h ) help_message; exit 0;; + s ) status=true;; u ) update=true;; - d ) delete=true;; v ) version_message; exit 0;; - h ) help_message; exit 0;; esac done -# TODO: Condition / Error handling for if -u and -d passed +action_flag_count=0 +if [ -n "$delete" ]; then + action_flag_count=$[$action_flag_count +1] + flags_used+="-d " +fi +if [ -n "$update" ]; then + action_flag_count=$[$action_flag_count +1] + flags_used+="-u " +fi +if [ -n "$status" ]; then + action_flag_count=$[$action_flag_count +1] + flags_used+="-s " +fi +if [ "$action_flag_count" -gt 1 ]; then + usage + echo "ERROR: Multiple Action Flags detected! ($flags_used)" + exit 1 +fi if [ "$config_file_path" == "" ]; then usage @@ -126,35 +155,26 @@ function show_header { TEMPLATE=${yaml_templatelocal} fi - HEADER=" ------------------------------------------------------------------------------------------------------------------------ -||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ------------------------------------------------------------------------------------------------------------------------ -CloudFormation Launcher -v$script_version ------------------------------------------------------------------------------------------------------------------------ -PARAMETERS ------------------------------------------------------------------------------------------------------------------------ -ACTION: $ACTION -STACK NAME: $yaml_stackname -PROFILE: $yaml_profilename -TEMPLATE: $TEMPLATE -PARAMETERS FILE: $yaml_parametersfilepath -CAPABILITY IAM: $yaml_capabilityiam -CAPABILITY NAMED IAM: $yaml_capabilitynamediam -NO LOG: $yaml_nolog -LOG FILE: $yaml_logfile -VERBOSE: $yaml_verbose -LAUNCHER CONFIG: $config_file_path -DELETE ON FAILURE: $yaml_deletecreatefailures -WAIT TIME (Sec): $yaml_waittime -MAX WAITS (Loops): $yaml_maxwaits ------------------------------------------------------------------------------------------------------------------------ - " - if [ "$yaml_noheader" == "true" ]; then - message '' + if [ "$debug" == "true" ]; then + message "** Start CloudFormation Launcher v$script_version **" + message '** PARAMETERS **' + message "ACTION: $ACTION" + message "STACK NAME: $yaml_stackname" + message "PROFILE: $yaml_profilename" + message "TEMPLATE: $TEMPLATE" + message "PARAMETERS FILE: $yaml_parametersfilepath" + message "CAPABILITY IAM: $yaml_capabilityiam" + message "CAPABILITY NAMED IAM: $yaml_capabilitynamediam" + message "NO LOG: $yaml_nolog" + message "LOG FILE: $yaml_logfile" + message "VERBOSE: $yaml_verbose" + message "LAUNCHER CONFIG: $config_file_path" + message "DELETE ON FAILURE: $yaml_deletecreatefailures" + message "WAIT TIME (Sec): $yaml_waittime" + message "MAX WAITS (Loops): $yaml_maxwaits" else - message "$HEADER" + message "** Start CloudFormation Launcher v$script_version **" + message "ACTION: $ACTION" fi } @@ -200,7 +220,7 @@ function run_stack_command { --parameters file://${yaml_parametersfilepath} fi exit_check $? "Executed ${ACTION} Stack Command" - message '' + echo '' monitor_stack_status } @@ -217,20 +237,20 @@ function output_create_complete { # If Verbose True then Output all the Create Complete Events for Debugging if [ "$yaml_verbose" == "true" ]; then message "REPORT: CREATE COMPLETE EVENTS..." - message '' + echo '' create_complete=$(aws cloudformation describe-stack-events --profile ${yaml_profilename} --stack-name ${yaml_stackname} --output json --query 'StackEvents[?ResourceStatus==`CREATE_COMPLETE`]') message "$create_complete" - message '' + echo '' fi } function output_create_failed { # Output all the Create Failed Events for Debugging message "REPORT: CREATE FAILED EVENTS..." - message '' + echo '' create_failed=$(aws cloudformation describe-stack-events --profile ${yaml_profilename} --stack-name ${yaml_stackname} --output json --query 'StackEvents[?ResourceStatus==`CREATE_FAILED`]') message "$create_failed" - message '' + echo '' } function monitor_delete_stack_status { @@ -242,14 +262,18 @@ function monitor_delete_stack_status { STATUS=$(aws cloudformation describe-stacks --profile ${yaml_profilename} --stack-name "$yaml_stackname" --output text --query 'Stacks[*].StackStatus') exit_code=$? exit_check ${exit_code} "Executing Status Check" - message "REPORT: Status (${STATUS})" + if [ ${exit_code} -eq 255 ]; then + message "REPORT: Status (DOES NOT EXIST)" + else + message "REPORT: Status (${STATUS})" + fi if [[ "$STATUS" == "DELETE_IN_PROGRESS" && ${count} -lt ${max_waits} ]]; then message "REPORT: Delete not complete!" message "REPORT: Attempt $count of $max_waits." display_runtime message "Polling again in $wait_time seconds..." - message '' + echo '' sleep ${wait_time} count=$(( count + 1 )) elif [ ${exit_code} -eq 255 ]; then @@ -263,6 +287,15 @@ function monitor_delete_stack_status { done } +function get_stack_status { + STATUS=$(aws cloudformation describe-stacks --profile ${yaml_profilename} --stack-name "$yaml_stackname" --output text --query 'Stacks[*].StackStatus' 2>/dev/null) + if [ $? -eq 255 ]; then + echo "Statck Status: (DOES NOT EXIST)" + else + echo "Stack Status: ($STATUS)" + fi +} + function monitor_stack_status { wait_time=${yaml_waittime} max_waits=${yaml_maxwaits} @@ -278,7 +311,7 @@ function monitor_stack_status { message "REPORT: Attempt $count of $max_waits." display_runtime message "REPORT: Polling again in $wait_time seconds..." - message '' + echo '' sleep ${wait_time} count=$(( count + 1 )) elif [ "$STATUS" == "${ACTION}_COMPLETE" ]; then @@ -292,14 +325,14 @@ function monitor_stack_status { message "REPORT: Attempt $count of $max_waits." display_runtime message "REPORT: Polling again in $wait_time seconds..." - message '' + echo '' sleep ${wait_time} count=$(( count + 1 )) elif [ "$STATUS" == "ROLLBACK_IN_PROGRESS" ]; then # If Delete Stack on failures when Creating is True then Delete the Stack after grabbing Events if [[ "$task_type" == "create-stack" && "$yaml_deletecreatefailures" == "true" ]]; then message 'ERROR: Failed and Rolling Back!' - message '' + echo '' output_create_complete output_create_failed delete_stack_command @@ -313,7 +346,7 @@ function monitor_stack_status { message "REPORT: Attempt $count of $max_waits." display_runtime message "Polling again in $wait_time seconds..." - message '' + echo '' sleep ${wait_time} count=$(( count + 1 )) fi @@ -322,11 +355,11 @@ function monitor_stack_status { break elif [ "$STATUS" == "ROLLBACK_COMPLETE" ]; then message "REPORT: Rollback complete!" - message '' + echo '' break else message 'ERROR: The stack has not create or update has failed.' - message '' + echo '' break fi done @@ -344,7 +377,10 @@ start_time=$(date +%s) eval $(parse_yaml ${config_file_path} "yaml_") #set | grep yaml_ count=1 -if [ "$delete" == "true" ]; then +if [ "$status" == "true" ]; then + echo "$config_file_path" + get_stack_status +elif [ "$delete" == "true" ]; then ACTION=DELETE show_header delete_stack_command @@ -359,24 +395,28 @@ else run_stack_command fi -# Runtime -end_time=$(date +%s) -message '' -message "ENDTIME: ($(date))" -display_runtime -message '' - -# Results -if [[ "$delete_successful" == "true" && "$ACTION" == "DELETE" ]]; then - message "REPORT: DELETE SUCCESS!" - message '' - exit 0 -elif [ "$successful" == "true" ]; then - message "REPORT: $ACTION SUCCESS!" - message '' - exit 0 +if [ "$status" == "true" ]; then + echo '' else - message "ERROR: $ACTION FAILED!" - message '' - exit 1 -fi + # Runtime + end_time=$(date +%s) + echo '' + message "ENDTIME: ($(date))" + display_runtime + echo '' + + # Results + if [[ "$delete_successful" == "true" && "$ACTION" == "DELETE" ]]; then + message "REPORT: DELETE SUCCESS!" + echo '' + exit 0 + elif [ "$successful" == "true" ]; then + message "REPORT: $ACTION SUCCESS!" + echo '' + exit 0 + else + message "ERROR: $ACTION FAILED!" + echo '' + exit 1 + fi +fi \ No newline at end of file