From cd94e707df2b34a1aa760049f0ef26558ae3f52f Mon Sep 17 00:00:00 2001 From: jqln Date: Tue, 1 Apr 2025 17:26:04 -0500 Subject: [PATCH 1/8] add getAllRegions function --- cli/getAllRegions.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 cli/getAllRegions.ts diff --git a/cli/getAllRegions.ts b/cli/getAllRegions.ts new file mode 100644 index 0000000..998d67f --- /dev/null +++ b/cli/getAllRegions.ts @@ -0,0 +1,14 @@ +import { EC2Client, DescribeRegionsCommand } from "@aws-sdk/client-ec2"; + +export const getAllRegions = async () => { + const client = new EC2Client({ region: "us-east-1" }); + + try { + const command = new DescribeRegionsCommand({}); + const response = await client.send(command); + const regions = response.Regions?.map(region => region.RegionName); + return regions; + } catch (error) { + console.error("Error fetching regions:", error); + } +}; From 1427f680fd2706fb77fb0f0641bf36495887fc28 Mon Sep 17 00:00:00 2001 From: jqln Date: Tue, 1 Apr 2025 17:54:11 -0500 Subject: [PATCH 2/8] change get to fetch to avoid confusion --- cli/fetchAllRegions.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 cli/fetchAllRegions.ts diff --git a/cli/fetchAllRegions.ts b/cli/fetchAllRegions.ts new file mode 100644 index 0000000..21c784d --- /dev/null +++ b/cli/fetchAllRegions.ts @@ -0,0 +1,15 @@ +import { EC2Client, DescribeRegionsCommand } from "@aws-sdk/client-ec2"; + +export const fetchAllRegions = async () => { + const client = new EC2Client({ region: "us-east-1" }); + + try { + const command = new DescribeRegionsCommand({}); + const response = await client.send(command); + const regions = response.Regions?.map(region => region.RegionName); + if (regions === undefined) throw new Error("Error fetching regions"); + return regions.filter((region) => region !== undefined); + } catch (error) { + console.error("Error fetching regions:", error); + } +}; From 44b1fa1ca9e8b1aa66883397d07756c3702bb728 Mon Sep 17 00:00:00 2001 From: jqln Date: Tue, 1 Apr 2025 18:31:36 -0500 Subject: [PATCH 3/8] delete SG, brokers, IAM, etc in every region --- cli/destroy.ts | 76 +++++++++++++++++++++++++------------------- cli/getAllRegions.ts | 14 -------- 2 files changed, 44 insertions(+), 46 deletions(-) delete mode 100644 cli/getAllRegions.ts diff --git a/cli/destroy.ts b/cli/destroy.ts index f760d90..75367a7 100644 --- a/cli/destroy.ts +++ b/cli/destroy.ts @@ -8,57 +8,69 @@ import { getInstanceIdsByPublisher } from "./getInstanceIdsByPublisher"; import { runWithSpinner } from "./spinner"; import { getRegion } from "./getRegion"; import chalk from "chalk"; +import { fetchAllRegions } from "./fetchAllRegions"; export const destroy = async () => { try { const controlPanelName = "RabbitoryControlPanel"; - const region: string = await getRegion(); - const brokerIds = await getInstanceIdsByPublisher("Rabbitory", region); - const instanceIds: string[] | undefined = await getRunningInstanceIdsByName( - controlPanelName, - region, - ); - const instanceId: string | undefined = - instanceIds !== undefined && instanceIds.length > 0 - ? instanceIds[0] - : undefined; + const primaryRegion: string = await getRegion(); + + const regions = await fetchAllRegions(); + + if (!regions || regions.length === 0) { + console.error("Error fetching regions"); + process.exit(1); + } await runWithSpinner( "Deleting DynamoDB Table...", - () => deleteTable(region), + () => deleteTable(primaryRegion), "Deleted DynamoDB Table", ); - await runWithSpinner( - "Deleting RabbitMQ Broker Instances...", - () => - Promise.all(brokerIds?.map((id) => deleteInstance(id, region)) || []), - "Deleted RabbitMQ Broker Instances", + const instanceIds: string[] | undefined = await getRunningInstanceIdsByName( + controlPanelName, + primaryRegion, ); + const instanceId: string | undefined = + instanceIds !== undefined && instanceIds.length > 0 + ? instanceIds[0] + : undefined; if (instanceId !== undefined) { await runWithSpinner( "Terminating Control Panel EC2 instance...", - () => deleteInstance(instanceId, region), + () => deleteInstance(instanceId, primaryRegion), "Terminated EC2 instance", ); } - await runWithSpinner( - "Deleting Rabbitory security group...", - () => deleteRabbitorySG(region), - "Deleted Rabbitory security group", - ); - await runWithSpinner( - "Deleting RMQ Broker IAM role...", - () => deleteBrokerRole(region), - "Deleted RMQ Broker IAM role", - ); - await runWithSpinner( - "Deleting Rabbitory IAM role...", - () => deleteRabbitoryRole(region), - "Deleted Rabbitory IAM role", - ); + for (const region of regions) { + const brokerIds = await getInstanceIdsByPublisher("Rabbitory", region); + + await runWithSpinner( + "Deleting RabbitMQ Broker Instances...", + () => Promise.all(brokerIds?.map((id) => deleteInstance(id, region)) || []), + "Deleted RabbitMQ Broker Instances", + ); + + await runWithSpinner( + "Deleting Rabbitory security group...", + () => deleteRabbitorySG(region), + "Deleted Rabbitory security group", + ); + + await runWithSpinner( + "Deleting RMQ Broker IAM role...", + () => deleteBrokerRole(region), + "Deleted RMQ Broker IAM role", + ); + await runWithSpinner( + "Deleting Rabbitory IAM role...", + () => deleteRabbitoryRole(region), + "Deleted Rabbitory IAM role", + ); + } } catch (error) { console.error( chalk.redBright("\nRabbitory destruction failed\n"), diff --git a/cli/getAllRegions.ts b/cli/getAllRegions.ts deleted file mode 100644 index 998d67f..0000000 --- a/cli/getAllRegions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EC2Client, DescribeRegionsCommand } from "@aws-sdk/client-ec2"; - -export const getAllRegions = async () => { - const client = new EC2Client({ region: "us-east-1" }); - - try { - const command = new DescribeRegionsCommand({}); - const response = await client.send(command); - const regions = response.Regions?.map(region => region.RegionName); - return regions; - } catch (error) { - console.error("Error fetching regions:", error); - } -}; From 10a48b98c0aa3710fc424e9ccc35e96acff19168 Mon Sep 17 00:00:00 2001 From: jqln Date: Tue, 1 Apr 2025 18:39:36 -0500 Subject: [PATCH 4/8] refactor for loop into indiviual functions --- cli/destroy.ts | 73 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/cli/destroy.ts b/cli/destroy.ts index 75367a7..0e4108c 100644 --- a/cli/destroy.ts +++ b/cli/destroy.ts @@ -10,6 +10,35 @@ import { getRegion } from "./getRegion"; import chalk from "chalk"; import { fetchAllRegions } from "./fetchAllRegions"; +const deleteAllBrokerInstances = async (regions: string[]) => { + for (const region of regions) { + const brokerIds = await getInstanceIdsByPublisher("Rabbitory", region); + if (brokerIds !== undefined) { + for (const brokerId of brokerIds) { + await deleteInstance(brokerId, region); + } + } + } +} + +const deleteAllSecurityGroups = async (regions: string[]) => { + for (const region of regions) { + await deleteRabbitorySG(region); + } +} + +const deleteAllBrokerRoles = async (regions: string[]) => { + for (const region of regions) { + await deleteBrokerRole(region); + } +} + +const deleteAllRabbitoryRoles = async (regions: string[]) => { + for (const region of regions) { + await deleteRabbitoryRole(region); + } +} + export const destroy = async () => { try { const controlPanelName = "RabbitoryControlPanel"; @@ -45,32 +74,28 @@ export const destroy = async () => { ); } - for (const region of regions) { - const brokerIds = await getInstanceIdsByPublisher("Rabbitory", region); - - await runWithSpinner( - "Deleting RabbitMQ Broker Instances...", - () => Promise.all(brokerIds?.map((id) => deleteInstance(id, region)) || []), - "Deleted RabbitMQ Broker Instances", - ); + await runWithSpinner( + "Deleting RabbitMQ Broker Instances...", + () => deleteAllBrokerInstances(regions), + "Deleted RabbitMQ Broker Instances", + ); - await runWithSpinner( - "Deleting Rabbitory security group...", - () => deleteRabbitorySG(region), - "Deleted Rabbitory security group", - ); + await runWithSpinner( + "Deleting Rabbitory security group...", + () => deleteAllSecurityGroups(regions), + "Deleted Rabbitory security group", + ); - await runWithSpinner( - "Deleting RMQ Broker IAM role...", - () => deleteBrokerRole(region), - "Deleted RMQ Broker IAM role", - ); - await runWithSpinner( - "Deleting Rabbitory IAM role...", - () => deleteRabbitoryRole(region), - "Deleted Rabbitory IAM role", - ); - } + await runWithSpinner( + "Deleting RMQ Broker IAM role...", + () => deleteAllBrokerRoles(regions), + "Deleted RMQ Broker IAM role", + ); + await runWithSpinner( + "Deleting Rabbitory IAM role...", + () => deleteAllRabbitoryRoles(regions), + "Deleted Rabbitory IAM role", + ); } catch (error) { console.error( chalk.redBright("\nRabbitory destruction failed\n"), From 7d17406d6a0fab0c2b455dc75b4239d6ba5ea904 Mon Sep 17 00:00:00 2001 From: jqln Date: Tue, 1 Apr 2025 20:44:18 -0500 Subject: [PATCH 5/8] remove superfluous IAM role deletion --- cli/destroy.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/cli/destroy.ts b/cli/destroy.ts index 0e4108c..17b1cb5 100644 --- a/cli/destroy.ts +++ b/cli/destroy.ts @@ -27,18 +27,6 @@ const deleteAllSecurityGroups = async (regions: string[]) => { } } -const deleteAllBrokerRoles = async (regions: string[]) => { - for (const region of regions) { - await deleteBrokerRole(region); - } -} - -const deleteAllRabbitoryRoles = async (regions: string[]) => { - for (const region of regions) { - await deleteRabbitoryRole(region); - } -} - export const destroy = async () => { try { const controlPanelName = "RabbitoryControlPanel"; @@ -88,12 +76,12 @@ export const destroy = async () => { await runWithSpinner( "Deleting RMQ Broker IAM role...", - () => deleteAllBrokerRoles(regions), + () => deleteBrokerRole(primaryRegion), "Deleted RMQ Broker IAM role", ); await runWithSpinner( "Deleting Rabbitory IAM role...", - () => deleteAllRabbitoryRoles(regions), + () => deleteRabbitoryRole(primaryRegion), "Deleted Rabbitory IAM role", ); } catch (error) { From 6517b0282f52ca390f90b56cd15199b0c873a462 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 2 Apr 2025 12:49:42 -0500 Subject: [PATCH 6/8] use name to filter security groups to delete --- aws/security-groups/deleteRabbitorySG.ts | 87 ++++++++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/aws/security-groups/deleteRabbitorySG.ts b/aws/security-groups/deleteRabbitorySG.ts index aa8d4f8..975bf2b 100644 --- a/aws/security-groups/deleteRabbitorySG.ts +++ b/aws/security-groups/deleteRabbitorySG.ts @@ -1,23 +1,79 @@ +// import { +// EC2Client, +// DescribeSecurityGroupsCommand, +// DeleteSecurityGroupCommand, +// } from "@aws-sdk/client-ec2"; +// +// const GROUP_NAME = "RabbitorySG"; +// +// const isAwsError = (error: unknown): error is { name: string; message: string } => { +// return typeof error === "object" && error !== null && "name" in error && "message" in error; +// }; +// +// const getSecurityGroupId = async (client: EC2Client): Promise => { +// try { +// const command = new DescribeSecurityGroupsCommand({}); +// const response = await client.send(command); +// return response.SecurityGroups?.[0]?.GroupId ?? null; +// } catch (error: unknown) { +// if (isAwsError(error) && error.name === "InvalidGroup.NotFound") return null; // return if SG does not exist +// throw new Error(`Error retrieving security group ID for ${GROUP_NAME}\n${error instanceof Error ? error.message : String(error)}`); +// } +// }; +// +// const getSecurityGroupNames = async (client: EC2Client): Promise => { +// try { +// const command = new DescribeSecurityGroupsCommand({}); +// const response = await client.send(command); +// return response.SecurityGroups?.map(sg => sg.GroupName ?? '').filter(name => name !== '') ?? []; +// } catch (error: unknown) { +// throw new Error(`Error retrieving security group names\n${error instanceof Error ? error.message : String(error)}`); +// } +// }; +// +// const deleteSecurityGroup = async (groupId: string, client: EC2Client): Promise => { +// try { +// const command = new DeleteSecurityGroupCommand({ GroupId: groupId }); +// await client.send(command); +// } catch (error: unknown) { +// if (error instanceof Error) { +// throw new Error(`Error deleting security group ${groupId}\n${error.message}`); +// } +// throw new Error(`Unknown error deleting security group ${groupId}\n${String(error)}`); +// } +// }; +// +// export const deleteRabbitorySG = async (region: string): Promise => { +// const client = new EC2Client({ region: region }); +// +// try { +// const securityGroupId = await getSecurityGroupId(client); +// if (!securityGroupId) return; // return if no sg exists +// await deleteSecurityGroup(securityGroupId, client); +// } catch (error: unknown) { +// throw new Error(`Failed to delete RabbitorySG\n${error instanceof Error ? error.message : String(error)}`); +// } +// }; + + import { EC2Client, DescribeSecurityGroupsCommand, DeleteSecurityGroupCommand, } from "@aws-sdk/client-ec2"; -const GROUP_NAME = "RabbitorySG"; - -const isAwsError = (error: unknown): error is { name: string; message: string } => { - return typeof error === "object" && error !== null && "name" in error && "message" in error; -}; - -const getSecurityGroupId = async (client: EC2Client): Promise => { +const getRabbitorySGIds = async (client: EC2Client): Promise => { try { - const command = new DescribeSecurityGroupsCommand({ GroupNames: [GROUP_NAME] }); + const command = new DescribeSecurityGroupsCommand({}); const response = await client.send(command); - return response.SecurityGroups?.[0]?.GroupId ?? null; + return response.SecurityGroups?.reduce((ids, sg) => { + if (sg.GroupId && sg.GroupName?.toLowerCase().includes('rabbit')) { + ids.push(sg.GroupId); + } + return ids; + }, []) ?? []; } catch (error: unknown) { - if (isAwsError(error) && error.name === "InvalidGroup.NotFound") return null; // return if SG does not exist - throw new Error(`Error retrieving security group ID for ${GROUP_NAME}\n${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Error retrieving Rabbitory security groups\n${error instanceof Error ? error.message : String(error)}`); } }; @@ -37,10 +93,11 @@ export const deleteRabbitorySG = async (region: string): Promise => { const client = new EC2Client({ region: region }); try { - const securityGroupId = await getSecurityGroupId(client); - if (!securityGroupId) return; // return if no sg exists - await deleteSecurityGroup(securityGroupId, client); + const securityGroupIds = await getRabbitorySGIds(client); + for (const sgId of securityGroupIds) { + await deleteSecurityGroup(sgId, client); + } } catch (error: unknown) { - throw new Error(`Failed to delete RabbitorySG\n${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to delete Rabbitory security groups\n${error instanceof Error ? error.message : String(error)}`); } }; From ff311bb840c35d5fb03324325ebe80fac287b594 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 2 Apr 2025 13:15:33 -0500 Subject: [PATCH 7/8] clean it up --- aws/security-groups/deleteRabbitorySG.ts | 58 ------------------------ 1 file changed, 58 deletions(-) diff --git a/aws/security-groups/deleteRabbitorySG.ts b/aws/security-groups/deleteRabbitorySG.ts index 975bf2b..98f1138 100644 --- a/aws/security-groups/deleteRabbitorySG.ts +++ b/aws/security-groups/deleteRabbitorySG.ts @@ -1,61 +1,3 @@ -// import { -// EC2Client, -// DescribeSecurityGroupsCommand, -// DeleteSecurityGroupCommand, -// } from "@aws-sdk/client-ec2"; -// -// const GROUP_NAME = "RabbitorySG"; -// -// const isAwsError = (error: unknown): error is { name: string; message: string } => { -// return typeof error === "object" && error !== null && "name" in error && "message" in error; -// }; -// -// const getSecurityGroupId = async (client: EC2Client): Promise => { -// try { -// const command = new DescribeSecurityGroupsCommand({}); -// const response = await client.send(command); -// return response.SecurityGroups?.[0]?.GroupId ?? null; -// } catch (error: unknown) { -// if (isAwsError(error) && error.name === "InvalidGroup.NotFound") return null; // return if SG does not exist -// throw new Error(`Error retrieving security group ID for ${GROUP_NAME}\n${error instanceof Error ? error.message : String(error)}`); -// } -// }; -// -// const getSecurityGroupNames = async (client: EC2Client): Promise => { -// try { -// const command = new DescribeSecurityGroupsCommand({}); -// const response = await client.send(command); -// return response.SecurityGroups?.map(sg => sg.GroupName ?? '').filter(name => name !== '') ?? []; -// } catch (error: unknown) { -// throw new Error(`Error retrieving security group names\n${error instanceof Error ? error.message : String(error)}`); -// } -// }; -// -// const deleteSecurityGroup = async (groupId: string, client: EC2Client): Promise => { -// try { -// const command = new DeleteSecurityGroupCommand({ GroupId: groupId }); -// await client.send(command); -// } catch (error: unknown) { -// if (error instanceof Error) { -// throw new Error(`Error deleting security group ${groupId}\n${error.message}`); -// } -// throw new Error(`Unknown error deleting security group ${groupId}\n${String(error)}`); -// } -// }; -// -// export const deleteRabbitorySG = async (region: string): Promise => { -// const client = new EC2Client({ region: region }); -// -// try { -// const securityGroupId = await getSecurityGroupId(client); -// if (!securityGroupId) return; // return if no sg exists -// await deleteSecurityGroup(securityGroupId, client); -// } catch (error: unknown) { -// throw new Error(`Failed to delete RabbitorySG\n${error instanceof Error ? error.message : String(error)}`); -// } -// }; - - import { EC2Client, DescribeSecurityGroupsCommand, From b5baf2909f84041db969420e868c51e995adbfc4 Mon Sep 17 00:00:00 2001 From: Jacqueline Amherst Date: Wed, 2 Apr 2025 14:56:20 -0500 Subject: [PATCH 8/8] use a regex to be as positive as possible that only our security groups are deleted --- aws/security-groups/deleteRabbitorySG.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws/security-groups/deleteRabbitorySG.ts b/aws/security-groups/deleteRabbitorySG.ts index 98f1138..2ecd3b3 100644 --- a/aws/security-groups/deleteRabbitorySG.ts +++ b/aws/security-groups/deleteRabbitorySG.ts @@ -8,8 +8,10 @@ const getRabbitorySGIds = async (client: EC2Client): Promise => { try { const command = new DescribeSecurityGroupsCommand({}); const response = await client.send(command); + const regex = /(^rabbitmq(-[a-z]+){4}$|^rabbitory)/i return response.SecurityGroups?.reduce((ids, sg) => { - if (sg.GroupId && sg.GroupName?.toLowerCase().includes('rabbit')) { + const groupName = sg.GroupName?.toLowerCase(); + if (sg.GroupId && groupName && regex.test(groupName)) { ids.push(sg.GroupId); } return ids;