Skip to content
This repository was archived by the owner on Aug 6, 2025. It is now read-only.

Commit 08bb923

Browse files
authored
[DOP-3638]: Transpile API handlers from TS to JS (#800)
* [DOP-3638]: Update tsconfig.json to include api directory and add shell script * [DOP-3638]: Finish shell script to archive lambdas * [DOP-3638]: Finish shell script to archive lambdas * [DOP-3638]: Update typing and runtime validations for dochub * [DOP-3638]: Update typing for PrepGithubPushPayload * [DOP-3638]: Remove empty if statement * [DOP-3638]: Resolve typescript errors * [DOP-3638]: Remove unnecessary dependency * [DOP-3638]: Add additional data validation and update more types * [DOP-3638]: Add additional data validation and update more types * [DOP-3638]: Additional refactor * [DOP-3638]: Refactor error handling to be more consistent * [DOP-3638]: Return object instead of throwing an error for the api handler * [DOP-3638]: Correct error message * [DOP-3638]: Correct error message * [DOP-3638]: Move check for event.body * [DOP-3638]: Return instead of throw when validating dochub env vars * [DOP-3638]: Fix isNumber logic * [DOP-3638]: remove overly complicated typeguard * [DOP-3638]: Remove unused code * [DOP-3638]: Remove unused connection
1 parent 377a7b3 commit 08bb923

File tree

12 files changed

+18679
-8481
lines changed

12 files changed

+18679
-8481
lines changed

api/controllers/v1/dochub.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,48 @@
11
import { FastlyConnector } from '../../../src/services/cdn';
22
import { ConsoleLogger } from '../../../src/services/logger';
33
import { CDNCreds } from '../../../src/entities/creds';
4+
import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda';
5+
6+
export const UpsertEdgeDictionaryItem = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => {
7+
if (!event.body) {
8+
return {
9+
statusCode: 400,
10+
body: 'event.body is null',
11+
};
12+
}
413

5-
export const UpsertEdgeDictionaryItem = async (event: any = {}): Promise<any> => {
614
const body = JSON.parse(event.body);
15+
716
const pair = {
817
key: body.source,
918
value: body.target,
1019
};
11-
// TODO: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
12-
// Above applies for all 'process.env' variables, they should be validated
13-
const creds = new CDNCreds(process.env.FASTLY_DOCHUB_SERVICE_ID, process.env.FASTLY_DOCHUB_TOKEN);
14-
await new FastlyConnector(new ConsoleLogger()).upsertEdgeDictionaryItem(pair, process.env.FASTLY_DOCHUB_MAP, creds);
20+
21+
const { FASTLY_DOCHUB_SERVICE_ID, FASTLY_DOCHUB_TOKEN, FASTLY_DOCHUB_MAP } = process.env;
22+
23+
if (!FASTLY_DOCHUB_SERVICE_ID) {
24+
return {
25+
statusCode: 500,
26+
body: 'Fastly dochub service ID is not defined',
27+
};
28+
}
29+
30+
if (!FASTLY_DOCHUB_TOKEN) {
31+
return {
32+
statusCode: 500,
33+
body: 'Fastly dochub token is not defined',
34+
};
35+
}
36+
37+
if (!FASTLY_DOCHUB_MAP) {
38+
return {
39+
statusCode: 500,
40+
body: 'Fastly dochub map is not defined',
41+
};
42+
}
43+
44+
const creds = new CDNCreds(FASTLY_DOCHUB_SERVICE_ID, FASTLY_DOCHUB_TOKEN);
45+
await new FastlyConnector(new ConsoleLogger()).upsertEdgeDictionaryItem(pair, FASTLY_DOCHUB_MAP, creds);
1546
return {
1647
statusCode: 202,
1748
headers: { 'Content-Type': 'text/plain' },

api/controllers/v1/github.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
import * as c from 'config';
22
import * as crypto from 'crypto';
33
import * as mongodb from 'mongodb';
4+
import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda';
5+
46
import { JobRepository } from '../../../src/repositories/jobRepository';
57
import { ConsoleLogger } from '../../../src/services/logger';
68
import { BranchRepository } from '../../../src/repositories/branchRepository';
9+
import { Job, JobStatus } from '../../../src/entities/job';
10+
import { PushEvent } from '@octokit/webhooks-types';
711

812
// This function will validate your payload from GitHub
913
// See docs at https://developer.github.com/webhooks/securing/#validating-payloads-from-github
10-
function validateJsonWebhook(request: any, secret: string) {
11-
const expectedSignature = 'sha256=' + crypto.createHmac('sha256', secret).update(request.body).digest('hex');
14+
function validateJsonWebhook(request: APIGatewayEvent, secret: string) {
15+
const expectedSignature =
16+
'sha256=' +
17+
crypto
18+
.createHmac('sha256', secret)
19+
.update(request.body ?? '')
20+
.digest('hex');
1221
const signature = request.headers['X-Hub-Signature-256'];
1322
if (signature !== expectedSignature) {
1423
return false;
1524
}
1625
return true;
1726
}
1827

19-
async function prepGithubPushPayload(githubEvent: any, branchRepository: BranchRepository, prefix: string) {
28+
async function prepGithubPushPayload(
29+
githubEvent: PushEvent,
30+
branchRepository: BranchRepository,
31+
prefix: string
32+
): Promise<Omit<Job, '_id'>> {
2033
const branch_name = githubEvent.ref.split('/')[2];
2134
const branch_info = await branchRepository.getRepoBranchAliases(githubEvent.repository.name, branch_name);
2235
const urlSlug = branch_info.aliasObject?.urlSlug ?? branch_name;
@@ -27,7 +40,7 @@ async function prepGithubPushPayload(githubEvent: any, branchRepository: BranchR
2740
title: githubEvent.repository.full_name,
2841
user: githubEvent.pusher.name,
2942
email: githubEvent.pusher.email,
30-
status: 'inQueue',
43+
status: JobStatus.inQueue,
3144
createdTime: new Date(),
3245
startTime: null,
3346
endTime: null,
@@ -42,7 +55,6 @@ async function prepGithubPushPayload(githubEvent: any, branchRepository: BranchR
4255
branchName: githubEvent.ref.split('/')[2],
4356
isFork: githubEvent.repository.fork,
4457
private: githubEvent.repository.private,
45-
isXlarge: true,
4658
repoOwner: githubEvent.repository.owner.login,
4759
url: githubEvent.repository.clone_url,
4860
newHead: githubEvent.after,
@@ -54,14 +66,24 @@ async function prepGithubPushPayload(githubEvent: any, branchRepository: BranchR
5466
};
5567
}
5668

57-
export const TriggerBuild = async (event: any = {}, context: any = {}): Promise<any> => {
69+
export const TriggerBuild = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => {
5870
const client = new mongodb.MongoClient(c.get('dbUrl'));
5971
await client.connect();
6072
const db = client.db(c.get('dbName'));
6173
const consoleLogger = new ConsoleLogger();
6274
const jobRepository = new JobRepository(db, c, consoleLogger);
6375
const branchRepository = new BranchRepository(db, c, consoleLogger);
64-
const sig = event.headers['X-Hub-Signature-256'];
76+
77+
if (!event.body) {
78+
const err = 'Trigger build does not have a body in event payload';
79+
consoleLogger.error('TriggerBuildError', err);
80+
return {
81+
statusCode: 400,
82+
headers: { 'Content-Type': 'text/plain' },
83+
body: err,
84+
};
85+
}
86+
6587
if (!validateJsonWebhook(event, c.get<string>('githubSecret'))) {
6688
const errMsg = "X-Hub-Signature incorrect. Github webhook token doesn't match";
6789
return {
@@ -70,6 +92,7 @@ export const TriggerBuild = async (event: any = {}, context: any = {}): Promise<
7092
body: errMsg,
7193
};
7294
}
95+
7396
const body = JSON.parse(event.body);
7497

7598
if (body.deleted) {
@@ -83,8 +106,9 @@ export const TriggerBuild = async (event: any = {}, context: any = {}): Promise<
83106
const env = c.get<string>('env');
84107
const repoInfo = await branchRepository.getRepo(body.repository.name);
85108
const jobPrefix = repoInfo?.prefix ? repoInfo['prefix'][env] : '';
86-
// TODO: Make job be of type Job
109+
87110
const job = await prepGithubPushPayload(body, branchRepository, jobPrefix);
111+
88112
try {
89113
consoleLogger.info(job.title, 'Creating Job');
90114
const jobId = await jobRepository.insertJob(job, c.get('jobsQueueUrl'));

api/controllers/v1/jobs.ts

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ import { Job, JobStatus } from '../../../src/entities/job';
1111
import { ECSContainer } from '../../../src/services/containerServices';
1212
import { SQSConnector } from '../../../src/services/queue';
1313
import { Batch } from '../../../src/services/batch';
14+
import { APIGatewayEvent, APIGatewayProxyResult, SQSEvent, SQSRecord } from 'aws-lambda';
1415

15-
export const TriggerLocalBuild = async (event: any = {}, context: any = {}): Promise<any> => {
16-
const client = new mongodb.MongoClient(c.get('dbUrl'));
17-
await client.connect();
18-
const db = client.db(c.get('dbName'));
16+
export const TriggerLocalBuild = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => {
1917
const consoleLogger = new ConsoleLogger();
2018
const sqs = new SQSConnector(consoleLogger, c);
19+
20+
if (!event.body) {
21+
const err = 'Trigger local build does not have a body in event payload';
22+
consoleLogger.error('TriggerLocalBuildError', err);
23+
return {
24+
statusCode: 400,
25+
headers: { 'Content-Type': 'text/plain' },
26+
body: err,
27+
};
28+
}
29+
2130
const body = JSON.parse(event.body);
2231
try {
2332
consoleLogger.info(body.jobId, 'enqueuing Job');
@@ -38,26 +47,23 @@ export const TriggerLocalBuild = async (event: any = {}, context: any = {}): Pro
3847
}
3948
};
4049

41-
// TODO: use @types/aws-lambda
42-
export const HandleJobs = async (event: any = {}): Promise<any> => {
50+
export const HandleJobs = async (event: SQSEvent): Promise<void> => {
4351
/**
4452
* Check the status of the incoming jobs
4553
* if it is inqueue start a task
4654
* if it is inprogress call NotifyBuildProgress
4755
* if it is completed call NotifyBuildSummary
4856
*/
49-
const messages: JobQueueMessage[] = event.Records;
57+
const messages = event.Records;
5058
await Promise.all(
51-
messages.map(async (message: any) => {
59+
messages.map(async (message: SQSRecord) => {
5260
const consoleLogger = new ConsoleLogger();
5361
const body = JSON.parse(message.body);
54-
let queueUrl = '';
5562
const jobId = body['jobId'];
5663
const jobStatus = body['jobStatus'];
5764
try {
5865
switch (jobStatus) {
5966
case JobStatus[JobStatus.inQueue]:
60-
queueUrl = c.get('jobsQueueUrl');
6167
await NotifyBuildProgress(jobId);
6268
// start the task , don't start the process before processing the notification
6369
const ecsServices = new ECSContainer(c, consoleLogger);
@@ -68,7 +74,6 @@ export const HandleJobs = async (event: any = {}): Promise<any> => {
6874
consoleLogger.info(jobId, JSON.stringify(res));
6975
break;
7076
case JobStatus[JobStatus.inProgress]:
71-
queueUrl = c.get('jobUpdatesQueueUrl');
7277
await NotifyBuildProgress(jobId);
7378
break;
7479
case JobStatus[JobStatus.timedOut]:
@@ -80,9 +85,7 @@ export const HandleJobs = async (event: any = {}): Promise<any> => {
8085
break;
8186
case JobStatus[JobStatus.failed]:
8287
case JobStatus[JobStatus.completed]:
83-
queueUrl = c.get('jobUpdatesQueueUrl');
84-
await NotifyBuildSummary(jobId);
85-
await SubmitArchiveJob(jobId);
88+
await Promise.all([NotifyBuildSummary(jobId), SubmitArchiveJob(jobId)]);
8689
break;
8790
default:
8891
consoleLogger.error(jobId, 'Invalid status');
@@ -134,42 +137,35 @@ async function stopECSTask(taskId: string, consoleLogger: ConsoleLogger) {
134137
await ecs.stopZombieECSTask(taskId);
135138
}
136139

137-
async function retry(message: JobQueueMessage, consoleLogger: ConsoleLogger, url: string): Promise<any> {
138-
try {
139-
const tries = message.tries;
140-
// TODO: c.get('maxRetries') is of type 'Unknown', needs validation
141-
if (tries < c.get('maxRetries')) {
142-
const sqs = new SQSConnector(consoleLogger, c);
143-
message['tries'] += 1;
144-
let retryDelay = 10;
145-
if (c.get('retryDelay')) {
146-
retryDelay = c.get('retryDelay');
147-
}
148-
await sqs.sendMessage(message, url, retryDelay * tries);
149-
}
150-
} catch (err) {
151-
consoleLogger.error(message['jobId'], err);
152-
}
153-
}
154-
async function NotifyBuildSummary(jobId: string): Promise<any> {
140+
async function NotifyBuildSummary(jobId: string): Promise<void> {
155141
const consoleLogger = new ConsoleLogger();
156142
const client = new mongodb.MongoClient(c.get('dbUrl'));
157143
await client.connect();
158144
const db = client.db(c.get('dbName'));
159145
const env = c.get<string>('env');
160146

161147
const jobRepository = new JobRepository(db, c, consoleLogger);
162-
// TODO: Make fullDocument be of type Job, validate existence
163148
const fullDocument = await jobRepository.getJobById(jobId);
149+
150+
if (!fullDocument) {
151+
consoleLogger.error(
152+
`NotifyBuildSummary_${jobId}`,
153+
`Notify build summary failed. Job does not exist for Job ID: ${jobId}`
154+
);
155+
return;
156+
}
157+
164158
const repoName = fullDocument.payload.repoName;
165159
const username = fullDocument.user;
166160
const slackConnector = new SlackConnector(consoleLogger, c);
167161
const repoEntitlementRepository = new RepoEntitlementsRepository(db, c, consoleLogger);
168162
const entitlement = await repoEntitlementRepository.getSlackUserIdByGithubUsername(username);
163+
169164
if (!entitlement?.['slack_user_id']) {
170165
consoleLogger.error(username, 'Entitlement failed');
171166
return;
172167
}
168+
173169
await slackConnector.sendMessage(
174170
await prepSummaryMessage(
175171
env,
@@ -181,9 +177,6 @@ async function NotifyBuildSummary(jobId: string): Promise<any> {
181177
),
182178
entitlement['slack_user_id']
183179
);
184-
return {
185-
statusCode: 200,
186-
};
187180
}
188181

189182
export const extractUrlFromMessage = (fullDocument): string[] => {
@@ -201,12 +194,11 @@ async function prepSummaryMessage(
201194
failed = false
202195
): Promise<string> {
203196
const urls = extractUrlFromMessage(fullDocument);
204-
let mms_urls = [null, null];
197+
let mms_urls: Array<string | null> = [null, null];
205198
// mms-docs needs special handling as it builds two sites (cloudmanager & ops manager)
206199
// so we need to extract both URLs
207200
if (repoName === 'mms-docs') {
208201
if (urls.length >= 2) {
209-
// TODO: Type 'string[]' is not assignable to type 'null[]'.
210202
mms_urls = urls.slice(-2);
211203
}
212204
}
@@ -257,15 +249,24 @@ function prepProgressMessage(
257249
}
258250
}
259251

260-
async function NotifyBuildProgress(jobId: string): Promise<any> {
252+
async function NotifyBuildProgress(jobId: string): Promise<void> {
261253
const consoleLogger = new ConsoleLogger();
262254
const client = new mongodb.MongoClient(c.get('dbUrl'));
263255
await client.connect();
264256
const db = client.db(c.get('dbName'));
265257
const slackConnector = new SlackConnector(consoleLogger, c);
266258
const jobRepository = new JobRepository(db, c, consoleLogger);
267-
// TODO: Make fullDocument be of type Job, validate existence
259+
268260
const fullDocument = await jobRepository.getJobById(jobId);
261+
262+
if (!fullDocument) {
263+
consoleLogger.error(
264+
`NotifyBuildProgress_${jobId}`,
265+
`Notify build progress failed. Job does not exist for Job ID: ${jobId}`
266+
);
267+
return;
268+
}
269+
269270
const jobTitle = fullDocument.title;
270271
const username = fullDocument.user;
271272
const repoEntitlementRepository = new RepoEntitlementsRepository(db, c, consoleLogger);
@@ -274,7 +275,8 @@ async function NotifyBuildProgress(jobId: string): Promise<any> {
274275
consoleLogger.error(username, 'Entitlement Failed');
275276
return;
276277
}
277-
const resp = await slackConnector.sendMessage(
278+
279+
await slackConnector.sendMessage(
278280
prepProgressMessage(
279281
c.get('dashboardUrl'),
280282
jobId,
@@ -284,9 +286,6 @@ async function NotifyBuildProgress(jobId: string): Promise<any> {
284286
),
285287
entitlement['slack_user_id']
286288
);
287-
return {
288-
statusCode: 200,
289-
};
290289
}
291290

292291
function getMongoClient(config: IConfig): mongodb.MongoClient {
@@ -317,6 +316,15 @@ async function SubmitArchiveJob(jobId: string) {
317316
branches: new BranchRepository(db, c, consoleLogger),
318317
};
319318
const job = await models.jobs.getJobById(jobId);
319+
320+
if (!job) {
321+
consoleLogger.error(
322+
`SubmitArchiveJob_${jobId}`,
323+
`Submit archive job failed. Job does not exist for Job ID: ${jobId}`
324+
);
325+
return;
326+
}
327+
320328
const repo = await models.branches.getRepo(job.payload.repoName);
321329

322330
/* NOTE

api/controllers/v1/slack.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ function createPayload(
245245
urlSlug,
246246
isFork: true,
247247
private: repoOwner === '10gen',
248-
isXlarge: true,
249248
repoOwner,
250249
url: 'https://github.com/' + repoOwner + '/' + repoName,
251250
newHead,

0 commit comments

Comments
 (0)