-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathbuilder-server.js
More file actions
73 lines (66 loc) · 3.24 KB
/
Copy pathbuilder-server.js
File metadata and controls
73 lines (66 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
'use strict'
const express = require('express')
const config = require('./config.json')
const { createBuilderService } = require('./app/builder-service')
const { asyncRoute, observedRoute, requireBearer } = require('./app/ops/http')
const { createInternalOpsRouter } = require('./app/ops/internal-router')
const PUBLIC_ERRORS = {
ARCHIVE_ERROR: 'Source archive extraction failed',
DOWNLOADER_ERROR: 'Downloader request failed',
DOWNLOADER_TIMEOUT: 'Downloader request timed out',
INVALID_BUILD: 'Build did not produce the requested file',
INVALID_COMMIT: 'Commit must be a canonical 40-character SHA',
INVALID_MODE: 'Unknown build mode',
INVALID_OPTIONS: 'Options must be an object',
INVALID_PATH: 'Path must be a normalized relative output path',
QUEUE_FULL: 'Build capacity is unavailable',
SOURCE_INCOMPLETE: 'Source archive is incomplete'
}
function createApp (options = {}) {
const token = options.token === undefined ? process.env.INTERNAL_SERVICE_TOKEN : options.token
if (!token) throw new Error('INTERNAL_SERVICE_TOKEN is required')
const app = express()
const service = options.service || createBuilderService(options)
app.locals.service = service
app.get('/health', (req, res) => res.json({ status: 'ok' }))
app.use('/v1', (req, res, next) => {
try {
requireBearer(req, token)
} catch {
return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Unauthorized' } })
}
next()
})
if (typeof service.snapshot === 'function' && service.cacheManager) {
app.use(createInternalOpsRouter({ token, service: 'builder', snapshot: service.snapshot, cache: service.cacheManager }))
}
app.use(express.json())
app.post('/v1/build', observedRoute(service, '/v1/build', req => ({ commit: req.body && req.body.commit, buildMode: req.body && req.body.mode }), async (req, res) => {
const result = await service.build(req.body, { correlationId: req.correlationId })
res.set('X-Built-With', result.builtWith)
res.set('X-Build-Path', result.path)
result.stream.on('error', error => res.destroy(error))
result.stream.pipe(res)
}))
app.post('/v1/cleanup', asyncRoute(async (req, res) => {
res.json({ removed: await service.cleanup(Boolean(req.body && req.body.force)) })
}))
app.use((error, req, res, next) => {
if (res.headersSent) return next(error)
if (error.retryAfter) res.set('Retry-After', error.retryAfter)
if (error.rateLimitRemaining) res.set('X-GitHub-RateLimit-Remaining', error.rateLimitRemaining)
if (error.rateLimitReset) res.set('X-GitHub-RateLimit-Reset', error.rateLimitReset)
const code = Object.hasOwn(PUBLIC_ERRORS, error.code) ? error.code : 'INTERNAL_ERROR'
res.status(code === 'INTERNAL_ERROR' ? 500 : error.status || 500).json({ error: { code, message: PUBLIC_ERRORS[code] || 'An internal error occurred' } })
})
return app
}
function start () {
const app = createApp()
const interval = Number(process.env.BUILDER_CLEAN_INTERVAL || config.builderCleanInterval || 2 * 60 * 1000)
const timer = setInterval(() => app.locals.service.cleanup().catch(console.error), interval)
timer.unref()
return app.listen(Number(process.env.BUILDER_PORT || config.builderPort || 8082))
}
if (require.main === module) start()
module.exports = { createApp, start }