mirror of
https://github.com/grafana/grafana.git
synced 2025-07-23 21:12:30 +08:00

* add verify-api-clients step to drone * change to check the verify step fails * ignore conf dir * rerun make drone * undo change to make step fail
179 lines
5.7 KiB
TypeScript
179 lines
5.7 KiB
TypeScript
import fs from 'fs';
|
|
import { OpenAPIV3 } from 'openapi-types';
|
|
import path from 'path';
|
|
|
|
/**
|
|
* Process an OpenAPI spec to remove k8s metadata from names and paths:
|
|
* - Remove paths containing "/watch/" as they're deprecated.
|
|
* - Remove 'ForAllNamespaces' endpoints
|
|
* - Remove the prefix: "/apis/<group>/<version>/namespaces/{namespace}" from paths.
|
|
* - Filter out `namespace` from path parameters.
|
|
* - Update all $ref fields to remove k8s metadata from schema names.
|
|
* - Simplify schema names in "components.schemas".
|
|
*/
|
|
function processOpenAPISpec(spec: OpenAPIV3.Document) {
|
|
// Create a deep copy of the spec to avoid mutating the original
|
|
const newSpec = JSON.parse(JSON.stringify(spec));
|
|
|
|
// Process 'paths' property
|
|
const newPaths: Record<string, unknown> = {};
|
|
for (const [path, pathItem] of Object.entries<OpenAPIV3.PathItemObject>(newSpec.paths)) {
|
|
// Remove empty path items
|
|
if (!pathItem) {
|
|
continue;
|
|
}
|
|
// Remove the specified part from the path key
|
|
const newPathKey = path.replace(/^\/apis\/[^\/]+\/[^\/]+\/namespaces\/\{namespace}/, '');
|
|
|
|
// Process each method in the path (e.g., get, post)
|
|
const newPathItem: Record<string, unknown> = {};
|
|
|
|
// Filter out namespace parameter at path level
|
|
if (Array.isArray(pathItem.parameters)) {
|
|
pathItem.parameters = filterNamespaceParameters(pathItem.parameters);
|
|
}
|
|
|
|
for (const method of Object.keys(pathItem)) {
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
const operation = pathItem[method as keyof OpenAPIV3.PathItemObject];
|
|
|
|
if (
|
|
typeof operation === 'object' &&
|
|
operation !== null &&
|
|
'operationId' in operation &&
|
|
operation.operationId?.includes('ForAllNamespaces')
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Filter out namespace parameter at operation level
|
|
if (
|
|
operation &&
|
|
typeof operation === 'object' &&
|
|
'parameters' in operation &&
|
|
Array.isArray(operation.parameters)
|
|
) {
|
|
operation.parameters = filterNamespaceParameters(operation.parameters);
|
|
}
|
|
|
|
updateRefs(operation);
|
|
|
|
newPathItem[method] = operation;
|
|
}
|
|
|
|
newPaths[newPathKey] = newPathItem;
|
|
}
|
|
newSpec.paths = newPaths;
|
|
|
|
// Process 'components.schemas', i.e., type definitions
|
|
const newSchemas: Record<string, unknown> = {};
|
|
for (const schemaKey of Object.keys(newSpec.components.schemas)) {
|
|
const newKey = simplifySchemaName(schemaKey);
|
|
|
|
const schemaObject = newSpec.components.schemas[schemaKey];
|
|
updateRefs(schemaObject);
|
|
|
|
newSchemas[newKey] = schemaObject;
|
|
}
|
|
newSpec.components.schemas = newSchemas;
|
|
|
|
return newSpec;
|
|
}
|
|
|
|
/**
|
|
* Filter out namespace parameters from an array of parameters
|
|
*/
|
|
function filterNamespaceParameters(parameters: Array<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>) {
|
|
return parameters.filter((param) => 'name' in param && param.name !== 'namespace');
|
|
}
|
|
|
|
/**
|
|
* Recursively update all $ref fields to remove k8s metadata from names
|
|
*/
|
|
function updateRefs(obj: unknown) {
|
|
if (Array.isArray(obj)) {
|
|
for (const item of obj) {
|
|
updateRefs(item);
|
|
}
|
|
} else if (typeof obj === 'object' && obj !== null) {
|
|
if ('$ref' in obj && typeof obj.$ref === 'string') {
|
|
const refParts = obj.$ref.split('/');
|
|
const lastRefPart = refParts[refParts.length - 1];
|
|
const newRefName = simplifySchemaName(lastRefPart);
|
|
obj.$ref = `#/components/schemas/${newRefName}`;
|
|
}
|
|
for (const key in obj) {
|
|
if (key !== '$ref') {
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
updateRefs(obj[key as keyof typeof obj]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simplify a schema name by removing the version prefix if present.
|
|
* For example, 'io.k8s.apimachinery.pkg.apis.meta.v1.Time' becomes 'Time'.
|
|
*/
|
|
function simplifySchemaName(schemaName: string) {
|
|
const parts = schemaName.split('.');
|
|
|
|
// Regex to match version segments like 'v1', 'v1beta1', 'v0alpha1', etc.
|
|
const versionRegex = /^v\d+[a-zA-Z0-9]*$/;
|
|
const versionIndex = parts.findIndex((part) => versionRegex.test(part));
|
|
|
|
if (versionIndex !== -1 && versionIndex + 1 < parts.length) {
|
|
return parts.slice(versionIndex + 1).join('.');
|
|
} else {
|
|
return schemaName;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process all files in a source directory and write results to output directory
|
|
*/
|
|
function processDirectory(sourceDir: string, outputDir: string) {
|
|
// Skip if source directory doesn't exist
|
|
if (!fs.existsSync(sourceDir)) {
|
|
return;
|
|
}
|
|
|
|
// Create the output directory if it doesn't exist
|
|
if (!fs.existsSync(outputDir)) {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
}
|
|
|
|
const files = fs.readdirSync(sourceDir).filter((file: string) => file.endsWith('.json'));
|
|
|
|
for (const file of files) {
|
|
const inputPath = path.join(sourceDir, file);
|
|
const outputPath = path.join(outputDir, file);
|
|
|
|
console.log(`Processing file "${file}"...`);
|
|
|
|
const fileContent = fs.readFileSync(inputPath, 'utf-8');
|
|
|
|
let inputSpec;
|
|
try {
|
|
inputSpec = JSON.parse(fileContent);
|
|
} catch (err) {
|
|
console.error(`Invalid JSON file "${file}". Skipping this file.`);
|
|
continue;
|
|
}
|
|
|
|
const outputSpec = processOpenAPISpec(inputSpec);
|
|
fs.writeFileSync(outputPath, JSON.stringify(outputSpec, null, 2), 'utf-8');
|
|
console.log(`Processing completed for file "${file}".`);
|
|
}
|
|
}
|
|
|
|
const sourceDirs = [
|
|
path.resolve(import.meta.dirname, '../pkg/tests/apis/openapi_snapshots'),
|
|
path.resolve(import.meta.dirname, '../pkg/extensions/apiserver/tests/openapi_snapshots'),
|
|
];
|
|
const outputDir = path.resolve(import.meta.dirname, '../data/openapi');
|
|
|
|
for (const sourceDir of sourceDirs) {
|
|
processDirectory(sourceDir, outputDir);
|
|
}
|