diff --git a/Dockerfile b/Dockerfile index e6e9b90..9c3e57b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,15 @@ USER node COPY --chown=node:node . . RUN npm install +ARG GIT_SHA=dev +ARG RUN_ID=unknown # Get the current HSTS list RUN npm run updateHsts +RUN env + +ENV RUN_ID=${RUN_ID} +ENV GIT_SHA=${GIT_SHA} ENV NODE_EXTRA_CA_CERTS=node_modules/extra_certs/ca_bundle/ca_intermediate_bundle.pem EXPOSE 8080 CMD [ "node", "src/api/index.js" ] diff --git a/src/api/server.js b/src/api/server.js index 93969a5..6a14c9f 100644 --- a/src/api/server.js +++ b/src/api/server.js @@ -9,6 +9,7 @@ import analyzeApiV2 from "./v2/analyze/index.js"; import scanApiV2 from "./v2/scan/index.js"; import statsApiV2 from "./v2/stats/index.js"; import recommendationMatrixApiV2 from "./v2/recommendations/index.js"; +import version from "./version/index.js"; import globalErrorHandler from "./global-error-handler.js"; import pool from "@fastify/postgres"; import { poolOptions } from "../database/repository.js"; @@ -76,6 +77,7 @@ export async function createServer() { server.register(scanApiV2, { prefix: "/api/v2" }), server.register(statsApiV2, { prefix: "/api/v2" }), server.register(recommendationMatrixApiV2, { prefix: "/api/v2" }), + server.register(version, { prefix: "/api/v2" }), ]); ["SIGINT", "SIGTERM"].forEach((signal) => { diff --git a/src/api/v2/schemas.js b/src/api/v2/schemas.js index ccd8b90..832a0f0 100644 --- a/src/api/v2/schemas.js +++ b/src/api/v2/schemas.js @@ -100,6 +100,17 @@ const gradeDistributionResponse = { }, }; +const versionResponse = { + type: "object", + properties: { + commit: { type: "string" }, + version: { type: "string" }, + source: { type: "string" }, + build: { type: "string" }, + }, + required: ["commit", "version", "source", "build"], +}; + const recommendationMatrixResponse = { type: "array", items: { @@ -200,6 +211,12 @@ export const SCHEMAS = { 200: recommendationMatrixResponse, }, }, + + version: { + response: { + 200: versionResponse, + }, + }, }; /** @@ -318,4 +335,4 @@ export class PolicyResponse { } // import jsdoc from "json-schema-to-jsdoc"; -// console.log(jsdoc(SCHEMAS.gradeDistribution.response["200"])); +// console.log(jsdoc(SCHEMAS.version.response["200"])); diff --git a/src/api/version/index.js b/src/api/version/index.js new file mode 100644 index 0000000..e240429 --- /dev/null +++ b/src/api/version/index.js @@ -0,0 +1,40 @@ +import { version } from "tough-cookie"; +import { SCHEMAS } from "../v2/schemas.js"; +import fs from "node:fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +// Get the directory name of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const packageJson = JSON.parse( + fs.readFileSync( + path.join(__dirname, "..", "..", "..", "package.json"), + "utf8" + ) +); + +/** + * Register the API - default export + * @param {import('fastify').FastifyInstance} fastify + * @returns {Promise} + */ +export default async function (fastify) { + const pool = fastify.pg.pool; + + fastify.get( + "/version", + { schema: SCHEMAS.version }, + async (request, reply) => { + /** @type {import("../../types.js").VersionResponse} */ + const ret = { + version: packageJson.version, + commit: process.env.GIT_SHA || "unknown", + source: "https://github.com/mdn/mdn-http-observatory", + build: process.env.RUN_ID || "unknown", + }; + return ret; + } + ); +} diff --git a/src/types.js b/src/types.js index 31a93bb..090db15 100644 --- a/src/types.js +++ b/src/types.js @@ -360,3 +360,11 @@ export class Policy { * @property {string} grade * @property {number} count */ + +/** + * @typedef {object} VersionResponse + * @property {string} commit + * @property {string} version + * @property {string} source + * @property {string} build + */ diff --git a/test/apiv2.test.js b/test/apiv2.test.js index 05df4be..22efc3b 100644 --- a/test/apiv2.test.js +++ b/test/apiv2.test.js @@ -8,6 +8,7 @@ import { } from "../src/database/repository.js"; import { EventEmitter } from "events"; import { NUM_TESTS } from "../src/constants.js"; +import fs from "node:fs"; const pool = createPool(); EventEmitter.defaultMaxListeners = 20; @@ -25,6 +26,25 @@ describeOrSkip("API V2", function () { await migrateDatabase("max", pool); }); + it("serves the version path", async function () { + process.env.RUN_ID = "buildinfo"; + process.env.GIT_SHA = "commitinfo"; + const app = await createServer(); + const response = await app.inject({ + method: "GET", + url: "/api/v2/version", + }); + assert.equal(response.statusCode, 200); + const j = response.json(); + const p = JSON.parse(fs.readFileSync("package.json", "utf8")); + assert.deepEqual(j, { + version: p.version, + commit: "commitinfo", + build: "buildinfo", + source: "https://github.com/mdn/mdn-http-observatory", + }); + }); + it("serves the root path with a greeting", async function () { const app = await createServer(); const response = await app.inject({