From 3cb700abb7c4e04fb771212e97a506fb3309afcd Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Tue, 28 Sep 2021 18:54:07 +0530 Subject: [PATCH 1/2] add support for resolving "$ref" in the CLI Signed-off-by: Utkarsh Srivastava --- helper/output.js | 10 ++-- helper/toJSONSchema.js | 44 ++++++++++++---- index.js | 6 ++- package-lock.json | 114 +++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 5 files changed, 160 insertions(+), 15 deletions(-) diff --git a/helper/output.js b/helper/output.js index b29fb13..fbf7e47 100644 --- a/helper/output.js +++ b/helper/output.js @@ -4,15 +4,17 @@ const jp = require("jsonpath"); /** * Output takes in the data that needs to be printed and * an output format - * @param {*} data + * @param {Promise} data * @param {"json" | "yaml"} format output format */ function Output(data, format = "json", filter = "$", silent = false) { if (silent) return; - data = jp.query(data, filter); - if (format === "yaml") return console.log(dump(data)); - if (format === "json") return console.log(JSON.stringify(data, null, 2)); + data.then(data => { + data = jp.query(data, filter); + if (format === "yaml") return console.log(dump(data)); + if (format === "json") return console.log(JSON.stringify(data, null, 2)); + }) } module.exports = Output; diff --git a/helper/toJSONSchema.js b/helper/toJSONSchema.js index 4ba347b..02ffa8b 100644 --- a/helper/toJSONSchema.js +++ b/helper/toJSONSchema.js @@ -5,6 +5,7 @@ const { readFileSync, writeFileSync } = require("fs"); const { tmpdir } = require("os"); const path = require("path"); const jp = require("jsonpath"); +const { Resolver } = require("@stoplight/json-ref-resolver"); /** * convertAllSchemasToJSONSchema takes in the OpenAPIV3 Schemas in an array @@ -23,17 +24,39 @@ function convertAllSchemasToJSONSchema(schemas) { * readSchema will read schema file from the given location, it expects * the schema to be in JSON format * - * readSchema will also apply the given jsonpath filter to the read schema - * and will return only the filtered JSONs + * readSchema will also resolve the references if a resolveQuery is passed * @param {string} location - * @param {string} query jsonpath based query - * @returns {any[]} + * @param {string} resolveQuery jsonpath based query - must resolve to EXACTLY one match or else is ignored + * @returns {Promise} */ -function readSchema(location, query) { +async function readSchema(location, resolveQuery) { const data = readFileSync(location, "utf-8"); const parsed = JSON.parse(data); - return jp.query(parsed, query); + if (resolveQuery) { + const inner = jp.query(parsed, resolveQuery); + + if (inner.length !== 1) return parsed; + + const resolver = new Resolver(); + const resolved = await resolver.resolve(inner[0], {}); + + if (resolved.errors.length) console.error(resolved.errors); + + return resolved.result; + } + + return parsed; +} + +/** + * filterSchemas takes in an array of schemas and will return an array of filtered schemas + * @param {Array} schemas - OpenAPI schema in JSON format + * @param {string} query jsonpath based query to filter out the data + * @returns {Array} + */ +function filterSchemas(schemas, query) { + return jp.query(schemas, query); } /** @@ -72,16 +95,19 @@ function setupFiles(location, type) { * @param {string} location location of the schemas in open api v3 format * @param {"yaml" | "json"} type encoding in which the openapi schema is present * @param {string} query jsonpath query to filter the read schemas + * @param {string} resolve jsonpath query to reach to the root of the openAPI spec */ -function ToJSONSchema(location, type = "yaml", query = "") { +async function ToJSONSchema(location, type = "yaml", query = "", resolve = "") { if (type !== "yaml" && type !== "json") throw Error('invalid type received: can be either "yaml" or "json"'); const source = setupFiles(location, type); - const schemas = readSchema(source, query); + const schemas = await readSchema(source, resolve); + + const filtered = filterSchemas(schemas, query); - return convertAllSchemasToJSONSchema(schemas); + return convertAllSchemasToJSONSchema(filtered); } module.exports = ToJSONSchema; diff --git a/index.js b/index.js index 3006fcd..41966c7 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,8 @@ program .option("--kubernetes", "enable kubernetes specific filters", false) .option("-o [output-format]", "output format", "json") .option("--o-filter [output-filter]", "output filter query") - .option("--silent", "skip output", false); + .option("--silent", "skip output", false) + .option("--resolve [resolve-filter]", "root of the OpenAPI spec to resolve the $ref", "") program.parse(process.argv); @@ -25,7 +26,8 @@ Output( ToJSONSchema( options.location, options.type, - CreateQuery(options.filter, options.kubernetes) + CreateQuery(options.filter, options.kubernetes), + options.resolve, ), options.o, options.oFilter, diff --git a/package-lock.json b/package-lock.json index 4afb38b..1e511cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,65 @@ "fast-deep-equal": "^3.1.3" } }, + "@stoplight/json": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.0.tgz", + "integrity": "sha512-WW0z2bb0D4t8FTl+zNTCu46J8lEOsrUhBPgwEYQ3Ri2Y0MiRE4U1/9ZV8Ki+pIJznZgY9i42bbFwOBxyZn5/6w==", + "requires": { + "@stoplight/ordered-object-literal": "^1.0.2", + "@stoplight/types": "^12.3.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + } + }, + "@stoplight/json-ref-resolver": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz", + "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==", + "requires": { + "@stoplight/json": "^3.17.0", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^12.3.0", + "@types/urijs": "^1.19.16", + "dependency-graph": "~0.11.0", + "fast-memoize": "^2.5.2", + "immer": "^9.0.6", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "tslib": "^2.3.1", + "urijs": "^1.19.6" + } + }, + "@stoplight/ordered-object-literal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz", + "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w==" + }, + "@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==" + }, + "@stoplight/types": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.3.0.tgz", + "integrity": "sha512-hgzUR1z5BlYvIzUeFK5pjs5JXSvEutA9Pww31+dVicBlunsG1iXopDx/cvfBY7rHOrgtZDuvyeK4seqkwAZ6Cg==", + "requires": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + } + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + }, + "@types/urijs": { + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz", + "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ==" + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -27,6 +86,11 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==" + }, "escodegen": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", @@ -71,6 +135,16 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + }, + "immer": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==" + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -79,6 +153,11 @@ "argparse": "^2.0.1" } }, + "jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==" + }, "jsonpath": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", @@ -98,6 +177,21 @@ "type-check": "~0.3.2" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -116,6 +210,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, + "safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -130,6 +229,11 @@ "escodegen": "^1.8.1" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -143,6 +247,16 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, + "urijs": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", + "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==" + }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index fcbc375..e2830cc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1", + "@stoplight/json-ref-resolver": "^3.1.3", "commander": "^7.2.0", "js-yaml": "^4.1.0", "jsonpath": "^1.1.1" From 947170ed3e098545b1eabcac6ff457b5732d9b69 Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Tue, 28 Sep 2021 19:06:59 +0530 Subject: [PATCH 2/2] improve docs Signed-off-by: Utkarsh Srivastava --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8782158..885d47c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a very basic node based CLI for converting OpenAPI schema to JSON Schema ``` -Usage: openapi-jsonschema [options] +Usage: kubeopenapi-jsonschema [options] Options: -t, --type [type] set type of input, can be either yaml or json (default: "yaml") @@ -14,6 +14,7 @@ Options: -o [output-format] output format (default: "json") --o-filter [output-filter] output filter query --silent skip output (default: false) + --resolve [resolve-filter] root of the OpenAPI spec to resolve the $ref. It is important to note that this jsonpath MUST evaluate to one object (default: "") -h, --help display help for command ``` @@ -22,9 +23,18 @@ Options: Download the binaries from the github releases. Only linux-x64, darwin-x64 and windows-x64 binaries are released ```bash -openapi-jsonschema --location ./istio.yaml -t yaml --filter '$[?(@.kind=="CustomResourceDefinition" && @.spec.names.kind=="EnvoyFilter")]..validation.openAPIV3Schema.properties.spec' -o yaml --o-filter '$[0]' +kubeopenapi-jsonschema --location ./k8s.json -f '$.definitions' -t json --o-filter '$[0][?(@["x-kubernetes-group-version-kind"][0].kind=="Deployment")].properties.spec' --resolve "$" ``` +The above will consume kubernetes open API schema and will produce schema for Kubernetes `Deployment` + + +```bash +kubeopenapi-jsonschema --location ./istio.yaml -t yaml --filter '$[?(@.kind=="CustomResourceDefinition")]..schema.openAPIV3Schema.properties.spec' --o-filter '$' +``` + +The above will consume istio CRD manifest and will produce schema for all of the CustomResourceDefinition objects +
 
## Join the service mesh community!