pull/235/head
mdugdale 2021-04-03 00:46:48 +01:00
parent c3f276bbaf
commit c1f304fce0
3 changed files with 230 additions and 205 deletions

258
dist/index.js vendored
View File

@ -408,6 +408,137 @@ class AWS {
return __awaiter(this, void 0, void 0, function* () {
const ECS = new SDK.ECS();
const CF = new SDK.CloudFormation();
const taskDef = yield this.setupClusters(ECS, CF, buildUid, stackName, image, entrypoint, commands, mountdir, workingdir, environment, secrets);
core.info('Build cluster created successfully (skipping waiting for cleanup cluster to start)');
const taskDefResources = yield CF.describeStackResources({
StackName: taskDef.taskDefStackName,
}).promise();
const baseResources = yield CF.describeStackResources({ StackName: stackName }).promise();
const clusterName = ((_b = (_a = baseResources.StackResources) === null || _a === void 0 ? void 0 : _a.find((x) => x.LogicalResourceId === 'ECSCluster')) === null || _b === void 0 ? void 0 : _b.PhysicalResourceId) || '';
const task = yield ECS.runTask({
cluster: clusterName,
taskDefinition: ((_d = (_c = taskDefResources.StackResources) === null || _c === void 0 ? void 0 : _c.find((x) => x.LogicalResourceId === 'TaskDefinition')) === null || _d === void 0 ? void 0 : _d.PhysicalResourceId) ||
'',
platformVersion: '1.4.0',
overrides: {
containerOverrides: [
{
name: taskDef.taskDefStackName,
environment: [...environment, { name: 'BUILDID', value: buildUid }],
},
],
},
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
subnets: [
((_f = (_e = baseResources.StackResources) === null || _e === void 0 ? void 0 : _e.find((x) => x.LogicalResourceId === 'PublicSubnetOne')) === null || _f === void 0 ? void 0 : _f.PhysicalResourceId) ||
'',
((_h = (_g = baseResources.StackResources) === null || _g === void 0 ? void 0 : _g.find((x) => x.LogicalResourceId === 'PublicSubnetTwo')) === null || _h === void 0 ? void 0 : _h.PhysicalResourceId) ||
'',
],
assignPublicIp: 'ENABLED',
securityGroups: [
((_k = (_j = baseResources.StackResources) === null || _j === void 0 ? void 0 : _j.find((x) => x.LogicalResourceId === 'ContainerSecurityGroup')) === null || _k === void 0 ? void 0 : _k.PhysicalResourceId) || '',
],
},
},
}).promise();
core.info('Build job is starting');
try {
yield ECS.waitFor('tasksRunning', { tasks: [((_l = task.tasks) === null || _l === void 0 ? void 0 : _l[0].taskArn) || ''], cluster: clusterName }).promise();
}
catch (error) {
yield new Promise((resolve) => setTimeout(resolve, 3000));
const describeTasks = yield ECS.describeTasks({
tasks: [((_m = task.tasks) === null || _m === void 0 ? void 0 : _m[0].taskArn) || ''],
cluster: clusterName,
}).promise();
core.info(`Build job has ended ${(_p = (_o = describeTasks.tasks) === null || _o === void 0 ? void 0 : _o[0].containers) === null || _p === void 0 ? void 0 : _p[0].lastStatus}`);
core.setFailed(error);
core.error(error);
}
core.info(`Build job is running`);
// watching logs
const kinesis = new SDK.Kinesis();
const getTaskStatus = () => __awaiter(this, void 0, void 0, function* () {
var _w, _x;
const tasks = yield ECS.describeTasks({
cluster: clusterName,
tasks: [((_w = task.tasks) === null || _w === void 0 ? void 0 : _w[0].taskArn) || ''],
}).promise();
return (_x = tasks.tasks) === null || _x === void 0 ? void 0 : _x[0].lastStatus;
});
const stream = yield kinesis
.describeStream({
StreamName: ((_r = (_q = taskDefResources.StackResources) === null || _q === void 0 ? void 0 : _q.find((x) => x.LogicalResourceId === 'KinesisStream')) === null || _r === void 0 ? void 0 : _r.PhysicalResourceId) ||
'',
})
.promise();
let iterator = (yield kinesis
.getShardIterator({
ShardIteratorType: 'TRIM_HORIZON',
StreamName: stream.StreamDescription.StreamName,
ShardId: stream.StreamDescription.Shards[0].ShardId,
})
.promise()).ShardIterator || '';
yield CF.waitFor('stackCreateComplete', { StackName: taskDef.taskDefStackNameTTL }).promise();
core.info(`Task status is ${yield getTaskStatus()}`);
const logBaseUrl = `https://console.aws.amazon.com/cloudwatch/home?region=${SDK.config.region}#logsV2:log-groups/${taskDef.taskDefStackName}`;
core.info(`You can also watch the logs at AWS Cloud Watch: ${logBaseUrl}`);
let readingLogs = true;
while (readingLogs) {
yield new Promise((resolve) => setTimeout(resolve, 1500));
if ((yield getTaskStatus()) !== 'RUNNING') {
readingLogs = false;
yield new Promise((resolve) => setTimeout(resolve, 35000));
}
const records = yield 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);
}
}
}
}
}
yield ECS.waitFor('tasksStopped', { cluster: clusterName, tasks: [((_s = task.tasks) === null || _s === void 0 ? void 0 : _s[0].taskArn) || ''] }).promise();
const exitCode = (_v = (_u = (yield ECS.describeTasks({
tasks: [((_t = task.tasks) === null || _t === void 0 ? void 0 : _t[0].taskArn) || ''],
cluster: clusterName,
}).promise()).tasks) === null || _u === void 0 ? void 0 : _u[0].containers) === null || _v === void 0 ? void 0 : _v[0].exitCode;
if (exitCode !== 0) {
core.error(`job finished with exit code ${exitCode}`);
}
else {
core.info(`Build job has finished with exit code 0`);
}
yield CF.deleteStack({
StackName: taskDef.taskDefStackName,
}).promise();
yield CF.deleteStack({
StackName: taskDef.taskDefStackNameTTL,
}).promise();
yield CF.waitFor('stackDeleteComplete', {
StackName: taskDef.taskDefStackName,
}).promise();
// Currently too slow and causes too much waiting
yield CF.waitFor('stackDeleteComplete', {
StackName: taskDef.taskDefStackNameTTL,
}).promise();
core.info('Cleanup complete');
});
}
static setupClusters(ECS, CF, buildUid, stackName, image, entrypoint, commands, mountdir, workingdir, environment, secrets) {
return __awaiter(this, void 0, void 0, function* () {
const taskDefStackName = `${stackName}-taskDef-${image}-${buildUid}`.toString().replace(/[^\da-z]/gi, '');
const taskDefCloudFormation = fs.readFileSync(`${__dirname}/task-def-formation.yml`, 'utf8');
yield CF.createStack({
@ -478,132 +609,7 @@ class AWS {
catch (error) {
core.error(error);
}
core.info('Build cluster created successfully (skipping waiting for cleanup cluster to start)');
const taskDefResources = yield CF.describeStackResources({
StackName: taskDefStackName,
}).promise();
const baseResources = yield CF.describeStackResources({ StackName: stackName }).promise();
const clusterName = ((_b = (_a = baseResources.StackResources) === null || _a === void 0 ? void 0 : _a.find((x) => x.LogicalResourceId === 'ECSCluster')) === null || _b === void 0 ? void 0 : _b.PhysicalResourceId) || '';
const task = yield ECS.runTask({
cluster: clusterName,
taskDefinition: ((_d = (_c = taskDefResources.StackResources) === null || _c === void 0 ? void 0 : _c.find((x) => x.LogicalResourceId === 'TaskDefinition')) === null || _d === void 0 ? void 0 : _d.PhysicalResourceId) ||
'',
platformVersion: '1.4.0',
overrides: {
containerOverrides: [
{
name: taskDefStackName,
environment: [...environment, { name: 'BUILDID', value: buildUid }],
},
],
},
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
subnets: [
((_f = (_e = baseResources.StackResources) === null || _e === void 0 ? void 0 : _e.find((x) => x.LogicalResourceId === 'PublicSubnetOne')) === null || _f === void 0 ? void 0 : _f.PhysicalResourceId) ||
'',
((_h = (_g = baseResources.StackResources) === null || _g === void 0 ? void 0 : _g.find((x) => x.LogicalResourceId === 'PublicSubnetTwo')) === null || _h === void 0 ? void 0 : _h.PhysicalResourceId) ||
'',
],
assignPublicIp: 'ENABLED',
securityGroups: [
((_k = (_j = baseResources.StackResources) === null || _j === void 0 ? void 0 : _j.find((x) => x.LogicalResourceId === 'ContainerSecurityGroup')) === null || _k === void 0 ? void 0 : _k.PhysicalResourceId) || '',
],
},
},
}).promise();
core.info('Build job is starting');
try {
yield ECS.waitFor('tasksRunning', { tasks: [((_l = task.tasks) === null || _l === void 0 ? void 0 : _l[0].taskArn) || ''], cluster: clusterName }).promise();
}
catch (error) {
yield new Promise((resolve) => setTimeout(resolve, 3000));
const describeTasks = yield ECS.describeTasks({
tasks: [((_m = task.tasks) === null || _m === void 0 ? void 0 : _m[0].taskArn) || ''],
cluster: clusterName,
}).promise();
core.info(`Build job has ended ${(_p = (_o = describeTasks.tasks) === null || _o === void 0 ? void 0 : _o[0].containers) === null || _p === void 0 ? void 0 : _p[0].lastStatus}`);
core.setFailed(error);
core.error(error);
}
core.info(`Build job is running`);
// watching logs
const kinesis = new SDK.Kinesis();
const getTaskStatus = () => __awaiter(this, void 0, void 0, function* () {
var _w, _x;
const tasks = yield ECS.describeTasks({
cluster: clusterName,
tasks: [((_w = task.tasks) === null || _w === void 0 ? void 0 : _w[0].taskArn) || ''],
}).promise();
return (_x = tasks.tasks) === null || _x === void 0 ? void 0 : _x[0].lastStatus;
});
const stream = yield kinesis
.describeStream({
StreamName: ((_r = (_q = taskDefResources.StackResources) === null || _q === void 0 ? void 0 : _q.find((x) => x.LogicalResourceId === 'KinesisStream')) === null || _r === void 0 ? void 0 : _r.PhysicalResourceId) ||
'',
})
.promise();
let iterator = (yield kinesis
.getShardIterator({
ShardIteratorType: 'TRIM_HORIZON',
StreamName: stream.StreamDescription.StreamName,
ShardId: stream.StreamDescription.Shards[0].ShardId,
})
.promise()).ShardIterator || '';
yield CF.waitFor('stackCreateComplete', { StackName: taskDefStackNameTTL }).promise();
core.info(`Task status is ${yield getTaskStatus()}`);
const logBaseUrl = `https://console.aws.amazon.com/cloudwatch/home?region=${SDK.config.region}#logsV2:log-groups/${taskDefStackName}`;
core.info(`You can also watch the logs at AWS Cloud Watch: ${logBaseUrl}`);
let readingLogs = true;
while (readingLogs) {
yield new Promise((resolve) => setTimeout(resolve, 1500));
if ((yield getTaskStatus()) !== 'RUNNING') {
readingLogs = false;
yield new Promise((resolve) => setTimeout(resolve, 35000));
}
const records = yield 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);
}
}
}
}
}
yield ECS.waitFor('tasksStopped', { cluster: clusterName, tasks: [((_s = task.tasks) === null || _s === void 0 ? void 0 : _s[0].taskArn) || ''] }).promise();
const exitCode = (_v = (_u = (yield ECS.describeTasks({
tasks: [((_t = task.tasks) === null || _t === void 0 ? void 0 : _t[0].taskArn) || ''],
cluster: clusterName,
}).promise()).tasks) === null || _u === void 0 ? void 0 : _u[0].containers) === null || _v === void 0 ? void 0 : _v[0].exitCode;
if (exitCode !== 0) {
core.error(`job finished with exit code ${exitCode}`);
}
else {
core.info(`Build job has finished with exit code 0`);
}
yield CF.deleteStack({
StackName: taskDefStackName,
}).promise();
yield CF.deleteStack({
StackName: taskDefStackNameTTL,
}).promise();
yield CF.waitFor('stackDeleteComplete', {
StackName: taskDefStackName,
}).promise();
// Currently too slow and causes too much waiting
yield CF.waitFor('stackDeleteComplete', {
StackName: taskDefStackNameTTL,
}).promise();
core.info('Cleanup complete');
return { taskDefStackName, taskDefCloudFormation, taskDefStackNameTTL, ttlCloudFormation };
});
}
static onlog(batch) {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -239,81 +239,12 @@ class AWS {
const ECS = new SDK.ECS();
const CF = new SDK.CloudFormation();
const taskDefStackName = `${stackName}-taskDef-${image}-${buildUid}`.toString().replace(/[^\da-z]/gi, '');
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,
},
{
ParameterKey: 'BUILDID',
ParameterValue: buildUid,
},
...secrets,
],
}).promise();
core.info('Creating build cluster...');
const taskDef = await this.setupClusters(ECS, CF, buildUid, stackName, image, entrypoint, commands, mountdir, workingdir, environment, secrets);
const taskDefStackNameTTL = `${taskDefStackName}-ttl`;
const ttlCloudFormation = fs.readFileSync(`${__dirname}/cloudformation-stack-ttl.yml`, 'utf8');
await CF.createStack({
StackName: taskDefStackNameTTL,
TemplateBody: ttlCloudFormation,
Capabilities: ['CAPABILITY_IAM'],
Parameters: [
{
ParameterKey: 'StackName',
ParameterValue: taskDefStackName,
},
{
ParameterKey: 'DeleteStackName',
ParameterValue: taskDefStackNameTTL,
},
{
ParameterKey: 'TTL',
ParameterValue: '100',
},
{
ParameterKey: 'BUILDID',
ParameterValue: buildUid,
},
],
}).promise();
core.info('Creating cleanup cluster...');
try {
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
} catch (error) {
core.error(error);
}
core.info('Build cluster created successfully (skipping waiting for cleanup cluster to start)');
const taskDefResources = await CF.describeStackResources({
StackName: taskDefStackName,
StackName: taskDef.taskDefStackName,
}).promise();
const baseResources = await CF.describeStackResources({ StackName: stackName }).promise();
@ -329,7 +260,7 @@ class AWS {
overrides: {
containerOverrides: [
{
name: taskDefStackName,
name: taskDef.taskDefStackName,
environment: [...environment, { name: 'BUILDID', value: buildUid }],
},
],
@ -399,11 +330,11 @@ class AWS {
.promise()
).ShardIterator || '';
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackNameTTL }).promise();
await CF.waitFor('stackCreateComplete', { StackName: taskDef.taskDefStackNameTTL }).promise();
core.info(`Task status is ${await getTaskStatus()}`);
const logBaseUrl = `https://console.aws.amazon.com/cloudwatch/home?region=${SDK.config.region}#logsV2:log-groups/${taskDefStackName}`;
const logBaseUrl = `https://console.aws.amazon.com/cloudwatch/home?region=${SDK.config.region}#logsV2:log-groups/${taskDef.taskDefStackName}`;
core.info(`You can also watch the logs at AWS Cloud Watch: ${logBaseUrl}`);
let readingLogs = true;
@ -449,25 +380,113 @@ class AWS {
}
await CF.deleteStack({
StackName: taskDefStackName,
StackName: taskDef.taskDefStackName,
}).promise();
await CF.deleteStack({
StackName: taskDefStackNameTTL,
StackName: taskDef.taskDefStackNameTTL,
}).promise();
await CF.waitFor('stackDeleteComplete', {
StackName: taskDefStackName,
StackName: taskDef.taskDefStackName,
}).promise();
// Currently too slow and causes too much waiting
await CF.waitFor('stackDeleteComplete', {
StackName: taskDefStackNameTTL,
StackName: taskDef.taskDefStackNameTTL,
}).promise();
core.info('Cleanup complete');
}
static async setupClusters(
ECS,
CF,
buildUid: string,
stackName: string,
image: string,
entrypoint: string[],
commands,
mountdir,
workingdir,
environment,
secrets) {
const taskDefStackName = `${stackName}-taskDef-${image}-${buildUid}`.toString().replace(/[^\da-z]/gi, '');
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,
},
{
ParameterKey: 'BUILDID',
ParameterValue: buildUid,
},
...secrets,
],
}).promise();
core.info('Creating build cluster...');
const taskDefStackNameTTL = `${taskDefStackName}-ttl`;
const ttlCloudFormation = fs.readFileSync(`${__dirname}/cloudformation-stack-ttl.yml`, 'utf8');
await CF.createStack({
StackName: taskDefStackNameTTL,
TemplateBody: ttlCloudFormation,
Capabilities: ['CAPABILITY_IAM'],
Parameters: [
{
ParameterKey: 'StackName',
ParameterValue: taskDefStackName,
},
{
ParameterKey: 'DeleteStackName',
ParameterValue: taskDefStackNameTTL,
},
{
ParameterKey: 'TTL',
ParameterValue: '100',
},
{
ParameterKey: 'BUILDID',
ParameterValue: buildUid,
},
],
}).promise();
core.info('Creating cleanup cluster...');
try {
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
} catch (error) {
core.error(error);
}
return { taskDefStackName, taskDefCloudFormation, taskDefStackNameTTL, ttlCloudFormation };
}
static onlog(batch) {
for (const log of batch) {
core.info(`log: ${log}`);