mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			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);
 | |
| }
 |