From 7d4bb748be2bad37af73081e67e6acdf11c5e639 Mon Sep 17 00:00:00 2001 From: Dan Barr <6922515+danbarr@users.noreply.github.com> Date: Wed, 4 Nov 2026 12:56:18 -0500 Subject: [PATCH] Fix Enterprise release reference inputs Resolve Helm chart paths and description overrides from the source release version. Support the gateway layout in v0.26.0 while retaining the chart mapping for older releases. Report all missing Helm inputs before generating reference pages. --- .github/upstream-projects.yaml | 22 ++++++ .../generate-helm-reference.mjs | 66 +++++++++++++++- .../generate-helm-reference.test.mjs | 76 +++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index eaea19bf..0419bfc2 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -147,8 +147,25 @@ projects: - source: enterprise/ai-gateway/docs/operator-guide/management-api.openapi.yaml destination: static/api-specs/ai-gateway-management-api.yaml public_sources: false + helm_version_source: enterprise/stacklok-enterprise-platform/helm/Chart.yaml helm: - source: enterprise/stacklok-enterprise-platform/helm + release_overrides: + - since: v0.26.0 + description_overrides: + workspace.production: >- + Enforce production requirements for Workspace, including external + PostgreSQL and object storage, credentials, and resource limits. + workspace.image.repository: Container image repository for Workspace. + workspace.postgres.enabled: >- + Deploy the development PostgreSQL instance for Workspace. + Requires workspace.production to be false. + workspace.objectStore.enabled: >- + Deploy the development MinIO object store for Workspace. + Requires workspace.production to be false. + workspace.networkPolicy.cloudUI.enabled: >- + Allow console pods to reach Workspace when + workspace.networkPolicy.enabled is true. page: docs/platform/reference/helm/platform.mdx title: Platform Helm values description: Values, types, and defaults for the Stacklok Enterprise platform chart. @@ -209,6 +226,7 @@ projects: - label: Deploy the platform href: ../../enterprise-platform/deployment.mdx - source: enterprise/ai-gateway/helm/operator + before: v0.26.0 page: docs/platform/reference/helm/ai-gateway-operator.mdx title: Legacy AI Gateway operator Helm values description: Values, types, and defaults for the legacy AI Gateway operator chart in Stacklok Enterprise. @@ -219,6 +237,7 @@ projects: - label: Deploy the platform href: ../../enterprise-platform/deployment.mdx - source: enterprise/ai-gateway/helm/operator-crds + before: v0.26.0 page: docs/platform/reference/helm/ai-gateway-operator-crds.mdx title: Legacy AI Gateway operator CRD Helm values description: Values, types, and defaults for the legacy AI Gateway operator CRD chart in Stacklok Enterprise. @@ -239,6 +258,9 @@ projects: - label: Deploy the platform href: ../../enterprise-platform/deployment.mdx - source: enterprise/sprout/helm + release_overrides: + - since: v0.26.0 + source: enterprise/ai-gateway/helm page: docs/platform/reference/helm/ai-gateway.mdx title: AI Gateway Helm values description: Values, types, and defaults for the AI Gateway chart in Stacklok Enterprise. diff --git a/scripts/upstream-release/generate-helm-reference.mjs b/scripts/upstream-release/generate-helm-reference.mjs index debc3890..22747b56 100644 --- a/scripts/upstream-release/generate-helm-reference.mjs +++ b/scripts/upstream-release/generate-helm-reference.mjs @@ -15,6 +15,68 @@ import yaml from 'yaml'; const repoRoot = fileURLToPath(new URL('../../', import.meta.url)); +// Select chart paths from the source release's version, including SHA-based exports. +export function resolveHelmCharts(project, clone) { + const charts = project.helm || []; + if (!project.helm_version_source) return charts; + const metadata = yaml.parse( + fs.readFileSync(path.join(clone, project.helm_version_source), 'utf8') + ); + const version = (value) => { + const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(value); + if (!match) throw new Error(`Invalid Helm release version: ${value}`); + return match.slice(1, 4).map(Number); + }; + const current = version(metadata.version); + const atLeast = (value) => { + const target = version(value); + for (let i = 0; i < 3; i++) { + if (current[i] !== target[i]) return current[i] > target[i]; + } + return true; + }; + return charts + .filter((chart) => !chart.before || !atLeast(chart.before)) + .map((chart) => { + const overrides = (chart.release_overrides || []) + .filter((override) => atLeast(override.since)) + .sort((a, b) => { + const left = version(a.since); + const right = version(b.since); + return left[0] - right[0] || left[1] - right[1] || left[2] - right[2]; + }); + return overrides.reduce( + (resolved, override) => ({ + ...resolved, + ...override, + description_overrides: { + ...resolved.description_overrides, + ...override.description_overrides, + }, + }), + chart + ); + }); +} + +export function validateHelmInputs(charts, clone) { + const missing = charts.flatMap((chart) => + ['Chart.yaml', 'values.yaml'] + .map((file) => path.join(chart.source, file)) + .filter((file) => { + const input = path.join(clone, file); + return ( + !fs.existsSync(input) || + !fs.statSync(input).isFile() || + !fs.statSync(input).size + ); + }) + ); + if (missing.length) { + throw new Error(`Missing release Helm inputs:\n${missing.join('\n')}`); + } +} + // Preserve inline code while escaping MDX expressions and JSX in upstream prose. export function mdxText(value) { return String(value).replace( @@ -274,11 +336,13 @@ function main(argv) { if (!args.clone || (!args.check && !tag)) throw new Error('Helm generation requires --clone and a release tag'); + const charts = resolveHelmCharts(project, args.clone); + validateHelmInputs(charts, args.clone); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-reference-')); try { // Copy only documentation inputs: helm-docs must never overwrite upstream READMEs. // Prepare every page before replacing outputs, so incomplete annotations fail early. - const inputs = project.helm.map((chart, index) => { + const inputs = charts.map((chart, index) => { const dir = path.join(tmp, String(index)); fs.mkdirSync(dir); for (const file of ['Chart.yaml', 'values.yaml']) { diff --git a/scripts/upstream-release/generate-helm-reference.test.mjs b/scripts/upstream-release/generate-helm-reference.test.mjs index c41cf80e..4f4c4732 100644 --- a/scripts/upstream-release/generate-helm-reference.test.mjs +++ b/scripts/upstream-release/generate-helm-reference.test.mjs @@ -15,6 +15,8 @@ import { renderPage, renderDescription, validateDescriptions, + resolveHelmCharts, + validateHelmInputs, } from './generate-helm-reference.mjs'; const chart = { @@ -91,6 +93,80 @@ test('coverage validation reports every missing value across charts', () => { ); }); +test('release chart selection supports legacy and current gateway layouts', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-release-layout-')); + try { + const { projects } = yaml.parse( + fs.readFileSync( + new URL('../../.github/upstream-projects.yaml', import.meta.url), + 'utf8' + ) + ); + const project = projects.find( + (p) => p.id === 'stacklok-enterprise-platform' + ); + const metadata = path.join(root, project.helm_version_source); + fs.mkdirSync(path.dirname(metadata), { recursive: true }); + for (const [version, legacy] of [ + ['0.25.0', true], + ['0.26.0', false], + ['0.27.0', false], + ['0.100.0', false], + ]) { + fs.writeFileSync(metadata, yaml.stringify({ version })); + const charts = resolveHelmCharts(project, root); + const sources = charts.map((chart) => chart.source); + assert.equal( + sources.includes('enterprise/ai-gateway/helm/operator'), + legacy + ); + assert.equal( + sources.includes('enterprise/ai-gateway/helm/operator-crds'), + legacy + ); + assert.equal(sources.includes('enterprise/sprout/helm'), legacy); + assert.equal(sources.includes('enterprise/ai-gateway/helm'), !legacy); + assert.equal(charts.length, legacy ? 10 : 8); + const platform = charts.find((chart) => + chart.page.endsWith('/platform.mdx') + ); + assert.equal( + Boolean(platform.description_overrides?.['workspace.production']), + !legacy + ); + } + fs.writeFileSync(metadata, 'version: invalid\n'); + assert.throws( + () => resolveHelmCharts(project, root), + /Invalid Helm release version/ + ); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test('missing Helm inputs are reported together before generation', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-missing-inputs-')); + try { + fs.mkdirSync(path.join(root, 'first')); + fs.writeFileSync(path.join(root, 'first/Chart.yaml'), 'name: first\n'); + fs.writeFileSync(path.join(root, 'first/values.yaml'), ''); + assert.throws( + () => + validateHelmInputs([{ source: 'first' }, { source: 'second' }], root), + (error) => { + assert.match(error.message, /first\/values.yaml/); + assert.match(error.message, /second\/Chart.yaml/); + assert.match(error.message, /second\/values.yaml/); + assert.doesNotMatch(error.message, /first\/Chart.yaml/); + return true; + } + ); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + test('check mode validates all charts without writing pages or formatting', () => { const root = fs.realpathSync( fs.mkdtempSync(path.join(os.tmpdir(), 'helm-reference-check-'))