mirror of
https://github.com/game-ci/unity-builder.git
synced 2025-07-04 12:25:19 -04:00
Feat/migrate aws sdk v3 (#698)
* chore(cloud-runner): migrate/replace deps aws-sdk v2 to v3 * chore(aws): refactor aws services to support SDK v3 * chore(aws): refactor aws runner to support SDK v3 * chore(aws): update dist * fix(aws): error handling wrap try/catch to avoid unhandled promise rejections. * fix(aws): keeping the syntax simpler for arrays
This commit is contained in:
parent
9d6bdcbdc5
commit
81ed299e10
BIN
dist/index.js
generated
vendored
BIN
dist/index.js
generated
vendored
Binary file not shown.
BIN
dist/index.js.map
generated
vendored
BIN
dist/index.js.map
generated
vendored
Binary file not shown.
BIN
dist/licenses.txt
generated
vendored
BIN
dist/licenses.txt
generated
vendored
Binary file not shown.
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 || ''
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<string> {
|
||||
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');
|
||||
}
|
||||
|
@ -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 || '' }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<LogGroup> = [];
|
||||
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 || [];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user