@@ -26,15 +26,123 @@ 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
30- async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
133+ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount ) {
31134 core.debug('Updating the service');
32- await ecs.updateService( {
135+ let params = {
33136 cluster: clusterName,
34137 service: service,
35138 taskDefinition: taskDefArn,
36139 forceNewDeployment: forceNewDeployment
37- }).promise();
140+ };
141+ // Add the desiredCount property only if it is defined and a number.
142+ if (!isNaN(desiredCount) && desiredCount !== undefined) {
143+ params.desiredCount = desiredCount;
144+ }
145+ await ecs.updateService(params).promise();
38146
39147 const consoleHostname = aws.config.region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
40148
@@ -268,13 +376,17 @@ async function run() {
268376 const cluster = core.getInput('cluster', { required: false });
269377 const waitForService = core.getInput('wait-for-service-stability', { required: false });
270378 let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
379+ const initTaskCommand = core.getInput('init-task-command');
271380 if (waitForMinutes > MAX_WAIT_MINUTES) {
272381 waitForMinutes = MAX_WAIT_MINUTES;
273382 }
274383
275384 const forceNewDeployInput = core.getInput('force-new-deployment', { required: false }) || 'false';
276385 const forceNewDeployment = forceNewDeployInput.toLowerCase() === 'true';
277386
387+ const desiredCount = parseInt((core.getInput('desired-count', {required: false})));
388+
389+
278390 // Register the task definition
279391 core.debug('Registering the task definition');
280392 const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
@@ -314,9 +426,15 @@ async function run() {
314426 throw new Error(`Service is ${serviceResponse.status}`);
315427 }
316428
429+ if (initTaskCommand) {
430+ await runInitTask(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, initTaskCommand);
431+ } else {
432+ core.debug('InitTaskCommand was not specified, no init run task.');
433+ }
434+
317435 if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
318436 // Service uses the 'ECS' deployment controller, so we can call UpdateService
319- await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
437+ await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount );
320438 } else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') {
321439 // Service uses CodeDeploy, so we should start a CodeDeploy deployment
322440 await createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForMinutes);
@@ -328,6 +446,7 @@ async function run() {
328446 }
329447 }
330448 catch (error) {
449+ console.log(error);
331450 core.setFailed(error.message);
332451 core.debug(error.stack);
333452 }
0 commit comments