-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DOP-4414: Lambda function to handle automated test deploys #1011
Changes from 219 commits
4db21c2
278012d
51c62aa
b68c870
6539150
8cdff97
a26d20d
c432cdb
4f784e2
269718b
7d6edd6
f9d4616
63b183f
295f27f
8d09650
807be13
2671250
01dc7b8
e60b482
b73845d
c44d6bf
9b9b25d
9d6d0d5
e9dd2a1
47eee25
aa1904e
c85b210
1298971
b76af20
46cf92e
aba0f43
dcebb4f
c6809ff
f43dd05
e80cdab
c187a36
c088704
e886331
9acfb29
93a360a
ca3814c
0bf36a5
1ce574b
c51a0e0
a43b0db
285f88e
b420a80
14280a6
8844c5a
dfa6854
81461c2
3a6c918
c9a40ea
d603529
e650b15
26f648b
0fb91a1
33f91cf
d895253
b1c8c27
64e1c34
0a83774
bddde4c
e3f4151
ba4dbae
666debf
97f61fd
b1a0423
c81dce1
b4bca05
783dd3b
ad1bd54
a574352
622f914
65572bb
23cab1f
81ff14f
452bd57
ca67083
e1c7d11
8dd1e0c
521092b
ea6eb17
3864344
61eb93e
62d9ee3
7d55d9d
3cddb7f
5dc7ff3
8100244
49e106a
33c1d31
20ab10f
221187a
56e63cf
099bbbe
16a4057
ac84af3
c3c32aa
0c7b269
c15f5bb
39f729d
8e378d2
ab21345
d3a7632
cdc2a80
49262cc
4eb40e0
4c27e3a
debac8f
fbb6987
0dd6ecc
8280c1b
84768ee
a324aa9
d1aa508
c3f01ff
51bc791
7035d96
bed862c
304cc33
511d6d7
f509707
98a2e69
2b2c108
0811ee2
f09d8e1
4a37c8d
68bd960
bb47d60
48722fe
8edf348
48e8e72
6237463
a2f7f1f
489efa1
7a9b1ae
cc36292
fd61bfc
bc14ddf
3bc7982
484d6a1
d5aa217
69a0086
25f19b8
50a685a
a0088eb
60ddb03
41787bf
7799369
c11c3ba
10bf719
db82c8d
635025b
98c4a15
aee5cd4
bce69c3
c86c466
c4ac837
2c3693a
242cf10
40a1194
6ccdc00
c2b1f57
abdd426
1a79d7b
273da47
ca9bb9f
dccda66
d953ccd
c0fe254
b28d519
61b2c33
47d0600
654f59b
3fefba1
025a872
2797e71
b82af1a
70c67bd
73ce79e
2563973
bfb75a9
b48eff1
b0fcb25
98be689
7a85e8b
aaf71e9
d0071fd
2c71ab2
7a62fc3
cde1bdd
88def81
e6a2906
2f6b1a0
ea685a6
7d1a06f
8e39c0b
0838c01
73875bf
770ffcc
2da0106
3bf25c6
a164201
7fddfaa
9867c1a
64c4d9a
b1a415d
5b79c9f
3632a74
2be4d3e
ae39269
0203e96
cc64860
2f42c08
71bbf83
7e19595
cfb3f13
cc3a702
d38b616
d65eb0c
a935da1
245cb6a
a7c5833
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
on: | ||
push: | ||
paths: ['src/**', 'cdk-infra/lib/constructs/worker/**', 'Dockerfile', 'modules/**'] | ||
# paths: ['src/**', 'cdk-infra/lib/constructs/worker/**', 'Dockerfile', 'modules/**'] | ||
branches: | ||
- 'main' | ||
- 'integration' | ||
|
||
- 'DOP-4414' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. idk if this is purely for testing purposes, but marking to see if we need revert this back |
||
concurrency: | ||
group: environment-stg-enhanced-worker-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
@@ -25,6 +25,7 @@ jobs: | |
aws-region: us-east-2 | ||
- name: Deploy Enhanced Infrastructure | ||
run: | | ||
npm ci | ||
cd cdk-infra/ | ||
npm ci | ||
npm run deploy:feature:stack -- -c env=dotcomstg -c customFeatureName=enhancedApp-dotcomstg auto-builder-stack-enhancedApp-dotcomstg-worker | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,125 @@ | ||
import * as c from 'config'; | ||
import * as mongodb from 'mongodb'; | ||
import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda'; | ||
import { PushEvent } from '@octokit/webhooks-types'; | ||
import { PushEvent, WorkflowRunCompletedEvent } from '@octokit/webhooks-types'; | ||
|
||
import { JobRepository } from '../../../src/repositories/jobRepository'; | ||
import { ConsoleLogger } from '../../../src/services/logger'; | ||
import { RepoBranchesRepository } from '../../../src/repositories/repoBranchesRepository'; | ||
import { EnhancedJob, JobStatus } from '../../../src/entities/job'; | ||
import { ProjectsRepository } from '../../../src/repositories/projectsRepository'; | ||
import { EnhancedJob, EnhancedPayload, JobStatus } from '../../../src/entities/job'; | ||
import { markBuildArtifactsForDeletion, validateJsonWebhook } from '../../handlers/github'; | ||
import { DocsetsRepository } from '../../../src/repositories/docsetsRepository'; | ||
import { getMonorepoPaths } from '../../../src/monorepo'; | ||
import { getUpdatedFilePaths } from '../../../src/monorepo/utils/path-utils'; | ||
import { ReposBranchesDocsetsDocument } from '../../../modules/persistence/src/services/metadata/repos_branches'; | ||
import { MONOREPO_NAME } from '../../../src/monorepo/utils/monorepo-constants'; | ||
|
||
const SMOKETEST_SITES = [ | ||
'docs-landing', | ||
'cloud-docs', | ||
'docs-realm', | ||
'docs', | ||
'docs-atlas-cli', | ||
'docs-node', | ||
'docs-app-services', | ||
]; | ||
|
||
//EnhancedPayload and EnhancedJob are used here for both githubPush (feature branch) events as well as productionDeploy(smoke test deploy) events for typing purposes | ||
async function prepGithubPushPayload( | ||
githubEvent: PushEvent, | ||
repoBranchesRepository: RepoBranchesRepository, | ||
prefix: string, | ||
repoInfo: ReposBranchesDocsetsDocument, | ||
directory?: string | ||
githubEvent: PushEvent | WorkflowRunCompletedEvent, | ||
payload: EnhancedPayload, | ||
title: string | ||
): Promise<Omit<EnhancedJob, '_id'>> { | ||
const branch_name = githubEvent.ref.split('/')[2]; | ||
const branch_info = await repoBranchesRepository.getRepoBranchAliases( | ||
githubEvent.repository.name, | ||
branch_name, | ||
repoInfo.project | ||
); | ||
const urlSlug = branch_info.aliasObject?.urlSlug ?? branch_name; | ||
const project = repoInfo?.project ?? githubEvent.repository.name; | ||
|
||
return { | ||
title: githubEvent.repository.full_name, | ||
user: githubEvent.pusher.name, | ||
email: githubEvent.pusher.email ?? '', | ||
title: title, | ||
user: githubEvent.sender.login, | ||
status: JobStatus.inQueue, | ||
createdTime: new Date(), | ||
startTime: null, | ||
endTime: null, | ||
priority: 1, | ||
error: {}, | ||
result: null, | ||
payload: { | ||
jobType: 'githubPush', | ||
source: 'github', | ||
action: 'push', | ||
repoName: githubEvent.repository.name, | ||
branchName: githubEvent.ref.split('/')[2], | ||
isFork: githubEvent.repository.fork, | ||
repoOwner: githubEvent.repository.owner.login, | ||
url: githubEvent.repository.clone_url, | ||
newHead: githubEvent.after, | ||
urlSlug: urlSlug, | ||
prefix: prefix, | ||
project: project, | ||
directory: directory, | ||
}, | ||
payload, | ||
logs: [], | ||
}; | ||
} | ||
|
||
export const TriggerBuild = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => { | ||
interface CreatePayloadProps { | ||
repoName: string; | ||
isSmokeTestDeploy?: boolean; | ||
prefix: string; | ||
repoBranchesRepository: RepoBranchesRepository; | ||
repoInfo: ReposBranchesDocsetsDocument; | ||
newHead?: string; | ||
repoOwner?: string; | ||
githubEvent?: PushEvent; | ||
directory?: string; | ||
} | ||
|
||
async function createPayload({ | ||
repoName, | ||
isSmokeTestDeploy = false, | ||
prefix, | ||
repoBranchesRepository, | ||
repoInfo, | ||
newHead, | ||
repoOwner = '', | ||
githubEvent, | ||
directory, | ||
}: CreatePayloadProps): Promise<EnhancedPayload> { | ||
const source = 'github'; | ||
const project = repoInfo?.project ?? repoName; | ||
|
||
let branchName: string; | ||
let jobType: string; | ||
let action: string; | ||
let url: string; | ||
|
||
if (isSmokeTestDeploy) { | ||
url = 'https://github.com/' + repoOwner + '/' + repoName; | ||
action = 'automatedTest'; | ||
jobType = 'productionDeploy'; | ||
branchName = 'master'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not blocking (I don't think): I believe we've instituted a way within the parser to deal with a content repo's master branch to be named either "master" or "main". I wonder if we need to find a way to be agnostic here as well... once again, this is maybe a different ticket. To my knowledge all content repos use "master"... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, thank you. I did consider this, but I checked at least the specific content repos that Allison requested always be deployed for smoke tests, and those at least all use "master" as the master branch. If, at a later date, someone, for some reason, adds a repo to the list that uses main, they will have to add additional logic to handle that then. However, I agree that I don't believe it should be blocking right now for that hypothetical There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed! |
||
} else { | ||
if (!githubEvent) { | ||
throw new Error(`Non SmokeTest Deploy jobs must have a github Event`); | ||
} | ||
action = 'push'; | ||
jobType = 'githubPush'; | ||
branchName = githubEvent.ref.split('/')[2]; | ||
url = githubEvent.repository?.clone_url; | ||
newHead = githubEvent.after; | ||
repoOwner = githubEvent.repository?.owner?.login; | ||
} | ||
|
||
const branchInfo = await repoBranchesRepository.getRepoBranchAliases(repoName, branchName, repoInfo.project); | ||
const urlSlug = branchInfo.aliasObject?.urlSlug ?? branchName; | ||
|
||
return { | ||
jobType, | ||
source, | ||
action, | ||
repoName, | ||
repoOwner, | ||
branchName, | ||
project, | ||
prefix, | ||
urlSlug, | ||
url, | ||
newHead, | ||
directory, | ||
}; | ||
} | ||
|
||
export const triggerSmokeTestAutomatedBuild = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult | null> => { | ||
const client = new mongodb.MongoClient(c.get('dbUrl')); | ||
await client.connect(); | ||
const db = client.db(c.get('dbName')); | ||
const consoleLogger = new ConsoleLogger(); | ||
const jobRepository = new JobRepository(db, c, consoleLogger); | ||
const projectsRepository = new ProjectsRepository(client.db(process.env.METADATA_DB_NAME), c, consoleLogger); | ||
const repoBranchesRepository = new RepoBranchesRepository(db, c, consoleLogger); | ||
const docsetsRepository = new DocsetsRepository(db, c, consoleLogger); | ||
|
||
|
@@ -79,6 +133,132 @@ export const TriggerBuild = async (event: APIGatewayEvent): Promise<APIGatewayPr | |
}; | ||
} | ||
|
||
if (!validateJsonWebhook(event, c.get<string>('githubSecret'))) { | ||
const errMsg = "X-Hub-Signature incorrect. Github webhook token doesn't match"; | ||
return { | ||
statusCode: 401, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: errMsg, | ||
}; | ||
} | ||
|
||
let body: WorkflowRunCompletedEvent; | ||
try { | ||
body = JSON.parse(event.body) as WorkflowRunCompletedEvent; | ||
} catch (e) { | ||
console.log('[TriggerBuild]: ERROR! Could not parse event.body', e); | ||
return { | ||
statusCode: 502, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: ' ERROR! Could not parse event.body', | ||
}; | ||
} | ||
|
||
if (body.workflow_run.conclusion != 'success') | ||
return { | ||
statusCode: 202, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: `Build on branch ${body.workflow_run.head_branch} is not complete and will not trigger smoke test site deployments`, | ||
}; | ||
|
||
if (body.workflow_run.name != 'Deploy Staging ECS') | ||
return { | ||
statusCode: 202, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: `Workflow ${body.workflow_run.name} completed successfully but only Deploy Staging ECS workflow completion will trigger smoke test site deployments`, | ||
}; | ||
|
||
// if the build was not building main branch, no need for smoke test sites | ||
// if (body.workflow_run.head_branch != 'main' || body.repository.fork) { | ||
// console.log('Build was not on master branch in main repo, sites will not deploy as no smoke tests are needed'); | ||
// return { | ||
// statusCode: 202, | ||
// headers: { 'Content-Type': 'text/plain' }, | ||
// body: `Build on branch ${body.workflow_run.head_branch} will not trigger site deployments as it was not on main branch in upstream repo`, | ||
// }; | ||
// } | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove? Or maybe you're actively working on this. |
||
//automated test builds will always deploy in dotcomstg | ||
const env = 'dotcomstg'; | ||
|
||
async function createAndInsertJob() { | ||
return await Promise.all( | ||
SMOKETEST_SITES.map(async (repoName): Promise<string> => { | ||
const jobTitle = `Smoke Test ${repoName} site for commit ${body.workflow_run.head_sha} on docs-worker-pool main branch`; | ||
let repoInfo, projectEntry, repoOwner; | ||
try { | ||
repoInfo = await docsetsRepository.getRepo(repoName); | ||
projectEntry = await projectsRepository.getProjectEntry(repoInfo.project); | ||
repoOwner = projectEntry.github.organization; | ||
} catch (err) { | ||
consoleLogger.error( | ||
`Atlas Repo Information Error`, | ||
`RepoInfo, projectEntry, or repoOwner not found for docs site ${repoName}. RepoInfo: ${repoInfo}, projectEntry: ${projectEntry}, repoOwner: ${repoOwner}` | ||
); | ||
return err; | ||
} | ||
|
||
const jobPrefix = repoInfo?.prefix ? repoInfo['prefix'][env] : ''; | ||
const payload = await createPayload({ | ||
repoName, | ||
isSmokeTestDeploy: true, | ||
prefix: jobPrefix, | ||
repoBranchesRepository, | ||
repoInfo, | ||
repoOwner, | ||
}); | ||
|
||
//add logic for getting master branch, latest stable branch | ||
const job = await prepGithubPushPayload(body, payload, jobTitle); | ||
|
||
try { | ||
consoleLogger.info(job.title, 'Creating Job'); | ||
const jobId = await jobRepository.insertJob(job, c.get('jobsQueueUrl')); | ||
jobRepository.notify(jobId, c.get('jobUpdatesQueueUrl'), JobStatus.inQueue, 0); | ||
consoleLogger.info(job.title, `Created Job ${jobId}`); | ||
return jobId; | ||
} catch (err) { | ||
consoleLogger.error('TriggerBuildError', `${err} Error inserting job for ${repoName}`); | ||
return err; | ||
} | ||
}) | ||
); | ||
} | ||
|
||
try { | ||
const returnVal = await createAndInsertJob(); | ||
return { | ||
statusCode: 202, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: 'Smoke Test Jobs Queued with the following Job Ids ' + returnVal, | ||
}; | ||
} catch (err) { | ||
return { | ||
statusCode: 500, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: err, | ||
}; | ||
} | ||
}; | ||
|
||
export const TriggerBuild = async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => { | ||
const client = new mongodb.MongoClient(c.get('dbUrl')); | ||
await client.connect(); | ||
const db = client.db(c.get('dbName')); | ||
const consoleLogger = new ConsoleLogger(); | ||
const jobRepository = new JobRepository(db, c, consoleLogger); | ||
const repoBranchesRepository = new RepoBranchesRepository(db, c, consoleLogger); | ||
const docsetsRepository = new DocsetsRepository(db, c, consoleLogger); | ||
|
||
if (!event.body) { | ||
const err = 'Trigger build does not have a body in event payload'; | ||
return { | ||
statusCode: 400, | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: err, | ||
}; | ||
} | ||
|
||
if (!validateJsonWebhook(event, c.get<string>('githubSecret'))) { | ||
const errMsg = "X-Hub-Signature incorrect. Github webhook token doesn't match"; | ||
return { | ||
|
@@ -110,9 +290,19 @@ export const TriggerBuild = async (event: APIGatewayEvent): Promise<APIGatewayPr | |
const env = c.get<string>('env'); | ||
|
||
async function createAndInsertJob(path?: string) { | ||
const repoInfo = await docsetsRepository.getRepo(body.repository.name, path); | ||
const repo = body.repository; | ||
const repoInfo = await docsetsRepository.getRepo(repo.name, path); | ||
const jobPrefix = repoInfo?.prefix ? repoInfo['prefix'][env] : ''; | ||
const job = await prepGithubPushPayload(body, repoBranchesRepository, jobPrefix, repoInfo, path); | ||
const jobTitle = repo.full_name; | ||
const payload = await createPayload({ | ||
repoName: repo.name, | ||
prefix: jobPrefix, | ||
repoBranchesRepository, | ||
repoInfo, | ||
githubEvent: body, | ||
}); | ||
|
||
const job = await prepGithubPushPayload(body, payload, jobTitle); | ||
|
||
consoleLogger.info(job.title, 'Creating Job'); | ||
const jobId = await jobRepository.insertJob(job, c.get('jobsQueueUrl')); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: Marking this as a reminder to remove before merging