diff --git a/src/cli/lib/handle-destroy.mjs b/src/cli/lib/handle-destroy.mjs index 2216fde7..e5b1cb88 100644 --- a/src/cli/lib/handle-destroy.mjs +++ b/src/cli/lib/handle-destroy.mjs @@ -17,7 +17,12 @@ const handleDestroy = async ({ argv, globalOptions, sitesInfo }) => { process.exit(3) // eslint-disable-line no-process-exit } - await destroy({ globalOptions, siteInfo, verbose : true }) + const deleted = await destroy({ globalOptions, siteInfo, verbose : true }) + + if (deleted === true) { + process.stdout.write(`Removing ${apexDomain} from local DB.\n`) + delete sitesInfo[apexDomain] + } } export { handleDestroy } diff --git a/src/lib/actions/destroy.mjs b/src/lib/actions/destroy.mjs index 0d3d530d..cb5bd4e2 100644 --- a/src/lib/actions/destroy.mjs +++ b/src/lib/actions/destroy.mjs @@ -15,18 +15,47 @@ const destroy = async ({ globalOptions, siteInfo, verbose }) => { const s3Client = new S3Client({ credentials }) // this method provides user udptaes - await emptyBucket({ bucketName, s3Client, verbose }) + try { + progressLogger?.write('Deleting site bucket...\n') + await emptyBucket({ bucketName, s3Client, verbose }) + } catch (e) { + if (e.name === 'NoSuchBucket') { + progressLogger?.write('Bucket already deleted.\n') + } else { + throw e + } + } const siteTemplate = new SiteTemplate({ credentials, siteInfo }) await siteTemplate.destroyPlugins() - progressLogger.write('Deleting stack...\n') + progressLogger.write('Deleting stack...') const cloudFormationClient = new CloudFormationClient({ credentials }) const deleteStackCommand = new DeleteStackCommand({ StackName : stackName }) await cloudFormationClient.send(deleteStackCommand) - const finalStatus = await trackStackStatus({ cloudFormationClient, noDeleteOnFailure : true, stackName }) - progressLogger?.write('Final status: ' + finalStatus + '\n') + // the delete command is doesn't mind if the bucket doesn't exist, but trackStackStatus does + try { + const finalStatus = await trackStackStatus({ cloudFormationClient, noDeleteOnFailure : true, stackName }) + progressLogger?.write('Final status: ' + finalStatus + '\n') + + if (finalStatus === 'DELETE_FAILED' && progressLogger !== undefined) { + progressLogger.write('\nThe delete is expected to fail at first because the \'replicated Lambda functions\' take a while to clear and the stack cannot be fully deleted until AWS clears the replicated functions. Give it at least 30 min and up to a few hours and try again.') + return false + } else if (finalStatus === 'DELETE_COMPLETE') { + return true + } + } catch (e) { + // oddly, if the stack does not exist we get a ValidationError; which means it's already deleted + if (e.name === 'ValidationError') { + progressLogger.write(' already deleted.\n') + return true + } else { + throw e + } + } finally { + progressLogger?.write('\n') + } } export { destroy } diff --git a/src/lib/shared/site-template.mjs b/src/lib/shared/site-template.mjs index ff3a9164..c0917c9b 100644 --- a/src/lib/shared/site-template.mjs +++ b/src/lib/shared/site-template.mjs @@ -2,9 +2,10 @@ import yaml from 'js-yaml' import { S3Client, DeleteBucketCommand } from '@aws-sdk/client-s3' -import * as plugins from '../plugins' import { determineBucketName } from './determine-bucket-name' import { determineOACName } from './determine-oac-name' +import * as plugins from '../plugins' +import { progressLogger } from './progress-logger' /** * Class encapsulating site stack configuration. Any enabled plugins are loaded and processed by this class. @@ -153,6 +154,7 @@ const SiteTemplate = class { const { siteInfo } = this const { sharedLoggingBucket } = siteInfo + progressLogger.write('Deleting shared logging bucket...\n') const s3Client = new S3Client({ credentials : this.credentials }) const deleteBucketCommand = new DeleteBucketCommand({ Bucket : sharedLoggingBucket }) await s3Client.send(deleteBucketCommand) @@ -197,7 +199,10 @@ const SiteTemplate = class { throw new Error(`Unknown plugin found in '${apexDomain}' plugin settings.`) } - await plugin.preStackDestroyHandler({ siteTemplate : this, settings }) + const { preStackDestroyHandler } = plugin + if (preStackDestroyHandler !== undefined) { + await preStackDestroyHandler({ siteTemplate : this, settings }) + } } }