Skip to content
This repository was archived by the owner on Apr 18, 2020. It is now read-only.

Commit 0e31476

Browse files
committed
Refactored Lambda functions to TypeScript
1 parent fe26798 commit 0e31476

File tree

7 files changed

+802
-0
lines changed

7 files changed

+802
-0
lines changed

share/lambda/src/status.ts

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
* Copyright (c) 2017 Martin Donath <martin.donath@squidfunk.com>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to
6+
* deal in the Software without restriction, including without limitation the
7+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8+
* sell copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20+
* IN THE SOFTWARE.
21+
*/
22+
23+
import * as GitHub from "github"
24+
25+
import { Callback, Context } from "aws-lambda"
26+
import { S3 } from "aws-sdk"
27+
28+
import errored from "../assets/errored.svg"
29+
import failing from "../assets/failing.svg"
30+
import passing from "../assets/passing.svg"
31+
32+
/* ----------------------------------------------------------------------------
33+
* Types
34+
* ------------------------------------------------------------------------- */
35+
36+
/**
37+
* CodeBuild phase type
38+
*/
39+
export type CodeBuildPhaseType =
40+
"SUBMITTED" | /* Build submitted */
41+
"PROVISIONING" | /* Provisioning instance */
42+
"DOWNLOAD_SOURCE" | /* Checkout source repository */
43+
"INSTALL" | /* Build phase: install */
44+
"PRE_BUILD" | /* Build phase: pre-build */
45+
"BUILD" | /* Build phase: build */
46+
"POST_BUILD" | /* Build phase: post-build */
47+
"UPLOAD_ARTIFACTS" | /* Upload build artifacts */
48+
"FINALIZING" | /* Finalize build */
49+
"COMPLETED" /* Build completed */
50+
51+
/**
52+
* CodeBuild phase status
53+
*/
54+
export type CodeBuildPhaseStatus =
55+
"TIMED_OUT" | /* Build timed out */
56+
"STOPPED" | /* Build stopped */
57+
"FAILED" | /* Build failed */
58+
"SUCCEEDED" | /* Build succeeded */
59+
"FAULT" | /* Build system error */
60+
"CLIENT_ERROR" /* Client error */
61+
62+
/**
63+
* GitHub build status
64+
*/
65+
export type GitHubBuildStatus =
66+
"pending" | /* Build running */
67+
"success" | /* Build successful */
68+
"failure" | /* Build failed */
69+
"error" /* Build system error */
70+
71+
/**
72+
* GitHub build status to badge mapping
73+
*/
74+
export type GitHubBadgeMapping = {
75+
[type in GitHubBuildStatus]?: any
76+
}
77+
78+
/**
79+
* CodeBuild phase type and status to GitHub build status mapping
80+
*/
81+
export type CodeBuildGitHubMapping = {
82+
[type in CodeBuildPhaseType]?: {
83+
[status in CodeBuildPhaseStatus]?: [GitHubBuildStatus, string]
84+
}
85+
}
86+
87+
/* ------------------------------------------------------------------------- */
88+
89+
/**
90+
* CodeBuild phase change event
91+
*/
92+
export interface CodeBuildPhaseChange {
93+
"source": [
94+
"aws.codebuild"
95+
],
96+
"detail-type": [
97+
"CodeBuild Build Phase Change"
98+
],
99+
"detail": {
100+
"build-id": string
101+
"additional-information": {
102+
"environment": {
103+
"environment-variables": Array<{
104+
"name": string
105+
"value": string
106+
}>
107+
}
108+
"source-version": string
109+
"source": {
110+
"location": string
111+
},
112+
"phases": Array<{
113+
"phase-type": CodeBuildPhaseType
114+
"phase-status": CodeBuildPhaseStatus
115+
}>
116+
},
117+
"completed-phase": CodeBuildPhaseType
118+
"completed-phase-status": CodeBuildPhaseStatus
119+
}
120+
}
121+
122+
/* ----------------------------------------------------------------------------
123+
* Constants
124+
* ------------------------------------------------------------------------- */
125+
126+
/**
127+
* Map CodeBuild phase type and status to GitHub build status
128+
*/
129+
const mapping: CodeBuildGitHubMapping = {
130+
SUBMITTED: {
131+
SUCCEEDED: ["pending", "Provisioning"]
132+
},
133+
INSTALL: {
134+
FAILED: ["error", "Provisioning failed"],
135+
SUCCEEDED: ["pending", "Build running"]
136+
},
137+
BUILD: {
138+
FAILED: ["failure", "Build failed"],
139+
FAULT: ["error", "Build errored"],
140+
STOPPED: ["error", "Build stopped"],
141+
TIMED_OUT: ["error", "Build timed out"]
142+
}
143+
}
144+
145+
/**
146+
* GitHub build status to badge mapping
147+
*/
148+
const badges: GitHubBadgeMapping = {
149+
success: passing,
150+
failure: failing,
151+
error: errored
152+
}
153+
154+
/* ----------------------------------------------------------------------------
155+
* Variables
156+
* ------------------------------------------------------------------------- */
157+
158+
/**
159+
* S3 client
160+
*/
161+
const s3 = new S3({ apiVersion: "2006-03-01" })
162+
163+
/**
164+
* GitHub client
165+
*/
166+
const github = new GitHub()
167+
if (process.env.GITHUB_OAUTH_TOKEN)
168+
github.authenticate({
169+
type: "oauth",
170+
token: process.env.GITHUB_OAUTH_TOKEN
171+
})
172+
173+
/* ----------------------------------------------------------------------------
174+
* Functions
175+
* ------------------------------------------------------------------------- */
176+
177+
/**
178+
* Update commit SHA with pipeline state
179+
*
180+
* @param event - CodeBuild phase change event
181+
* @param context - Context
182+
* @param cb - Completion callback
183+
*/
184+
export default (event: CodeBuildPhaseChange, _: Context, cb: Callback) => {
185+
const info = event.detail["additional-information"]
186+
187+
/* Retrieve commit SHA, owner and repository */
188+
const sha = info["source-version"]
189+
const [, owner, repo]: string[] = /github.com\/([^/]+)\/([^/.]+)/
190+
.exec(info.source.location) || []
191+
192+
/* Resolve phase and state mapping */
193+
const phase = mapping[event.detail["completed-phase"]] || {}
194+
let [state, description] =
195+
phase[event.detail["completed-phase-status"]] || [undefined, undefined]
196+
197+
/* Mark build successful in finalizing phase if no errors occured */
198+
if (event.detail["completed-phase"] === "FINALIZING")
199+
if (!info.phases.find(prev => {
200+
return prev["phase-type"] !== "COMPLETED" &&
201+
prev["phase-status"] !== "SUCCEEDED"
202+
}))
203+
[state, description] = ["success", "Build successful"]
204+
205+
/* Resolve build reference and run URL */
206+
const ref = info.environment["environment-variables"][0].value
207+
const run = event.detail["build-id"].split(":").pop()
208+
const url = `https://console.aws.amazon.com/codebuild/home?region=${
209+
process.env.AWS_REGION}#/builds/${repo}:${run}/view/new`
210+
211+
/* Report current state and phase description, if any */
212+
if (state && description) {
213+
github.repos.createStatus({
214+
owner, repo, sha, state, description,
215+
target_url: url, // eslint-disable-line camelcase
216+
context: process.env.GITHUB_REPORTER
217+
})
218+
219+
/* Update status badge on S3 */
220+
.then(() => {
221+
return new Promise((resolve, reject) => {
222+
if (ref === "master" && badges[state!]) {
223+
s3.putObject({
224+
Bucket: process.env.CODEBUILD_BUCKET!,
225+
Key: `${repo}/status.svg`,
226+
Body: badges[state!],
227+
ACL: "public-read",
228+
CacheControl: "no-cache, no-store, must-revalidate",
229+
ContentType: "image/svg+xml"
230+
}, err => {
231+
return err
232+
? reject(err)
233+
: resolve()
234+
})
235+
} else {
236+
resolve()
237+
}
238+
})
239+
})
240+
241+
/* The event was processed */
242+
.then(data => cb(undefined, data))
243+
244+
/* An error occurred */
245+
.catch(cb)
246+
}
247+
}

share/lambda/src/webhook.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright (c) 2017 Martin Donath <martin.donath@squidfunk.com>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to
6+
* deal in the Software without restriction, including without limitation the
7+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8+
* sell copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20+
* IN THE SOFTWARE.
21+
*/
22+
23+
import { Callback, Context } from "aws-lambda"
24+
import { CodeBuild, SNS } from "aws-sdk"
25+
import * as GitHub from "github"
26+
27+
/* ----------------------------------------------------------------------------
28+
* Types
29+
* ------------------------------------------------------------------------- */
30+
31+
/**
32+
* GitHub source change
33+
*/
34+
export interface GitHubSourceChange {
35+
repository: {
36+
owner: {
37+
login: string /* Repository owner name */
38+
}
39+
name: string /* Repository name */
40+
}
41+
ref: string /* Commit SHA */
42+
after: string /* Last commit SHA */
43+
pull_request: {
44+
head: {
45+
ref: string /* Pull Request branch name */
46+
sha: string /* Pull Request commit SHA */
47+
}
48+
state: string /* Pull request state */
49+
}
50+
}
51+
52+
/**
53+
* GitHub webhook event
54+
*/
55+
export interface GitHubWebhookEvent {
56+
Records: Array<{
57+
Sns: SNS.PublishInput
58+
}>
59+
}
60+
61+
/* ----------------------------------------------------------------------------
62+
* Variables
63+
* ------------------------------------------------------------------------- */
64+
65+
/**
66+
* Build manager
67+
*/
68+
const codebuild = new CodeBuild({ apiVersion: "2016-10-06" })
69+
70+
/**
71+
* GitHub client
72+
*/
73+
const github = new GitHub()
74+
if (process.env.GITHUB_OAUTH_TOKEN)
75+
github.authenticate({
76+
type: "oauth",
77+
token: process.env.GITHUB_OAUTH_TOKEN
78+
})
79+
80+
/* ----------------------------------------------------------------------------
81+
* Functions
82+
* ------------------------------------------------------------------------- */
83+
84+
/**
85+
* Run build on source change
86+
*
87+
* @param event - GitHub webhook event
88+
* @param context - Context
89+
* @param cb - Completion callback
90+
*/
91+
export default (event: GitHubWebhookEvent, _: Context, cb: Callback) => {
92+
event.Records.reduce((promise, record) => {
93+
const type = record.Sns.MessageAttributes!["X-Github-Event"].StringValue
94+
const message: GitHubSourceChange = JSON.parse(record.Sns.Message)
95+
96+
/* Retrieve commit SHA and reference */
97+
const [sha, ref] = type === "pull_request"
98+
? [message.pull_request.head.sha, message.pull_request.head.ref]
99+
: [message.after, message.ref.replace("refs/heads/", "")]
100+
101+
/* Return promise chain */
102+
return promise.then(() => {
103+
104+
/* Start build for open pull request or master branch */
105+
if (type === "pull_request" && message.pull_request.state !== "closed" ||
106+
type === "push" && ref === "master") {
107+
return new Promise((resolve, reject) => {
108+
codebuild.startBuild({
109+
projectName: message.repository.name,
110+
sourceVersion: sha,
111+
environmentVariablesOverride: [
112+
{
113+
name: "GIT_BRANCH",
114+
value: ref
115+
},
116+
{
117+
name: "GIT_COMMIT",
118+
value: sha
119+
}
120+
]
121+
}, err => {
122+
return err
123+
? reject(err)
124+
: resolve()
125+
})
126+
})
127+
128+
/* Update commit SHA with pipeline state */
129+
.then(() => {
130+
return github.repos.createStatus({
131+
owner: message.repository.owner.login,
132+
repo: message.repository.name,
133+
sha,
134+
state: "pending",
135+
context: process.env.GITHUB_REPORTER,
136+
description: "Waiting for status to be reported"
137+
})
138+
})
139+
} else {
140+
return Promise.resolve() // TODO: ugly...
141+
}
142+
})
143+
}, Promise.resolve())
144+
145+
/* The event was processed */
146+
.then(data => cb(undefined, data))
147+
148+
/* An error occurred */
149+
.catch(cb)
150+
}

0 commit comments

Comments
 (0)