Skip to content

Commit bce0b50

Browse files
committed
Run init task before updateing service
1 parent fb88acc commit bce0b50

File tree

5 files changed

+2809
-1081
lines changed

5 files changed

+2809
-1081
lines changed

action.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ inputs:
3434
force-new-deployment:
3535
description: 'Whether to force a new deployment of the service. Valid value is "true". Will default to not force a new deployment.'
3636
required: false
37+
38+
init-task-command:
39+
description: 'A command to be run as a task before deploy.'
40+
required: false
41+
init-task-name:
42+
description: 'Container name for init run task.'
43+
default: 'app'
44+
required: false
45+
init-task-network-configuration:
46+
description: 'Network configuration as json string required for network mode `awsvpc`.'
47+
required: false
48+
started-by:
49+
description: "The value of the task started-by"
50+
required: false
51+
3752
outputs:
3853
task-definition-arn:
3954
description: 'The ARN of the registered ECS task definition'

dist/index.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,109 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
2626
'registeredBy'
2727
];
2828

29+
async function runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand) {
30+
core.info(`Starting init task "${initTaskCommand}"`)
31+
32+
const agent = 'amazon-ecs-run-task-for-github-actions'
33+
const startedBy = core.getInput('started-by', { required: false }) || agent;
34+
const networkConfiguration = JSON.parse(core.getInput('init-task-network-configuration', { required : false }));
35+
const containerName = core.getInput('init-task-name', { required : false })
36+
37+
const runTaskResponse = await ecs.runTask({
38+
startedBy: startedBy,
39+
cluster: clusterName,
40+
taskDefinition: taskDefArn,
41+
enableExecuteCommand: true,
42+
overrides: {
43+
containerOverrides: [
44+
{
45+
name: containerName,
46+
command: initTaskCommand.split(' ')
47+
}
48+
]
49+
},
50+
launchType: 'FARGATE',
51+
networkConfiguration: networkConfiguration
52+
}).promise();
53+
54+
core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
55+
56+
const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
57+
core.setOutput('init-task-arn', taskArns);
58+
59+
taskArns.map(taskArn =>{
60+
let taskId = taskArn.split('/').pop();
61+
62+
core.info(`Init task started. Watch the task logs in https://${aws.config.region}.console.aws.amazon.com/cloudwatch/home?region=${aws.config.region}#logsV2:log-groups/log-group/ecs$252F${service}/log-events/ecs$252Fapp$252F${taskId}`)
63+
});
64+
65+
if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
66+
const failure = runTaskResponse.failures[0];
67+
throw new Error(`${failure.arn} is ${failure.reason}`);
68+
}
69+
70+
// Wait for task to end
71+
if (waitForService && waitForService.toLowerCase() === 'true') {
72+
core.debug(`Waiting for the service to become stable. Will wait for ${waitForMinutes} minutes`);
73+
await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
74+
await tasksExitCode(ecs, clusterName, taskArns)
75+
} else {
76+
core.debug('Not waiting for the service to become stable');
77+
}
78+
}
79+
80+
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
81+
if (waitForMinutes > MAX_WAIT_MINUTES) {
82+
waitForMinutes = MAX_WAIT_MINUTES;
83+
}
84+
85+
const maxAttempts = (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC;
86+
87+
core.info('Waiting for tasks to stop');
88+
89+
const waitTaskResponse = await ecs.waitFor('tasksStopped', {
90+
cluster: clusterName,
91+
tasks: taskArns,
92+
$waiter: {
93+
delay: WAIT_DEFAULT_DELAY_SEC,
94+
maxAttempts: maxAttempts
95+
}
96+
}).promise();
97+
98+
core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`)
99+
100+
core.info(`All tasks have stopped. Watch progress in the Amazon ECS console: https://console.aws.amazon.com/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/tasks`);
101+
}
102+
103+
async function tasksExitCode(ecs, clusterName, taskArns) {
104+
const describeResponse = await ecs.describeTasks({
105+
cluster: clusterName,
106+
tasks: taskArns
107+
}).promise();
108+
109+
const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
110+
const exitCodes = containers.map(container => container.exitCode)
111+
const reasons = containers.map(container => container.reason)
112+
113+
const failuresIdx = [];
114+
115+
exitCodes.filter((exitCode, index) => {
116+
if (exitCode !== 0) {
117+
failuresIdx.push(index)
118+
}
119+
})
120+
121+
const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
122+
123+
if (failures.length > 0) {
124+
console.log(`failed to with exit code${failures}`)
125+
core.setFailed(failures.join("\n"));
126+
throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
127+
} else {
128+
core.info(`All tasks have exited successfully.`);
129+
}
130+
}
131+
29132
// Deploy to a service that uses the 'ECS' deployment controller
30133
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
31134
core.debug('Updating the service');
@@ -268,6 +371,7 @@ async function run() {
268371
const cluster = core.getInput('cluster', { required: false });
269372
const waitForService = core.getInput('wait-for-service-stability', { required: false });
270373
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
374+
const initTaskCommand = core.getInput('init-task-command');
271375
if (waitForMinutes > MAX_WAIT_MINUTES) {
272376
waitForMinutes = MAX_WAIT_MINUTES;
273377
}
@@ -314,6 +418,12 @@ async function run() {
314418
throw new Error(`Service is ${serviceResponse.status}`);
315419
}
316420

421+
if (initTaskCommand) {
422+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
423+
} else {
424+
core.debug('InitTaskCommand was not specified, no init run task.');
425+
}
426+
317427
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
318428
// Service uses the 'ECS' deployment controller, so we can call UpdateService
319429
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -328,6 +438,7 @@ async function run() {
328438
}
329439
}
330440
catch (error) {
441+
console.log(error);
331442
core.setFailed(error.message);
332443
core.debug(error.stack);
333444
}

index.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,109 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
2020
'registeredBy'
2121
];
2222

23+
async function runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand) {
24+
core.info(`Starting init task "${initTaskCommand}"`)
25+
26+
const agent = 'amazon-ecs-run-task-for-github-actions'
27+
const startedBy = core.getInput('started-by', { required: false }) || agent;
28+
const networkConfiguration = JSON.parse(core.getInput('init-task-network-configuration', { required : false }));
29+
const containerName = core.getInput('init-task-name', { required : false })
30+
31+
const runTaskResponse = await ecs.runTask({
32+
startedBy: startedBy,
33+
cluster: clusterName,
34+
taskDefinition: taskDefArn,
35+
enableExecuteCommand: true,
36+
overrides: {
37+
containerOverrides: [
38+
{
39+
name: containerName,
40+
command: initTaskCommand.split(' ')
41+
}
42+
]
43+
},
44+
launchType: 'FARGATE',
45+
networkConfiguration: networkConfiguration
46+
}).promise();
47+
48+
core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
49+
50+
const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
51+
core.setOutput('init-task-arn', taskArns);
52+
53+
taskArns.map(taskArn =>{
54+
let taskId = taskArn.split('/').pop();
55+
56+
core.info(`Init task started. Watch the task logs in https://${aws.config.region}.console.aws.amazon.com/cloudwatch/home?region=${aws.config.region}#logsV2:log-groups/log-group/ecs$252F${service}/log-events/ecs$252Fapp$252F${taskId}`)
57+
});
58+
59+
if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
60+
const failure = runTaskResponse.failures[0];
61+
throw new Error(`${failure.arn} is ${failure.reason}`);
62+
}
63+
64+
// Wait for task to end
65+
if (waitForService && waitForService.toLowerCase() === 'true') {
66+
core.debug(`Waiting for the service to become stable. Will wait for ${waitForMinutes} minutes`);
67+
await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
68+
await tasksExitCode(ecs, clusterName, taskArns)
69+
} else {
70+
core.debug('Not waiting for the service to become stable');
71+
}
72+
}
73+
74+
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
75+
if (waitForMinutes > MAX_WAIT_MINUTES) {
76+
waitForMinutes = MAX_WAIT_MINUTES;
77+
}
78+
79+
const maxAttempts = (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC;
80+
81+
core.info('Waiting for tasks to stop');
82+
83+
const waitTaskResponse = await ecs.waitFor('tasksStopped', {
84+
cluster: clusterName,
85+
tasks: taskArns,
86+
$waiter: {
87+
delay: WAIT_DEFAULT_DELAY_SEC,
88+
maxAttempts: maxAttempts
89+
}
90+
}).promise();
91+
92+
core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`)
93+
94+
core.info(`All tasks have stopped. Watch progress in the Amazon ECS console: https://console.aws.amazon.com/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/tasks`);
95+
}
96+
97+
async function tasksExitCode(ecs, clusterName, taskArns) {
98+
const describeResponse = await ecs.describeTasks({
99+
cluster: clusterName,
100+
tasks: taskArns
101+
}).promise();
102+
103+
const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
104+
const exitCodes = containers.map(container => container.exitCode)
105+
const reasons = containers.map(container => container.reason)
106+
107+
const failuresIdx = [];
108+
109+
exitCodes.filter((exitCode, index) => {
110+
if (exitCode !== 0) {
111+
failuresIdx.push(index)
112+
}
113+
})
114+
115+
const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
116+
117+
if (failures.length > 0) {
118+
console.log(`failed to with exit code${failures}`)
119+
core.setFailed(failures.join("\n"));
120+
throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
121+
} else {
122+
core.info(`All tasks have exited successfully.`);
123+
}
124+
}
125+
23126
// Deploy to a service that uses the 'ECS' deployment controller
24127
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
25128
core.debug('Updating the service');
@@ -262,6 +365,7 @@ async function run() {
262365
const cluster = core.getInput('cluster', { required: false });
263366
const waitForService = core.getInput('wait-for-service-stability', { required: false });
264367
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
368+
const initTaskCommand = core.getInput('init-task-command');
265369
if (waitForMinutes > MAX_WAIT_MINUTES) {
266370
waitForMinutes = MAX_WAIT_MINUTES;
267371
}
@@ -308,6 +412,12 @@ async function run() {
308412
throw new Error(`Service is ${serviceResponse.status}`);
309413
}
310414

415+
if (initTaskCommand) {
416+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
417+
} else {
418+
core.debug('InitTaskCommand was not specified, no init run task.');
419+
}
420+
311421
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
312422
// Service uses the 'ECS' deployment controller, so we can call UpdateService
313423
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -322,6 +432,7 @@ async function run() {
322432
}
323433
}
324434
catch (error) {
435+
console.log(error);
325436
core.setFailed(error.message);
326437
core.debug(error.stack);
327438
}

0 commit comments

Comments
 (0)