From 4264e93f14f8b97345f1c0d05338415061163940 Mon Sep 17 00:00:00 2001 From: Theo Nam Truong Date: Tue, 23 Apr 2024 05:16:27 -0700 Subject: [PATCH] Generate _opendistro endpoints through merger tool (#257) * Generate _opendistro endpoints through merger tool Signed-off-by: Theo Truong * # Renamed `replaced` with `superseded` Signed-off-by: Theo Truong * # Rebased DEVELOPER_GUIDE.md Signed-off-by: Theo Truong * # Set Tabsize from 4 to 2 Signed-off-by: Theo Truong --------- Signed-off-by: Theo Truong --- DEVELOPER_GUIDE.md | 16 + spec/_superseded_operations.yaml | 566 ++++++++++++++++++ tools/merger/OpenApiMerger.ts | 29 +- tools/merger/OpenDistro.ts | 25 + tools/merger/SupersededOpsGenerator.ts | 49 ++ tools/merger/merge.ts | 2 +- tools/test/merger/fixtures/expected.yaml | 23 +- .../fixtures/spec/_superseded_operations.yaml | 10 + .../fixtures/spec/namespaces/shelter.yaml | 11 +- .../fixtures/spec/opensearch-openapi.yaml | 4 +- tools/types.ts | 6 +- 11 files changed, 724 insertions(+), 17 deletions(-) create mode 100644 spec/_superseded_operations.yaml create mode 100644 tools/merger/OpenDistro.ts create mode 100644 tools/merger/SupersededOpsGenerator.ts create mode 100644 tools/test/merger/fixtures/spec/_superseded_operations.yaml diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 4ac4577d..6af28c78 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -93,6 +93,22 @@ We authored a number of tools to merge and lint specs that live in [tools](tools The spec merger "builds", aka combines various `.yaml` files into a complete OpenAPI spec. A [workflow](./.github/workflows/build.yml) publishes the output into [releases](https://github.com/opensearch-project/opensearch-api-specification/releases). +#### Auto-generating Superseded Operations + +When an operation is superseded by another operation with **IDENTICAL FUNCTIONALITY**, that is a rename or a change in the URL, it should be listed in [_superseded_operations.yaml](./spec/_superseded_operations.yaml) file. The merger tool will automatically generate the superseded operation in the OpenAPI spec. The superseded operation will have `deprecated` and `x-ignorable` properties set to `true` to indicate that it should be ignored by the client generator. +For example, if the `_superseded_operations.yaml` file contains the following entry: +```yaml +/_opendistro/_anomaly_detection/{nodeId}/stats/{stat}: + superseded_by: /_plugins/_anomaly_detection/{nodeId}/stats/{stat} + operations: + - GET + - POST +``` +Then, the merger tool will generate 2 operations: `GET /_opendistro/_anomaly_detection/{nodeId}/stats/{stat}` and `POST /_opendistro/_anomaly_detection/{nodeId}/stats/{stat}` from `GET /_plugins/_anomaly_detection/{nodeId}/stats/{stat}` and `POST /_plugins/_anomaly_detection/{nodeId}/stats/{stat}` respectively, if they exist (A warning will be printed on the console if they do not). Note that the path parameter names do not need to match. So, if the actual superseding operations have path of `/_plugins/_anomaly_detection/{node_id}/stats/{stat_id}`, the merger tool will recognize that it is the same as `/_plugins/_anomaly_detection/{nodeId}/stats/{stat}` and generate the superseded operations accordingly with the correct path parameter names. + +#### Auto-generating global parameters +Certain query parameters are global, and they are accepted by every operation. These parameters are listed in the [root file](spec/opensearch-openapi.yaml) under the `parameters` section with `x-global` set to true. The merger tool will automatically add these parameters to all operations. + ### Linter The spec linter that validates every `.yaml` file in the `./spec` folder to assure that they follow the guidelines we have set. Check out the [Linter README](tools/README.md#linter) for more information on how to run it locally. Make sure to run the linter before submitting a PR. diff --git a/spec/_superseded_operations.yaml b/spec/_superseded_operations.yaml new file mode 100644 index 00000000..5191a5b6 --- /dev/null +++ b/spec/_superseded_operations.yaml @@ -0,0 +1,566 @@ +/_opendistro/_alerting/destinations: + superseded_by: /_plugins/_alerting/destinations + operations: + - GET +/_opendistro/_alerting/destinations/email_accounts/_search: + superseded_by: /_plugins/_alerting/destinations/email_accounts/_search + operations: + - GET + - POST +/_opendistro/_alerting/destinations/email_accounts/{emailAccountID}: + superseded_by: /_plugins/_alerting/destinations/email_accounts/{emailAccountID} + operations: + - GET + - HEAD +/_opendistro/_alerting/destinations/email_groups/_search: + superseded_by: /_plugins/_alerting/destinations/email_groups/_search + operations: + - GET + - POST +/_opendistro/_alerting/destinations/email_groups/{emailGroupID}: + superseded_by: /_plugins/_alerting/destinations/email_groups/{emailGroupID} + operations: + - GET + - HEAD +/_opendistro/_alerting/destinations/{destinationID}: + superseded_by: /_plugins/_alerting/destinations/{destinationID} + operations: + - GET +/_opendistro/_alerting/monitors: + superseded_by: /_plugins/_alerting/monitors + operations: + - POST +/_opendistro/_alerting/monitors/_execute: + superseded_by: /_plugins/_alerting/monitors/_execute + operations: + - POST +/_opendistro/_alerting/monitors/_search: + superseded_by: /_plugins/_alerting/monitors/_search + operations: + - GET + - POST +/_opendistro/_alerting/monitors/alerts: + superseded_by: /_plugins/_alerting/monitors/alerts + operations: + - GET +/_opendistro/_alerting/monitors/{monitorID}: + superseded_by: /_plugins/_alerting/monitors/{monitorID} + operations: + - GET + - PUT + - DELETE + - HEAD +/_opendistro/_alerting/monitors/{monitorID}/_acknowledge/alerts: + superseded_by: /_plugins/_alerting/monitors/{monitorID}/_acknowledge/alerts + operations: + - POST +/_opendistro/_alerting/monitors/{monitorID}/_execute: + superseded_by: /_plugins/_alerting/monitors/{monitorID}/_execute + operations: + - POST +/_opendistro/_alerting/stats/: + superseded_by: /_plugins/_alerting/stats/ + operations: + - GET +/_opendistro/_alerting/stats/{metric}: + superseded_by: /_plugins/_alerting/stats/{metric} + operations: + - GET +/_opendistro/_alerting/{nodeId}/stats/: + superseded_by: /_plugins/_alerting/{nodeId}/stats/ + operations: + - GET +/_opendistro/_alerting/{nodeId}/stats/{metric}: + superseded_by: /_plugins/_alerting/{nodeId}/stats/{metric} + operations: + - GET +/_opendistro/_anomaly_detection/detectors: + superseded_by: /_plugins/_anomaly_detection/detectors + operations: + - POST +/_opendistro/_anomaly_detection/detectors/_search: + superseded_by: /_plugins/_anomaly_detection/detectors/_search + operations: + - GET + - POST +/_opendistro/_anomaly_detection/detectors/count: + superseded_by: /_plugins/_anomaly_detection/detectors/count + operations: + - GET +/_opendistro/_anomaly_detection/detectors/match: + superseded_by: /_plugins/_anomaly_detection/detectors/match + operations: + - GET +/_opendistro/_anomaly_detection/detectors/results/_search: + superseded_by: /_plugins/_anomaly_detection/detectors/results/_search + operations: + - GET + - POST +/_opendistro/_anomaly_detection/detectors/tasks/_search: + superseded_by: /_plugins/_anomaly_detection/detectors/tasks/_search + operations: + - GET + - POST +/_opendistro/_anomaly_detection/detectors/{detectorID}: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID} + operations: + - GET + - PUT + - DELETE + - HEAD +/_opendistro/_anomaly_detection/detectors/{detectorID}/_preview: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_preview + operations: + - POST +/_opendistro/_anomaly_detection/detectors/{detectorID}/_profile: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_profile + operations: + - GET +/_opendistro/_anomaly_detection/detectors/{detectorID}/_profile/{type}: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_profile/{type} + operations: + - GET +/_opendistro/_anomaly_detection/detectors/{detectorID}/_run: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_run + operations: + - POST +/_opendistro/_anomaly_detection/detectors/{detectorID}/_start: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_start + operations: + - POST +/_opendistro/_anomaly_detection/detectors/{detectorID}/_stop: + superseded_by: /_plugins/_anomaly_detection/detectors/{detectorID}/_stop + operations: + - POST +/_opendistro/_anomaly_detection/stats/: + superseded_by: /_plugins/_anomaly_detection/stats/ + operations: + - GET +/_opendistro/_anomaly_detection/stats/{stat}: + superseded_by: /_plugins/_anomaly_detection/stats/{stat} + operations: + - GET +/_opendistro/_anomaly_detection/{nodeId}/stats/: + superseded_by: /_plugins/_anomaly_detection/{nodeId}/stats/ + operations: + - GET +/_opendistro/_anomaly_detection/{nodeId}/stats/{stat}: + superseded_by: /_plugins/_anomaly_detection/{nodeId}/stats/{stat} + operations: + - GET +/_opendistro/_asynchronous_search: + superseded_by: /_plugins/_asynchronous_search + operations: + - POST +/_opendistro/_asynchronous_search/_nodes/{nodeId}/stats: + superseded_by: /_plugins/_asynchronous_search/_nodes/{nodeId}/stats + operations: + - GET +/_opendistro/_asynchronous_search/stats: + superseded_by: /_plugins/_asynchronous_search/stats + operations: + - GET +/_opendistro/_asynchronous_search/{id}: + superseded_by: /_plugins/_asynchronous_search/{id} + operations: + - GET + - DELETE +/_opendistro/_ism/add: + superseded_by: /_plugins/_ism/add + operations: + - POST +/_opendistro/_ism/add/{index}: + superseded_by: /_plugins/_ism/add/{index} + operations: + - POST +/_opendistro/_ism/change_policy: + superseded_by: /_plugins/_ism/change_policy + operations: + - POST +/_opendistro/_ism/change_policy/{index}: + superseded_by: /_plugins/_ism/change_policy/{index} + operations: + - POST +/_opendistro/_ism/explain: + superseded_by: /_plugins/_ism/explain + operations: + - GET +/_opendistro/_ism/explain/{index}: + superseded_by: /_plugins/_ism/explain/{index} + operations: + - GET +/_opendistro/_ism/policies: + superseded_by: /_plugins/_ism/policies + operations: + - GET + - PUT +/_opendistro/_ism/policies/{policyID}: + superseded_by: /_plugins/_ism/policies/{policyID} + operations: + - GET + - PUT + - DELETE + - HEAD +/_opendistro/_ism/remove: + superseded_by: /_plugins/_ism/remove + operations: + - POST +/_opendistro/_ism/remove/{index}: + superseded_by: /_plugins/_ism/remove/{index} + operations: + - POST +/_opendistro/_ism/retry: + superseded_by: /_plugins/_ism/retry + operations: + - POST +/_opendistro/_ism/retry/{index}: + superseded_by: /_plugins/_ism/retry/{index} + operations: + - POST +/_opendistro/_knn/stats/: + superseded_by: /_plugins/_knn/stats/ + operations: + - GET +/_opendistro/_knn/stats/{stat}: + superseded_by: /_plugins/_knn/stats/{stat} + operations: + - GET +/_opendistro/_knn/warmup/{index}: + superseded_by: /_plugins/_knn/warmup/{index} + operations: + - GET +/_opendistro/_knn/{nodeId}/stats/: + superseded_by: /_plugins/_knn/{nodeId}/stats/ + operations: + - GET +/_opendistro/_knn/{nodeId}/stats/{stat}: + superseded_by: /_plugins/_knn/{nodeId}/stats/{stat} + operations: + - GET +/_opendistro/_performanceanalyzer/_agent/{redirectEndpoint}: + superseded_by: /_plugins/_performanceanalyzer/_agent/{redirectEndpoint} + operations: + - GET +/_opendistro/_performanceanalyzer/batch/cluster/config: + superseded_by: /_plugins/_performanceanalyzer/batch/cluster/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/batch/config: + superseded_by: /_plugins/_performanceanalyzer/batch/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/cluster/config: + superseded_by: /_plugins/_performanceanalyzer/cluster/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/config: + superseded_by: /_plugins/_performanceanalyzer/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/logging/cluster/config: + superseded_by: /_plugins/_performanceanalyzer/logging/cluster/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/logging/config: + superseded_by: /_plugins/_performanceanalyzer/logging/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/override/cluster/config: + superseded_by: /_plugins/_performanceanalyzer/override/cluster/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/rca/cluster/config: + superseded_by: /_plugins/_performanceanalyzer/rca/cluster/config + operations: + - GET + - POST +/_opendistro/_performanceanalyzer/rca/config: + superseded_by: /_plugins/_performanceanalyzer/rca/config + operations: + - GET + - POST +/_opendistro/_ppl: + superseded_by: /_plugins/_ppl + operations: + - POST +/_opendistro/_ppl/_explain: + superseded_by: /_plugins/_ppl/_explain + operations: + - POST +/_opendistro/_ppl/stats: + superseded_by: /_plugins/_ppl/stats + operations: + - GET + - POST +/_opendistro/_refresh_search_analyzers: + superseded_by: /_plugins/_refresh_search_analyzers + operations: + - POST +/_opendistro/_refresh_search_analyzers/{index}: + superseded_by: /_plugins/_refresh_search_analyzers/{index} + operations: + - POST +/_opendistro/_reports/_local/stats: + superseded_by: /_plugins/_reports/_local/stats + operations: + - GET +/_opendistro/_reports/definition: + superseded_by: /_plugins/_reports/definition + operations: + - POST +/_opendistro/_reports/definition/{reportDefinitionId}: + superseded_by: /_plugins/_reports/definition/{reportDefinitionId} + operations: + - GET + - PUT + - DELETE +/_opendistro/_reports/definitions: + superseded_by: /_plugins/_reports/definitions + operations: + - GET +/_opendistro/_reports/instance/{reportInstanceId}: + superseded_by: /_plugins/_reports/instance/{reportInstanceId} + operations: + - GET + - POST +/_opendistro/_reports/instances: + superseded_by: /_plugins/_reports/instances + operations: + - GET +/_opendistro/_reports/on_demand: + superseded_by: /_plugins/_reports/on_demand + operations: + - PUT +/_opendistro/_reports/on_demand/{reportDefinitionId}: + superseded_by: /_plugins/_reports/on_demand/{reportDefinitionId} + operations: + - POST +/_opendistro/_rollup/jobs: + superseded_by: /_plugins/_rollup/jobs + operations: + - GET + - PUT +/_opendistro/_rollup/jobs/{rollupID}: + superseded_by: /_plugins/_rollup/jobs/{rollupID} + operations: + - GET + - PUT + - DELETE + - HEAD +/_opendistro/_rollup/jobs/{rollupID}/_explain: + superseded_by: /_plugins/_rollup/jobs/{rollupID}/_explain + operations: + - GET +/_opendistro/_rollup/jobs/{rollupID}/_start: + superseded_by: /_plugins/_rollup/jobs/{rollupID}/_start + operations: + - POST +/_opendistro/_rollup/jobs/{rollupID}/_stop: + superseded_by: /_plugins/_rollup/jobs/{rollupID}/_stop + operations: + - POST +/_opendistro/_security/api/account: + superseded_by: /_plugins/_security/api/account + operations: + - GET + - PUT +/_opendistro/_security/api/actiongroup/: + superseded_by: /_plugins/_security/api/actiongroup/ + operations: + - GET +/_opendistro/_security/api/actiongroup/{name}: + superseded_by: /_plugins/_security/api/actiongroup/{name} + operations: + - GET + - PUT + - DELETE +/_opendistro/_security/api/actiongroups/: + superseded_by: /_plugins/_security/api/actiongroups/ + operations: + - GET + - PATCH +/_opendistro/_security/api/actiongroups/{name}: + superseded_by: /_plugins/_security/api/actiongroups/{name} + operations: + - GET + - PUT + - DELETE + - PATCH +/_opendistro/_security/api/audit/: + superseded_by: /_plugins/_security/api/audit/ + operations: + - GET + - PATCH +/_opendistro/_security/api/audit/config: + superseded_by: /_plugins/_security/api/audit/config + operations: + - PUT +/_opendistro/_security/api/authtoken: + superseded_by: /_plugins/_security/api/authtoken + operations: + - POST +/_opendistro/_security/api/cache: + superseded_by: /_plugins/_security/api/cache + operations: + - GET + - PUT + - POST + - DELETE +/_opendistro/_security/api/internalusers/: + superseded_by: /_plugins/_security/api/internalusers/ + operations: + - GET + - PATCH +/_opendistro/_security/api/internalusers/{name}: + superseded_by: /_plugins/_security/api/internalusers/{name} + operations: + - GET + - PUT + - DELETE + - PATCH +/_opendistro/_security/api/internalusers/{name}/authtoken: + superseded_by: /_plugins/_security/api/internalusers/{name}/authtoken + operations: + - POST +/_opendistro/_security/api/migrate: + superseded_by: /_plugins/_security/api/migrate + operations: + - POST +/_opendistro/_security/api/permissionsinfo: + superseded_by: /_plugins/_security/api/permissionsinfo + operations: + - GET +/_opendistro/_security/api/roles/: + superseded_by: /_plugins/_security/api/roles/ + operations: + - GET + - PATCH +/_opendistro/_security/api/roles/{name}: + superseded_by: /_plugins/_security/api/roles/{name} + operations: + - GET + - PUT + - DELETE + - PATCH +/_opendistro/_security/api/rolesmapping/: + superseded_by: /_plugins/_security/api/rolesmapping/ + operations: + - GET + - PATCH +/_opendistro/_security/api/rolesmapping/{name}: + superseded_by: /_plugins/_security/api/rolesmapping/{name} + operations: + - GET + - PUT + - DELETE + - PATCH +/_opendistro/_security/api/securityconfig: + superseded_by: /_plugins/_security/api/securityconfig + operations: + - GET + - PATCH +/_opendistro/_security/api/securityconfig/config: + superseded_by: /_plugins/_security/api/securityconfig/config + operations: + - PUT +/_opendistro/_security/api/ssl/certs: + superseded_by: /_plugins/_security/api/ssl/certs + operations: + - GET +/_opendistro/_security/api/ssl/{certType}/reloadcerts/: + superseded_by: /_plugins/_security/api/ssl/{certType}/reloadcerts/ + operations: + - PUT +/_opendistro/_security/api/tenancy/config: + superseded_by: /_plugins/_security/api/tenancy/config + operations: + - GET + - PUT +/_opendistro/_security/api/tenants/: + superseded_by: /_plugins/_security/api/tenants/ + operations: + - GET + - PATCH +/_opendistro/_security/api/tenants/{name}: + superseded_by: /_plugins/_security/api/tenants/{name} + operations: + - GET + - PUT + - DELETE + - PATCH +/_opendistro/_security/api/user/: + superseded_by: /_plugins/_security/api/user/ + operations: + - GET +/_opendistro/_security/api/user/{name}: + superseded_by: /_plugins/_security/api/user/{name} + operations: + - GET + - PUT + - DELETE +/_opendistro/_security/api/user/{name}/authtoken: + superseded_by: /_plugins/_security/api/user/{name}/authtoken + operations: + - POST +/_opendistro/_security/api/validate: + superseded_by: /_plugins/_security/api/validate + operations: + - GET +/_opendistro/_security/api/whitelist: + superseded_by: /_plugins/_security/api/whitelist + operations: + - GET + - PUT + - PATCH +/_opendistro/_security/authinfo: + superseded_by: /_plugins/_security/authinfo + operations: + - GET + - POST +/_opendistro/_security/health: + superseded_by: /_plugins/_security/health + operations: + - GET + - POST +/_opendistro/_security/kibanainfo: + superseded_by: /_plugins/_security/kibanainfo + operations: + - GET + - POST +/_opendistro/_security/sslinfo: + superseded_by: /_plugins/_security/sslinfo + operations: + - GET +/_opendistro/_security/tenantinfo: + superseded_by: /_plugins/_security/tenantinfo + operations: + - GET + - POST +/_opendistro/_sql: + superseded_by: /_plugins/_sql + operations: + - POST +/_opendistro/_sql/_explain: + superseded_by: /_plugins/_sql/_explain + operations: + - POST +/_opendistro/_sql/close: + superseded_by: /_plugins/_sql/close + operations: + - POST +/_opendistro/_sql/settings: + superseded_by: /_plugins/_sql/settings + operations: + - PUT +/_opendistro/_sql/stats: + superseded_by: /_plugins/_sql/stats + operations: + - GET + - POST diff --git a/tools/merger/OpenApiMerger.ts b/tools/merger/OpenApiMerger.ts index 4d9520d9..4bec290b 100644 --- a/tools/merger/OpenApiMerger.ts +++ b/tools/merger/OpenApiMerger.ts @@ -1,8 +1,9 @@ -import { OpenAPIV3 } from "openapi-types"; +import {OpenAPIV3} from "openapi-types"; import fs from 'fs'; import _ from 'lodash'; import yaml from 'yaml'; -import { write2file } from '../helpers'; +import {write2file} from '../helpers'; +import SupersededOpsGenerator from "./SupersededOpsGenerator"; // Create a single-file OpenAPI spec from multiple files for OpenAPI validation and programmatic consumption export default class OpenApiMerger { @@ -33,8 +34,9 @@ export default class OpenApiMerger { this.#merge_namespaces(); this.#apply_global_params(); this.#sort_spec_keys(); + this.#generate_replaced_ops(); - if(output_path) write2file(output_path, this.spec); + if (output_path) write2file(output_path, this.spec); return this.spec as OpenAPIV3.Document; } @@ -63,7 +65,7 @@ export default class OpenApiMerger { Object.entries(this.spec.paths).forEach(([path, refObj]) => { const ref = (refObj as Record).$ref!; - const namespace = ref.match(/namespaces\/(.*)\.yaml/)![1]; + const namespace = ref.match(/namespaces\/(.*)\.yaml/)![1]; this.spec.paths[path] = this.paths[namespace][path]; }); } @@ -71,11 +73,11 @@ export default class OpenApiMerger { // Redirect schema references in namespace files to local references in single-file spec. redirect_refs_in_namespace(obj: Record): void { const ref = obj.$ref; - if(ref?.startsWith('../schemas/')) + if (ref?.startsWith('../schemas/')) obj.$ref = ref.replace('../schemas/', '#/components/schemas/').replace('.yaml#/components/schemas/', ':'); - for(const key in obj) - if(typeof obj[key] === 'object') + for (const key in obj) + if (typeof obj[key] === 'object') this.redirect_refs_in_namespace(obj[key]); } @@ -99,16 +101,16 @@ export default class OpenApiMerger { // Redirect schema references in schema files to local references in single-file spec. redirect_refs_in_schema(category: string, obj: Record): void { const ref = obj.$ref; - if(ref) - if(ref.startsWith('#/components/schemas')) + if (ref) + if (ref.startsWith('#/components/schemas')) obj.$ref = `#/components/schemas/${category}:${ref.split('/').pop()}`; else { const other_category = ref.match(/(.*)\.yaml/)![1]; obj.$ref = `#/components/schemas/${other_category}:${ref.split('/').pop()}`; } - for(const key in obj) - if(typeof obj[key] === 'object') + for (const key in obj) + if (typeof obj[key] === 'object') this.redirect_refs_in_schema(category, obj[key]); } @@ -124,4 +126,9 @@ export default class OpenApiMerger { this.spec.paths[path] = _.fromPairs(Object.entries(pathItem!).sort()); }); } + + #generate_replaced_ops(): void { + const gen = new SupersededOpsGenerator(this.root_folder); + gen.generate(this.spec); + } } \ No newline at end of file diff --git a/tools/merger/OpenDistro.ts b/tools/merger/OpenDistro.ts new file mode 100644 index 00000000..23e6128b --- /dev/null +++ b/tools/merger/OpenDistro.ts @@ -0,0 +1,25 @@ +import fs from "fs"; +import YAML from "yaml"; +import {HttpVerb, OperationPath, SupersededOperationMap} from "../types"; +import {write2file} from "../helpers"; + +// One-time script to generate _superseded_operations.yaml file for OpenDistro +// Keeping this for now in case we need to update the file in the near future. Can be removed after a few months. +// TODO: Remove this file in 2025. +export default class OpenDistro { + input: Record; + output: SupersededOperationMap = {}; + + constructor(file_path: string) { + this.input = YAML.parse(fs.readFileSync(file_path, 'utf8')); + this.build_output(); + write2file(file_path, this.output); + } + + build_output() { + for (const [path, operations] of Object.entries(this.input)) { + const replaced_by = path.replace('_opendistro', '_plugins'); + this.output[path] = {superseded_by: replaced_by, operations}; + } + } +} \ No newline at end of file diff --git a/tools/merger/SupersededOpsGenerator.ts b/tools/merger/SupersededOpsGenerator.ts new file mode 100644 index 00000000..1820888d --- /dev/null +++ b/tools/merger/SupersededOpsGenerator.ts @@ -0,0 +1,49 @@ +import {OperationSpec, SupersededOperationMap} from "../types"; +import YAML from "yaml"; +import fs from "fs"; +import _ from "lodash"; + +export default class SupersededOpsGenerator { + superseded_ops: SupersededOperationMap; + + constructor(root_path: string) { + const file_path = root_path + '/_superseded_operations.yaml'; + this.superseded_ops = YAML.parse(fs.readFileSync(file_path, 'utf8')); + } + + generate(spec: Record): void { + for (const [path, {superseded_by, operations}] of _.entries(this.superseded_ops)) { + const regex = this.path_to_regex(superseded_by); + const operation_keys = operations.map(op => op.toLowerCase()); + const superseded_path = this.copy_params(superseded_by, path); + const path_entry = _.entries(spec.paths).find(([path, _]) => regex.test(path)); + if (!path_entry) console.log(`Path not found: ${superseded_by}`); + else spec.paths[superseded_path] = this.path_object(path_entry[1] as any, operation_keys); + } + } + + path_object(obj: Record, keys: string[]): Record { + const cloned_obj = _.cloneDeep(_.pick(obj, keys)); + for (const key in cloned_obj) { + const operation = cloned_obj[key] as OperationSpec; + operation.operationId = operation.operationId + '_superseded'; + operation.deprecated = true; + operation['x-ignorable'] = true; + } + return cloned_obj; + } + + path_to_regex(path: string): RegExp { + const source = '^' + path.replace(/\{.+?}/g, '\\{.+?\\}').replace(/\//g, '\\/') + '$'; + return new RegExp(source, 'g'); + } + + copy_params(source: string, target: string): string { + const target_parts = target.split('/'); + const target_params = target_parts.filter(part => part.startsWith('{')); + const source_params = source.split('/').filter(part => part.startsWith('{')).reverse(); + if (target_params.length !== source_params.length) + throw new Error('Mismatched parameters in source and target paths: ' + source + ' -> ' + target); + return target_parts.map((part) => part.startsWith('{') ? source_params.pop()! : part).join('/'); + } +} \ No newline at end of file diff --git a/tools/merger/merge.ts b/tools/merger/merge.ts index 98758cb3..60a07ea4 100644 --- a/tools/merger/merge.ts +++ b/tools/merger/merge.ts @@ -4,4 +4,4 @@ import OpenApiMerger from "./OpenApiMerger"; const root_path: string = process.argv[2] || '../spec/opensearch-openapi.yaml' const output_path: string = process.argv[3] || '../opensearch-openapi.yaml' const merger = new OpenApiMerger(root_path); -merger.merge(output_path); \ No newline at end of file +merger.merge(output_path); diff --git a/tools/test/merger/fixtures/expected.yaml b/tools/test/merger/fixtures/expected.yaml index 1d28db66..95772eb3 100644 --- a/tools/test/merger/fixtures/expected.yaml +++ b/tools/test/merger/fixtures/expected.yaml @@ -4,23 +4,39 @@ info: description: OpenSearch API version: 1.0.0 paths: - /adopt/{animal}: + /adopt/{animal}/dockets/{docket}: get: + operationId: adopt.0 parameters: - $ref: '#/components/parameters/adopt::path.animal' + - $ref: '#/components/parameters/adopt::path.docket' - $ref: '#/components/parameters/_global::query.human' responses: '200': $ref: '#/components/responses/adopt@200' post: + operationId: adopt.1 parameters: - $ref: '#/components/parameters/adopt::path.animal' + - $ref: '#/components/parameters/adopt::path.docket' - $ref: '#/components/parameters/_global::query.human' requestBody: $ref: '#/components/requestBodies/adopt' responses: '200': $ref: '#/components/responses/adopt@200' + /replaced/adopting/{animal}/something/{docket}: + get: + operationId: adopt.0_superseded + parameters: + - $ref: '#/components/parameters/adopt::path.animal' + - $ref: '#/components/parameters/adopt::path.docket' + - $ref: '#/components/parameters/_global::query.human' + responses: + '200': + $ref: '#/components/responses/adopt@200' + deprecated: true + x-ignorable: true components: parameters: _global::query.human: @@ -36,6 +52,11 @@ components: in: path schema: $ref: '#/components/schemas/animals:Animal' + adopt::path.docket: + name: docket + in: path + schema: + type: number indices.create::path.index: name: index in: path diff --git a/tools/test/merger/fixtures/spec/_superseded_operations.yaml b/tools/test/merger/fixtures/spec/_superseded_operations.yaml new file mode 100644 index 00000000..4ddca20c --- /dev/null +++ b/tools/test/merger/fixtures/spec/_superseded_operations.yaml @@ -0,0 +1,10 @@ +/replaced/adopting/{a}/something/{b}: + superseded_by: /adopt/{animal}/dockets/{docket} + operations: + - GET + - DELETE +/something/else: + superseded_by: /not/here + operations: + - POST + - PUT \ No newline at end of file diff --git a/tools/test/merger/fixtures/spec/namespaces/shelter.yaml b/tools/test/merger/fixtures/spec/namespaces/shelter.yaml index b91dc827..0f1a2970 100644 --- a/tools/test/merger/fixtures/spec/namespaces/shelter.yaml +++ b/tools/test/merger/fixtures/spec/namespaces/shelter.yaml @@ -4,16 +4,20 @@ info: description: OpenSearch API version: 1.0.0 paths: - '/adopt/{animal}': + '/adopt/{animal}/dockets/{docket}': get: + operationId: adopt.0 parameters: - $ref: '#/components/parameters/adopt::path.animal' + - $ref: '#/components/parameters/adopt::path.docket' responses: '200': $ref: '#/components/responses/adopt@200' post: + operationId: adopt.1 parameters: - $ref: '#/components/parameters/adopt::path.animal' + - $ref: '#/components/parameters/adopt::path.docket' requestBody: $ref: '#/components/requestBodies/adopt' responses: @@ -28,6 +32,11 @@ components: in: path schema: $ref: '../schemas/animals.yaml#/components/schemas/Animal' + adopt::path.docket: + name: docket + in: path + schema: + type: number responses: adopt@200: description: '' diff --git a/tools/test/merger/fixtures/spec/opensearch-openapi.yaml b/tools/test/merger/fixtures/spec/opensearch-openapi.yaml index 1e9a0661..b9bdd719 100644 --- a/tools/test/merger/fixtures/spec/opensearch-openapi.yaml +++ b/tools/test/merger/fixtures/spec/opensearch-openapi.yaml @@ -4,8 +4,8 @@ info: description: OpenSearch API version: 1.0.0 paths: - '/adopt/{animal}': - $ref: 'namespaces/shelter.yaml#/paths/~1adopt~1{animal}' + '/adopt/{animal}/dockets/{docket}': + $ref: 'namespaces/shelter.yaml#/paths/~1adopt~1{animal}~1dockets~1{docket}' components: parameters: _global::query.human: diff --git a/tools/types.ts b/tools/types.ts index fa4010d7..9da06a62 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -25,4 +25,8 @@ export interface ValidationError { file: string; location?: string; message: string; -} \ No newline at end of file +} + +export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'TRACE' +export type OperationPath = string; +export type SupersededOperationMap = Record;