Skip to content

Commit 7264b76

Browse files
committed
Run init task before updateing service
1 parent a87e76a commit 7264b76

File tree

4 files changed

+372
-0
lines changed

4 files changed

+372
-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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,108 @@ 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+
overrides: {
201+
containerOverrides: [
202+
{
203+
name: containerName,
204+
command: initTaskCommand.split(' ')
205+
}
206+
]
207+
},
208+
launchType: 'FARGATE',
209+
networkConfiguration: networkConfiguration
210+
}).promise();
211+
212+
core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
213+
214+
const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
215+
core.setOutput('init-task-arn', taskArns);
216+
217+
taskArns.map(taskArn =>{
218+
let taskId = taskArn.split('/').pop();
219+
220+
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}`)
221+
});
222+
223+
if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
224+
const failure = runTaskResponse.failures[0];
225+
throw new Error(`${failure.arn} is ${failure.reason}`);
226+
}
227+
228+
// Wait for task to end
229+
if (waitForService && waitForService.toLowerCase() === 'true') {
230+
core.debug(`Waiting for the service to become stable. Will wait for ${waitForMinutes} minutes`);
231+
await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
232+
await tasksExitCode(ecs, clusterName, taskArns)
233+
} else {
234+
core.debug('Not waiting for the service to become stable');
235+
}
236+
}
237+
238+
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
239+
if (waitForMinutes > MAX_WAIT_MINUTES) {
240+
waitForMinutes = MAX_WAIT_MINUTES;
241+
}
242+
243+
const maxAttempts = (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC;
244+
245+
core.info('Waiting for tasks to stop');
246+
247+
const waitTaskResponse = await ecs.waitFor('tasksStopped', {
248+
cluster: clusterName,
249+
tasks: taskArns,
250+
$waiter: {
251+
delay: WAIT_DEFAULT_DELAY_SEC,
252+
maxAttempts: maxAttempts
253+
}
254+
}).promise();
255+
256+
core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`)
257+
258+
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`);
259+
}
260+
261+
async function tasksExitCode(ecs, clusterName, taskArns) {
262+
const describeResponse = await ecs.describeTasks({
263+
cluster: clusterName,
264+
tasks: taskArns
265+
}).promise();
266+
267+
const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
268+
const exitCodes = containers.map(container => container.exitCode)
269+
const reasons = containers.map(container => container.reason)
270+
271+
const failuresIdx = [];
272+
273+
exitCodes.filter((exitCode, index) => {
274+
if (exitCode !== 0) {
275+
failuresIdx.push(index)
276+
}
277+
})
278+
279+
const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
280+
281+
if (failures.length > 0) {
282+
console.log(`failed to with exit code${failures}`)
283+
core.setFailed(failures.join("\n"));
284+
throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
285+
} else {
286+
core.info(`All tasks have exited successfully.`);
287+
}
288+
}
289+
188290
// Deploy to a service that uses the 'ECS' deployment controller
189291
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
190292
core.debug('Updating the service');
@@ -424,6 +526,7 @@ async function run() {
424526
const cluster = core.getInput('cluster', { required: false });
425527
const waitForService = core.getInput('wait-for-service-stability', { required: false });
426528
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
529+
const initTaskCommand = core.getInput('init-task-command');
427530
if (waitForMinutes > MAX_WAIT_MINUTES) {
428531
waitForMinutes = MAX_WAIT_MINUTES;
429532
}
@@ -470,6 +573,12 @@ async function run() {
470573
throw new Error(`Service is ${serviceResponse.status}`);
471574
}
472575

576+
if (initTaskCommand) {
577+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
578+
} else {
579+
core.debug('InitTaskCommadn was not specified, no init run task.');
580+
}
581+
473582
if (!serviceResponse.deploymentController) {
474583
// Service uses the 'ECS' deployment controller, so we can call UpdateService
475584
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -484,6 +593,7 @@ async function run() {
484593
}
485594
}
486595
catch (error) {
596+
console.log(error);
487597
core.setFailed(error.message);
488598
core.debug(error.stack);
489599
}

index.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,108 @@ 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+
overrides: {
36+
containerOverrides: [
37+
{
38+
name: containerName,
39+
command: initTaskCommand.split(' ')
40+
}
41+
]
42+
},
43+
launchType: 'FARGATE',
44+
networkConfiguration: networkConfiguration
45+
}).promise();
46+
47+
core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
48+
49+
const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
50+
core.setOutput('init-task-arn', taskArns);
51+
52+
taskArns.map(taskArn =>{
53+
let taskId = taskArn.split('/').pop();
54+
55+
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}`)
56+
});
57+
58+
if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
59+
const failure = runTaskResponse.failures[0];
60+
throw new Error(`${failure.arn} is ${failure.reason}`);
61+
}
62+
63+
// Wait for task to end
64+
if (waitForService && waitForService.toLowerCase() === 'true') {
65+
core.debug(`Waiting for the service to become stable. Will wait for ${waitForMinutes} minutes`);
66+
await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
67+
await tasksExitCode(ecs, clusterName, taskArns)
68+
} else {
69+
core.debug('Not waiting for the service to become stable');
70+
}
71+
}
72+
73+
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
74+
if (waitForMinutes > MAX_WAIT_MINUTES) {
75+
waitForMinutes = MAX_WAIT_MINUTES;
76+
}
77+
78+
const maxAttempts = (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC;
79+
80+
core.info('Waiting for tasks to stop');
81+
82+
const waitTaskResponse = await ecs.waitFor('tasksStopped', {
83+
cluster: clusterName,
84+
tasks: taskArns,
85+
$waiter: {
86+
delay: WAIT_DEFAULT_DELAY_SEC,
87+
maxAttempts: maxAttempts
88+
}
89+
}).promise();
90+
91+
core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`)
92+
93+
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`);
94+
}
95+
96+
async function tasksExitCode(ecs, clusterName, taskArns) {
97+
const describeResponse = await ecs.describeTasks({
98+
cluster: clusterName,
99+
tasks: taskArns
100+
}).promise();
101+
102+
const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
103+
const exitCodes = containers.map(container => container.exitCode)
104+
const reasons = containers.map(container => container.reason)
105+
106+
const failuresIdx = [];
107+
108+
exitCodes.filter((exitCode, index) => {
109+
if (exitCode !== 0) {
110+
failuresIdx.push(index)
111+
}
112+
})
113+
114+
const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
115+
116+
if (failures.length > 0) {
117+
console.log(`failed to with exit code${failures}`)
118+
core.setFailed(failures.join("\n"));
119+
throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
120+
} else {
121+
core.info(`All tasks have exited successfully.`);
122+
}
123+
}
124+
23125
// Deploy to a service that uses the 'ECS' deployment controller
24126
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
25127
core.debug('Updating the service');
@@ -259,6 +361,7 @@ async function run() {
259361
const cluster = core.getInput('cluster', { required: false });
260362
const waitForService = core.getInput('wait-for-service-stability', { required: false });
261363
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
364+
const initTaskCommand = core.getInput('init-task-command');
262365
if (waitForMinutes > MAX_WAIT_MINUTES) {
263366
waitForMinutes = MAX_WAIT_MINUTES;
264367
}
@@ -305,6 +408,12 @@ async function run() {
305408
throw new Error(`Service is ${serviceResponse.status}`);
306409
}
307410

411+
if (initTaskCommand) {
412+
await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
413+
} else {
414+
core.debug('InitTaskCommadn was not specified, no init run task.');
415+
}
416+
308417
if (!serviceResponse.deploymentController) {
309418
// Service uses the 'ECS' deployment controller, so we can call UpdateService
310419
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
@@ -319,6 +428,7 @@ async function run() {
319428
}
320429
}
321430
catch (error) {
431+
console.log(error);
322432
core.setFailed(error.message);
323433
core.debug(error.stack);
324434
}

0 commit comments

Comments
 (0)