diff --git a/action.yml b/action.yml index e9cdfc22..e4457e24 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,9 @@ inputs: wait-for-service-stability: 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.' required: false + wait-for-service-stability-max-delay-seconds: + description: 'The maximum amount of time to wait between checs for service stability (default: 120 seconds, min: 15 seconds).' + required: false wait-for-minutes: description: 'How long to wait for the ECS service to reach stable state, in minutes (default: 30 minutes, max: 6 hours). For CodeDeploy deployments, any wait time configured in the CodeDeploy deployment group will be added to this value.' required: false diff --git a/index.js b/index.js index f5d29bf0..c9278037 100644 --- a/index.js +++ b/index.js @@ -191,7 +191,7 @@ async function tasksExitCode(ecs, clusterName, taskArns) { } // Deploy to a service that uses the 'ECS' deployment controller -async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) { +async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForServiceMaxDelaySeconds, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) { core.debug('Updating the service'); const serviceManagedEBSVolumeName = core.getInput('service-managed-ebs-volume-name', { required: false }) || ''; @@ -239,6 +239,7 @@ async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForSe await waitUntilServicesStable({ client: ecs, minDelay: WAIT_DEFAULT_DELAY_SEC, + maxDelay: waitForServiceMaxDelaySeconds, maxWaitTime: waitForMinutes * 60 }, { services: [service], @@ -361,7 +362,7 @@ function validateProxyConfigurations(taskDef){ } // Deploy to a service that uses the 'CODE_DEPLOY' deployment controller -async function createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForMinutes) { +async function createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForServiceMaxDelaySeconds, waitForMinutes) { core.debug('Updating AppSpec file with new task definition ARN'); let codeDeployAppSpecFile = core.getInput('codedeploy-appspec', { required : false }); @@ -443,6 +444,7 @@ async function createCodeDeployDeployment(codedeploy, clusterName, service, task await waitUntilDeploymentSuccessful({ client: codedeploy, minDelay: WAIT_DEFAULT_DELAY_SEC, + maxDelay: waitForServiceMaxDelaySeconds, maxWaitTime: totalWaitMin * 60 }, { deploymentId: createDeployResponse.deploymentId @@ -466,6 +468,7 @@ async function run() { const service = core.getInput('service', { required: false }); const cluster = core.getInput('cluster', { required: false }); const waitForService = core.getInput('wait-for-service-stability', { required: false }); + const waitForServiceMaxDelaySeconds = parseInt(core.getInput('wait-for-service-stability-max-delay-seconds', { required: false })) || undefined; let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30; if (waitForMinutes > MAX_WAIT_MINUTES) { @@ -536,12 +539,12 @@ async function run() { if (!serviceResponse.deploymentController || !serviceResponse.deploymentController.type || serviceResponse.deploymentController.type === 'ECS') { // Service uses the 'ECS' deployment controller, so we can call UpdateService core.debug('Updating service...'); - await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags); + await updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForServiceMaxDelaySeconds, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags); } else if (serviceResponse.deploymentController.type === 'CODE_DEPLOY') { // Service uses CodeDeploy, so we should start a CodeDeploy deployment core.debug('Deploying service in the default cluster'); - await createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForMinutes); + await createCodeDeployDeployment(codedeploy, clusterName, service, taskDefArn, waitForService, waitForServiceMaxDelaySeconds, waitForMinutes); } else { throw new Error(`Unsupported deployment controller: ${serviceResponse.deploymentController.type}`); } diff --git a/index.test.js b/index.test.js index 459a722b..9716f80e 100644 --- a/index.test.js +++ b/index.test.js @@ -562,10 +562,11 @@ describe('Deploy to ECS', () => { core.getInput = jest .fn() .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('service-456') // service - .mockReturnValueOnce('cluster-789') // cluster - .mockReturnValueOnce('TRUE') // wait-for-service-stability - .mockReturnValueOnce('60'); // wait-for-minutes + .mockReturnValueOnce('service-456') // service + .mockReturnValueOnce('cluster-789') // cluster + .mockReturnValueOnce('TRUE') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds + .mockReturnValueOnce('60'); // wait-for-minutes mockEcsDescribeServices.mockImplementation( () => Promise.resolve({ @@ -619,6 +620,7 @@ describe('Deploy to ECS', () => { { client: mockCodeDeployClient, minDelay: 15, + maxDelay: 60, maxWaitTime: ( 60 + EXPECTED_CODE_DEPLOY_TERMINATION_WAIT_TIME + @@ -638,10 +640,11 @@ describe('Deploy to ECS', () => { core.getInput = jest .fn() .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('service-456') // service - .mockReturnValueOnce('cluster-789') // cluster - .mockReturnValueOnce('TRUE') // wait-for-service-stability - .mockReturnValueOnce('1000'); // wait-for-minutes + .mockReturnValueOnce('service-456') // service + .mockReturnValueOnce('cluster-789') // cluster + .mockReturnValueOnce('TRUE') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds + .mockReturnValueOnce('1000'); // wait-for-minutes mockEcsDescribeServices.mockImplementation( () => Promise.resolve({ @@ -695,6 +698,7 @@ describe('Deploy to ECS', () => { { client: mockCodeDeployClient, minDelay: 15, + maxDelay: 60, maxWaitTime: 6 * 60 * 60, }, { @@ -713,6 +717,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('false') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // run-task @@ -834,6 +839,7 @@ describe('Deploy to ECS', () => { 'service': 'service-456', 'cluster': 'cluster-789', 'wait-for-service-stability': 'TRUE', + 'wait-for-service-stability-max-delay-seconds': '60', 'codedeploy-application': 'Custom-Application', 'codedeploy-deployment-group': 'Custom-Deployment-Group', 'codedeploy-deployment-description': 'Custom-Deployment', @@ -940,6 +946,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('TRUE') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce(''); // desired count await run(); @@ -965,6 +972,7 @@ describe('Deploy to ECS', () => { { client: mockEcsClient, minDelay: 15, + maxDelay: 60, maxWaitTime: EXPECTED_DEFAULT_WAIT_TIME * 60, }, { @@ -981,6 +989,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('TRUE') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('60') // wait-for-minutes .mockReturnValueOnce(''); // desired count @@ -1007,6 +1016,7 @@ describe('Deploy to ECS', () => { { client: mockEcsClient, minDelay: 15, + maxDelay: 60, maxWaitTime: 60 * 60, }, { @@ -1023,6 +1033,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('TRUE') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('1000') // wait-for-minutes .mockReturnValueOnce('abc'); // desired count is NaN @@ -1049,6 +1060,7 @@ describe('Deploy to ECS', () => { { client: mockEcsClient, minDelay: 15, + maxDelay: 60, maxWaitTime: 6 * 60 * 60, }, { @@ -1065,6 +1077,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('false') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('true') // force-new-deployment .mockReturnValueOnce('4'); // desired count is number @@ -1138,6 +1151,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // enable-ecs-managed-tags .mockReturnValueOnce('') // propagate-tags @@ -1174,6 +1188,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1216,6 +1231,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1259,6 +1275,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('true') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1312,6 +1329,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1337,6 +1355,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('true') // wait-for-service-stability + .mockReturnValueOnce('60') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // enable-ecs-managed-tags .mockReturnValueOnce('') // force-new-deployment @@ -1373,6 +1392,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1402,6 +1422,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1431,6 +1452,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1474,6 +1496,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('') // service .mockReturnValueOnce('somecluster') // cluster .mockReturnValueOnce('') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1597,6 +1620,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('false') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1630,6 +1654,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('false') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count @@ -1663,6 +1688,7 @@ describe('Deploy to ECS', () => { .mockReturnValueOnce('service-456') // service .mockReturnValueOnce('cluster-789') // cluster .mockReturnValueOnce('false') // wait-for-service-stability + .mockReturnValueOnce('') // wait-for-service-stability-max-delay-seconds .mockReturnValueOnce('') // wait-for-minutes .mockReturnValueOnce('') // force-new-deployment .mockReturnValueOnce('') // desired-count