diff --git a/dist/index.js b/dist/index.js index 5111463f..7ffa95af 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/dist/index.js.map b/dist/index.js.map index b304c2c7..ccde7107 100644 Binary files a/dist/index.js.map and b/dist/index.js.map differ diff --git a/dist/licenses.txt b/dist/licenses.txt index 43200692..497a23d4 100644 Binary files a/dist/licenses.txt and b/dist/licenses.txt differ diff --git a/package.json b/package.json index 2b752045..968ca1d7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,11 @@ "@actions/core": "^1.11.1", "@actions/exec": "^1.1.1", "@actions/github": "^6.0.0", + "@aws-sdk/client-cloudformation": "^3.777.0", + "@aws-sdk/client-cloudwatch-logs": "^3.777.0", + "@aws-sdk/client-ecs": "^3.778.0", + "@aws-sdk/client-kinesis": "^3.777.0", + "@aws-sdk/client-s3": "^3.779.0", "@kubernetes/client-node": "^0.16.3", "@octokit/core": "^5.1.0", "async-wait-until": "^2.0.12", @@ -79,4 +84,4 @@ "node": "20.5.1", "yarn": "1.22.19" } -} +} \ No newline at end of file diff --git a/src/model/cloud-runner/providers/aws/aws-base-stack.ts b/src/model/cloud-runner/providers/aws/aws-base-stack.ts index 6ddea5ff..34b4b466 100644 --- a/src/model/cloud-runner/providers/aws/aws-base-stack.ts +++ b/src/model/cloud-runner/providers/aws/aws-base-stack.ts @@ -1,6 +1,18 @@ import CloudRunnerLogger from '../../services/core/cloud-runner-logger'; import * as core from '@actions/core'; -import * as SDK from 'aws-sdk'; +import { + CloudFormation, + CreateStackCommand, + CreateStackCommandInput, + DescribeStacksCommand, + DescribeStacksCommandInput, + ListStacksCommand, + Parameter, + UpdateStackCommand, + UpdateStackCommandInput, + waitUntilStackCreateComplete, + waitUntilStackUpdateComplete, +} from '@aws-sdk/client-cloudformation'; import { BaseStackFormation } from './cloud-formations/base-stack-formation'; import crypto from 'node:crypto'; @@ -10,51 +22,49 @@ export class AWSBaseStack { } private baseStackName: string; - async setupBaseStack(CF: SDK.CloudFormation) { + async setupBaseStack(CF: CloudFormation) { const baseStackName = this.baseStackName; const baseStack = BaseStackFormation.formation; // Cloud Formation Input - const describeStackInput: SDK.CloudFormation.DescribeStacksInput = { + const describeStackInput: DescribeStacksCommandInput = { StackName: baseStackName, }; - const parametersWithoutHash: SDK.CloudFormation.Parameter[] = [ - { ParameterKey: 'EnvironmentName', ParameterValue: baseStackName }, - ]; + const parametersWithoutHash: Parameter[] = [{ ParameterKey: 'EnvironmentName', ParameterValue: baseStackName }]; const parametersHash = crypto .createHash('md5') .update(baseStack + JSON.stringify(parametersWithoutHash)) .digest('hex'); - const parameters: SDK.CloudFormation.Parameter[] = [ + const parameters: Parameter[] = [ ...parametersWithoutHash, ...[{ ParameterKey: 'Version', ParameterValue: parametersHash }], ]; - const updateInput: SDK.CloudFormation.UpdateStackInput = { + const updateInput: UpdateStackCommandInput = { StackName: baseStackName, TemplateBody: baseStack, Parameters: parameters, Capabilities: ['CAPABILITY_IAM'], }; - const createStackInput: SDK.CloudFormation.CreateStackInput = { + const createStackInput: CreateStackCommandInput = { StackName: baseStackName, TemplateBody: baseStack, Parameters: parameters, Capabilities: ['CAPABILITY_IAM'], }; - const stacks = await CF.listStacks({ - StackStatusFilter: ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE'], - }).promise(); + const stacks = await CF.send( + new ListStacksCommand({ StackStatusFilter: ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE'] }), + ); const stackNames = stacks.StackSummaries?.map((x) => x.StackName) || []; const stackExists: Boolean = stackNames.includes(baseStackName) || false; const describeStack = async () => { - return await CF.describeStacks(describeStackInput).promise(); + return await CF.send(new DescribeStacksCommand(describeStackInput)); }; try { if (!stackExists) { CloudRunnerLogger.log(`${baseStackName} stack does not exist (${JSON.stringify(stackNames)})`); - await CF.createStack(createStackInput).promise(); + await CF.send(new CreateStackCommand(createStackInput)); CloudRunnerLogger.log(`created stack (version: ${parametersHash})`); } const CFState = await describeStack(); @@ -65,7 +75,13 @@ export class AWSBaseStack { const stackVersion = stack.Parameters?.find((x) => x.ParameterKey === 'Version')?.ParameterValue; if (stack.StackStatus === 'CREATE_IN_PROGRESS') { - await CF.waitFor('stackCreateComplete', describeStackInput).promise(); + await waitUntilStackCreateComplete( + { + client: CF, + maxWaitTime: 200, + }, + describeStackInput, + ); } if (stackExists) { @@ -73,7 +89,7 @@ export class AWSBaseStack { if (parametersHash !== stackVersion) { CloudRunnerLogger.log(`Attempting update of base stack`); try { - await CF.updateStack(updateInput).promise(); + await CF.send(new UpdateStackCommand(updateInput)); } catch (error: any) { if (error['message'].includes('No updates are to be performed')) { CloudRunnerLogger.log(`No updates are to be performed`); @@ -93,7 +109,13 @@ export class AWSBaseStack { ); } if (stack.StackStatus === 'UPDATE_IN_PROGRESS') { - await CF.waitFor('stackUpdateComplete', describeStackInput).promise(); + await waitUntilStackUpdateComplete( + { + client: CF, + maxWaitTime: 200, + }, + describeStackInput, + ); } } CloudRunnerLogger.log('base stack is now ready'); diff --git a/src/model/cloud-runner/providers/aws/aws-error.ts b/src/model/cloud-runner/providers/aws/aws-error.ts index bf58e20b..ae9fc0ab 100644 --- a/src/model/cloud-runner/providers/aws/aws-error.ts +++ b/src/model/cloud-runner/providers/aws/aws-error.ts @@ -1,15 +1,15 @@ import CloudRunnerLogger from '../../services/core/cloud-runner-logger'; -import * as SDK from 'aws-sdk'; +import { CloudFormation, DescribeStackEventsCommand } from '@aws-sdk/client-cloudformation'; import * as core from '@actions/core'; import CloudRunner from '../../cloud-runner'; export class AWSError { - static async handleStackCreationFailure(error: any, CF: SDK.CloudFormation, taskDefStackName: string) { + static async handleStackCreationFailure(error: any, CF: CloudFormation, taskDefStackName: string) { CloudRunnerLogger.log('aws error: '); core.error(JSON.stringify(error, undefined, 4)); if (CloudRunner.buildParameters.cloudRunnerDebug) { CloudRunnerLogger.log('Getting events and resources for task stack'); - const events = (await CF.describeStackEvents({ StackName: taskDefStackName }).promise()).StackEvents; + const events = (await CF.send(new DescribeStackEventsCommand({ StackName: taskDefStackName }))).StackEvents; CloudRunnerLogger.log(JSON.stringify(events, undefined, 4)); } } diff --git a/src/model/cloud-runner/providers/aws/aws-job-stack.ts b/src/model/cloud-runner/providers/aws/aws-job-stack.ts index 189155bc..aaf62218 100644 --- a/src/model/cloud-runner/providers/aws/aws-job-stack.ts +++ b/src/model/cloud-runner/providers/aws/aws-job-stack.ts @@ -1,4 +1,12 @@ -import * as SDK from 'aws-sdk'; +import { + CloudFormation, + CreateStackCommand, + CreateStackCommandInput, + DescribeStackResourcesCommand, + DescribeStacksCommand, + ListStacksCommand, + waitUntilStackCreateComplete, +} from '@aws-sdk/client-cloudformation'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; import CloudRunnerSecret from '../../options/cloud-runner-secret'; import { AWSCloudFormationTemplates } from './aws-cloud-formation-templates'; @@ -16,7 +24,7 @@ export class AWSJobStack { } public async setupCloudFormations( - CF: SDK.CloudFormation, + CF: CloudFormation, buildGuid: string, image: string, entrypoint: string[], @@ -119,7 +127,7 @@ export class AWSJobStack { let previousStackExists = true; while (previousStackExists) { previousStackExists = false; - const stacks = await CF.listStacks().promise(); + const stacks = await CF.send(new ListStacksCommand({})); if (!stacks.StackSummaries) { throw new Error('Faild to get stacks'); } @@ -132,7 +140,7 @@ export class AWSJobStack { } } } - const createStackInput: SDK.CloudFormation.CreateStackInput = { + const createStackInput: CreateStackCommandInput = { StackName: taskDefStackName, TemplateBody: taskDefCloudFormation, Capabilities: ['CAPABILITY_IAM'], @@ -140,9 +148,15 @@ export class AWSJobStack { }; try { CloudRunnerLogger.log(`Creating job aws formation ${taskDefStackName}`); - await CF.createStack(createStackInput).promise(); - await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); - const describeStack = await CF.describeStacks({ StackName: taskDefStackName }).promise(); + await CF.send(new CreateStackCommand(createStackInput)); + await waitUntilStackCreateComplete( + { + client: CF, + maxWaitTime: 200, + }, + { StackName: taskDefStackName }, + ); + const describeStack = await CF.send(new DescribeStacksCommand({ StackName: taskDefStackName })); for (const parameter of parameters) { if (!describeStack.Stacks?.[0].Parameters?.some((x) => x.ParameterKey === parameter.ParameterKey)) { throw new Error(`Parameter ${parameter.ParameterKey} not found in stack`); @@ -153,7 +167,7 @@ export class AWSJobStack { throw error; } - const createCleanupStackInput: SDK.CloudFormation.CreateStackInput = { + const createCleanupStackInput: CreateStackCommandInput = { StackName: `${taskDefStackName}-cleanup`, TemplateBody: CleanupCronFormation.formation, Capabilities: ['CAPABILITY_IAM'], @@ -183,7 +197,7 @@ export class AWSJobStack { if (CloudRunnerOptions.useCleanupCron) { try { CloudRunnerLogger.log(`Creating job cleanup formation`); - await CF.createStack(createCleanupStackInput).promise(); + await CF.send(new CreateStackCommand(createCleanupStackInput)); // await CF.waitFor('stackCreateComplete', { StackName: createCleanupStackInput.StackName }).promise(); } catch (error) { @@ -193,12 +207,15 @@ export class AWSJobStack { } const taskDefResources = ( - await CF.describeStackResources({ - StackName: taskDefStackName, - }).promise() + await CF.send( + new DescribeStackResourcesCommand({ + StackName: taskDefStackName, + }), + ) ).StackResources; - const baseResources = (await CF.describeStackResources({ StackName: this.baseStackName }).promise()).StackResources; + const baseResources = (await CF.send(new DescribeStackResourcesCommand({ StackName: this.baseStackName }))) + .StackResources; return { taskDefStackName, diff --git a/src/model/cloud-runner/providers/aws/aws-task-runner.ts b/src/model/cloud-runner/providers/aws/aws-task-runner.ts index 2030d845..69175115 100644 --- a/src/model/cloud-runner/providers/aws/aws-task-runner.ts +++ b/src/model/cloud-runner/providers/aws/aws-task-runner.ts @@ -1,4 +1,19 @@ -import * as AWS from 'aws-sdk'; +import { + DescribeTasksCommand, + ECS, + RunTaskCommand, + RunTaskCommandInput, + Task, + waitUntilTasksRunning, +} from '@aws-sdk/client-ecs'; +import { + DescribeStreamCommand, + DescribeStreamCommandOutput, + GetRecordsCommand, + GetRecordsCommandOutput, + GetShardIteratorCommand, + Kinesis, +} from '@aws-sdk/client-kinesis'; import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable'; import * as core from '@actions/core'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; @@ -12,8 +27,8 @@ import CloudRunnerOptions from '../../options/cloud-runner-options'; import GitHub from '../../../github'; class AWSTaskRunner { - public static ECS: AWS.ECS; - public static Kinesis: AWS.Kinesis; + public static ECS: ECS; + public static Kinesis: Kinesis; private static readonly encodedUnderscore = `$252F`; static async runTask( taskDef: CloudRunnerAWSTaskDef, @@ -60,7 +75,7 @@ class AWSTaskRunner { throw new Error(`Container Overrides length must be at most 8192`); } - const task = await AWSTaskRunner.ECS.runTask(runParameters).promise(); + const task = await AWSTaskRunner.ECS.send(new RunTaskCommand(runParameters as RunTaskCommandInput)); const taskArn = task.tasks?.[0].taskArn || ''; CloudRunnerLogger.log('Cloud runner job is starting'); await AWSTaskRunner.waitUntilTaskRunning(taskArn, cluster); @@ -108,7 +123,13 @@ class AWSTaskRunner { private static async waitUntilTaskRunning(taskArn: string, cluster: string) { try { - await AWSTaskRunner.ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise(); + await waitUntilTasksRunning( + { + client: AWSTaskRunner.ECS, + maxWaitTime: 120, + }, + { tasks: [taskArn], cluster }, + ); } catch (error_) { const error = error_ as Error; await new Promise((resolve) => setTimeout(resolve, 3000)); @@ -124,10 +145,7 @@ class AWSTaskRunner { } static async describeTasks(clusterName: string, taskArn: string) { - const tasks = await AWSTaskRunner.ECS.describeTasks({ - cluster: clusterName, - tasks: [taskArn], - }).promise(); + const tasks = await AWSTaskRunner.ECS.send(new DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] })); if (tasks.tasks?.[0]) { return tasks.tasks?.[0]; } else { @@ -169,9 +187,7 @@ class AWSTaskRunner { output: string, shouldCleanup: boolean, ) { - const records = await AWSTaskRunner.Kinesis.getRecords({ - ShardIterator: iterator, - }).promise(); + const records = await AWSTaskRunner.Kinesis.send(new GetRecordsCommand({ ShardIterator: iterator })); iterator = records.NextShardIterator || ''; ({ shouldReadLogs, output, shouldCleanup } = AWSTaskRunner.logRecords( records, @@ -184,7 +200,7 @@ class AWSTaskRunner { return { iterator, shouldReadLogs, output, shouldCleanup }; } - private static checkStreamingShouldContinue(taskData: AWS.ECS.Task, timestamp: number, shouldReadLogs: boolean) { + private static checkStreamingShouldContinue(taskData: Task, timestamp: number, shouldReadLogs: boolean) { if (taskData?.lastStatus === 'UNKNOWN') { CloudRunnerLogger.log('## Cloud runner job unknwon'); } @@ -204,15 +220,17 @@ class AWSTaskRunner { } private static logRecords( - records: AWS.Kinesis.GetRecordsOutput, + records: GetRecordsCommandOutput, iterator: string, shouldReadLogs: boolean, output: string, shouldCleanup: boolean, ) { - if (records.Records.length > 0 && iterator) { - for (const record of records.Records) { - const json = JSON.parse(zlib.gunzipSync(Buffer.from(record.Data as string, 'base64')).toString('utf8')); + if ((records.Records ?? []).length > 0 && iterator) { + for (const record of records.Records ?? []) { + const json = JSON.parse( + zlib.gunzipSync(Buffer.from(record.Data as unknown as string, 'base64')).toString('utf8'), + ); if (json.messageType === 'DATA_MESSAGE') { for (const logEvent of json.logEvents) { ({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration( @@ -230,19 +248,19 @@ class AWSTaskRunner { } private static async getLogStream(kinesisStreamName: string) { - return await AWSTaskRunner.Kinesis.describeStream({ - StreamName: kinesisStreamName, - }).promise(); + return await AWSTaskRunner.Kinesis.send(new DescribeStreamCommand({ StreamName: kinesisStreamName })); } - private static async getLogIterator(stream: AWS.Kinesis.DescribeStreamOutput) { + private static async getLogIterator(stream: DescribeStreamCommandOutput) { return ( ( - await AWSTaskRunner.Kinesis.getShardIterator({ - ShardIteratorType: 'TRIM_HORIZON', - StreamName: stream.StreamDescription.StreamName, - ShardId: stream.StreamDescription.Shards[0].ShardId, - }).promise() + await AWSTaskRunner.Kinesis.send( + new GetShardIteratorCommand({ + ShardIteratorType: 'TRIM_HORIZON', + StreamName: stream.StreamDescription?.StreamName ?? '', + ShardId: stream.StreamDescription?.Shards?.[0]?.ShardId || '', + }), + ) ).ShardIterator || '' ); } diff --git a/src/model/cloud-runner/providers/aws/cloud-runner-aws-task-def.ts b/src/model/cloud-runner/providers/aws/cloud-runner-aws-task-def.ts index 4ec16196..b222c796 100644 --- a/src/model/cloud-runner/providers/aws/cloud-runner-aws-task-def.ts +++ b/src/model/cloud-runner/providers/aws/cloud-runner-aws-task-def.ts @@ -1,9 +1,9 @@ -import * as AWS from 'aws-sdk'; +import { StackResource } from '@aws-sdk/client-cloudformation'; class CloudRunnerAWSTaskDef { public taskDefStackName!: string; public taskDefCloudFormation!: string; - public taskDefResources: AWS.CloudFormation.StackResources | undefined; - public baseResources: AWS.CloudFormation.StackResources | undefined; + public taskDefResources: StackResource[] | undefined; + public baseResources: StackResource[] | undefined; } export default CloudRunnerAWSTaskDef; diff --git a/src/model/cloud-runner/providers/aws/index.ts b/src/model/cloud-runner/providers/aws/index.ts index 2acd48c9..2486d92e 100644 --- a/src/model/cloud-runner/providers/aws/index.ts +++ b/src/model/cloud-runner/providers/aws/index.ts @@ -1,4 +1,6 @@ -import * as SDK from 'aws-sdk'; +import { CloudFormation, DeleteStackCommand, waitUntilStackDeleteComplete } from '@aws-sdk/client-cloudformation'; +import { ECS as ECSClient } from '@aws-sdk/client-ecs'; +import { Kinesis } from '@aws-sdk/client-kinesis'; import CloudRunnerSecret from '../../options/cloud-runner-secret'; import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; @@ -75,7 +77,7 @@ class AWSBuildEnvironment implements ProviderInterface { defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[], ) { process.env.AWS_REGION = Input.region; - const CF = new SDK.CloudFormation(); + const CF = new CloudFormation({ region: Input.region }); await new AwsBaseStack(this.baseStackName).setupBaseStack(CF); } @@ -89,10 +91,10 @@ class AWSBuildEnvironment implements ProviderInterface { secrets: CloudRunnerSecret[], ): Promise { process.env.AWS_REGION = Input.region; - const ECS = new SDK.ECS(); - const CF = new SDK.CloudFormation(); + const ECS = new ECSClient({ region: Input.region }); + const CF = new CloudFormation({ region: Input.region }); AwsTaskRunner.ECS = ECS; - AwsTaskRunner.Kinesis = new SDK.Kinesis(); + AwsTaskRunner.Kinesis = new Kinesis({ region: Input.region }); CloudRunnerLogger.log(`AWS Region: ${CF.config.region}`); const entrypoint = ['/bin/sh']; const startTimeMs = Date.now(); @@ -129,23 +131,31 @@ class AWSBuildEnvironment implements ProviderInterface { } } - async cleanupResources(CF: SDK.CloudFormation, taskDef: CloudRunnerAWSTaskDef) { + async cleanupResources(CF: CloudFormation, taskDef: CloudRunnerAWSTaskDef) { CloudRunnerLogger.log('Cleanup starting'); - await CF.deleteStack({ - StackName: taskDef.taskDefStackName, - }).promise(); + await CF.send(new DeleteStackCommand({ StackName: taskDef.taskDefStackName })); if (CloudRunnerOptions.useCleanupCron) { - await CF.deleteStack({ - StackName: `${taskDef.taskDefStackName}-cleanup`, - }).promise(); + await CF.send(new DeleteStackCommand({ StackName: `${taskDef.taskDefStackName}-cleanup` })); } - await CF.waitFor('stackDeleteComplete', { - StackName: taskDef.taskDefStackName, - }).promise(); - await CF.waitFor('stackDeleteComplete', { - StackName: `${taskDef.taskDefStackName}-cleanup`, - }).promise(); + await waitUntilStackDeleteComplete( + { + client: CF, + maxWaitTime: 200, + }, + { + StackName: taskDef.taskDefStackName, + }, + ); + await waitUntilStackDeleteComplete( + { + client: CF, + maxWaitTime: 200, + }, + { + StackName: `${taskDef.taskDefStackName}-cleanup`, + }, + ); CloudRunnerLogger.log(`Deleted Stack: ${taskDef.taskDefStackName}`); CloudRunnerLogger.log('Cleanup complete'); } diff --git a/src/model/cloud-runner/providers/aws/services/garbage-collection-service.ts b/src/model/cloud-runner/providers/aws/services/garbage-collection-service.ts index 27d299da..ccdddf7a 100644 --- a/src/model/cloud-runner/providers/aws/services/garbage-collection-service.ts +++ b/src/model/cloud-runner/providers/aws/services/garbage-collection-service.ts @@ -1,4 +1,11 @@ -import AWS from 'aws-sdk'; +import { + CloudFormation, + DeleteStackCommand, + DeleteStackCommandInput, + DescribeStackResourcesCommand, +} from '@aws-sdk/client-cloudformation'; +import { CloudWatchLogs, DeleteLogGroupCommand } from '@aws-sdk/client-cloudwatch-logs'; +import { ECS, StopTaskCommand } from '@aws-sdk/client-ecs'; import Input from '../../../../input'; import CloudRunnerLogger from '../../../services/core/cloud-runner-logger'; import { TaskService } from './task-service'; @@ -12,9 +19,9 @@ export class GarbageCollectionService { public 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(); + const CF = new CloudFormation({ region: Input.region }); + const ecs = new ECS({ region: Input.region }); + const cwl = new CloudWatchLogs({ region: Input.region }); const taskDefinitionsInUse = new Array(); const tasks = await TaskService.getTasks(); @@ -23,14 +30,14 @@ export class GarbageCollectionService { taskDefinitionsInUse.push(taskElement.taskDefinitionArn); if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(taskElement.createdAt!))) { CloudRunnerLogger.log(`Stopping task ${taskElement.containers?.[0].name}`); - await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise(); + await ecs.send(new StopTaskCommand({ task: taskElement.taskArn || '', cluster: element })); } } const jobStacks = await TaskService.getCloudFormationJobStacks(); for (const element of jobStacks) { if ( - (await CF.describeStackResources({ StackName: element.StackName }).promise()).StackResources?.some( + (await CF.send(new DescribeStackResourcesCommand({ StackName: element.StackName }))).StackResources?.some( (x) => x.ResourceType === 'AWS::ECS::TaskDefinition' && taskDefinitionsInUse.includes(x.PhysicalResourceId), ) ) { @@ -39,7 +46,10 @@ export class GarbageCollectionService { return; } - if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(element.CreationTime))) { + if ( + deleteResources && + (!OneDayOlderOnly || (element.CreationTime && GarbageCollectionService.isOlderThan1day(element.CreationTime))) + ) { if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') { CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`); @@ -47,8 +57,8 @@ export class GarbageCollectionService { } CloudRunnerLogger.log(`Deleting ${element.StackName}`); - const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName }; - await CF.deleteStack(deleteStackInput).promise(); + const deleteStackInput: DeleteStackCommandInput = { StackName: element.StackName }; + await CF.send(new DeleteStackCommand(deleteStackInput)); } } const logGroups = await TaskService.getLogGroups(); @@ -58,7 +68,7 @@ export class GarbageCollectionService { (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(new Date(element.creationTime!))) ) { CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); - await cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise(); + await cwl.send(new DeleteLogGroupCommand({ logGroupName: element.logGroupName || '' })); } } diff --git a/src/model/cloud-runner/providers/aws/services/task-service.ts b/src/model/cloud-runner/providers/aws/services/task-service.ts index ee5fb5c0..039969bb 100644 --- a/src/model/cloud-runner/providers/aws/services/task-service.ts +++ b/src/model/cloud-runner/providers/aws/services/task-service.ts @@ -1,12 +1,31 @@ -import AWS from 'aws-sdk'; +import { + CloudFormation, + DescribeStackResourcesCommand, + DescribeStacksCommand, + ListStacksCommand, + StackSummary, +} from '@aws-sdk/client-cloudformation'; +import { + CloudWatchLogs, + DescribeLogGroupsCommand, + DescribeLogGroupsCommandInput, + LogGroup, +} from '@aws-sdk/client-cloudwatch-logs'; +import { + DescribeTasksCommand, + DescribeTasksCommandInput, + ECS, + ListClustersCommand, + ListTasksCommand, + ListTasksCommandInput, + Task, +} from '@aws-sdk/client-ecs'; +import { ListObjectsCommand, ListObjectsCommandInput, S3 } from '@aws-sdk/client-s3'; import Input from '../../../../input'; import CloudRunnerLogger from '../../../services/core/cloud-runner-logger'; import { BaseStackFormation } from '../cloud-formations/base-stack-formation'; import AwsTaskRunner from '../aws-task-runner'; -import { ListObjectsRequest } from 'aws-sdk/clients/s3'; import CloudRunner from '../../../cloud-runner'; -import { StackSummaries } from 'aws-sdk/clients/cloudformation'; -import { LogGroups } from 'aws-sdk/clients/cloudwatchlogs'; export class TaskService { static async watch() { @@ -20,20 +39,24 @@ export class TaskService { return output; } public static async getCloudFormationJobStacks() { - const result: StackSummaries = []; + const result: StackSummary[] = []; CloudRunnerLogger.log(``); CloudRunnerLogger.log(`List Cloud Formation Stacks`); process.env.AWS_REGION = Input.region; - const CF = new AWS.CloudFormation(); + const CF = new CloudFormation({ region: Input.region }); const stacks = - (await CF.listStacks().promise()).StackSummaries?.filter( + (await CF.send(new ListStacksCommand({}))).StackSummaries?.filter( (_x) => _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription !== BaseStackFormation.baseStackDecription, ) || []; CloudRunnerLogger.log(``); CloudRunnerLogger.log(`Cloud Formation Stacks ${stacks.length}`); for (const element of stacks) { - const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime()); + if (!element.CreationTime) { + CloudRunnerLogger.log(`${element.StackName} due to undefined CreationTime`); + } + + const ageDate: Date = new Date(Date.now() - (element.CreationTime?.getTime() ?? 0)); CloudRunnerLogger.log( `Task Stack ${element.StackName} - Age D${Math.floor( @@ -43,14 +66,18 @@ export class TaskService { result.push(element); } const baseStacks = - (await CF.listStacks().promise()).StackSummaries?.filter( + (await CF.send(new ListStacksCommand({}))).StackSummaries?.filter( (_x) => _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription, ) || []; CloudRunnerLogger.log(``); CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`); for (const element of baseStacks) { - const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime()); + if (!element.CreationTime) { + CloudRunnerLogger.log(`${element.StackName} due to undefined CreationTime`); + } + + const ageDate: Date = new Date(Date.now() - (element.CreationTime?.getTime() ?? 0)); CloudRunnerLogger.log( `Task Stack ${element.StackName} - Age D${Math.floor( @@ -64,22 +91,22 @@ export class TaskService { return result; } public static async getTasks() { - const result: { taskElement: AWS.ECS.Task; element: string }[] = []; + const result: { taskElement: Task; element: string }[] = []; CloudRunnerLogger.log(``); CloudRunnerLogger.log(`List Tasks`); process.env.AWS_REGION = Input.region; - const ecs = new AWS.ECS(); - const clusters = (await ecs.listClusters().promise()).clusterArns || []; + const ecs = new ECS({ region: Input.region }); + const clusters = (await ecs.send(new ListClustersCommand({}))).clusterArns || []; CloudRunnerLogger.log(`Task Clusters ${clusters.length}`); for (const element of clusters) { - const input: AWS.ECS.ListTasksRequest = { + const input: ListTasksCommandInput = { cluster: element, }; - const list = (await ecs.listTasks(input).promise()).taskArns || []; + const list = (await ecs.send(new ListTasksCommand(input))).taskArns || []; if (list.length > 0) { - const describeInput: AWS.ECS.DescribeTasksRequest = { tasks: list, cluster: element }; - const describeList = (await ecs.describeTasks(describeInput).promise()).tasks || []; + const describeInput: DescribeTasksCommandInput = { tasks: list, cluster: element }; + const describeList = (await ecs.send(new DescribeTasksCommand(describeInput))).tasks || []; if (describeList.length === 0) { CloudRunnerLogger.log(`No Tasks`); continue; @@ -105,37 +132,48 @@ export class TaskService { } public static async awsDescribeJob(job: string) { process.env.AWS_REGION = Input.region; - const CF = new AWS.CloudFormation(); - const stack = (await CF.listStacks().promise()).StackSummaries?.find((_x) => _x.StackName === job) || undefined; - const stackInfo = (await CF.describeStackResources({ StackName: job }).promise()) || undefined; - const stackInfo2 = (await CF.describeStacks({ StackName: job }).promise()) || undefined; - if (stack === undefined) { - throw new Error('stack not defined'); - } - const ageDate: Date = new Date(Date.now() - stack.CreationTime.getTime()); - const message = ` + const CF = new CloudFormation({ region: Input.region }); + try { + const stack = + (await CF.send(new ListStacksCommand({}))).StackSummaries?.find((_x) => _x.StackName === job) || undefined; + const stackInfo = (await CF.send(new DescribeStackResourcesCommand({ StackName: job }))) || undefined; + const stackInfo2 = (await CF.send(new DescribeStacksCommand({ StackName: job }))) || undefined; + if (stack === undefined) { + throw new Error('stack not defined'); + } + if (!stack.CreationTime) { + CloudRunnerLogger.log(`${stack.StackName} due to undefined CreationTime`); + } + const ageDate: Date = new Date(Date.now() - (stack.CreationTime?.getTime() ?? 0)); + const message = ` Task Stack ${stack.StackName} Age D${Math.floor(ageDate.getHours() / 24)} H${ageDate.getHours()} M${ageDate.getMinutes()} ${JSON.stringify(stack, undefined, 4)} ${JSON.stringify(stackInfo, undefined, 4)} ${JSON.stringify(stackInfo2, undefined, 4)} `; - CloudRunnerLogger.log(message); + CloudRunnerLogger.log(message); - return message; + return message; + } catch (error) { + CloudRunnerLogger.error( + `Failed to describe job ${job}: ${error instanceof Error ? error.message : String(error)}`, + ); + throw error; + } } public static async getLogGroups() { - const result: LogGroups = []; + const result: Array = []; process.env.AWS_REGION = Input.region; - const ecs = new AWS.CloudWatchLogs(); - let logStreamInput: AWS.CloudWatchLogs.DescribeLogGroupsRequest = { + const ecs = new CloudWatchLogs(); + let logStreamInput: DescribeLogGroupsCommandInput = { /* logGroupNamePrefix: 'game-ci' */ }; - let logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); + let logGroupsDescribe = await ecs.send(new DescribeLogGroupsCommand(logStreamInput)); const logGroups = logGroupsDescribe.logGroups || []; while (logGroupsDescribe.nextToken) { logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken }; - logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); + logGroupsDescribe = await ecs.send(new DescribeLogGroupsCommand(logStreamInput)); logGroups.push(...(logGroupsDescribe?.logGroups || [])); } @@ -159,11 +197,12 @@ export class TaskService { } public static async getLocks() { process.env.AWS_REGION = Input.region; - const s3 = new AWS.S3(); - const listRequest: ListObjectsRequest = { + const s3 = new S3({ region: Input.region }); + const listRequest: ListObjectsCommandInput = { Bucket: CloudRunner.buildParameters.awsStackName, }; - const results = await s3.listObjects(listRequest).promise(); + + const results = await s3.send(new ListObjectsCommand(listRequest)); return results.Contents || []; }