aws-cfm-utils
Advanced tools
Comparing version 1.0.0 to 1.1.0
271
index.js
@@ -6,227 +6,49 @@ #!/usr/bin/env node | ||
const { version } = require(__dirname + '/package.json'); | ||
const { cliopts } = require('./lib/cli_options.js'); | ||
const { processopts } = require('./lib/process_cli_options.js'); | ||
const deletion_timeout = 1800000; //30 mins | ||
const create_timeout = 3600000; //60 mins | ||
const update_timeout = 3600000; //60 mins | ||
const { describestack } = require('./helpers/cfm_describe_stack.js'); //describestack(cfm, stackname) | ||
const { deletestack } = require('./helpers/cfm_delete_stack.js'); //deletestack(cfm, stackname) | ||
const { createstack } = require('./helpers/cfm_create_stack.js'); //createstack(cfm, args) | ||
const { updatestack } = require('./helpers/cfm_update_stack.js'); //updatestack(cfm, args) | ||
const sleep = require('util').promisify(setTimeout); | ||
AWS.config.update({ | ||
region: 'eu-west-1' | ||
}); | ||
const cfm = new AWS.CloudFormation(); | ||
const readjsonfile = (filename) => { | ||
const file_dir = __dirname + '/' + filename; | ||
const file_content = fs.readFileSync(file_dir, 'utf8'); //JSON.parse(fs.readFileSync(file_dir, 'utf8')); | ||
return file_content; | ||
}; | ||
//Function: describe cfm stack status | ||
const describestack = async (stackname) => { | ||
let data; | ||
try { | ||
data = await cfm.describeStacks({ StackName: stackname }).promise(); | ||
return data.Stacks[0].StackStatus; | ||
} | ||
catch (err) { | ||
return err.statusCode; | ||
} | ||
}; | ||
//Function: change stackprotection on the stack | ||
const stackprotection = async (stackname, termination) => { | ||
console.log('Changing termination protection on stack: ' + stackname); | ||
const params = { | ||
EnableTerminationProtection: termination, | ||
StackName: stackname | ||
const cfmclient = (region, profile) => { | ||
const options = { | ||
apiVersion: '2010-05-15', | ||
region: region, | ||
}; | ||
try { | ||
await cfm.updateTerminationProtection(params).promise(); | ||
} | ||
catch (err) { | ||
console.error('Exiting with error: ' + err.stack); | ||
process.exit(2); | ||
if (profile !== undefined) { | ||
options.credentials = new AWS.SharedIniFileCredentials({profile: profile}); | ||
} | ||
console.log('Termination Protection set to ' + termination + ' on stack: ' + stackname + '\n'); | ||
return new AWS.CloudFormation(options); | ||
}; | ||
//Function: createstack | ||
const createstack = async (cfm, stackname, template, policy) => { | ||
console.log('Creating stack: ' + stackname); | ||
const main = async (cfm, args) => { | ||
const stack_status = await describestack(cfm, args.stackName); | ||
console.log('status: ' + stack_status); | ||
let data; | ||
let count = 0; | ||
let stack_status = await describestack(stackname); | ||
const params = { | ||
StackName: stackname, | ||
Capabilities: [ 'CAPABILITY_NAMED_IAM' ], | ||
EnableTerminationProtection: true, | ||
StackPolicyBody: policy, | ||
TemplateBody: template | ||
}; | ||
try { | ||
data = await cfm.createStack(params).promise(); | ||
console.log(data); | ||
} | ||
catch (err) { | ||
console.error('Exiting with error: ' + err.stack); | ||
process.exit(2); | ||
} | ||
while (stack_status == 'CREATE_IN_PROGRESS' || stack_status == 400) { | ||
count = count++; | ||
stack_status = await describestack(stackname); | ||
console.log('CFM stack status: ', stack_status); | ||
if (count > create_timeout * 60 / 10) { | ||
console.log('Aborting - Timeout while creating!'); | ||
process.exit(1); | ||
} | ||
else { | ||
console.log('Waiting...'); | ||
await sleep(15000); //15 secs | ||
} | ||
} | ||
if (stack_status != 'CREATE_COMPLETE') { | ||
console.log('Failure - Stack creation unsuccessful!'); | ||
process.exit(1); | ||
} | ||
console.log('Success - Stack Creation successful!'); | ||
process.exit(0); | ||
}; | ||
//Function: updatestack | ||
const updatestack = async (cfm, stackname, template, policy) => { | ||
console.log('Updating stack: ' + stackname); | ||
const params = { | ||
StackName: stackname, | ||
Capabilities: [ 'CAPABILITY_NAMED_IAM' ], | ||
StackPolicyBody: policy, | ||
TemplateBody: template | ||
}; | ||
let data; | ||
try { | ||
data = await cfm.updateStack(params).promise(); | ||
console.log(data); | ||
} | ||
catch (err) { | ||
if (err == 'ValidationError: No updates are to be performed.') { | ||
console.log('Update not required. No changes to the cfm stack! ' + err + '\n'); | ||
process.exit(0); | ||
} | ||
console.error('Exiting with error type: ' + err.stack); | ||
process.exit(2); | ||
} | ||
let stack_status = await describestack(stackname); | ||
let count = 0; | ||
//Wait for stack update | ||
while (stack_status == 'UPDATE_IN_PROGRESS' || stack_status == 'CREATE_COMPLETE' || stack_status == 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS') { | ||
count = count++; | ||
stack_status = await describestack(stackname); | ||
console.log('CFM stack status: ', stack_status); | ||
if (count > update_timeout * 60 / 10) { | ||
console.log('Aborting - Timeout while updating'); | ||
process.exit(1); | ||
} else { | ||
console.log('Waiting...'); | ||
await sleep(15000); //15 sec | ||
} | ||
} | ||
if (stack_status != 'UPDATE_COMPLETE') { | ||
console.log('Failure - Stack update unsuccessful'); | ||
process.exit(1); | ||
} | ||
console.log('Success - Stack Updated successfully! \n'); | ||
process.exit(0); | ||
}; | ||
//Function: deletestack | ||
const deletestack = async (cfm, stackname) => { | ||
console.log('Deleting stack: ' + stackname); | ||
const params = { | ||
StackName: stackname | ||
}; | ||
let data; | ||
try { | ||
data = await cfm.deleteStack(params).promise(); | ||
console.log(data); | ||
} | ||
catch (err) { | ||
if (err == 'ValidationError: Stack [' + stackname + '] cannot be deleted while TerminationProtection is enabled') { | ||
console.log('Termination Protection is enabled on the stack! \n'); | ||
console.log('Disabling Termination Protection on ' + stackname + '\n'); | ||
await stackprotection(stackname, false); | ||
console.log('Trigger deployment of the stack again. Exiting...\n'); | ||
process.exit(0); | ||
} | ||
console.error('Exiting with error: ' + err.stack); | ||
process.exit(2); | ||
} | ||
let stack_status = await describestack(stackname); | ||
let count = 0; | ||
//Wait for stack delete | ||
while (stack_status != 400) { | ||
count = count++; | ||
stack_status = await describestack(stackname); | ||
console.log('CFM stack status: ', stack_status); | ||
if (count > deletion_timeout * 60 / 5) { | ||
console.log('Aborting - Timeout while deleting'); | ||
process.exit(1); | ||
} | ||
else { | ||
console.log('Waiting...'); | ||
await sleep(15000); //15 secs | ||
} | ||
} | ||
console.log('Success - Stack Deleted successfully!'); | ||
}; | ||
const main = async (cfm, stackname, template, policy) => { | ||
const stack_status = await describestack(stackname); | ||
if (stack_status != 400) { | ||
try { | ||
console.log('Stack: ' + stackname + ' exists! Status: ' + stack_status); | ||
console.log('Stack: ' + args.stackName + ' exists! Status: ' + stack_status); | ||
switch (stack_status) { | ||
case 'CREATE_COMPLETE': | ||
await updatestack(cfm, stackname, template, policy); | ||
await updatestack(cfm, args); | ||
break; | ||
case 'UPDATE_COMPLETE': | ||
await updatestack(cfm, stackname, template, policy); | ||
await updatestack(cfm, args); | ||
break; | ||
case 'ROLLBACK_COMPLETE': | ||
await updatestack(cfm, stackname, template, policy); | ||
await updatestack(cfm, args); | ||
break; | ||
case 'UPDATE_ROLLBACK_COMPLETE': | ||
await updatestack(cfm, stackname, template, policy); | ||
await updatestack(cfm, args); | ||
break; | ||
case 'UPDATE_ROLLBACK_FAILED': | ||
await updatestack(cfm, stackname, template, policy); | ||
await updatestack(cfm, args); | ||
break; | ||
case 'CREATE_FAILED': | ||
await deletestack(cfm, stackname); | ||
await createstack(cfm, stackname, template, policy); | ||
await deletestack(cfm, args.stackName); | ||
await createstack(cfm, args); | ||
break; | ||
@@ -244,4 +66,4 @@ default: | ||
try { | ||
console.log('Stack: ' + stackname + ' does not exist, creating new one!'); | ||
await createstack(cfm, stackname, template, policy); | ||
console.log('Stack: ' + args.stackName + ' does not exist, creating new one!'); | ||
await createstack(cfm, args); | ||
} | ||
@@ -255,37 +77,10 @@ catch (err) { | ||
//Parse command line options | ||
const argv = require('yargs') // eslint-disable-line | ||
.usage('Usage: $0 [options]') | ||
.example('$0 -n stackname -t cfmtemplate -p stackpolicy','Create/update stack using AWS default credentials') | ||
.example('$0 -n stackname -t cfmtemplate -p stackpolicy -k accesskeyid -s secretkey','Create/update stack using provided credentials') | ||
.example('$0 --name stackname --template cfmtemplate --stackpolicy stackpolicy') | ||
.example('$0 --name stackname --template cfmtemplate --stackpolicy stackpolicy --accesskeyid accesskeyid --secretkey secretkey') | ||
.alias('n', 'name') | ||
.nargs('n', 1) | ||
.describe('n', 'AWS stack name') | ||
.alias('t', 'template') | ||
.nargs('t', 1) | ||
.describe('t', 'CFM template file name') | ||
.alias('p', 'stackpolicy') | ||
.nargs('p', 1) | ||
.describe('p', 'Stack policy file name') | ||
.demandOption(['n', 't', 'p']) | ||
.string(['n', 't', 'p']) | ||
.describe('k', 'Your AWS access key') | ||
.alias('k', 'accesskeyid') | ||
.describe('s', 'Your AWS secret key') | ||
.alias('s', 'secretkey') | ||
.string(['k', 's']) | ||
.implies('k', 's') | ||
.version(version) | ||
.alias('v', 'version') | ||
.help('h') | ||
.alias('h', 'help') | ||
.showHelpOnFail(false, 'Something went wrong! run with --help or -h') | ||
.argv; | ||
// Collect and transform input options | ||
const input_args = process.argv; | ||
const args = processopts(cliopts(input_args)); | ||
const stackname = argv.name; | ||
const template = readjsonfile(argv.template); | ||
const policy = readjsonfile(argv.stackpolicy); | ||
// Create AWS.CloudFormation client for specified region/profile | ||
cfm = cfmclient(args.region, args.profile); | ||
main(cfm, stackname, template, policy); | ||
// Create/Update cloudformation stack using input args and cfm client | ||
main(cfm, args); |
{ | ||
"name": "aws-cfm-utils", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "AWS utils to deploy cloudformation stack/templates", | ||
@@ -16,3 +16,4 @@ "main": "index.js", | ||
"eslint": "eslint .", | ||
"eslint-fix": "eslint --fix ." | ||
"eslint-fix": "eslint --fix .", | ||
"test": "make mocha" | ||
}, | ||
@@ -24,2 +25,3 @@ "author": "Marcin Cuber", | ||
"fs": "0.0.1-security", | ||
"path": "^0.12.7", | ||
"util": "^0.10.3", | ||
@@ -35,4 +37,5 @@ "yargs": "^11.0.0" | ||
"eslint-plugin-standard": "^3.1.0", | ||
"rimraf": "^2.6.2" | ||
"rimraf": "^2.6.2", | ||
"mocha": "^5.1.1" | ||
} | ||
} |
@@ -19,15 +19,67 @@ # AWS CLOUDFORMATION UTILS | ||
Options: | ||
-n, --name AWS stack name [string] [required] | ||
-t, --template CFM template file name [string] [required] | ||
-p, --stackpolicy Stack policy file name [string] [required] | ||
-k, --accesskeyid Your AWS access key [string] | ||
-s, --secretkey Your AWS secret key [string] | ||
-h, --help Show help [boolean] | ||
-v, --version Show version number [boolean] | ||
Options: | ||
--stack-name [string] [required] | ||
--template-body CFM template file name [string] | ||
--stack-policy-body Stack policy file name [string] | ||
--accesskeyid AWS access key [string] | ||
--secretkey AWS secret key [string] | ||
-h, --help Show help [boolean] | ||
--parameters CFM Parameters [array] | ||
--tags CFM Tags [array] | ||
--region [string] [default: "eu-west-1"] | ||
--capabilities [array] [choices: "CAPABILITY_NAMED_IAM", "CAPABILITY_IAM"] | ||
--profile [string] | ||
--role-arn [string] | ||
--resource-types [array] | ||
--disable-rollback [boolean] | ||
--template-url [string] | ||
--stack-policy-url [string] | ||
--notification-arns [array] | ||
--timeout-in-minutes [number] | ||
--on-failure [string] [choices: "DO_NOTHING", "ROLLBACK", "DELETE"] | ||
--use-previous-template [boolean] | ||
--stack-policy-during-update-body [string] | ||
--stack-policy-during-update-url [string] | ||
--wait [boolean] | ||
--enable-termination-protection [boolean] | ||
--version Show version number [boolean] | ||
Examples: | ||
aws-cfm-utils -n stackname -t cfmtemplate -p stackpolicy | ||
aws-cfm-utils --name stackname --template cfmtemplate --stackpolicy stackpolicy | ||
1. aws-cfm-utils --stack-name stackname --template-body cfmtemplate --stack-policy-body stackpolicy --region eu-west-1 --enable-termination-protection true | ||
2. aws-cfm-utils --stack-name mynewstack --template-body test/fixtures/template.json --stack-policy-body test/fixtures/stackpolicy.json --enable-termination-protection true --region eu-west-1 --parameters test/fixtures/parameters.json --tags Key=TestTag,Value=TestTagValue Key=TestTag2,Value=TestTagValue2 Key=TestTag3,Value=TestTagValue4 | ||
3. aws-cfm-utils --stack-name mynewstack --template-body test/fixtures/template.json --stack-policy-body test/fixtures/stackpolicy.json --enable-termination-protection true --region eu-west-1 --parameters test/fixtures/parameters.json --tags test/fixtures/tags.json | ||
4. aws-cfm-utils --stack-name mynewstack --template-body test/template.json --stack-policy-body test/stackpolicy.json --enable-termination-protection true --region eu-west-1 --parameters ParameterKey=TestName,ParameterValue=TestKey ParameterKey=TestName2,ParameterValue=TestKey2 | ||
### Global parameters ([AWS CLI Docs](http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#general-options)): | ||
``` | ||
--profile //optional | ||
--region //optional, defaults to Ireland region eu-west-1 | ||
``` | ||
### Used during creation of the stack, otherwise ignored ([create-stack](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html)): | ||
``` | ||
--enable-termination-protection | --no-enable-termination-protection | ||
--disable-rollback | --no-disable-rollback | ||
--timeout-in-minutes | ||
--on-failure | ||
``` | ||
### Used during update of the stack, otherwise ignored ([update-stack](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/update-stack.html)): | ||
``` | ||
--use-previous-template | --no-use-previous-template] | ||
--stack-policy-during-update-body | ||
--stack-policy-during-update-url | ||
``` | ||
## Unit Tests | ||
``` | ||
npm run test | ||
``` | ||
## Requirements and Dependencies | ||
@@ -34,0 +86,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
46463
19
1355
101
5
8
1
+ Addedpath@^0.12.7
+ Addedpath@0.12.7(transitive)
+ Addedprocess@0.11.10(transitive)