Skip to content

Commit 61fc2fd

Browse files
felipem1210Felipe Macias
authored and
Felipe Macias
committed
feat: adds code for showing service event info when using ecs deploy controller
1 parent 88fc08e commit 61fc2fd

File tree

5 files changed

+376
-15
lines changed

5 files changed

+376
-15
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ To tag your tasks:
326326

327327
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.
328328

329+
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.
330+
329331
## License Summary
330332

331333
This code is made available under the MIT license.

action.yml

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ inputs:
1616
cluster:
1717
description: "The name of the ECS service's cluster. Will default to the 'default' cluster."
1818
required: false
19+
show-service-events:
20+
description: "Whether to see or not the service deployment events when deployment rolloutState is 'FAILED'. Useful to see errors when a deployment fails."
21+
required: false
22+
show-service-events-frequency:
23+
description: "The frequency for showing a log line of the service events (default: 15 seconds)."
24+
required: false
1925
wait-for-service-stability:
2026
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.'
2127
required: false

dist/index.js

+88-6
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
137137
}
138138

139139
// Deploy to a service that uses the 'ECS' deployment controller
140-
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
140+
async function updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
141141
core.debug('Updating the service');
142142

143143
let params = {
@@ -155,10 +155,65 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
155155
}
156156
await ecs.updateService(params);
157157

158-
const region = await ecs.config.region();
159-
const consoleHostname = region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
158+
// Create a while loop to print the events of the service if deployment rollout state is failed
159+
// or if there are failed tasks but rollout state is still in progress
160+
if (showServiceEvents && showServiceEvents.toLowerCase() === 'true') {
161+
core.debug(`Deployment started. The option show-service-events is set to true. Showing logs each ${showServiceEventsFrequency} seconds.`);
162+
const initialState = 'IN_PROGRESS';
163+
let describeResponse = await ecs.describeServices({
164+
services: [service],
165+
cluster: clusterName
166+
});
167+
const deployTime = describeResponse.services[0].events[0].createdAt
168+
let newEvents = [];
169+
170+
while (initialState == 'IN_PROGRESS') {
171+
const showServiceEventsFrequencyMilisec = (showServiceEventsFrequency * 1000)
172+
await delay(showServiceEventsFrequencyMilisec);
173+
let describeResponse = await ecs.describeServices({
174+
services: [service],
175+
cluster: clusterName
176+
});
160177

161-
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${region}.${consoleHostname}/ecs/v2/clusters/${clusterName}/services/${service}/events?region=${region}`);
178+
let serviceResponse = describeResponse.services[0];
179+
let rolloutState = serviceResponse.deployments[0].rolloutState;
180+
let rolloutStateReason = serviceResponse.deployments[0].rolloutStateReason;
181+
let failedTasksCount = serviceResponse.deployments[0].failedTasks;
182+
let indexEventContainDeployDate = getPosition(deployTime.toString(), serviceResponse.events);
183+
newEvents = serviceResponse.events.slice(0, indexEventContainDeployDate);
184+
185+
if (rolloutState == 'COMPLETED') {
186+
printEvents(newEvents.reverse());
187+
break;
188+
} else if (rolloutState == 'FAILED') {
189+
printEvents(newEvents.reverse());
190+
throw new Error(`Rollout state is ${rolloutState}. Reason: ${rolloutStateReason}.`);
191+
} else if (rolloutState == 'IN_PROGRESS' && failedTasksCount > 0) {
192+
printEvents(newEvents.reverse());
193+
let tasksList = await ecs.listTasks({
194+
serviceName: service,
195+
cluster: clusterName,
196+
desiredStatus: 'STOPPED'
197+
});
198+
let describeTaskResponse = await ecs.describeTasks({
199+
tasks: [tasksList.taskArns[0]],
200+
cluster: clusterName,
201+
});
202+
let stopCode = describeTaskResponse.tasks[0].stopCode;
203+
let stoppedReason = describeTaskResponse.tasks[0].stoppedReason;
204+
let containerLastStatus = describeTaskResponse.tasks[0].containers[0].lastStatus;
205+
core.info(`Task status: ${stopCode}. The reason is: ${stoppedReason}.`);
206+
if (containerLastStatus == 'STOPPED') {
207+
core.info(`Container status: ${containerLastStatus}. The reason is: ${describeTaskResponse.tasks[0].containers[0].reason}.`);
208+
}
209+
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.`);
210+
}
211+
}
212+
} else {
213+
const region = await ecs.config.region();
214+
const consoleHostname = region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
215+
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${region}.${consoleHostname}/ecs/v2/clusters/${clusterName}/services/${service}/events?region=${region}`);
216+
}
162217

163218
// Wait for service stability
164219
if (waitForService && waitForService.toLowerCase() === 'true') {
@@ -176,6 +231,29 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
176231
}
177232
}
178233

234+
// Function to print the events of the service
235+
function printEvents(events) {
236+
core.debug('Showing the service events:')
237+
for (let i = 0; i < events.length; i++) {
238+
core.info(events[i].createdAt.toString().split('(')[0]);
239+
core.info(events[i].message);
240+
}
241+
}
242+
243+
// Function to get the position of an element in an array
244+
function getPosition(elementToFind, arrayElements) {
245+
for (let i = 0; i < arrayElements.length; i += 1) {
246+
if (arrayElements[i].createdAt.toString().includes(elementToFind)) {
247+
return i;
248+
}
249+
}
250+
}
251+
252+
// Fuction to create a delay
253+
function delay(time) {
254+
return new Promise(resolve => setTimeout(resolve, time));
255+
}
256+
179257
// Find value in a CodeDeploy AppSpec file with a case-insensitive key
180258
function findAppSpecValue(obj, keyName) {
181259
return obj[findAppSpecKey(obj, keyName)];
@@ -392,7 +470,8 @@ async function run() {
392470
const taskDefinitionFile = core.getInput('task-definition', { required: true });
393471
const service = core.getInput('service', { required: false });
394472
const cluster = core.getInput('cluster', { required: false });
395-
const waitForService = core.getInput('wait-for-service-stability', { required: false });
473+
const waitForService = core.getInput('wait-for-service-stability', { required: false });
474+
396475
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
397476

398477
if (waitForMinutes > MAX_WAIT_MINUTES) {
@@ -406,6 +485,9 @@ async function run() {
406485
const enableECSManagedTags = enableECSManagedTagsInput.toLowerCase() === 'true';
407486
const propagateTags = core.getInput('propagate-tags', { required: false }) || 'NONE';
408487

488+
const showServiceEvents = core.getInput('show-service-events', { required: false });
489+
let showServiceEventsFrequency = core.getInput('show-service-events-frequency', { required: false }) || 15;
490+
409491
// Register the task definition
410492
core.debug('Registering the task definition');
411493
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
@@ -456,7 +538,7 @@ async function run() {
456538
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
457539
// Service uses the 'ECS' deployment controller, so we can call UpdateService
458540
core.debug('Updating service...');
459-
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags);
541+
await updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags);
460542

461543
} else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') {
462544
// Service uses CodeDeploy, so we should start a CodeDeploy deployment

index.js

+88-6
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
131131
}
132132

133133
// Deploy to a service that uses the 'ECS' deployment controller
134-
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
134+
async function updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
135135
core.debug('Updating the service');
136136

137137
let params = {
@@ -149,10 +149,65 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
149149
}
150150
await ecs.updateService(params);
151151

152-
const region = await ecs.config.region();
153-
const consoleHostname = region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
152+
// Create a while loop to print the events of the service if deployment rollout state is failed
153+
// or if there are failed tasks but rollout state is still in progress
154+
if (showServiceEvents && showServiceEvents.toLowerCase() === 'true') {
155+
core.debug(`Deployment started. The option show-service-events is set to true. Showing logs each ${showServiceEventsFrequency} seconds.`);
156+
const initialState = 'IN_PROGRESS';
157+
let describeResponse = await ecs.describeServices({
158+
services: [service],
159+
cluster: clusterName
160+
});
161+
const deployTime = describeResponse.services[0].events[0].createdAt
162+
let newEvents = [];
154163

155-
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${region}.${consoleHostname}/ecs/v2/clusters/${clusterName}/services/${service}/events?region=${region}`);
164+
while (initialState == 'IN_PROGRESS') {
165+
const showServiceEventsFrequencyMilisec = (showServiceEventsFrequency * 1000)
166+
await delay(showServiceEventsFrequencyMilisec);
167+
let describeResponse = await ecs.describeServices({
168+
services: [service],
169+
cluster: clusterName
170+
});
171+
172+
let serviceResponse = describeResponse.services[0];
173+
let rolloutState = serviceResponse.deployments[0].rolloutState;
174+
let rolloutStateReason = serviceResponse.deployments[0].rolloutStateReason;
175+
let failedTasksCount = serviceResponse.deployments[0].failedTasks;
176+
let indexEventContainDeployDate = getPosition(deployTime.toString(), serviceResponse.events);
177+
newEvents = serviceResponse.events.slice(0, indexEventContainDeployDate);
178+
179+
if (rolloutState == 'COMPLETED') {
180+
printEvents(newEvents.reverse());
181+
break;
182+
} else if (rolloutState == 'FAILED') {
183+
printEvents(newEvents.reverse());
184+
throw new Error(`Rollout state is ${rolloutState}. Reason: ${rolloutStateReason}.`);
185+
} else if (rolloutState == 'IN_PROGRESS' && failedTasksCount > 0) {
186+
printEvents(newEvents.reverse());
187+
let tasksList = await ecs.listTasks({
188+
serviceName: service,
189+
cluster: clusterName,
190+
desiredStatus: 'STOPPED'
191+
});
192+
let describeTaskResponse = await ecs.describeTasks({
193+
tasks: [tasksList.taskArns[0]],
194+
cluster: clusterName,
195+
});
196+
let stopCode = describeTaskResponse.tasks[0].stopCode;
197+
let stoppedReason = describeTaskResponse.tasks[0].stoppedReason;
198+
let containerLastStatus = describeTaskResponse.tasks[0].containers[0].lastStatus;
199+
core.info(`Task status: ${stopCode}. The reason is: ${stoppedReason}.`);
200+
if (containerLastStatus == 'STOPPED') {
201+
core.info(`Container status: ${containerLastStatus}. The reason is: ${describeTaskResponse.tasks[0].containers[0].reason}.`);
202+
}
203+
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.`);
204+
}
205+
}
206+
} else {
207+
const region = await ecs.config.region();
208+
const consoleHostname = region.startsWith('cn') ? 'console.amazonaws.cn' : 'console.aws.amazon.com';
209+
core.info(`Deployment started. Watch this deployment's progress in the Amazon ECS console: https://${region}.${consoleHostname}/ecs/v2/clusters/${clusterName}/services/${service}/events?region=${region}`);
210+
}
156211

157212
// Wait for service stability
158213
if (waitForService && waitForService.toLowerCase() === 'true') {
@@ -170,6 +225,29 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe
170225
}
171226
}
172227

228+
// Function to print the events of the service
229+
function printEvents(events) {
230+
core.debug('Showing the service events:')
231+
for (let i = 0; i < events.length; i++) {
232+
core.info(events[i].createdAt.toString().split('(')[0]);
233+
core.info(events[i].message);
234+
}
235+
}
236+
237+
// Function to get the position of an element in an array
238+
function getPosition(elementToFind, arrayElements) {
239+
for (let i = 0; i < arrayElements.length; i += 1) {
240+
if (arrayElements[i].createdAt.toString().includes(elementToFind)) {
241+
return i;
242+
}
243+
}
244+
}
245+
246+
// Fuction to create a delay
247+
function delay(time) {
248+
return new Promise(resolve => setTimeout(resolve, time));
249+
}
250+
173251
// Find value in a CodeDeploy AppSpec file with a case-insensitive key
174252
function findAppSpecValue(obj, keyName) {
175253
return obj[findAppSpecKey(obj, keyName)];
@@ -386,7 +464,8 @@ async function run() {
386464
const taskDefinitionFile = core.getInput('task-definition', { required: true });
387465
const service = core.getInput('service', { required: false });
388466
const cluster = core.getInput('cluster', { required: false });
389-
const waitForService = core.getInput('wait-for-service-stability', { required: false });
467+
const waitForService = core.getInput('wait-for-service-stability', { required: false });
468+
390469
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;
391470

392471
if (waitForMinutes > MAX_WAIT_MINUTES) {
@@ -400,6 +479,9 @@ async function run() {
400479
const enableECSManagedTags = enableECSManagedTagsInput.toLowerCase() === 'true';
401480
const propagateTags = core.getInput('propagate-tags', { required: false }) || 'NONE';
402481

482+
const showServiceEvents = core.getInput('show-service-events', { required: false });
483+
let showServiceEventsFrequency = core.getInput('show-service-events-frequency', { required: false }) || 15;
484+
403485
// Register the task definition
404486
core.debug('Registering the task definition');
405487
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
@@ -450,7 +532,7 @@ async function run() {
450532
if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') {
451533
// Service uses the 'ECS' deployment controller, so we can call UpdateService
452534
core.debug('Updating service...');
453-
await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags);
535+
await updateEcsService(ecs, clusterName, service, taskDefArn, showServiceEvents, showServiceEventsFrequency, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags);
454536

455537
} else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') {
456538
// Service uses CodeDeploy, so we should start a CodeDeploy deployment

0 commit comments

Comments
 (0)