Merge pull request #4559 from kaos/kaos/cli-config-schema
backstage-cli: add config:schema command.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
New config command to export the configuration schema. When running backstage-cli with yarn, consider using `yarn --silent backstage-cli config:schema` to get a clean output on `stdout`.
|
||||
@@ -46,6 +46,7 @@ remove-plugin Removes plugin in the current repository
|
||||
|
||||
config:print Print the app configuration for the current package
|
||||
config:check Validate that the given configuration loads and matches schema
|
||||
config:schema Dump the app configuration schema
|
||||
|
||||
versions:bump Bump Backstage packages to the latest versions
|
||||
versions:check Check Backstage package versioning
|
||||
@@ -484,6 +485,27 @@ Options:
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
## config:schema
|
||||
|
||||
Scope: `root`
|
||||
|
||||
Dump the configuration schema that was collected from all local packages in the
|
||||
repo.
|
||||
|
||||
Note: when run by `yarn`, supply the yarn option `--silent` if you are using the
|
||||
output in a command line pipe to avoid non schema output in the pipeline.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli config:schema [options]
|
||||
|
||||
Print configuration schema
|
||||
|
||||
Options:
|
||||
--package <name> Only output config schema that applies to the given package
|
||||
-h, --help display help for command
|
||||
|
||||
```
|
||||
|
||||
## versions:bump
|
||||
|
||||
Scope: `root`
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"inquirer": "^7.0.4",
|
||||
"jest": "^26.0.1",
|
||||
"jest-css-modules": "^2.1.0",
|
||||
"json-schema": "^0.2.5",
|
||||
"lodash": "^4.17.19",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"ora": "^4.0.3",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { JSONSchema7 as JSONSchema } from 'json-schema';
|
||||
import { stringify as stringifyYaml } from 'yaml';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { mergeConfigSchemas } from '@backstage/config-loader';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const { schema } = await loadCliConfig({
|
||||
args: [],
|
||||
fromPackage: cmd.package,
|
||||
mockEnv: true,
|
||||
});
|
||||
|
||||
const merged = mergeConfigSchemas(
|
||||
(schema.serialize().schemas as JsonObject[]).map(
|
||||
_ => _.value as JSONSchema,
|
||||
),
|
||||
);
|
||||
|
||||
merged.title = 'Application Configuration Schema';
|
||||
merged.description =
|
||||
'This is the schema describing the structure of the app-config.yaml configuration file.';
|
||||
|
||||
if (cmd.format === 'json') {
|
||||
process.stdout.write(`${JSON.stringify(merged, null, 2)}\n`);
|
||||
} else {
|
||||
process.stdout.write(`${stringifyYaml(merged)}\n`);
|
||||
}
|
||||
};
|
||||
@@ -170,6 +170,15 @@ export function registerCommands(program: CommanderStatic) {
|
||||
)
|
||||
.action(lazy(() => import('./config/validate').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('config:schema')
|
||||
.option(
|
||||
'--package <name>',
|
||||
'Only output config schema that applies to the given package',
|
||||
)
|
||||
.description('Print configuration schema')
|
||||
.action(lazy(() => import('./config/schema').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('versions:bump')
|
||||
.description('Bump Backstage packages to the latest versions')
|
||||
|
||||
@@ -48,8 +48,10 @@ export async function loadCliConfig(options: Options) {
|
||||
configPaths,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
|
||||
// printing to stderr to not clobber stdout in case the cli command
|
||||
// outputs structured data (e.g. as config:schema does)
|
||||
process.stderr.write(
|
||||
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}\n`,
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^7.0.3",
|
||||
"fs-extra": "^9.0.0",
|
||||
"json-schema": "^0.2.5",
|
||||
@@ -42,7 +43,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"@types/json-schema-merge-allof": "^0.6.0",
|
||||
"@types/mock-fs": "^4.10.0",
|
||||
"@types/node": "^12.0.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { readEnvConfig, loadConfigSchema } from './lib';
|
||||
export { readEnvConfig, loadConfigSchema, mergeConfigSchemas } from './lib';
|
||||
export type { ConfigSchema, ConfigVisibility } from './lib';
|
||||
export { loadConfig } from './loader';
|
||||
export type { LoadConfigOptions } from './loader';
|
||||
|
||||
@@ -77,8 +77,41 @@ export function compileConfigSchemas(
|
||||
}
|
||||
}
|
||||
|
||||
const merged = mergeConfigSchemas(schemas.map(_ => _.value));
|
||||
const validate = ajv.compile(merged);
|
||||
|
||||
return configs => {
|
||||
const config = ConfigReader.fromConfigs(configs).get();
|
||||
|
||||
visibilityByPath.clear();
|
||||
|
||||
const valid = validate(config);
|
||||
if (!valid) {
|
||||
const errors = validate.errors ?? [];
|
||||
return {
|
||||
errors: errors.map(({ dataPath, message, params }) => {
|
||||
const paramStr = Object.entries(params)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join(' ');
|
||||
return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;
|
||||
}),
|
||||
visibilityByPath: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
visibilityByPath: new Map(visibilityByPath),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of configuration schemas from packages, merge them
|
||||
* into a single json schema.
|
||||
*/
|
||||
export function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {
|
||||
const merged = mergeAllOf(
|
||||
{ allOf: schemas.map(_ => _.value) },
|
||||
{ allOf: schemas },
|
||||
{
|
||||
// JSONSchema is typically subtractive, as in it always reduces the set of allowed
|
||||
// inputs through constraints. This changes the object property merging to be additive
|
||||
@@ -107,30 +140,5 @@ export function compileConfigSchemas(
|
||||
} as Partial<Resolvers<JSONSchema>>,
|
||||
},
|
||||
);
|
||||
|
||||
const validate = ajv.compile(merged);
|
||||
|
||||
return configs => {
|
||||
const config = ConfigReader.fromConfigs(configs).get();
|
||||
|
||||
visibilityByPath.clear();
|
||||
|
||||
const valid = validate(config);
|
||||
if (!valid) {
|
||||
const errors = validate.errors ?? [];
|
||||
return {
|
||||
errors: errors.map(({ dataPath, message, params }) => {
|
||||
const paramStr = Object.entries(params)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join(' ');
|
||||
return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;
|
||||
}),
|
||||
visibilityByPath: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
visibilityByPath: new Map(visibilityByPath),
|
||||
};
|
||||
};
|
||||
return merged;
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { mergeConfigSchemas } from './compile';
|
||||
export { loadConfigSchema } from './load';
|
||||
export type { ConfigSchema, ConfigVisibility } from './types';
|
||||
|
||||
Reference in New Issue
Block a user