Skip to content

Commit 081ac1e

Browse files
authored
chore(eng-prod): Improved logging output [CLK-134855] (#19)
1 parent 97f745d commit 081ac1e

File tree

4 files changed

+137
-55
lines changed

4 files changed

+137
-55
lines changed

src/deploy.ts

Lines changed: 97 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import chalk from 'chalk';
44
import log from 'loglevel';
55
import { create } from './helpers/create-app-version';
66
import { deploy } from './helpers/deploy-app-version-to-env';
7-
import { DBError } from './helpers/Errors';
7+
import { DBError, DBGroupDeployTriggerError, DBHealthinessCheckError } from './helpers/Errors';
88
import { waitForGroupHealthiness } from './helpers/healthiness';
99
import { IDeployToGroupProps, IHealthCheckProps } from './helpers/Interfaces';
1010

@@ -56,13 +56,56 @@ async function createAppVersionsForGroup(client: ElasticBeanstalkClient, props:
5656
log.info(chalk.green('All needed application versions exist.'));
5757
}
5858

59+
async function preDeployHealthcheck(client: ElasticBeanstalkClient, props: IDeployToGroupProps) {
60+
try {
61+
log.info(chalk.blue('Verifying environments are ready to receive deployment before initiating...'));
62+
await waitForGroupHealthiness({
63+
client,
64+
group: props.group,
65+
force: props.force ?? DEFAULT_FORCE,
66+
checkVersion: false,
67+
...(props.preDeployHealthCheckProps ?? DEFAULT_HEALTH_CHECK_PROPS),
68+
});
69+
} catch (e) {
70+
if (e instanceof DBHealthinessCheckError) {
71+
e.message = `preDeployHealthcheck(): ${e.message}`;
72+
}
73+
throw e;
74+
}
75+
}
76+
77+
async function postDeployHealthcheck(client: ElasticBeanstalkClient, props: IDeployToGroupProps) {
78+
log.info(chalk.blue('Verifying environments successfully receive the deployment...this could take a while.'));
79+
try {
80+
await waitForGroupHealthiness({
81+
client,
82+
group: props.group,
83+
force: props.force ?? DEFAULT_FORCE,
84+
checkVersion: true,
85+
...(props.postDeployHealthCheckProps ?? DEFAULT_HEALTH_CHECK_PROPS),
86+
});
87+
} catch (e) {
88+
if (e instanceof DBHealthinessCheckError) {
89+
e.message = `postDeployHealthcheck(): ${e.message}`;
90+
}
91+
throw e;
92+
}
93+
94+
log.info(
95+
chalk.green('Successfully deployed version ') +
96+
chalk.blue(props.group.versionProps.label) +
97+
chalk.green(' to beanstalk group ') +
98+
chalk.blue(props.group.name),
99+
);
100+
}
101+
59102
/**
60-
* For each Beanstalk Environment in group, deploys the respective Application
61-
* Version and then waits to verify their healthiness.
103+
* For each Beanstalk Environment in the group, deploys the respective
104+
* Application Version.
62105
*/
63106
async function deployAppVersionsToGroup(client: ElasticBeanstalkClient, props: IDeployToGroupProps) {
64107
log.info(`Asynchronously kicking off deployment to the ${props.group.name} group of beanstalks.`);
65-
const triggerErr = new DBError('deployAppVersionsToGroup: ', []);
108+
const triggerErr = new DBGroupDeployTriggerError('deployAppVersionsToGroup: ', []);
66109
const force = props.force ?? DEFAULT_FORCE;
67110
await Promise.all(
68111
props.group.environments.map(async (env) => {
@@ -80,59 +123,70 @@ async function deployAppVersionsToGroup(client: ElasticBeanstalkClient, props: I
80123
}
81124
}),
82125
);
83-
try {
84-
// Verify the group successfully receives the deployment.
85-
await waitForGroupHealthiness({
86-
client,
87-
group: props.group,
88-
force,
89-
checkVersion: true,
90-
...(props.postDeployHealthCheckProps ?? DEFAULT_HEALTH_CHECK_PROPS),
91-
});
92-
log.info(
93-
chalk.green('Successfully deployed version ') +
94-
chalk.blue(props.group.versionProps.label) +
95-
chalk.green(' to beanstalk group ') +
96-
chalk.blue(props.group.name),
97-
);
98-
} catch (e) {
99-
if (triggerErr.errors.length === 0) throw e;
100-
triggerErr.errors.push(e as Error);
101-
throw triggerErr;
102-
}
126+
if (triggerErr.errors.length !== 0) throw triggerErr;
103127
}
104128

105129
/**
106-
* Iterates over a group of Beanstalk Environments, creates Application
107-
* Versions for their respective Beanstalk Applications, and then deploys
108-
* those versions to the Beanstalk Environments and verifies their health.
130+
* Initializes the Beanstalk Client, creates the needed Application Versions,
131+
* and verifies the Beanstalk Environments in the group are ready to receive
132+
* the deployment.
109133
*/
110-
export async function deployToGroup(props: IDeployToGroupProps) {
111-
const group = props.group;
112-
const force = props.force ?? DEFAULT_FORCE;
134+
async function preDeploySteps(props: IDeployToGroupProps): Promise<ElasticBeanstalkClient> {
113135
try {
114-
log.setLevel(props.logLevel ?? log.levels.INFO);
115-
log.info(chalk.green('Beginning deploy process for beanstalk group ') + chalk.blue(group.name));
116136
const client = new ElasticBeanstalkClient({
117137
maxAttempts: AWS_CLIENT_REQUEST_MAX_ATTEMPTS_DEFAULT,
118-
region: group.region,
138+
region: props.group.region,
119139
});
120140
await createAppVersionsForGroup(client, props);
121-
log.info(chalk.blue('Verifying environments are ready to receive deployment before initiating...'));
122-
await waitForGroupHealthiness({
123-
client,
124-
group,
125-
force,
126-
checkVersion: false,
127-
...(props.preDeployHealthCheckProps ?? DEFAULT_HEALTH_CHECK_PROPS),
128-
});
129-
await deployAppVersionsToGroup(client, props);
141+
await preDeployHealthcheck(client, props);
142+
return client;
130143
} catch (e) {
131144
log.error(chalk.red(e));
132145
if (e instanceof DBError) {
133146
e.errors.forEach((err) => log.error(chalk.red(err)));
134147
}
135-
log.error(chalk.red('Deploy to beanstalk group ') + chalk.blue(group.name) + chalk.red(' failed.'));
148+
log.error(chalk.red('Could not trigger deployment to beanstalk group ') + chalk.blue(props.group.name));
136149
throw e;
137150
}
138151
}
152+
153+
/**
154+
* Triggers the deployment of the newly created Application Version to each
155+
* Beanstalk Environment in the group, then verifies they reach a healthy state
156+
* and successfully land the expected version.
157+
*/
158+
async function deploySteps(client: ElasticBeanstalkClient, props: IDeployToGroupProps) {
159+
let deployErrs: DBError = new DBError('deploySteps(): ', []);
160+
try {
161+
await deployAppVersionsToGroup(client, props);
162+
} catch (e) {
163+
// We still want to see status of Beanstalks who did have deploy triggered
164+
deployErrs.errors.push(e as Error);
165+
}
166+
167+
try {
168+
await postDeployHealthcheck(client, props);
169+
} catch (e) {
170+
log.error(chalk.red(e));
171+
if (e instanceof DBError) {
172+
e.errors.forEach((err) => log.error(chalk.red(err)));
173+
}
174+
log.error(chalk.red('Deployment to beanstalk group ') + chalk.blue(props.group.name) + chalk.red(' failed.'));
175+
deployErrs.errors.push(e as Error);
176+
}
177+
178+
if (deployErrs.errors.length !== 0) throw deployErrs;
179+
}
180+
181+
/**
182+
* Iterates over a group of Beanstalk Environments, creates Application
183+
* Versions for their respective Beanstalk Applications, and then deploys
184+
* those versions to the Beanstalk Environments and verifies their health.
185+
*/
186+
export async function deployToGroup(props: IDeployToGroupProps) {
187+
const group = props.group;
188+
log.setLevel(props.logLevel ?? log.levels.INFO);
189+
log.info(chalk.green('Beginning deploy process for beanstalk group ') + chalk.blue(group.name));
190+
191+
await deploySteps(await preDeploySteps(props), props);
192+
}

src/helpers/Errors.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* Houses multiple errors. Since we handle a group of multiple beanstalk
3-
* environments, we want to track errors for each one individually.
3+
* environments, we want to track errors for each one (and each type)
4+
* individually.
45
*/
56
export class DBError extends Error {
67
private readonly _errors: Error[];
@@ -17,6 +18,11 @@ export class DBError extends Error {
1718
return this._errors;
1819
}
1920
}
21+
22+
/**
23+
* Represents a single Application Version creation error, i.e., should be
24+
* thrown when a single Application Version could not be created.
25+
*/
2026
export class DBCreateApplicationVersionError extends Error {
2127
constructor(appName: string, versionLabel: string, error: Error) {
2228
const msg = `Beanstalk app version ${versionLabel} failed creation for app ${appName}. ${error}`;
@@ -27,16 +33,33 @@ export class DBCreateApplicationVersionError extends Error {
2733
}
2834
}
2935

36+
/**
37+
* Represents a single triggered deployment failure, i.e., should be thrown
38+
* when one Beanstalk Environment fails to have a deployment triggered.
39+
*/
3040
export class DBTriggerDeployError extends Error {
3141
constructor(envName: string, versionLabel: string, error: Error) {
32-
const msg = `Deployment of app version ${versionLabel} failed on environment ${envName}. ${error}`;
42+
const msg = `Deployment of app version '${versionLabel}' failed on environment '${envName}'. ${error}`;
3343
super(msg);
3444

3545
// Set the prototype explicitly.
3646
Object.setPrototypeOf(this, DBTriggerDeployError.prototype);
3747
}
3848
}
3949

50+
/**
51+
* Multiple beanstalk environments could fail to have their deploy triggered.
52+
* Hence the extension of DBError.
53+
*/
54+
export class DBGroupDeployTriggerError extends DBError {
55+
constructor(msg: string, errors: Error[]) {
56+
super(msg, errors);
57+
58+
// Set the prototype explicitly.
59+
Object.setPrototypeOf(this, DBGroupDeployTriggerError.prototype);
60+
}
61+
}
62+
4063
/**
4164
* Multiple beanstalk environments could fail to achieve a healthy state.
4265
* Hence the extension of DBError.

src/helpers/deploy-app-version-to-env.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ async function deployApplicationVersion(props: IDeployProps): Promise<void> {
3333
if (statusCode && statusCode >= 200 && statusCode < 300) {
3434
log.info(`Deployment of app version '${props.version.label}' triggered for '${props.env.name}'.`);
3535
} else {
36-
throw new Error(
37-
`Triggered deployment of app version '${props.version.label}' failed for '${
38-
props.env.name
39-
}'. Response metadata: ${JSON.stringify(resp.$metadata, undefined, 2)}`,
40-
);
36+
throw new Error(`Response metadata: ${JSON.stringify(resp.$metadata, undefined, 2)}`);
4137
}
4238
}
4339

test/deploy.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
IBeanstalkGroup,
1616
IDeployToGroupProps,
1717
DBTriggerDeployError,
18+
DBGroupDeployTriggerError,
1819
} from '../src/index';
1920

2021
const ebMock = mockClient(ElasticBeanstalkClient);
@@ -172,15 +173,19 @@ describe('Deployment to beanstalks in different apps', () => {
172173
],
173174
});
174175

175-
expect.assertions(5);
176+
expect.hasAssertions();
176177
const expectedErrCount = 2;
177178
try {
178179
await deployToGroup(deployProps);
179180
} catch (e) {
180181
expect(e).toBeInstanceOf(DBError);
181182
const errs = (e as DBError).errors;
182183
expect(errs).toHaveLength(expectedErrCount);
183-
expect(errs.filter((err) => err instanceof DBTriggerDeployError)).toHaveLength(1);
184+
const triggerFailureErrs = errs.filter(
185+
(err) => err instanceof DBGroupDeployTriggerError,
186+
) as DBGroupDeployTriggerError[];
187+
expect(triggerFailureErrs).toHaveLength(1);
188+
expect(triggerFailureErrs[0].errors.filter((err) => err instanceof DBTriggerDeployError)).toHaveLength(1);
184189
const healthCheckErrs = errs.filter((err) => err instanceof DBHealthinessCheckError);
185190
expect(healthCheckErrs).toHaveLength(1);
186191
// If multiple envs failed, this length would be higher
@@ -205,13 +210,17 @@ describe('Deployment to beanstalks in different apps', () => {
205210
],
206211
});
207212

208-
expect.assertions(2);
213+
expect.hasAssertions();
209214
const expectedErrs = 1;
210215
try {
211216
await deployToGroup(deployProps);
212217
} catch (e) {
213-
expect(e).toBeInstanceOf(DBHealthinessCheckError);
214-
expect((e as DBHealthinessCheckError).errors).toHaveLength(expectedErrs);
218+
expect(e).toBeInstanceOf(DBError);
219+
const healthinessErrs = (e as DBError).errors.filter(
220+
(err) => err instanceof DBHealthinessCheckError,
221+
) as DBHealthinessCheckError[];
222+
expect(healthinessErrs).toHaveLength(expectedErrs);
223+
expect(healthinessErrs[0].errors).toHaveLength(expectedErrs);
215224
}
216225
});
217226

0 commit comments

Comments
 (0)