Skip to content

Commit d4184a7

Browse files
authored
feat: Configurable unhealthiness status for beanstalk environments (#17)
1 parent b6e63fa commit d4184a7

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

src/deploy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export async function deployToGroup(props: IDeployToGroupProps) {
118118
region: group.region,
119119
});
120120
await createAppVersionsForGroup(client, props);
121-
// Must wait for envs to be healthy before issuing deployment
121+
log.info(chalk.blue('Verifying environments are ready to receive deployment before initiating...'));
122122
await waitForGroupHealthiness({
123123
client,
124124
group,

src/helpers/Interfaces.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { S3Location } from '@aws-sdk/client-elastic-beanstalk';
1+
import { S3Location, EnvironmentHealthStatus } from '@aws-sdk/client-elastic-beanstalk';
22
import log from 'loglevel';
33

44
/**
@@ -65,6 +65,11 @@ export interface IHealthCheckProps {
6565
* @default 60000
6666
*/
6767
readonly timeBetweenAttemptsMs: number;
68+
/**
69+
* Which statuses qualify a beanstalk environment as unhealthy.
70+
* @default ['Severe', 'Degraded', 'Warning']
71+
*/
72+
readonly unhealthyStatuses?: EnvironmentHealthStatus[];
6873
}
6974

7075
/**

src/helpers/healthiness.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import {
44
DescribeEnvironmentsCommandOutput,
55
ElasticBeanstalkClient,
66
EnvironmentDescription,
7+
EnvironmentHealthStatus,
78
} from '@aws-sdk/client-elastic-beanstalk';
89
import log from 'loglevel';
910
import { DBHealthinessCheckError } from './Errors';
1011
import { IBeanstalkEnvironment, IBeanstalkGroup, IHealthCheckProps } from './Interfaces';
1112

12-
const AWS_EB_HEALTH_CHECK_UNHEALTHY_STATES = ['Severe', 'Degraded', 'Warning'];
13+
const AWS_EB_HEALTH_CHECK_UNHEALTHY_STATES: EnvironmentHealthStatus[] = ['Severe', 'Degraded', 'Warning'];
1314

1415
interface IEnvironmentsByApp {
1516
[app: string]: IBeanstalkEnvironment[];
@@ -65,6 +66,7 @@ function groupEnvsByApp(envs: IBeanstalkEnvironment[]): IEnvironmentsByApp {
6566
*/
6667
function getEnvironmentsHealth(
6768
envs: EnvironmentDescription[],
69+
unhealthyStatuses: EnvironmentHealthStatus[],
6870
expectedVersionLabel?: string,
6971
): IBeanstalkHealthStatuses {
7072
return envs.reduce(
@@ -75,7 +77,7 @@ function getEnvironmentsHealth(
7577
`Beanstalk status for '${envDesc.EnvironmentName}' could not be retrieved. Cannot proceed safely.`,
7678
);
7779
}
78-
const isInHealthyState = !AWS_EB_HEALTH_CHECK_UNHEALTHY_STATES.includes(envDesc.HealthStatus);
80+
const isInHealthyState = !unhealthyStatuses.includes(envDesc.HealthStatus as EnvironmentHealthStatus);
7981
if (envDesc.Status === 'Ready' && isInHealthyState) {
8082
if (!expectedVersionLabel) {
8183
previousValue.healthy.push({
@@ -136,6 +138,7 @@ async function getGroupHealth(props: IHealthCheckPropsPrivate): Promise<IBeansta
136138

137139
const partitioned = getEnvironmentsHealth(
138140
resp.Environments,
141+
props.unhealthyStatuses ?? AWS_EB_HEALTH_CHECK_UNHEALTHY_STATES,
139142
props.checkVersion ? props.group.versionProps.label : undefined,
140143
);
141144
statuses.healthy = [...statuses.healthy, ...partitioned.healthy];

test/deploy.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,59 @@ describe('Deployment to beanstalks in different apps', () => {
257257
const expectedCalls = 6;
258258
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(expectedCalls);
259259
});
260+
261+
test('with custom unhealthyStatuses succeeds to become healthy after one retry attempt', async () => {
262+
ebMock
263+
.on(DescribeEnvironmentsCommand, {
264+
ApplicationName: TEST_BEANSTALK_GROUP.environments[1].app,
265+
EnvironmentNames: [TEST_BEANSTALK_GROUP.environments[1].name],
266+
})
267+
// Initial health check prior to deployment succeeds
268+
.resolvesOnce({
269+
Environments: [
270+
{
271+
EnvironmentName: TEST_BEANSTALK_GROUP.environments[1].name,
272+
HealthStatus: 'Ok',
273+
Status: 'Ready',
274+
VersionLabel: 'OLD_VERSION',
275+
},
276+
],
277+
})
278+
// Initial health check after deployment fails
279+
.resolvesOnce({
280+
Environments: [
281+
{
282+
EnvironmentName: TEST_BEANSTALK_GROUP.environments[1].name,
283+
HealthStatus: 'Degraded',
284+
Status: 'Updating',
285+
VersionLabel: 'OLD_VERSION',
286+
},
287+
],
288+
})
289+
// Second health check after deployment succeeds
290+
.resolves({
291+
Environments: [
292+
{
293+
EnvironmentName: TEST_BEANSTALK_GROUP.environments[1].name,
294+
HealthStatus: 'Warning',
295+
Status: 'Ready',
296+
VersionLabel: TEST_BEANSTALK_GROUP.versionProps.label,
297+
},
298+
],
299+
});
300+
301+
expect(
302+
await deployToGroup({
303+
...commonDeployProps,
304+
postDeployHealthCheckProps: {
305+
attempts: 3,
306+
timeBetweenAttemptsMs: 0,
307+
unhealthyStatuses: ['Severe'],
308+
},
309+
}),
310+
).not.toThrowError;
311+
// 2 pre-deploy checks (one per Application), 4 post-deploy checks (one retry)
312+
const expectedCalls = 6;
313+
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(expectedCalls);
314+
});
260315
});

0 commit comments

Comments
 (0)