Merge pull request #18837 from backstage/rugvip/strict-config-schema

config-loader: reject invalid configuration schema
This commit is contained in:
Patrik Oldsberg
2023-07-28 16:55:35 +02:00
committed by GitHub
16 changed files with 153 additions and 114 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/config-loader': minor
---
Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored.
In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`.
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
'@backstage/plugin-catalog-backend-module-puppetdb': patch
'@backstage/plugin-catalog-backend-module-msgraph': patch
'@backstage/plugin-catalog-backend-module-gerrit': patch
'@backstage/plugin-catalog-backend-module-github': patch
'@backstage/plugin-catalog-backend-module-gitlab': patch
'@backstage/plugin-events-backend-module-aws-sqs': patch
'@backstage/plugin-catalog-backend-module-azure': patch
'@backstage/plugin-catalog-backend-module-aws': patch
'@backstage/backend-common': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-proxy-backend': patch
---
Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+3 -3
View File
@@ -77,17 +77,17 @@ export interface Config {
*/
connection:
| string
| Partial<{
| {
/**
* Password that belongs to the client User
* @visibility secret
*/
password: string;
password?: string;
/**
* Other connection settings
*/
[key: string]: unknown;
}>;
};
/** Database name prefix override */
prefix?: string;
/**
+29 -5
View File
@@ -15,6 +15,7 @@
*/
import fs from 'fs-extra';
import { EOL } from 'os';
import {
resolve as resolvePath,
relative as relativePath,
@@ -163,7 +164,7 @@ async function compileTsSchemas(paths: string[]) {
// Lazy loaded, because this brings up all of TypeScript and we don't
// want that eagerly loaded in tests
const { getProgramFromFiles, generateSchema } = await import(
const { getProgramFromFiles, buildGenerator } = await import(
'typescript-json-schema'
);
@@ -183,17 +184,40 @@ async function compileTsSchemas(paths: string[]) {
const tsSchemas = paths.map(path => {
let value;
try {
value = generateSchema(
const generator = buildGenerator(
program,
// All schemas should export a `Config` symbol
'Config',
// This enables the use of these tags in TSDoc comments
{
required: true,
validationKeywords: ['visibility', 'deepVisibility', 'deprecated'],
},
[path.split(sep).join('/')], // Unix paths are expected for all OSes here
) as JsonObject | null;
);
// All schemas should export a `Config` symbol
value = generator?.getSchemaForSymbol('Config') as JsonObject | null;
// This makes sure that no additional symbols are defined in the schema. We don't allow
// this because they share a global namespace and will be merged together, leading to
// unpredictable behavior.
const userSymbols = new Set(generator?.getUserSymbols());
userSymbols.delete('Config');
if (userSymbols.size !== 0) {
const names = Array.from(userSymbols).join("', '");
throw new Error(
`Invalid configuration schema in ${path}, additional symbol definitions are not allowed, found '${names}'`,
);
}
// This makes sure that no unsupported types are used in the schema, for example `Record<,>`.
// The generator will extract these as a schema reference, which will in turn be broken for our usage.
const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {});
if (reffedDefs.length !== 0) {
const lines = reffedDefs.join(`${EOL} `);
throw new Error(
`Invalid configuration schema in ${path}, the following definitions are not supported:${EOL}${EOL} ${lines}`,
);
}
} catch (error) {
assertError(error);
if (error.message !== 'type Config not found') {
+4 -5
View File
@@ -59,9 +59,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
{
| {
[name: string]: {
/**
* (Required) AWS S3 Bucket Name
*/
@@ -81,8 +80,8 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
};
};
};
}
+30 -30
View File
@@ -16,35 +16,6 @@
import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks';
interface AzureDevOpsConfig {
/**
* (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host.
*/
host: string;
/**
* (Required) Your organization slug.
*/
organization: string;
/**
* (Required) Your project slug.
*/
project: string;
/**
* (Optional) The repository name. Wildcards are supported as show on the examples above.
* If not set, all repositories will be searched.
*/
repository?: string;
/**
* (Optional) Where to find catalog-info.yaml files. Wildcards are supported.
* If not set, defaults to /catalog-info.yaml.
*/
path?: string;
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
export interface Config {
catalog?: {
/**
@@ -54,7 +25,36 @@ export interface Config {
/**
* AzureDevopsEntityProvider configuration
*/
azureDevOps?: Record<string, AzureDevOpsConfig>;
azureDevOps?: {
[name: string]: {
/**
* (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host.
*/
host: string;
/**
* (Required) Your organization slug.
*/
organization: string;
/**
* (Required) Your project slug.
*/
project: string;
/**
* (Optional) The repository name. Wildcards are supported as show on the examples above.
* If not set, all repositories will be searched.
*/
repository?: string;
/**
* (Optional) Where to find catalog-info.yaml files. Wildcards are supported.
* If not set, defaults to /catalog-info.yaml.
*/
path?: string;
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
};
};
};
};
}
+12 -13
View File
@@ -45,24 +45,23 @@ export interface Config {
*/
filters?: {
/**
* (Optional) Filter for the repository slug.
* (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
repoSlug?: RegExp;
repoSlug?: string;
/**
* (Optional) Filter for the project key.
* (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
projectKey?: RegExp;
projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
{
| {
[name: string]: {
/**
* (Optional) Path to the catalog file. Default to "/catalog-info.yaml".
* @visibility frontend
@@ -79,22 +78,22 @@ export interface Config {
*/
filters?: {
/**
* (Optional) Filter for the repository slug.
* (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
repoSlug?: RegExp;
repoSlug?: string;
/**
* (Optional) Filter for the project key.
* (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
projectKey?: RegExp;
projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
};
};
};
}
+12 -13
View File
@@ -37,24 +37,23 @@ export interface Config {
*/
filters?: {
/**
* (Optional) Filter for the repository slug.
* (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
repoSlug?: RegExp;
repoSlug?: string;
/**
* (Optional) Filter for the project key.
* (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
projectKey?: RegExp;
projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
{
| {
[name: string]: {
/**
* (Optional) Path to the catalog file. Default to "/catalog-info.yaml".
* @visibility frontend
@@ -66,22 +65,22 @@ export interface Config {
*/
filters?: {
/**
* (Optional) Filter for the repository slug.
* (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
repoSlug?: RegExp;
repoSlug?: string;
/**
* (Optional) Filter for the project key.
* (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
projectKey?: RegExp;
projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
};
};
};
}
+4 -5
View File
@@ -25,9 +25,8 @@ export interface Config {
*
* Maps provider id with configuration.
*/
gerrit?: Record<
string,
{
gerrit?: {
[name: string]: {
/**
* (Required) The host of the Gerrit integration to use.
*/
@@ -42,8 +41,8 @@ export interface Config {
* The branch where the provider will try to find entities. Defaults to "master".
*/
branch?: string;
}
>;
};
};
};
};
}
+4 -5
View File
@@ -118,9 +118,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
{
| {
[name: string]: {
/**
* (Optional) The hostname of your GitHub Enterprise instance.
* Default: `github.com`.
@@ -182,8 +181,8 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
};
};
};
}
+7 -8
View File
@@ -22,9 +22,8 @@ export interface Config {
/**
* GitlabDiscoveryEntityProvider configuration
*/
gitlab?: Record<
string,
{
gitlab?: {
[name: string]: {
/**
* (Required) Gitlab's host name.
*/
@@ -51,21 +50,21 @@ export interface Config {
/**
* (Optional) RegExp for the Project Name Pattern
*/
projectPattern?: RegExp;
projectPattern?: string;
/**
* (Optional) RegExp for the User Name Pattern
*/
userPattern?: RegExp;
userPattern?: string;
/**
* (Optional) RegExp for the Group Name Pattern
*/
groupPattern?: RegExp;
groupPattern?: string;
/**
* (Optional) Skip forked repository
*/
skipForkedRepos?: boolean;
}
>;
};
};
};
};
}
+4 -5
View File
@@ -209,9 +209,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
{
| {
[name: string]: {
/**
* The prefix of the target that this matches on, e.g.
* "https://graph.microsoft.com/v1.0", with no trailing slash.
@@ -296,8 +295,8 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
};
};
};
}
+4 -5
View File
@@ -46,9 +46,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinition;
}
| Record<
string,
{
| {
[name: string]: {
/**
* (Required) The base URL of PuppetDB API instance.
*/
@@ -61,8 +60,8 @@ export interface Config {
* (Optional) Task schedule definition for the refresh.
*/
schedule?: TaskScheduleDefinition;
}
>;
};
};
};
};
}
+4 -5
View File
@@ -31,9 +31,8 @@ export interface Config {
* Contains a record per topic for which an AWS SQS queue
* should be used as source of events.
*/
topics: Record<
string,
{
topics: {
[name: string]: {
/**
* (Required) Queue-related configuration.
*/
@@ -69,8 +68,8 @@ export interface Config {
* Default: 1 minute.
*/
waitTimeAfterEmptyReceive: HumanDuration;
}
>;
};
};
};
};
};
+5 -5
View File
@@ -42,13 +42,13 @@ export interface Config {
username: string;
/** @visibility secret */
apiKey: string;
extraRequestHeaders?: Partial<{
extraRequestHeaders?: {
/** @visibility secret */
Authorization: string;
Authorization?: string;
/** @visibility secret */
authorization: string;
[key: string]: string;
}>;
authorization?: string;
[key: string]: string | undefined;
};
}[];
};
}
+7 -7
View File
@@ -31,17 +31,17 @@ export interface Config {
/**
* Object with extra headers to be added to target requests.
*/
headers?: Partial<{
headers?: {
/** @visibility secret */
Authorization: string;
Authorization?: string;
/** @visibility secret */
authorization: string;
authorization?: string;
/** @visibility secret */
'X-Api-Key': string;
'X-Api-Key'?: string;
/** @visibility secret */
'x-api-key': string;
[key: string]: string;
}>;
'x-api-key'?: string;
[key: string]: string | undefined;
};
/**
* Changes the origin of the host header to the target URL. Default: true.
*/