unity-builder/src/model/cloud-runner/aws/aws-build-runner.ts

215 lines
7.9 KiB
TypeScript
Raw Normal View History

import * as AWS from 'aws-sdk';
import CloudRunnerEnvironmentVariable from '../services/cloud-runner-environment-variable';
import * as core from '@actions/core';
import CloudRunnerTaskDef from '../services/cloud-runner-task-def';
import * as zlib from 'zlib';
import CloudRunnerLogger from '../services/cloud-runner-logger';
2021-12-19 22:35:28 +00:00
import { Input } from '../..';
2021-12-20 19:15:27 +00:00
import fs from 'fs';
import { CloudRunnerState } from '../state/cloud-runner-state';
class AWSBuildRunner {
static async runTask(
2021-08-17 20:09:42 +00:00
taskDef: CloudRunnerTaskDef,
ECS: AWS.ECS,
CF: AWS.CloudFormation,
2021-08-17 22:13:46 +00:00
environment: CloudRunnerEnvironmentVariable[],
buildGuid: string,
commands: string[],
) {
const cluster = taskDef.baseResources?.find((x) => x.LogicalResourceId === 'ECSCluster')?.PhysicalResourceId || '';
const taskDefinition =
taskDef.taskDefResources?.find((x) => x.LogicalResourceId === 'TaskDefinition')?.PhysicalResourceId || '';
const SubnetOne =
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'PublicSubnetOne')?.PhysicalResourceId || '';
const SubnetTwo =
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'PublicSubnetTwo')?.PhysicalResourceId || '';
const ContainerSecurityGroup =
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'ContainerSecurityGroup')?.PhysicalResourceId || '';
const streamName =
taskDef.taskDefResources?.find((x) => x.LogicalResourceId === 'KinesisStream')?.PhysicalResourceId || '';
const task = await ECS.runTask({
cluster,
taskDefinition,
platformVersion: '1.4.0',
overrides: {
containerOverrides: [
{
name: taskDef.taskDefStackName,
environment: [...environment, { name: 'BUILDID', value: buildGuid }],
command: ['-c', ...commands],
},
],
},
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
subnets: [SubnetOne, SubnetTwo],
assignPublicIp: 'ENABLED',
securityGroups: [ContainerSecurityGroup],
},
},
}).promise();
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log('Cloud runner job is starting');
const taskArn = task.tasks?.[0].taskArn || '';
try {
await ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise();
2021-09-08 22:17:23 +00:00
} catch (error_) {
const error = error_ as Error;
await new Promise((resolve) => setTimeout(resolve, 3000));
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(
2021-09-09 00:54:59 +00:00
`Cloud runner job has ended ${
(await AWSBuildRunner.describeTasks(ECS, cluster, taskArn)).containers?.[0].lastStatus
}`,
);
2021-09-08 22:17:23 +00:00
core.setFailed(error);
core.error(error);
}
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(`Cloud runner job is running`);
await this.streamLogsUntilTaskStops(ECS, CF, taskDef, cluster, taskArn, streamName);
2021-09-09 00:54:59 +00:00
const exitCode = (await AWSBuildRunner.describeTasks(ECS, cluster, taskArn)).containers?.[0].exitCode;
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(`Cloud runner job exit code ${exitCode}`);
2021-11-28 17:08:37 +00:00
if (exitCode !== 0 && exitCode !== undefined) {
core.error(
`job failed with exit code ${exitCode} ${JSON.stringify(
2021-09-09 00:54:59 +00:00
await ECS.describeTasks({ tasks: [taskArn], cluster }).promise(),
undefined,
4,
)}`,
);
throw new Error(`job failed with exit code ${exitCode}`);
} else {
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(`Cloud runner job has finished successfully`);
}
}
2021-09-09 00:54:59 +00:00
static async describeTasks(ECS: AWS.ECS, clusterName: string, taskArn: string) {
const tasks = await ECS.describeTasks({
cluster: clusterName,
tasks: [taskArn],
}).promise();
if (tasks.tasks?.[0]) {
return tasks.tasks?.[0];
} else {
throw new Error('No task found');
}
}
static async streamLogsUntilTaskStops(
ECS: AWS.ECS,
CF: AWS.CloudFormation,
2021-08-17 20:09:42 +00:00
taskDef: CloudRunnerTaskDef,
clusterName: string,
taskArn: string,
kinesisStreamName: string,
) {
const kinesis = new AWS.Kinesis();
2021-09-15 03:20:04 +00:00
const stream = await AWSBuildRunner.getLogStream(kinesis, kinesisStreamName);
let iterator = await AWSBuildRunner.getLogIterator(kinesis, stream);
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(
2021-09-09 00:54:59 +00:00
`Cloud runner job status is ${(await AWSBuildRunner.describeTasks(ECS, clusterName, taskArn))?.lastStatus}`,
);
2021-12-19 22:35:28 +00:00
const logBaseUrl = `https://${Input.region}.console.aws.amazon.com/cloudwatch/home?region=${AWS.config.region}#logsV2:log-groups/log-group/${taskDef.taskDefStackName}`;
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log(`You can also see the logs at AWS Cloud Watch: ${logBaseUrl}`);
let readingLogs = true;
2021-09-15 03:20:04 +00:00
let timestamp: number = 0;
while (readingLogs) {
await new Promise((resolve) => setTimeout(resolve, 1500));
2021-09-09 00:54:59 +00:00
const taskData = await AWSBuildRunner.describeTasks(ECS, clusterName, taskArn);
2021-09-15 03:20:04 +00:00
({ timestamp, readingLogs } = AWSBuildRunner.checkStreamingShouldContinue(taskData, timestamp, readingLogs));
({ iterator, readingLogs } = await AWSBuildRunner.handleLogStreamIteration(
kinesis,
iterator,
readingLogs,
taskDef,
));
}
}
private static async handleLogStreamIteration(
kinesis: AWS.Kinesis,
iterator: string,
readingLogs: boolean,
taskDef: CloudRunnerTaskDef,
) {
const records = await kinesis
.getRecords({
ShardIterator: iterator,
})
.promise();
iterator = records.NextShardIterator || '';
readingLogs = AWSBuildRunner.logRecords(records, iterator, taskDef, readingLogs);
return { iterator, readingLogs };
}
private static checkStreamingShouldContinue(taskData: AWS.ECS.Task, timestamp: number, readingLogs: boolean) {
if (taskData?.lastStatus !== 'RUNNING') {
if (timestamp === 0) {
2021-09-21 19:22:29 +00:00
CloudRunnerLogger.log('## Cloud runner job stopped, streaming end of logs');
2021-09-15 03:20:04 +00:00
timestamp = Date.now();
}
2021-11-06 21:20:22 +00:00
if (timestamp !== 0 && Date.now() - timestamp > 30000) {
2021-09-21 19:22:29 +00:00
CloudRunnerLogger.log('## Cloud runner status is not RUNNING for 30 seconds, last query for logs');
2021-09-09 00:54:59 +00:00
readingLogs = false;
}
2021-09-21 19:22:29 +00:00
CloudRunnerLogger.log(`## Status of job: ${taskData.lastStatus}`);
2021-09-15 03:20:04 +00:00
}
return { timestamp, readingLogs };
}
private static logRecords(records, iterator: string, taskDef: CloudRunnerTaskDef, readingLogs: boolean) {
if (records.Records.length > 0 && iterator) {
for (let index = 0; index < records.Records.length; index++) {
const json = JSON.parse(
zlib.gunzipSync(Buffer.from(records.Records[index].Data as string, 'base64')).toString('utf8'),
);
if (json.messageType === 'DATA_MESSAGE') {
for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) {
2021-11-06 21:45:42 +00:00
const message = json.logEvents[logEventsIndex].message;
2021-09-15 03:20:04 +00:00
if (json.logEvents[logEventsIndex].message.includes(taskDef.logid)) {
2021-09-21 18:27:04 +00:00
CloudRunnerLogger.log('End of cloud runner job logs');
2021-09-15 03:20:04 +00:00
readingLogs = false;
2021-11-06 21:45:42 +00:00
} else if (message.includes('Rebuilding Library because the asset database could not be found!')) {
core.warning('LIBRARY NOT FOUND!');
}
2021-12-20 19:15:27 +00:00
if (CloudRunnerState.buildParams.logToFile) {
fs.appendFileSync(`${CloudRunnerState.buildGuid}-outputfile.txt`, `${message}\r\n`);
}
2021-11-06 21:45:42 +00:00
CloudRunnerLogger.log(message);
}
}
}
}
2021-09-15 03:20:04 +00:00
return readingLogs;
}
private static async getLogStream(kinesis: AWS.Kinesis, kinesisStreamName: string) {
return await kinesis
.describeStream({
StreamName: kinesisStreamName,
})
.promise();
}
private static async getLogIterator(kinesis: AWS.Kinesis, stream) {
return (
(
await kinesis
.getShardIterator({
ShardIteratorType: 'TRIM_HORIZON',
StreamName: stream.StreamDescription.StreamName,
ShardId: stream.StreamDescription.Shards[0].ShardId,
})
.promise()
).ShardIterator || ''
);
}
}
export default AWSBuildRunner;