Skip to content

Commit 8b66948

Browse files
committed
feat: adds code for showing service event info when using ecs deploy controller
1 parent df96430 commit 8b66948

File tree

6 files changed

+352
-14
lines changed

6 files changed

+352
-14
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ The minimal permissions require access to CodeDeploy:
226226

227227
This action emits debug logs to help troubleshoot deployment failures. To see the debug logs, create a secret named `ACTIONS_STEP_DEBUG` with value `true` in your repository.
228228

229+
The input `show-service-events` helps you to check logs from the service deployment events without going to the AWS console. This is just for the `ECS deployment controller`. Is desirable to configure [deployment circuit breaker](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-circuit-breaker.html) to get a 'FAILED' rolloutState.
230+
229231
## License Summary
230232

231233
This code is made available under the MIT license.

action.yml

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ inputs:
1313
cluster:
1414
description: "The name of the ECS service's cluster. Will default to the 'default' cluster"
1515
required: false
16+
show-service-events:
17+
description: "Whether to see or not the service deployment events when deployment rolloutState is 'FAILED'. Useful to see errors when a deployment fails."
18+
required: false
19+
show-service-events-frequency:
20+
description: "The frequency for showing a log line of the service events (default: 15 seconds)."
21+
required: false
1622
wait-for-service-stability:
1723
description: 'Whether to wait for the ECS service to reach stable state after deploying the new task definition. Valid value is "true". Will default to not waiting.'
1824
required: false

dist/index.js

+83-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
2727
];
2828

2929
// Deploy to a service that uses the 'ECS' deployment controller
30-
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
30+
async function updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment) {
3131
core.debug('Updating the service');
3232
await ecs.updateService({
3333
cluster: clusterName,
@@ -36,9 +36,60 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
3636
forceNewDeployment: forceNewDeployment
3737
}).promise();
3838

39-
const consoleHostname = aws.config.region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
39+
// Create a while loop to print the events of the service if deployment rollout state is failed
40+
// or if there are failed tasks but rollout state is still in progress
41+
if (showServiceEvents && showServiceEvents.toLowerCase() === 'true') {
42+
core.debug(`Deployment started. The option show-service-events is set to true. Showing logs each ${showServiceEventsFrequency} seconds.`);
43+
const initialState = 'IN_PROGRESS';
44+
let describeResponse = await ecs.describeServices({
45+
services: [service],
46+
cluster: clusterName
47+
}).promise();
48+
const deployTime = describeResponse.services[0].events[0].createdAt
49+
let newEvents = [];
50+
51+
while (initialState == 'IN_PROGRESS') {
52+
const showServiceEventsFrequencyMilisec = (showServiceEventsFrequency * 1000)
53+
await delay(showServiceEventsFrequencyMilisec);
54+
let describeResponse = await ecs.describeServices({
55+
services: [service],
56+
cluster: clusterName
57+
}).promise();
4058

41-
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${consoleHostname}/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/services/${service}/events`);
59+
let serviceResponse = describeResponse.services[0];
60+
let rolloutState = serviceResponse.deployments[0].rolloutState;
61+
let rolloutStateReason = serviceResponse.deployments[0].rolloutStateReason;
62+
let failedTasksCount = serviceResponse.deployments[0].failedTasks;
63+
let indexEventContainDeployDate = getPosition(deployTime.toString(), serviceResponse.events);
64+
newEvents = serviceResponse.events.slice(0, indexEventContainDeployDate);
65+
66+
if (rolloutState == 'COMPLETED') {
67+
printEvents(newEvents.reverse());
68+
break;
69+
} else if (rolloutState == 'FAILED') {
70+
printEvents(newEvents.reverse());
71+
throw new Error(`Rollout state is ${rolloutState}. Reason: ${rolloutStateReason}.`);
72+
} else if (rolloutState == 'IN_PROGRESS' && failedTasksCount > 0) {
73+
printEvents(newEvents.reverse());
74+
let tasksList = await ecs.listTasks({
75+
serviceName: service,
76+
cluster: clusterName,
77+
desiredStatus: 'STOPPED'
78+
}).promise();
79+
let describeTaskResponse = await ecs.describeTasks({
80+
tasks: [tasksList.taskArns[0]],
81+
cluster: clusterName,
82+
}).promise();
83+
let stopCode = describeTaskResponse.tasks[0].stopCode;
84+
let stoppedReason = describeTaskResponse.tasks[0].stoppedReason;
85+
core.info(`Task status: ${stopCode}. The reason is: ${stoppedReason}.`);
86+
throw new Error(`There are failed tasks. This means the deployment didn't go well. Please check the logs of task, service or container for more information.`);
87+
}
88+
}
89+
} else {
90+
const consoleHostname = aws.config.region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
91+
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${consoleHostname}/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/services/${service}/events`);
92+
}
4293

4394
// Wait for service stability
4495
if (waitForService && waitForService.toLowerCase() === 'true') {
@@ -57,6 +108,29 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
57108
}
58109
}
59110

111+
// Function to print the events of the service
112+
function printEvents(events) {
113+
core.debug('Showing the service events:')
114+
for (let i = 0; i < events.length; i++) {
115+
core.info(events[i].createdAt.toString().split('(')[0]);
116+
core.info(events[i].message);
117+
}
118+
}
119+
120+
// Function to get the position of an element in an array
121+
function getPosition(elementToFind, arrayElements) {
122+
for (let i = 0; i < arrayElements.length; i += 1) {
123+
if (arrayElements[i].createdAt.toString().includes(elementToFind)) {
124+
return i;
125+
}
126+
}
127+
}
128+
129+
// Fuction to create a delay
130+
function delay(time) {
131+
return new Promise(resolve => setTimeout(resolve, time));
132+
}
133+
60134
// Find value in a CodeDeploy AppSpec file with a case-insensitive key
61135
function findAppSpecValue(obj, keyName) {
62136
return obj[findAppSpecKey(obj, keyName)];
@@ -266,7 +340,8 @@ async function run() {
266340
const taskDefinitionFile = core.getInput('task-definition', { required: true });
267341
const service = core.getInput('service', { required: false });
268342
const cluster = core.getInput('cluster', { required: false });
269-
const waitForService = core.getInput('wait-for-service-stability', { required: false });
343+
const waitForService = core.getInput('wait-for-service-stability', { required: false });
344+
270345
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
271346
if (waitForMinutes > MAX_WAIT_MINUTES) {
272347
waitForMinutes = MAX_WAIT_MINUTES;
@@ -275,6 +350,9 @@ async function run() {
275350
const forceNewDeployInput = core.getInput('force-new-deployment', { required: false }) || 'false';
276351
const forceNewDeployment = forceNewDeployInput.toLowerCase() === 'true';
277352

353+
const showServiceEvents = core.getInput('show-service-events', { required: false });
354+
let showServiceEventsFrequency = core.getInput('show-service-events-frequency', { required: false }) || 15;
355+
278356
// Register the task definition
279357
core.debug('Registering the task definition');
280358
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
@@ -316,7 +394,7 @@ async function run() {
316394

317395
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
318396
// Service uses the 'ECS' deployment controller, so we can call UpdateService
319-
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
397+
await updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment);
320398
} else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') {
321399
// Service uses CodeDeploy, so we should start a CodeDeploy deployment
322400
await createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForMinutes);

index.js

+87-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
2121
];
2222

2323
// Deploy to a service that uses the 'ECS' deployment controller
24-
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
24+
async function updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment) {
2525
core.debug('Updating the service');
2626
await ecs.updateService({
2727
cluster: clusterName,
@@ -30,9 +30,64 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
3030
forceNewDeployment: forceNewDeployment
3131
}).promise();
3232

33-
const consoleHostname = aws.config.region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
33+
// Create a while loop to print the events of the service if deployment rollout state is failed
34+
// or if there are failed tasks but rollout state is still in progress
35+
if (showServiceEvents && showServiceEvents.toLowerCase() === 'true') {
36+
core.debug(`Deployment started. The option show-service-events is set to true. Showing logs each ${showServiceEventsFrequency} seconds.`);
37+
const initialState = 'IN_PROGRESS';
38+
let describeResponse = await ecs.describeServices({
39+
services: [service],
40+
cluster: clusterName
41+
}).promise();
42+
const deployTime = describeResponse.services[0].events[0].createdAt
43+
let newEvents = [];
44+
45+
while (initialState == 'IN_PROGRESS') {
46+
const showServiceEventsFrequencyMilisec = (showServiceEventsFrequency * 1000)
47+
await delay(showServiceEventsFrequencyMilisec);
48+
let describeResponse = await ecs.describeServices({
49+
services: [service],
50+
cluster: clusterName
51+
}).promise();
3452

35-
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${consoleHostname}/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/services/${service}/events`);
53+
let serviceResponse = describeResponse.services[0];
54+
let rolloutState = serviceResponse.deployments[0].rolloutState;
55+
let rolloutStateReason = serviceResponse.deployments[0].rolloutStateReason;
56+
let failedTasksCount = serviceResponse.deployments[0].failedTasks;
57+
let indexEventContainDeployDate = getPosition(deployTime.toString(), serviceResponse.events);
58+
newEvents = serviceResponse.events.slice(0, indexEventContainDeployDate);
59+
60+
if (rolloutState == 'COMPLETED') {
61+
printEvents(newEvents.reverse());
62+
break;
63+
} else if (rolloutState == 'FAILED') {
64+
printEvents(newEvents.reverse());
65+
throw new Error(`Rollout state is ${rolloutState}. Reason: ${rolloutStateReason}.`);
66+
} else if (rolloutState == 'IN_PROGRESS' && failedTasksCount > 0) {
67+
printEvents(newEvents.reverse());
68+
let tasksList = await ecs.listTasks({
69+
serviceName: service,
70+
cluster: clusterName,
71+
desiredStatus: 'STOPPED'
72+
}).promise();
73+
let describeTaskResponse = await ecs.describeTasks({
74+
tasks: [tasksList.taskArns[0]],
75+
cluster: clusterName,
76+
}).promise();
77+
let stopCode = describeTaskResponse.tasks[0].stopCode;
78+
let stoppedReason = describeTaskResponse.tasks[0].stoppedReason;
79+
let containerLastStatus = describeTaskResponse.tasks[0].containers[0].lastStatus;
80+
core.info(`Task status: ${stopCode}. The reason is: ${stoppedReason}.`);
81+
if (containerLastStatus == 'STOPPED') {
82+
core.info(`Container status: ${containerLastStatus}. The reason is: ${describeTaskResponse.tasks[0].containers[0].reason}.`);
83+
}
84+
throw new Error(`There are failed tasks. This means the deployment didn't go well. Please check the logs of task, service or container for more information.`);
85+
}
86+
}
87+
} else {
88+
const consoleHostname = aws.config.region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
89+
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${consoleHostname}/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/services/${service}/events`);
90+
}
3691

3792
// Wait for service stability
3893
if (waitForService && waitForService.toLowerCase() === 'true') {
@@ -51,6 +106,29 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
51106
}
52107
}
53108

109+
// Function to print the events of the service
110+
function printEvents(events) {
111+
core.debug('Showing the service events:')
112+
for (let i = 0; i < events.length; i++) {
113+
core.info(events[i].createdAt.toString().split('(')[0]);
114+
core.info(events[i].message);
115+
}
116+
}
117+
118+
// Function to get the position of an element in an array
119+
function getPosition(elementToFind, arrayElements) {
120+
for (let i = 0; i < arrayElements.length; i += 1) {
121+
if (arrayElements[i].createdAt.toString().includes(elementToFind)) {
122+
return i;
123+
}
124+
}
125+
}
126+
127+
// Fuction to create a delay
128+
function delay(time) {
129+
return new Promise(resolve => setTimeout(resolve, time));
130+
}
131+
54132
// Find value in a CodeDeploy AppSpec file with a case-insensitive key
55133
function findAppSpecValue(obj, keyName) {
56134
return obj[findAppSpecKey(obj, keyName)];
@@ -260,7 +338,8 @@ async function run() {
260338
const taskDefinitionFile = core.getInput('task-definition', { required: true });
261339
const service = core.getInput('service', { required: false });
262340
const cluster = core.getInput('cluster', { required: false });
263-
const waitForService = core.getInput('wait-for-service-stability', { required: false });
341+
const waitForService = core.getInput('wait-for-service-stability', { required: false });
342+
264343
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
265344
if (waitForMinutes > MAX_WAIT_MINUTES) {
266345
waitForMinutes = MAX_WAIT_MINUTES;
@@ -269,6 +348,9 @@ async function run() {
269348
const forceNewDeployInput = core.getInput('force-new-deployment', { required: false }) || 'false';
270349
const forceNewDeployment = forceNewDeployInput.toLowerCase() === 'true';
271350

351+
const showServiceEvents = core.getInput('show-service-events', { required: false });
352+
let showServiceEventsFrequency = core.getInput('show-service-events-frequency', { required: false }) || 15;
353+
272354
// Register the task definition
273355
core.debug('Registering the task definition');
274356
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
@@ -310,7 +392,7 @@ async function run() {
310392

311393
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
312394
// Service uses the 'ECS' deployment controller, so we can call UpdateService
313-
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment);
395+
await updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment);
314396
} else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') {
315397
// Service uses CodeDeploy, so we should start a CodeDeploy deployment
316398
await createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForMinutes);

0 commit comments

Comments
 (0)