Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/upstream-projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
66 changes: 65 additions & 1 deletion scripts/upstream-release/generate-helm-reference.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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']) {
Expand Down
76 changes: 76 additions & 0 deletions scripts/upstream-release/generate-helm-reference.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
renderPage,
renderDescription,
validateDescriptions,
resolveHelmCharts,
validateHelmInputs,
} from './generate-helm-reference.mjs';

const chart = {
Expand Down Expand Up @@ -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-'))
Expand Down
Loading