diff --git a/index.js b/index.js index b6179c6a8070e192547ef7aef14188c9c1f0a3fb..e9ca732accb4166e81c4652d178f37814c7a0ec9 100644 --- a/index.js +++ b/index.js @@ -2,80 +2,5 @@ // Note: actual env vars supersede .env file and .env file supersedes .env.defaults file require('dotenv-defaults').config() -// Ignore paths for logging, metrics and docs -const ignorePaths = ['/ready', '/healthy'] - -// Fast, minimalist web framework for node. -const express = require('express') - -// Helmet helps you secure your Express app by setting various HTTP headers -const helmet = require('helmet') - -// Very low overhead Node.js logger. Logs in json use pino-pretty for dev. -const logger = require('pino')() - -// Fastest HTTP logger for Node.js in town -const httpLogger = require('pino-http')({ logger, autoLogging: { ignorePaths } }) - -// Readiness and liveness checks middleware -const health = require('@cloudnative/health-connect') -const healthCheck = new health.HealthChecker() - -// Prometheus middleware for standard api metrics -const apiMetrics = require('prometheus-api-metrics') - -// Swagger UI for api-docs -const swaggerUi = require('swagger-ui-express') -const yaml = require('js-yaml') -const fs = require('fs') -const swaggerDocument = yaml.load(fs.readFileSync('./swagger.yaml', 'utf8')) - -// The app returned by express() is in fact a JavaScript Function, designed to be passed to Node’s HTTP servers as a callback to handle requests. -const app = express() - -const fetch = require('node-fetch') - -// Application-level middleware -app.use(httpLogger) -app.use(helmet()) -app.use('/healthy', health.LivenessEndpoint(healthCheck)) -app.use('/ready', health.ReadinessEndpoint(healthCheck)) -app.use(apiMetrics({ excludeRoutes: ignorePaths })) -app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)) -app.use('/swagger.json', (req, res) => res.json(swaggerDocument)) - -const urls = yaml.load(fs.readFileSync('./config/manifests/urls.yaml', 'utf8')).manifests - -let manifestCache = [] -let lastCached - -const getManifests = async () => { - if (+new Date() < lastCached + 30000) return - const results = urls.map(url => fetch(url).then(result => { - if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`) - return result.json().catch(err => { throw new Error(`Failed to load manifest for url ${result.url}: ${err}`) }) - })) - manifestCache = (await Promise.all(results)).flat() - lastCached = +new Date() -} - -getManifests() - -app.get('/api/manifest.json', async (req, res, next) => { - try { - getManifests() - res.json(manifestCache || []) - } catch (err) { - next(err) - } -}) - -app.use(function (err, req, res, next) { - logger.error(err) - res.status(500).end() -}) - -// Binds and listens for connections on the specified host and port -app.listen(process.env.PORT, () => { - logger.info(`manifest-service listening on port ${process.env.PORT}`) -}) +const createApp = require('./src/server') +createApp() diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000000000000000000000000000000000000..a85d9813138b3e2b510c35bb55b8cbb494a83ae6 --- /dev/null +++ b/src/server.js @@ -0,0 +1,91 @@ +// Ignore paths for logging, metrics and docs +const ignorePaths = ['/ready', '/healthy'] + +// Fast, minimalist web framework for node. +const express = require('express') + +// Helmet helps you secure your Express app by setting various HTTP headers +const helmet = require('helmet') + +// Very low overhead Node.js logger. Logs in json use pino-pretty for dev. +const logger = require('pino')() + +// Fastest HTTP logger for Node.js in town +const httpLogger = require('pino-http')({ logger, autoLogging: { ignorePaths } }) + +// Readiness and liveness checks middleware +const health = require('@cloudnative/health-connect') + +// Prometheus middleware for standard api metrics +const apiMetrics = require('prometheus-api-metrics') + +// Swagger UI for api-docs +const swaggerUi = require('swagger-ui-express') +const yaml = require('js-yaml') +const fs = require('fs') +const fetch = require('node-fetch') +const swaggerDocument = yaml.load(fs.readFileSync('./swagger.yaml', 'utf8')) + +// The app returned by express() is in fact a JavaScript Function, designed to be passed to Node’s HTTP servers as a callback to handle requests. + +module.exports = () => { + const app = express() + + const healthCheck = new health.HealthChecker() + + // Application-level middleware + app.use(httpLogger) + app.use(helmet()) + app.use('/healthy', health.LivenessEndpoint(healthCheck)) + app.use('/ready', health.ReadinessEndpoint(healthCheck)) + app.use(apiMetrics({ excludeRoutes: ignorePaths })) + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)) + app.use('/swagger.json', (req, res) => res.json(swaggerDocument)) + + const isReady = async () => { + await fetchManifest() + if (manifestCache.length === 0) throw new Error('No manifests found.') + } + + const readinessCheck = new health.ReadinessCheck('readinessCheck', isReady) + healthCheck.registerReadinessCheck(readinessCheck) + + const livenessCheck = new health.LivenessCheck('livenessCheck', isReady) + healthCheck.registerLivenessCheck(livenessCheck) + + const urls = yaml.load(fs.readFileSync('./config/manifests/urls.yaml', 'utf8')).manifests + + let manifestCache = [] + let lastCached + + const fetchManifest = async () => { + if (+new Date() < lastCached + 30000) return + const results = urls.map(url => fetch(url).then(result => { + if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`) + return result.json().catch(err => { throw new Error(`Failed to load manifest for url ${result.url}: ${err}`) }) + })) + manifestCache = (await Promise.all(results)).flat() + lastCached = +new Date() + } + + app.get('/api/manifest.json', async (req, res, next) => { + try { + await fetchManifest() + res.json(manifestCache || []) + } catch (err) { + next(err) + } + }) + + app.use(function (err, req, res, next) { + logger.error(err) + res.status(500).end() + }) + + // Binds and listens for connections on the specified host and port + app.listen(process.env.PORT, () => { + logger.info(`manifest-service listening on port ${process.env.PORT}`) + }) + + return app +}