Skip to content

Commit 97f745d

Browse files
authored
fix: Error when beanstalk environment does not exist or is not found (#18)
1 parent d4184a7 commit 97f745d

File tree

3 files changed

+123
-31
lines changed

3 files changed

+123
-31
lines changed

src/deploy.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,9 @@ export async function deployToGroup(props: IDeployToGroupProps) {
128128
});
129129
await deployAppVersionsToGroup(client, props);
130130
} catch (e) {
131+
log.error(chalk.red(e));
131132
if (e instanceof DBError) {
132133
e.errors.forEach((err) => log.error(chalk.red(err)));
133-
} else {
134-
log.error(chalk.red(e));
135134
}
136135
log.error(chalk.red('Deploy to beanstalk group ') + chalk.blue(group.name) + chalk.red(' failed.'));
137136
throw e;

src/helpers/healthiness.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,38 @@ async function getGroupHealth(props: IHealthCheckPropsPrivate): Promise<IBeansta
120120
// For each Application Version
121121
for (const key in beansToDescribe) {
122122
if (beansToDescribe.hasOwnProperty(key)) {
123-
if (!props.force) {
124-
log.info(`DRY RUN: Would have waited for beanstalks in app '${key}' to become healthy.`);
125-
continue;
126-
}
127-
128123
const envs = beansToDescribe[key];
124+
const envNames = envs.map((env) => env.name);
129125
resp = await props.client.send(
130126
new DescribeEnvironmentsCommand({
131127
ApplicationName: key,
132-
EnvironmentNames: envs.map((env) => env.name),
128+
EnvironmentNames: envNames,
133129
}),
134130
);
135131
if (!resp.Environments) {
136132
throw new Error(`Failed to check status for Environments in App '${key}'`);
137133
}
138134

135+
if (resp.Environments.length != envs.length) {
136+
const missing = envNames.filter((env) => {
137+
let found = false;
138+
resp.Environments?.forEach((envDesc) => {
139+
if (env === envDesc.EnvironmentName) {
140+
found = true;
141+
}
142+
});
143+
return !found;
144+
});
145+
throw new Error(
146+
`The following Beanstalk Environments either do not exist or were not found: ${JSON.stringify(missing)}`,
147+
);
148+
}
149+
150+
if (!props.force) {
151+
log.info(`DRY RUN: Would have waited for beanstalks in app '${key}' to become healthy.`);
152+
continue;
153+
}
154+
139155
const partitioned = getEnvironmentsHealth(
140156
resp.Environments,
141157
props.unhealthyStatuses ?? AWS_EB_HEALTH_CHECK_UNHEALTHY_STATES,

test/deploy.test.ts

Lines changed: 100 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
UpdateEnvironmentCommand,
77
} from '@aws-sdk/client-elastic-beanstalk';
88
import { mockClient } from 'aws-sdk-client-mock';
9+
import { LogLevelDesc } from 'loglevel';
910
import {
1011
DBError,
1112
DBCreateApplicationVersionError,
@@ -18,16 +19,26 @@ import {
1819

1920
const ebMock = mockClient(ElasticBeanstalkClient);
2021

21-
const FORCE_DEPLOYMENT = true;
22-
let TEST_BEANSTALK_GROUP: IBeanstalkGroup;
22+
const COMMON_DEPLOY_PROPS = {
23+
force: true,
24+
logLevel: 'SILENT' as LogLevelDesc,
25+
preDeployHealthCheckProps: {
26+
attempts: 1,
27+
timeBetweenAttemptsMs: 0,
28+
},
29+
postDeployHealthCheckProps: {
30+
attempts: 3,
31+
timeBetweenAttemptsMs: 0,
32+
},
33+
};
2334

2435
// Must reset client prior to each test
2536
// https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
2637
beforeEach(() => ebMock.reset());
2738

2839
describe('Deployment to beanstalks in different apps', () => {
2940
// Define a single beanstalk group with two different environment and applications.
30-
TEST_BEANSTALK_GROUP = {
41+
const TEST_BEANSTALK_GROUP: IBeanstalkGroup = {
3142
environments: [
3243
{
3344
app: 'ClickupTestAppOne',
@@ -51,18 +62,9 @@ describe('Deployment to beanstalks in different apps', () => {
5162
region: 'us-west-2',
5263
};
5364

54-
const commonDeployProps: IDeployToGroupProps = {
65+
const deployProps: IDeployToGroupProps = {
66+
...COMMON_DEPLOY_PROPS,
5567
group: TEST_BEANSTALK_GROUP,
56-
force: FORCE_DEPLOYMENT,
57-
logLevel: 'SILENT',
58-
preDeployHealthCheckProps: {
59-
attempts: 1,
60-
timeBetweenAttemptsMs: 0,
61-
},
62-
postDeployHealthCheckProps: {
63-
attempts: 3,
64-
timeBetweenAttemptsMs: 0,
65-
},
6668
};
6769

6870
// Defines mock functions for AWS EB Client, mocking successful deployment
@@ -109,18 +111,19 @@ describe('Deployment to beanstalks in different apps', () => {
109111
});
110112
});
111113

112-
test('with dry run only checks application versions', async () => {
113-
expect(await deployToGroup({ ...commonDeployProps, force: false })).not.toThrowError;
114+
test('with dry run only runs checks', async () => {
115+
expect(await deployToGroup({ ...deployProps, force: false })).not.toThrowError;
114116
const noCalls = 0;
115117
const expectedDescribeAppVersionCalls = 2; // One per unique Application Version
118+
const expectedDescibeEnvironmentsCommandCalls = 4; // Pre and post health check per unique Application
116119
expect(ebMock.commandCalls(CreateApplicationVersionCommand)).toHaveLength(noCalls);
117120
expect(ebMock.commandCalls(DescribeApplicationVersionsCommand)).toHaveLength(expectedDescribeAppVersionCalls);
118-
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(noCalls);
121+
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(expectedDescibeEnvironmentsCommandCalls);
119122
expect(ebMock.commandCalls(UpdateEnvironmentCommand)).toHaveLength(noCalls);
120123
});
121124

122125
test('succeeds when AWS client does', async () => {
123-
expect(await deployToGroup(commonDeployProps)).not.toThrowError;
126+
expect(await deployToGroup(deployProps)).not.toThrowError;
124127
});
125128

126129
test('throws errors when versions already exists', async () => {
@@ -131,7 +134,7 @@ describe('Deployment to beanstalks in different apps', () => {
131134
expect.assertions(3);
132135
const expectedErrCount = 2;
133136
try {
134-
await deployToGroup(commonDeployProps);
137+
await deployToGroup(deployProps);
135138
} catch (e) {
136139
expect(e).toBeInstanceOf(DBError);
137140
const errs = (e as DBError).errors;
@@ -172,7 +175,7 @@ describe('Deployment to beanstalks in different apps', () => {
172175
expect.assertions(5);
173176
const expectedErrCount = 2;
174177
try {
175-
await deployToGroup(commonDeployProps);
178+
await deployToGroup(deployProps);
176179
} catch (e) {
177180
expect(e).toBeInstanceOf(DBError);
178181
const errs = (e as DBError).errors;
@@ -205,7 +208,7 @@ describe('Deployment to beanstalks in different apps', () => {
205208
expect.assertions(2);
206209
const expectedErrs = 1;
207210
try {
208-
await deployToGroup(commonDeployProps);
211+
await deployToGroup(deployProps);
209212
} catch (e) {
210213
expect(e).toBeInstanceOf(DBHealthinessCheckError);
211214
expect((e as DBHealthinessCheckError).errors).toHaveLength(expectedErrs);
@@ -252,7 +255,7 @@ describe('Deployment to beanstalks in different apps', () => {
252255
],
253256
});
254257

255-
expect(await deployToGroup(commonDeployProps)).not.toThrowError;
258+
expect(await deployToGroup(deployProps)).not.toThrowError;
256259
// 2 pre-deploy checks (one per Application), 4 post-deploy checks (one retry)
257260
const expectedCalls = 6;
258261
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(expectedCalls);
@@ -300,7 +303,7 @@ describe('Deployment to beanstalks in different apps', () => {
300303

301304
expect(
302305
await deployToGroup({
303-
...commonDeployProps,
306+
...deployProps,
304307
postDeployHealthCheckProps: {
305308
attempts: 3,
306309
timeBetweenAttemptsMs: 0,
@@ -313,3 +316,77 @@ describe('Deployment to beanstalks in different apps', () => {
313316
expect(ebMock.commandCalls(DescribeEnvironmentsCommand)).toHaveLength(expectedCalls);
314317
});
315318
});
319+
320+
describe('Deployment with a non-existent Beanstalk', () => {
321+
// Define a group where a single Environment does not exist
322+
const TEST_BEANSTALK_GROUP: IBeanstalkGroup = {
323+
environments: ['AnotherTestEnvironment', 'NonExistentEnvironment'].map((env) => {
324+
return {
325+
app: 'AnotherClickUpTestApp',
326+
name: env,
327+
};
328+
}),
329+
versionProps: {
330+
artifact: {
331+
S3Bucket: 'test-bucket-clickup',
332+
S3Key: 'testDir/clickupTestArtifact.zip',
333+
},
334+
label: 'TestLabel',
335+
description: 'Test desc',
336+
errorIfExists: true,
337+
},
338+
name: 'AnotherTestBeanstalkGroup',
339+
region: 'us-west-2',
340+
};
341+
342+
const deployProps: IDeployToGroupProps = {
343+
...COMMON_DEPLOY_PROPS,
344+
group: TEST_BEANSTALK_GROUP,
345+
};
346+
347+
// Defines mock functions for AWS EB Client
348+
beforeEach(() => {
349+
ebMock.on(CreateApplicationVersionCommand).resolves({
350+
$metadata: {
351+
httpStatusCode: 200,
352+
},
353+
});
354+
ebMock.on(DescribeApplicationVersionsCommand).resolves({
355+
ApplicationVersions: [],
356+
});
357+
ebMock
358+
.on(DescribeEnvironmentsCommand, {
359+
ApplicationName: TEST_BEANSTALK_GROUP.environments[0].app,
360+
EnvironmentNames: TEST_BEANSTALK_GROUP.environments.map((env) => env.name),
361+
})
362+
.resolves({
363+
// Only returns Environment that exists
364+
Environments: [
365+
{
366+
EnvironmentName: TEST_BEANSTALK_GROUP.environments[0].name,
367+
HealthStatus: 'Ok',
368+
Status: 'Ready',
369+
VersionLabel: 'OLD_VERSION',
370+
},
371+
],
372+
});
373+
});
374+
375+
test('with dry-run still throws an error', async () => {
376+
try {
377+
await deployToGroup({ ...deployProps, force: false });
378+
} catch (e) {
379+
expect(e).toBeInstanceOf(DBHealthinessCheckError);
380+
expect((e as DBHealthinessCheckError).errors).toHaveLength(1);
381+
}
382+
});
383+
384+
test('throws an error', async () => {
385+
try {
386+
await deployToGroup(deployProps);
387+
} catch (e) {
388+
expect(e).toBeInstanceOf(DBHealthinessCheckError);
389+
expect((e as DBHealthinessCheckError).errors).toHaveLength(1);
390+
}
391+
});
392+
});

0 commit comments

Comments
 (0)