import AWS from 'aws-sdk'; import { CliFunction } from '../../../../cli/cli-functions-repository'; import Input from '../../../../input'; import CloudRunnerLogger from '../../../services/cloud-runner-logger'; import { BaseStackFormation } from '../cloud-formations/base-stack-formation'; export class AwsCliCommands { @CliFunction(`aws-list-all`, `List all resources`) static async awsListAll() { await AwsCliCommands.awsListStacks(undefined, true); await AwsCliCommands.awsListTasks(); await AwsCliCommands.awsListLogGroups(undefined, true); } @CliFunction(`aws-garbage-collect`, `garbage collect aws resources not in use !WIP!`) static async garbageCollectAws() { await AwsCliCommands.cleanup(false); } @CliFunction(`aws-garbage-collect-all`, `garbage collect aws resources regardless of whether they are in use`) static async garbageCollectAwsAll() { await AwsCliCommands.cleanup(true); } @CliFunction( `aws-garbage-collect-all-1d-older`, `garbage collect aws resources created more than 1d ago (ignore if they are in use)`, ) static async garbageCollectAwsAllOlderThanOneDay() { await AwsCliCommands.cleanup(true, true); } static isOlderThan1day(date: any) { const ageDate = new Date(date.getTime() - Date.now()); return ageDate.getDay() > 0; } @CliFunction(`aws-list-stacks`, `List stacks`) static async awsListStacks(perResultCallback: any = false, verbose: boolean = false) { process.env.AWS_REGION = Input.region; const CF = new AWS.CloudFormation(); const stacks = (await CF.listStacks().promise()).StackSummaries?.filter( (_x) => _x.StackStatus !== 'DELETE_COMPLETE', // && // _x.TemplateDescription === TaskDefinitionFormation.description.replace('\n', ''), ) || []; CloudRunnerLogger.log(`Stacks ${stacks.length}`); for (const element of stacks) { const ageDate = new Date(element.CreationTime.getTime() - Date.now()); if (verbose) CloudRunnerLogger.log( `Task Stack ${element.StackName} - Age D${ageDate.getDay()} H${ageDate.getHours()} M${ageDate.getMinutes()}`, ); if (perResultCallback) await perResultCallback(element); } const baseStacks = (await CF.listStacks().promise()).StackSummaries?.filter( (_x) => _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription, ) || []; CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`); for (const element of baseStacks) { const ageDate = new Date(element.CreationTime.getTime() - Date.now()); if (verbose) CloudRunnerLogger.log( `Base Stack ${ element.StackName } - Age D${ageDate.getHours()} H${ageDate.getHours()} M${ageDate.getMinutes()}`, ); if (perResultCallback) await perResultCallback(element); } if (stacks === undefined) { return; } } @CliFunction(`aws-list-tasks`, `List tasks`) static async awsListTasks(perResultCallback: any = false) { process.env.AWS_REGION = Input.region; const ecs = new AWS.ECS(); const clusters = (await ecs.listClusters().promise()).clusterArns || []; CloudRunnerLogger.log(`Clusters ${clusters.length}`); for (const element of clusters) { const input: AWS.ECS.ListTasksRequest = { cluster: element, }; const list = (await ecs.listTasks(input).promise()).taskArns || []; if (list.length > 0) { const describeInput: AWS.ECS.DescribeTasksRequest = { tasks: list, cluster: element }; const describeList = (await ecs.describeTasks(describeInput).promise()).tasks || []; if (describeList === []) { continue; } CloudRunnerLogger.log(`Tasks ${describeList.length}`); for (const taskElement of describeList) { if (taskElement === undefined) { continue; } taskElement.overrides = {}; taskElement.attachments = []; if (taskElement.createdAt === undefined) { CloudRunnerLogger.log(`Skipping ${taskElement.taskDefinitionArn} no createdAt date`); continue; } if (perResultCallback) await perResultCallback(taskElement, element); } } } } @CliFunction(`aws-list-log-groups`, `List tasks`) static async awsListLogGroups(perResultCallback: any = false, verbose: boolean = false) { process.env.AWS_REGION = Input.region; const ecs = new AWS.CloudWatchLogs(); let logStreamInput: AWS.CloudWatchLogs.DescribeLogGroupsRequest = { /* logGroupNamePrefix: 'game-ci' */ }; let logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); const logGroups = logGroupsDescribe.logGroups || []; while (logGroupsDescribe.nextToken) { logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken }; logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); logGroups.push(...(logGroupsDescribe?.logGroups || [])); } CloudRunnerLogger.log(`Log Groups ${logGroups.length}`); for (const element of logGroups) { if (element.creationTime === undefined) { CloudRunnerLogger.log(`Skipping ${element.logGroupName} no createdAt date`); continue; } const ageDate = new Date(new Date(element.creationTime).getTime() - Date.now()); if (verbose) CloudRunnerLogger.log( `Log Group Name ${ element.logGroupName } - Age D${ageDate.getDay()} H${ageDate.getHours()} M${ageDate.getMinutes()} - 1d old ${AwsCliCommands.isOlderThan1day( new Date(element.creationTime), )}`, ); if (perResultCallback) await perResultCallback(element, element); } } private static async cleanup(deleteResources = false, OneDayOlderOnly: boolean = false) { process.env.AWS_REGION = Input.region; const CF = new AWS.CloudFormation(); const ecs = new AWS.ECS(); const cwl = new AWS.CloudWatchLogs(); await AwsCliCommands.awsListStacks(async (element) => { if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(element.CreationTime))) { if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') { CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`); return; } CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName }; await CF.deleteStack(deleteStackInput).promise(); } }); await AwsCliCommands.awsListTasks(async (taskElement, element) => { if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(taskElement.CreatedAt))) { CloudRunnerLogger.log(`Stopping task ${taskElement.containers?.[0].name}`); await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise(); } }); await AwsCliCommands.awsListLogGroups(async (element) => { if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(new Date(element.createdAt)))) { CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); await cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise(); } }); } }