| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 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)) { | 
					
						
							| 
									
										
										
										
											2025-02-03 19:50:15 +08:00
										 |  |  |     // Remove empty path items
 | 
					
						
							|  |  |  |     if (!pathItem) { | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  |       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> = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-03 19:50:15 +08:00
										 |  |  |     // Filter out namespace parameter at path level
 | 
					
						
							|  |  |  |     if (Array.isArray(pathItem.parameters)) { | 
					
						
							|  |  |  |       pathItem.parameters = filterNamespaceParameters(pathItem.parameters); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const method of Object.keys(pathItem)) { | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  |       // 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; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-03 19:50:15 +08:00
										 |  |  |       // Filter out namespace parameter at operation level
 | 
					
						
							|  |  |  |       if ( | 
					
						
							|  |  |  |         operation && | 
					
						
							|  |  |  |         typeof operation === 'object' && | 
					
						
							|  |  |  |         'parameters' in operation && | 
					
						
							|  |  |  |         Array.isArray(operation.parameters) | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         operation.parameters = filterNamespaceParameters(operation.parameters); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  |       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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-03 19:50:15 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * 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'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * 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; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * 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; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |   // Create the output directory if it doesn't exist
 | 
					
						
							|  |  |  |   if (!fs.existsSync(outputDir)) { | 
					
						
							|  |  |  |     fs.mkdirSync(outputDir, { recursive: true }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |   const files = fs.readdirSync(sourceDir).filter((file: string) => file.endsWith('.json')); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |   for (const file of files) { | 
					
						
							|  |  |  |     const inputPath = path.join(sourceDir, file); | 
					
						
							|  |  |  |     const outputPath = path.join(outputDir, file); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |     console.log(`Processing file "${file}"...`); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |     const fileContent = fs.readFileSync(inputPath, 'utf-8'); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  |     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}".`); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sourceDirs = [ | 
					
						
							| 
									
										
										
										
											2025-02-04 17:13:44 +08:00
										 |  |  |   path.resolve(import.meta.dirname, '../pkg/tests/apis/openapi_snapshots'), | 
					
						
							|  |  |  |   path.resolve(import.meta.dirname, '../pkg/extensions/apiserver/tests/openapi_snapshots'), | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  | ]; | 
					
						
							| 
									
										
										
										
											2025-02-04 17:13:44 +08:00
										 |  |  | const outputDir = path.resolve(import.meta.dirname, '../data/openapi'); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 16:32:29 +08:00
										 |  |  | for (const sourceDir of sourceDirs) { | 
					
						
							|  |  |  |   processDirectory(sourceDir, outputDir); | 
					
						
							| 
									
										
										
										
											2025-01-30 00:05:40 +08:00
										 |  |  | } |