Skip to content

Commit 708d38e

Browse files
committed
Run init task before updateing service
1 parent a87e76a commit 708d38e

File tree

4 files changed

+374
-0
lines changed

4 files changed

+374
-0
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
@@ -185,6 +185,109 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
185185
'registeredBy'
186186
];
187187

188+
async function runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand) {
189+
core.info(`Starting init task "${initTaskCommand}"`)
190+
191+
const agent = 'amazon-ecs-run-task-for-github-actions'
192+
const startedBy = core.getInput('started-by', { required: false }) || agent;
193+
const networkConfiguration = JSON.parse(core.getInput('init-task-network-configuration', { required : false }));
194+
const containerName = core.getInput('init-task-name', { required : false })
195+
196+
const runTaskResponse = await ecs.runTask({
197+
startedBy: startedBy,
198+
cluster: clusterName,
199+
taskDefinition: taskDefArn,
200+
enableExecuteCommand: true,
201+
overrides: {
202+
containerOverrides: [
203+
{
204+
name: containerName,
205+
command: initTaskCommand.split(' ')
206+
}
207+
]
208+
},
209+
launchType: 'FARGATE',
210+
networkConfiguration: networkConfiguration
211+
}).promise();
212+
213+
core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
214+
215+
const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
216+
core.setOutput('init-task-arn', taskArns);
217+
218+
taskArns.map(taskArn =>{
219+
let taskId = taskArn.split('/').pop();
220+
221+
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}`)
222+
});
223+
224+
if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
225+
const failure = runTaskResponse.failures[0];
226+
throw new Error(`${failure.arn} is ${failure.reason}`);
227+
}
228+
229+
// Wait for task to end
230+
if (waitForService && waitForService.toLowerCase() === 'true') {
231+
core.debug(`Waiting for the service to become stable. Will wait for ${waitForMinutes} minutes`);
232+
await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
233+
await tasksExitCode(ecs, clusterName, taskArns)
234+
} else {
235+
core.debug('Not waiting for the service to become stable');
236+
}
237+
}
238+
239+
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
240+
if (waitForMinutes > MAX_WAIT_MINUTES) {
241+
waitForMinutes = MAX_WAIT_MINUTES;
242+
}
243+
244+
const maxAttempts = (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC;
245+
246+
core.info('Waiting for tasks to stop');
247+
248+
const waitTaskResponse = await ecs.waitFor('tasksStopped', {
249+
cluster: clusterName,
250+
tasks: taskArns,
251+
$waiter: {
252+
delay: WAIT_DEFAULT_DELAY_SEC,
253+
maxAttempts: maxAttempts
254+
}
255+
}).promise();
256+
257+
core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`)
258+
259+
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`);
260+
}
261+
262+
async function tasksExitCode(ecs, clusterName, taskArns) {
263+
const describeResponse = await ecs.describeTasks({
264+
cluster: clusterName,
265+
tasks: taskArns
266+
}).promise();
267+
268+
const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
269+
const exitCodes = containers.map(container => container.exitCode)
270+
const reasons = containers.map(container => container.reason)
271+
272+
const failuresIdx = [];
273+
274+
exitCodes.filter((exitCode, index) => {
275+
if (exitCode !== 0) {
276+
failuresIdx.push(index)
277+
}
278+
})
279+
280+
const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
281+
282+
if (failures.length > 0) {
283+
console.log(`failed to with exit code${failures}`)
284+
core.setFailed(failures.join("\n"));
285+
throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
286+
} else {
287+
core.info(`All tasks have exited successfully.`);
288+
}
289+
}
290+
188291
// Deploy to a service that uses the 'ECS' deployment controller
189292
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
190293
core.debug('Updating the service');
@@ -424,6 +527,7 @@ async function run() {
424527
const cluster = core.getInput('cluster', { required: false });
425528
const waitForService = core.getInput('wait-for-service-stability', { required: false });
426529
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
530+
const initTaskCommand = core.getInput('init-task-command');
427531
if (waitForMinutes > MAX_WAIT_MINUTES) {
428532
waitForMinutes = MAX_WAIT_MINUTES;
429533
}
@@ -470,6 +574,12 @@ async function run() {
470574
throw new Error(`Service is ${serviceResponse.status}`);
471575
}
472576

577+
if (initTaskCommand) {
578+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
579+
} else {
580+
core.debug('InitTaskCommadn was not specified, no init run task.');
581+
}
582+
473583
if (!serviceResponse.deploymentController) {
474584
// Service uses the 'ECS' deployment controller, so we can call UpdateService
475585
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -484,6 +594,7 @@ async function run() {
484594
}
485595
}
486596
catch (error) {
597+
console.log(error);
487598
core.setFailed(error.message);
488599
core.debug(error.stack);
489600
}

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');
@@ -259,6 +362,7 @@ async function run() {
259362
const cluster = core.getInput('cluster', { required: false });
260363
const waitForService = core.getInput('wait-for-service-stability', { required: false });
261364
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
365+
const initTaskCommand = core.getInput('init-task-command');
262366
if (waitForMinutes > MAX_WAIT_MINUTES) {
263367
waitForMinutes = MAX_WAIT_MINUTES;
264368
}
@@ -305,6 +409,12 @@ async function run() {
305409
throw new Error(`Service is ${serviceResponse.status}`);
306410
}
307411

412+
if (initTaskCommand) {
413+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
414+
} else {
415+
core.debug('InitTaskCommadn was not specified, no init run task.');
416+
}
417+
308418
if (!serviceResponse.deploymentController) {
309419
// Service uses the 'ECS' deployment controller, so we can call UpdateService
310420
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -319,6 +429,7 @@ async function run() {
319429
}
320430
}
321431
catch (error) {
432+
console.log(error);
322433
core.setFailed(error.message);
323434
core.debug(error.stack);
324435
}

0 commit comments

Comments
 (0)