/* eslint-disable no-plusplus */ /* eslint-disable no-await-in-loop */ import * as SDK from 'aws-sdk'; import { nanoid } from 'nanoid'; const fs = require('fs'); const core = require('@actions/core'); const zlib = require('zlib'); class AWS { static async runBuildJob(buildParameters, baseImage) { await this.run( buildParameters.awsStackName, 'ubuntu', ['/bin/sh'], [ '-c', `apt update; apt install git-lfs -y; ls; git clone https://github.com/${process.env.GITHUB_REPOSITORY}.git repo; git clone https://github.com/webbertakken/unity-builder.git builder; ls; cd repo; git checkout $GITHUB_SHA; ls; cp -r /data/builder/action/default-build-script /UnityBuilderAction; cp -r /data/builder/action/entrypoint.sh /entrypoint.sh; cp -r /data/builder/action/steps /steps; `], '/efsdata', '/efsdata/', [ { name: 'GITHUB_SHA', value: process.env.GITHUB_SHA, }, ], ); await this.run( buildParameters.awsStackName, baseImage.toString(), ['/bin/sh'], ['-c', ` ls; cd repo; chmod -R +x /entrypoint.sh; chmod -R +x /steps; /entrypoint.sh; `], '/efsdata', '/efsdata/', [], ); } static async run(stackName, image, entrypoint, commands, mountdir, workingdir, environment) { const ECS = new SDK.ECS(); const CF = new SDK.CloudFormation(); const taskDefStackName = `${stackName}-taskDef-${image}-${nanoid()}` .toString() .replace(/[^\da-z]/gi, ''); core.info('Creating build job resources'); const taskDefCloudFormation = fs.readFileSync(`${__dirname}/task-def-formation.yml`, 'utf8'); await CF.createStack({ StackName: taskDefStackName, TemplateBody: taskDefCloudFormation, Parameters: [ { ParameterKey: 'ImageUrl', ParameterValue: image, }, { ParameterKey: 'ServiceName', ParameterValue: taskDefStackName, }, { ParameterKey: 'Command', ParameterValue: commands.join(','), }, { ParameterKey: 'EntryPoint', ParameterValue: entrypoint.join(','), }, { ParameterKey: 'WorkingDirectory', ParameterValue: workingdir, }, { ParameterKey: 'EFSMountDirectory', ParameterValue: mountdir, }, ], }).promise(); await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); const taskDefResources = await CF.describeStackResources({ StackName: taskDefStackName, }).promise(); const baseResources = await CF.describeStackResources({ StackName: stackName }).promise(); const clusterName = baseResources.StackResources.find( (x) => x.LogicalResourceId === 'ECSCluster', ).PhysicalResourceId; const task = await ECS.runTask({ cluster: clusterName, taskDefinition: taskDefResources.StackResources.find( (x) => x.LogicalResourceId === 'TaskDefinition', ).PhysicalResourceId, platformVersion: '1.4.0', overrides: { containerOverrides: [ { name: taskDefStackName, environment, }, ], }, launchType: 'FARGATE', networkConfiguration: { awsvpcConfiguration: { subnets: [ baseResources.StackResources.find((x) => x.LogicalResourceId === 'PublicSubnetOne') .PhysicalResourceId, baseResources.StackResources.find((x) => x.LogicalResourceId === 'PublicSubnetTwo') .PhysicalResourceId, ], assignPublicIp: 'ENABLED', securityGroups: [ baseResources.StackResources.find( (x) => x.LogicalResourceId === 'ContainerSecurityGroup', ).PhysicalResourceId, ], }, }, }).promise(); core.info('Build job is starting'); try { await ECS.waitFor('tasksRunning', { cluster: clusterName, tasks: [task.tasks[0].taskArn], }).promise(); } catch (error) { core.error(error); } core.info(`Build job is running`); // watching logs const kinesis = new SDK.Kinesis(); const getTaskStatus = async () => { const tasks = await ECS.describeTasks({ cluster: clusterName, tasks: [task.tasks[0].taskArn], }).promise(); return tasks.tasks[0].lastStatus; }; const stream = await kinesis .describeStream({ StreamName: taskDefResources.StackResources.find( (x) => x.LogicalResourceId === 'KinesisStream', ).PhysicalResourceId, }) .promise(); let iterator = ( await kinesis .getShardIterator({ ShardIteratorType: 'TRIM_HORIZON', StreamName: stream.StreamDescription.StreamName, ShardId: stream.StreamDescription.Shards[0].ShardId, }) .promise() ).ShardIterator; core.info(`Task status is ${await getTaskStatus()}`); let readingLogs = true; while (readingLogs) { await new Promise((resolve) => setTimeout(resolve, 3000)); if ((await getTaskStatus()) !== 'RUNNING') { readingLogs = false; await new Promise((resolve) => setTimeout(resolve, 35000)); } const records = await kinesis .getRecords({ ShardIterator: iterator, }) .promise(); iterator = records.NextShardIterator; if (records.Records.length > 0) { for (let index = 0; index < records.Records.length; index++) { const json = JSON.parse( zlib.gunzipSync(Buffer.from(records.Records[index].Data, 'base64')).toString('utf8'), ); if (json.messageType === 'DATA_MESSAGE') { for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) { core.info(json.logEvents[logEventsIndex].message); } } } } } await ECS.waitFor('tasksStopped', { cluster: clusterName, tasks: [task.tasks[0].taskArn], }).promise(); core.info( `Build job has ended ${ ( await ECS.describeTasks({ tasks: [task.tasks[0].taskArn], cluster: clusterName, }).promise() ).tasks[0].containers[0].exitCode }`, ); await CF.deleteStack({ StackName: taskDefStackName, }).promise(); core.info('Cleanup complete'); } static onlog(batch) { batch.forEach((log) => { core.info(`log: ${log}`); }); } } export default AWS;