@architect/destroy
Advanced tools
Comparing version 3.0.13 to 4.0.0-RC.0
{ | ||
"name": "@architect/destroy", | ||
"version": "3.0.13", | ||
"version": "4.0.0-RC.0", | ||
"description": "Destroy projects created with Architect", | ||
@@ -26,5 +26,9 @@ "main": "src/index.js", | ||
"dependencies": { | ||
"@architect/inventory": "~3.6.0", | ||
"@architect/inventory": "~3.6.6-RC.0", | ||
"@architect/utils": "~3.1.9", | ||
"aws-sdk": "^2.1363.0", | ||
"@aws-lite/client": "~0.13.4", | ||
"@aws-lite/cloudformation": "~0.0.1", | ||
"@aws-lite/cloudwatch-logs": "~0.0.2", | ||
"@aws-lite/s3": "~0.1.8", | ||
"@aws-lite/ssm": "~0.2.2", | ||
"minimist": "~1.2.8", | ||
@@ -35,10 +39,9 @@ "run-parallel": "~1.2.0", | ||
"devDependencies": { | ||
"@architect/deploy": "~4.5.1", | ||
"@architect/eslint-config": "^2.1.1", | ||
"aws-sdk-mock": "~5.8.0", | ||
"@architect/deploy": "~4.6.3", | ||
"@architect/eslint-config": "^2.1.2", | ||
"cross-env": "~7.0.3", | ||
"eslint": "^8.47.0", | ||
"eslint": "~8.56.0", | ||
"nyc": "~15.1.0", | ||
"tap-arc": "~0.3.5", | ||
"tape": "~5.6.6" | ||
"tap-arc": "~1.2.2", | ||
"tape": "~5.7.2" | ||
}, | ||
@@ -45,0 +48,0 @@ "eslintConfig": { |
@@ -1,54 +0,29 @@ | ||
let aws = require('aws-sdk') | ||
let waterfall = require('run-waterfall') | ||
module.exports = function deleteBucketContents ({ bucket }, callback) { | ||
let region = process.env.AWS_REGION | ||
let s3 = new aws.S3({ region }) | ||
let objects = [] | ||
module.exports = function deleteBucketContents ({ aws, bucket: Bucket }, callback) { | ||
let bucketExists = false | ||
function ensureBucket (callback) { | ||
s3.headBucket({ Bucket: bucket }, function done (err) { | ||
if (err) bucketExists = false | ||
else bucketExists = true | ||
callback(null) | ||
}) | ||
} | ||
function collectObjects (ContinuationToken, callback) { | ||
s3.listObjectsV2({ | ||
Bucket: bucket, | ||
ContinuationToken | ||
}, function done (err, result) { | ||
if (err) { | ||
callback(err) | ||
} | ||
else { | ||
objects = objects.concat(result.Contents) | ||
if (result.IsTruncated) { | ||
collectObjects(result.NextContinuationToken, callback) | ||
} | ||
else { | ||
callback(null, objects.map(item => ({ Key: item.Key }))) | ||
} | ||
} | ||
}) | ||
function collectObjects (callback) { | ||
aws.s3.ListObjectsV2({ Bucket, paginate: true }) | ||
.then(result => { | ||
let { Contents } = result | ||
let objectsToDelete = Contents.map(({ Key }) => ({ Key })).filter(Boolean) | ||
callback(null, objectsToDelete) | ||
}) | ||
.catch(err => callback(err)) | ||
} | ||
function deleteObjects (objs, callback) { | ||
let batch = objs.splice(0, 1000) // S3.deleteObjects supports up to 1k keys | ||
s3.deleteObjects({ | ||
Bucket: bucket, | ||
Delete: { | ||
Objects: batch | ||
} | ||
}, | ||
function done (err) { | ||
if (err) callback(err) | ||
else if (objs.length) { | ||
deleteObjects(objs, callback) | ||
} | ||
else callback() | ||
function deleteObjects (objectsToDelete, callback) { | ||
let Objects = objectsToDelete.splice(0, 1000) // S3.deleteObjects supports up to 1k keys | ||
aws.s3.DeleteObjects({ | ||
Bucket, | ||
Delete: { Objects }, | ||
}) | ||
.then(() => { | ||
if (objectsToDelete.length) { | ||
deleteObjects(objectsToDelete, callback) | ||
} | ||
else callback() | ||
}) | ||
.catch(err => callback(err)) | ||
} | ||
@@ -58,17 +33,22 @@ | ||
function checkBucketExists (callback) { | ||
ensureBucket(callback) | ||
aws.s3.HeadBucket({ Bucket }) | ||
.then(() => { | ||
bucketExists = true | ||
callback() | ||
}) | ||
.catch(() => callback()) | ||
}, | ||
function maybeCollectObjectsInBucket (callback) { | ||
if (bucketExists) collectObjects(null, callback) | ||
else callback(null, []) | ||
if (bucketExists) { | ||
collectObjects(callback) | ||
} | ||
else callback(null, false) | ||
}, | ||
function maybeDeleteBucketObjects (stuffToDelete, callback) { | ||
if (bucketExists && Array.isArray(stuffToDelete) && stuffToDelete.length > 0) { | ||
deleteObjects(stuffToDelete, callback) | ||
function maybeDeleteBucketObjects (objectsToDelete, callback) { | ||
if (bucketExists && objectsToDelete.length) { | ||
deleteObjects(objectsToDelete, callback) | ||
} | ||
else { | ||
callback() | ||
} | ||
else callback() | ||
}, | ||
@@ -78,6 +58,5 @@ | ||
if (bucketExists) { | ||
s3.deleteBucket({ Bucket: bucket }, function (err) { | ||
if (err) callback(err) | ||
else callback() | ||
}) | ||
aws.s3.DeleteBucket({ Bucket }) | ||
.then(() => callback()) | ||
.catch(err => callback(err)) | ||
} | ||
@@ -84,0 +63,0 @@ else callback() |
@@ -1,41 +0,33 @@ | ||
let aws = require('aws-sdk') | ||
module.exports = function deleteLogs ({ aws, StackName, update }, callback) { | ||
aws.cloudwatchlogs.DescribeLogGroups({ | ||
logGroupNamePrefix: `/aws/lambda/${StackName}-`, | ||
paginate: true, | ||
}) | ||
.then(data => { | ||
if (data?.logGroups?.length) { | ||
let logGroups = data.logGroups.map(({ logGroupName }) => logGroupName) | ||
deleter(aws, logGroups, update, callback) | ||
} | ||
else callback() | ||
}) | ||
.catch(err => callback(err)) | ||
} | ||
module.exports = function deleteLogs ({ StackName, update }, callback) { | ||
let cloudwatch = new aws.CloudWatchLogs() | ||
let logGroups = [] | ||
function getLogs (nextToken, cb) { | ||
let params = { | ||
logGroupNamePrefix: `/aws/lambda/${StackName}-` | ||
} | ||
if (nextToken) params.nextToken = nextToken | ||
cloudwatch.describeLogGroups(params, function (err, data) { | ||
if (err) cb(err) | ||
else { | ||
data.logGroups.forEach(l => { | ||
logGroups.push(l.logGroupName) | ||
function deleter (aws, logGroups, update, callback) { | ||
let timer = 0 | ||
let numComplete = 0 | ||
logGroups.forEach(log => { | ||
timer += 400 // max of about 2-3 transactions per second :/ | ||
setTimeout(function delayedDelete () { | ||
aws.cloudwatchlogs.DeleteLogGroup({ logGroupName: log }) | ||
.then(() => { | ||
numComplete++ | ||
if (logGroups.length === numComplete) callback() | ||
}) | ||
if (data.nextToken) getLogs(data.nextToken, cb) | ||
else cb() | ||
} | ||
}) | ||
} | ||
getLogs(null, function (err) { | ||
if (err) callback(err) | ||
else if (logGroups.length) { | ||
let timer = 0 | ||
let numComplete = 0 | ||
logGroups.forEach(log => { | ||
timer += 400 // max of about 2-3 transactions per second :/ | ||
let params = { logGroupName: log } | ||
setTimeout(function delayedDelete () { | ||
cloudwatch.deleteLogGroup(params, function (err /* , data */) { | ||
if (err) update.warn(err) | ||
numComplete++ | ||
if (logGroups.length === numComplete) callback() | ||
}) | ||
}, timer) | ||
}) | ||
} | ||
else callback() | ||
.catch(err => { | ||
numComplete++ | ||
update.warn(err) | ||
}) | ||
}, timer) | ||
}) | ||
} |
@@ -1,2 +0,1 @@ | ||
let aws = require('aws-sdk') | ||
let parallel = require('run-parallel') | ||
@@ -8,40 +7,34 @@ | ||
module.exports = { | ||
getDeployBucket: function getDeployBucket (appname, callback) { | ||
let region = process.env.AWS_REGION | ||
let ssm = new aws.SSM({ region }) | ||
ssm.getParameter({ | ||
getDeployBucket: function getDeployBucket (aws, appname, callback) { | ||
aws.ssm.GetParameter({ | ||
Name: `/${appname}/deploy/bucket`, | ||
WithDecryption: true | ||
}, function (err, data) { | ||
if (err && err.code !== 'ParameterNotFound') callback(err) | ||
else callback(null, (data && data.Parameter && data.Parameter.Value ? data.Parameter.Value : null)) | ||
}) | ||
.then(data => { | ||
let value = data?.Parameter?.Value ? data.Parameter.Value : null | ||
callback(null, value) | ||
}) | ||
.catch(err => { | ||
if (err && err.code !== 'ParameterNotFound') callback(err) | ||
else callback() | ||
}) | ||
}, | ||
deleteAll: function deleteAll (appname, env, callback) { | ||
let region = process.env.AWS_REGION | ||
let ssm = new aws.SSM({ region }) | ||
// set up for recursive retrieval of all parameters associated to the app | ||
// since SSM only support max 10 param retrieval at a time | ||
let results = {} | ||
function collectByPath (rootPath, NextToken, cb) { | ||
if (!results[rootPath]) results[rootPath] = [] | ||
let query = { | ||
deleteAll: function deleteAll (aws, appname, env, callback) { | ||
let Names = [] | ||
function collectByPath (rootPath, cb) { | ||
aws.ssm.GetParametersByPath({ | ||
Path: rootPath, | ||
Recursive: true, | ||
MaxResults: 10 | ||
} | ||
if (NextToken) query.NextToken = NextToken | ||
ssm.getParametersByPath(query, function (err, data) { | ||
// if the parameters are gone, that's fine too | ||
if (err && err.code !== 'ParameterNotFound') cb(err) | ||
else { | ||
if (data && data.Parameters && data.Parameters.length) { | ||
results[rootPath] = results[rootPath].concat(data.Parameters.map(param => param.Name)) | ||
if (data.NextToken) collectByPath(rootPath, data.NextToken, cb) | ||
else cb(null, results[rootPath]) | ||
paginate: true | ||
}) | ||
.then(data => { | ||
if (data?.Parameters?.length) { | ||
Names.push(...data.Parameters.map(({ Name }) => Name)) | ||
} | ||
else cb(null, results[rootPath]) | ||
} | ||
}) | ||
cb() | ||
}) | ||
.catch(err => { | ||
if (err && err.code !== 'ParameterNotFound') cb(err) | ||
else cb() | ||
}) | ||
} | ||
@@ -52,8 +45,8 @@ | ||
// /<app-name>/<env>/* - environment variables via `arc env` | ||
let paths = [ `/${appname}/${env}`, `/${appname}/deploy` ] | ||
parallel(paths.map(path => collectByPath.bind(null, path, null)), function paramsCollected (err, res) { | ||
let ops = [ `/${appname}/${env}`, `/${appname}/deploy` ] | ||
.map(path => collectByPath.bind(null, path)) | ||
parallel(ops, (err) => { | ||
if (err) callback(err) | ||
else { | ||
// combine all the various parameters by different path names into a single array | ||
let Names = res.reduce((aggregate, current) => aggregate.concat(current), []) | ||
function deleteThings () { | ||
@@ -63,7 +56,8 @@ if (Names.length) { | ||
let chunk = Names.splice(0, 10) | ||
ssm.deleteParameters({ Names: chunk }, function deleteParameters (err) { | ||
if (err) callback(err) | ||
else if (!Names.length) callback() | ||
else deleteThings() | ||
}) | ||
aws.ssm.DeleteParameters({ Names: chunk }) | ||
.then(() => { | ||
if (!Names.length) callback() | ||
else deleteThings() | ||
}) | ||
.catch(err => callback(err)) | ||
} | ||
@@ -70,0 +64,0 @@ else callback() |
159
src/index.js
@@ -1,5 +0,3 @@ | ||
// eslint-disable-next-line | ||
try { require('aws-sdk/lib/maintenance_mode_message').suppress = true } | ||
catch { /* Noop */ } | ||
let aws = require('aws-sdk') | ||
let _inventory = require('@architect/inventory') | ||
let awsLite = require('@aws-lite/client') | ||
let waterfall = require('run-waterfall') | ||
@@ -12,3 +10,4 @@ let deleteBucket = require('./_delete-bucket') | ||
function stackNotFound (StackName, err) { | ||
if (err && err.code == 'ValidationError' && err.message == `Stack with id ${StackName} does not exist`) { | ||
if (err && err.code == 'ValidationError' && | ||
err.message.includes(`Stack with id ${StackName} does not exist`)) { | ||
return true | ||
@@ -18,2 +17,3 @@ } | ||
} | ||
/** | ||
@@ -27,3 +27,3 @@ * @param {object} params - named parameters | ||
module.exports = function destroy (params, callback) { | ||
let { appname, env, stackname, force = false, now, retries, update } = params | ||
let { appname, env, force = false, inventory, now, retries, stackname, update } = params | ||
if (!update) update = updater('Destroy') | ||
@@ -45,3 +45,2 @@ | ||
// hack around no native promise in aws-sdk | ||
let promise | ||
@@ -57,6 +56,3 @@ if (!callback) { | ||
let stackExists | ||
// actual code | ||
let region = process.env.AWS_REGION | ||
let cloudformation = new aws.CloudFormation({ region }) | ||
let aws, stack | ||
@@ -78,42 +74,60 @@ waterfall([ | ||
// Set up inventory to get region | ||
function (callback) { | ||
if (!inventory) { | ||
_inventory({}, (err, result) => { | ||
if (err) callback(err) | ||
else { | ||
inventory = result | ||
callback() | ||
} | ||
}) | ||
} | ||
else callback() | ||
}, | ||
// Instantiate client | ||
function (callback) { | ||
awsLite({ region: inventory.inv.aws.region, debug: true }) | ||
.then(_aws => { | ||
aws = _aws | ||
callback() | ||
}) | ||
.catch(err => callback(err)) | ||
}, | ||
// check for the stack | ||
function (callback) { | ||
update.status(`Destroying ${StackName}`) | ||
cloudformation.describeStacks({ | ||
StackName | ||
}, | ||
function (err, data) { | ||
if (stackNotFound(StackName, err)) { | ||
stackExists = false | ||
callback(null, false) | ||
} | ||
else if (err) callback(err) | ||
else callback(null, data.Stacks[0]) | ||
}) | ||
aws.CloudFormation.DescribeStacks({ StackName }) | ||
.then(data => { | ||
stack = data.Stacks[0] | ||
callback() | ||
}) | ||
.catch(err => { | ||
if (stackNotFound(StackName, err)) { | ||
callback() | ||
} | ||
else callback(err) | ||
}) | ||
}, | ||
// check for dynamodb tables and if force flag not provided, error out | ||
function (stack, callback) { | ||
function (callback) { | ||
if (stack) { | ||
stackExists = true | ||
cloudformation.describeStackResources({ | ||
StackName | ||
}, | ||
function (err, data) { | ||
if (err) callback(err) | ||
else { | ||
aws.CloudFormation.DescribeStackResources({ StackName }) | ||
.then(data => { | ||
let type = t => t.ResourceType | ||
let table = i => i === 'AWS::DynamoDB::Table' | ||
let hasTables = data.StackResources.map(type).some(table) | ||
if (hasTables && !force) callback(Error('table_exists')) | ||
else callback(null, stack) | ||
} | ||
}) | ||
else callback() | ||
}) | ||
.catch(err => callback(err)) | ||
} | ||
else callback(null, stack) | ||
else callback() | ||
}, | ||
// check if static bucket exists in stack | ||
function (stack, callback) { | ||
function (callback) { | ||
if (stack) { | ||
@@ -132,5 +146,3 @@ let bucket = o => o.OutputKey === 'BucketURL' | ||
update.status('Deleting static S3 bucket...') | ||
deleteBucket({ | ||
bucket | ||
}, callback) | ||
deleteBucket({ aws, bucket }, callback) | ||
} | ||
@@ -149,3 +161,3 @@ else if (bucketExists && !force) { | ||
update.status('Retrieving deployment bucket...') | ||
ssm.getDeployBucket(appname, callback) | ||
ssm.getDeployBucket(aws, appname, callback) | ||
}, | ||
@@ -157,3 +169,3 @@ | ||
update.status('Deleting deployment S3 bucket...') | ||
deleteBucket({ bucket: deploymentBucket }, callback) | ||
deleteBucket({ aws, bucket: deploymentBucket }, callback) | ||
} | ||
@@ -171,3 +183,3 @@ else callback() | ||
update.status('Deleting SSM parameters...') | ||
ssm.deleteAll(appname, env, callback) | ||
ssm.deleteAll(aws, appname, env, callback) | ||
} | ||
@@ -179,3 +191,3 @@ }, | ||
update.status('Deleting CloudWatch log groups...') | ||
deleteLogs({ StackName, update }, callback) | ||
deleteLogs({ aws, StackName, update }, callback) | ||
}, | ||
@@ -185,11 +197,7 @@ | ||
function (callback) { | ||
if (stackExists) { | ||
if (stack) { | ||
update.start(`Destroying CloudFormation Stack ${StackName}...`) | ||
cloudformation.deleteStack({ | ||
StackName, | ||
}, | ||
function (err) { | ||
if (err) callback(err) | ||
else callback(null, true) | ||
}) | ||
aws.cloudformation.DeleteStack({ StackName }) | ||
.then(() => callback(null, true)) | ||
.catch(err => callback(err)) | ||
} | ||
@@ -204,29 +212,30 @@ else callback(null, false) | ||
let max = retries // typical values are 15 or 999; see cli.js | ||
function checkit () { | ||
cloudformation.describeStacks({ | ||
StackName | ||
}, | ||
function done (err, result) { | ||
if (stackNotFound(StackName, err)) { | ||
update.done(`Successfully destroyed ${StackName}`) | ||
return callback() | ||
} | ||
if (!err && result.Stacks) { | ||
let stack = result.Stacks.find(s => s.StackName === StackName) | ||
if (stack && stack.StackStatus === 'DELETE_FAILED') { | ||
return callback(Error(`CloudFormation Stack "${StackName}" destroy failed: ${stack.StackStatusReason}`)) | ||
function check () { | ||
aws.cloudformation.DescribeStacks({ StackName }) | ||
.then(result => { | ||
if (result.Stacks) { | ||
let stack = result.Stacks.find(s => s.StackName === StackName) | ||
if (stack && stack.StackStatus === 'DELETE_FAILED') { | ||
return callback(Error(`CloudFormation Stack "${StackName}" destroy failed: ${stack.StackStatusReason}`)) | ||
} | ||
} | ||
} | ||
setTimeout(function delay () { | ||
if (tries === max) { | ||
callback(Error(`CloudFormation Stack destroy still ongoing; aborting as we hit max number of retries (${max})`)) | ||
setTimeout(function delay () { | ||
if (tries === max) { | ||
callback(Error(`CloudFormation Stack destroy still ongoing; aborting as we hit max number of retries (${max})`)) | ||
} | ||
else { | ||
tries += 1 | ||
check() | ||
} | ||
}, 10000) | ||
}) | ||
.catch(err => { | ||
if (stackNotFound(StackName, err)) { | ||
update.done(`Successfully destroyed ${StackName}`) | ||
callback() | ||
} | ||
else { | ||
tries += 1 | ||
checkit() | ||
} | ||
}, 10000) | ||
}) | ||
else callback(err) | ||
}) | ||
} | ||
checkit() | ||
check() | ||
} | ||
@@ -233,0 +242,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
7
0
17081
10
439
2
+ Added@aws-lite/client@~0.13.4
+ Added@aws-lite/s3@~0.1.8
+ Added@aws-lite/ssm@~0.2.2
+ Added@architect/asap@6.1.0-RC.0(transitive)
+ Added@architect/inventory@3.6.6-RC.0(transitive)
+ Added@architect/utils@4.0.6(transitive)
+ Added@aws-lite/client@0.12.20.13.40.21.10(transitive)
+ Added@aws-lite/cloudformation@0.0.5(transitive)
+ Added@aws-lite/cloudwatch-logs@0.0.8(transitive)
+ Added@aws-lite/s3@0.1.22(transitive)
+ Added@aws-lite/ssm@0.2.5(transitive)
+ Addedaws4@1.13.2(transitive)
+ Addedglob@10.3.16(transitive)
+ Addedini@4.1.3(transitive)
+ Addedjackspeak@3.4.3(transitive)
+ Addedminipass@7.1.2(transitive)
- Removedaws-sdk@^2.1363.0
- Removed@architect/asap@6.0.6(transitive)
- Removed@architect/inventory@3.6.5(transitive)
- Removedavailable-typed-arrays@1.0.7(transitive)
- Removedaws-sdk@2.1692.0(transitive)
- Removedbase64-js@1.5.1(transitive)
- Removedbuffer@4.9.2(transitive)
- Removedcall-bind@1.0.8(transitive)
- Removedcall-bind-apply-helpers@1.0.1(transitive)
- Removedcall-bound@1.0.3(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.0.0(transitive)
- Removedevents@1.1.1(transitive)
- Removedfor-each@0.3.3(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.6(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhas-tostringtag@1.0.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedieee754@1.1.13(transitive)
- Removedinherits@2.0.4(transitive)
- Removedis-arguments@1.2.0(transitive)
- Removedis-callable@1.2.7(transitive)
- Removedis-generator-function@1.0.10(transitive)
- Removedis-typed-array@1.1.15(transitive)
- Removedisarray@1.0.0(transitive)
- Removedjmespath@0.16.0(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedpossible-typed-array-names@1.0.0(transitive)
- Removedpunycode@1.3.2(transitive)
- Removedquerystring@0.2.0(transitive)
- Removedsax@1.2.1(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedurl@0.10.3(transitive)
- Removedutil@0.12.5(transitive)
- Removeduuid@8.0.0(transitive)
- Removedwhich-typed-array@1.1.18(transitive)
- Removedxml2js@0.6.2(transitive)
- Removedxmlbuilder@11.0.1(transitive)