From 60b98d2554cf1f7e89b4b3bf7457f17326140dbc Mon Sep 17 00:00:00 2001 From: Zach Shilton <4624598+zchsh@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:13:56 -0400 Subject: [PATCH] refactor to fetch with latest sha not ref --- ...get-latest-content-sha-for-product.test.ts | 33 ++++++++++++ build-libs/__tests__/redirects.test.ts | 3 ++ .../get-latest-content-ref-for-product.js | 23 -------- .../get-latest-content-sha-for-product.js | 53 +++++++++++++++++++ build-libs/redirects.js | 48 ++++++++++------- 5 files changed, 117 insertions(+), 43 deletions(-) create mode 100644 build-libs/__tests__/get-latest-content-sha-for-product.test.ts delete mode 100644 build-libs/get-latest-content-ref-for-product.js create mode 100644 build-libs/get-latest-content-sha-for-product.js diff --git a/build-libs/__tests__/get-latest-content-sha-for-product.test.ts b/build-libs/__tests__/get-latest-content-sha-for-product.test.ts new file mode 100644 index 0000000000..c759a3bacc --- /dev/null +++ b/build-libs/__tests__/get-latest-content-sha-for-product.test.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import getLatestContentShaForProduct from '../get-latest-content-sha-for-product' +import fetchGithubFile from '@build-libs/fetch-github-file' +import { PRODUCT_REDIRECT_ENTRIES } from '@build-libs/redirects' + +describe('getLatestContentShaForProduct', () => { + PRODUCT_REDIRECT_ENTRIES.forEach(({ repo, path }) => { + it(`fetches the latest SHA for the "${repo}" repo`, async () => { + const latestSha = await getLatestContentShaForProduct(repo) + expect(typeof latestSha).toBe('string') + }) + + if (['hcp-docs', 'sentinel', 'ptfe-releases'].includes(repo)) { + console.log(`Skipping test for "${repo}" repo, as it's a private repo.`) + } else { + it(`fetches the latest SHA for the "${repo}" repo, then validates the SHA by fetching redirects`, async () => { + const latestSha = await getLatestContentShaForProduct(repo) + expect(typeof latestSha).toBe('string') + const redirectsFileString = await fetchGithubFile({ + owner: 'hashicorp', + repo: repo, + path: path, + ref: latestSha, + }) + expect(typeof redirectsFileString).toBe('string') + }) + } + }) +}) diff --git a/build-libs/__tests__/redirects.test.ts b/build-libs/__tests__/redirects.test.ts index f242a44c97..17cc2f3a1a 100644 --- a/build-libs/__tests__/redirects.test.ts +++ b/build-libs/__tests__/redirects.test.ts @@ -4,8 +4,11 @@ */ import { + /* @ts-expect-error Module does export this function, in CommonJS */ splitRedirectsByType, + /* @ts-expect-error Module does export this function, in CommonJS */ groupSimpleRedirects, + /* @ts-expect-error Module does export this function, in CommonJS */ filterInvalidRedirects, } from '../redirects' diff --git a/build-libs/get-latest-content-ref-for-product.js b/build-libs/get-latest-content-ref-for-product.js deleted file mode 100644 index 2f84d76132..0000000000 --- a/build-libs/get-latest-content-ref-for-product.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Fetch the latest ref from the content API to ensure the redirects are accurate. - * - * TODO: refactor to fetch from known nav-data, rather than from version - * metadata. Intent being to allow upstream changes to version metadata such - * that version metadata `ref` will only be updated when version metadata is - * actually updated (as opposed to on every content upload, as is currently - * the case). - * - * @param {string} product - * @returns {Promise} - */ -async function getLatestContentRefForProduct(product) { - const contentUrl = new URL('https://content.hashicorp.com') - contentUrl.pathname = `/api/content/${product}/version-metadata/latest` - const latestRef = await fetch(contentUrl.toString()) - .then((resp) => resp.json()) - .then((json) => json.result.ref) - - return latestRef -} - -module.exports = getLatestContentRefForProduct diff --git a/build-libs/get-latest-content-sha-for-product.js b/build-libs/get-latest-content-sha-for-product.js new file mode 100644 index 0000000000..6ea83fe127 --- /dev/null +++ b/build-libs/get-latest-content-sha-for-product.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +/** + * We're using the docs endpoints to fetch the latest SHA, so we use + * the env var for the docs API. + */ +const MKTG_CONTENT_DOCS_API = process.env.MKTG_CONTENT_DOCS_API + +/** + * A map of all possible `product` slugs to known content API endpoints that + * will return an object with a `ref` property that accurately reflects the + * ref from which the latest content was uploaded. + */ +const KNOWN_LATEST_REF_ENDPOINTS = { + boundary: '/api/content/boundary/nav-data/latest/docs', + nomad: '/api/content/nomad/nav-data/latest/docs', + vault: '/api/content/vault/nav-data/latest/docs', + vagrant: '/api/content/vagrant/nav-data/latest/docs', + packer: '/api/content/packer/nav-data/latest/docs', + consul: '/api/content/consul/nav-data/latest/docs', + 'terraform-docs-common': + '/api/content/terraform-docs-common/nav-data/latest/docs', + 'hcp-docs': '/api/content/hcp-docs/nav-data/latest/docs', + 'ptfe-releases': '/api/content/ptfe-releases/nav-data/latest/enterprise', + sentinel: '/api/content/sentinel/nav-data/latest/sentinel', +} + +/** + * Fetch the latest sha from the content API for a given product. + * This relies on known `nav-data` endpoints for each product. + * + * @param {string} product + * @returns {Promise} + */ +async function getLatestContentShaForProduct(product) { + const contentUrl = new URL(MKTG_CONTENT_DOCS_API) + const knownEndpoint = KNOWN_LATEST_REF_ENDPOINTS[product] + if (!knownEndpoint) { + throw new Error( + `getLatestContentShaForProduct failed, with unknown product: ${product}. Please add a known endpoint for this product to KNOWN_LATEST_REF_ENDPOINTS.` + ) + } + contentUrl.pathname = knownEndpoint + const latestSha = await fetch(contentUrl.toString()) + .then((resp) => resp.json()) + .then((json) => json.result.sha) + return latestSha +} + +module.exports = getLatestContentShaForProduct diff --git a/build-libs/redirects.js b/build-libs/redirects.js index 09a151502a..0aa47613de 100644 --- a/build-libs/redirects.js +++ b/build-libs/redirects.js @@ -10,7 +10,7 @@ const path = require('path') const { isDeployPreview } = require('../src/lib/env-checks') const fetchGithubFile = require('./fetch-github-file') -const getLatestContentRefForProduct = require('./get-latest-content-ref-for-product') +const getLatestContentShaForProduct = require('./get-latest-content-sha-for-product') const { getTutorialRedirects } = require('./tutorial-redirects') const { getDocsDotHashiCorpRedirects, @@ -49,13 +49,10 @@ const HOSTNAME_MAP = { * redirects and return early with an empty array. * * @param {string} repoName The name of the repo, owner is always `hashicorp`. - * @param {string?} redirectsPath Optional, custom path to the redirects file. + * @param {string} redirectsPath Path within the repo to the redirects file. * @returns {Promise} */ -async function getRedirectsFromContentRepo( - repoName, - redirectsPath = 'website/redirects.js' -) { +async function getRedirectsFromContentRepo(repoName, redirectsPath) { /** * Note: These constants are declared for clarity in build context intent. */ @@ -68,12 +65,12 @@ async function getRedirectsFromContentRepo( let redirectsFileString if (isDeveloperBuild) { // For `hashicorp/dev-portal` builds, load redirects remotely - const latestContentRef = await getLatestContentRefForProduct(repoName) + const latestContentSha = await getLatestContentShaForProduct(repoName) redirectsFileString = await fetchGithubFile({ owner: 'hashicorp', repo: repoName, path: redirectsPath, - ref: latestContentRef, + ref: latestContentSha, }) } else if (isLocalContentBuild) { // Load redirects from the filesystem, so that authors can see their changes @@ -92,6 +89,24 @@ async function getRedirectsFromContentRepo( return validRedirects } +/** + * @type {{ repo: string, path: string}[]} An array of redirect + * entries. Each entry specifies a repo and the path within that repo to the + * redirects file. + */ +export const PRODUCT_REDIRECT_ENTRIES = [ + { repo: 'boundary', path: 'website/redirects.js' }, + { repo: 'nomad', path: 'website/redirects.js' }, + { repo: 'vault', path: 'website/redirects.js' }, + { repo: 'vagrant', path: 'website/redirects.js' }, + { repo: 'packer', path: 'website/redirects.js' }, + { repo: 'consul', path: 'website/redirects.js' }, + { repo: 'terraform-docs-common', path: 'website/redirects.js' }, + { repo: 'hcp-docs', path: '/redirects.js' }, + { repo: 'ptfe-releases', path: 'website/redirects.js' }, + { repo: 'sentinel', path: 'website/redirects.js' }, +] + async function buildProductRedirects() { // Fetch author-oriented redirects from product repos, // and merge those with dev-oriented redirects from @@ -101,18 +116,11 @@ async function buildProductRedirects() { } const productRedirects = ( - await Promise.all([ - getRedirectsFromContentRepo('boundary'), - getRedirectsFromContentRepo('nomad'), - getRedirectsFromContentRepo('vault'), - getRedirectsFromContentRepo('vagrant'), - getRedirectsFromContentRepo('packer'), - getRedirectsFromContentRepo('consul'), - getRedirectsFromContentRepo('terraform-docs-common'), - getRedirectsFromContentRepo('hcp-docs', '/redirects.js'), - getRedirectsFromContentRepo('ptfe-releases'), - getRedirectsFromContentRepo('sentinel'), - ]) + await Promise.all( + PRODUCT_REDIRECT_ENTRIES.map((entry) => + getRedirectsFromContentRepo(entry.repo, entry.path) + ) + ) ).flat() return productRedirects