Merge pull request #14584 from AmericanAirlines/3574-catalog-import-filter
adding filters to catalog import
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Added a new `catalog.rules[].location` configuration that makes it possible to configure catalog rules to only apply to specific locations, either via exact match or a glob pattern.
|
||||
Vendored
+31
@@ -38,6 +38,37 @@ export interface Config {
|
||||
* E.g. ["Component", "API", "Template", "Location"]
|
||||
*/
|
||||
allow: Array<string>;
|
||||
/**
|
||||
* Limit this rule to a specific location
|
||||
*
|
||||
* Example with a fixed location
|
||||
* { "type": "url", "exact": "https://github.com/a/b/blob/file.yaml"}
|
||||
*
|
||||
* Example using a Regex
|
||||
* { "type": "url", "pattern": "https://github.com/org/*\/blob/master/*.yaml"}
|
||||
*
|
||||
* Using both exact and pattern will result in an error starting the application
|
||||
*/
|
||||
locations?: Array<{
|
||||
/**
|
||||
* The type of location, e.g. "url".
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* The exact location, e.g.
|
||||
* "https://github.com/org/repo/blob/master/users.yaml".
|
||||
*
|
||||
* The exact location can also be used to match on locations
|
||||
* that contain glob characters themselves, e.g.
|
||||
* "https://github.com/org/*\/blob/master/*.yaml".
|
||||
*/
|
||||
exact?: string;
|
||||
/**
|
||||
* The pattern allowed for the location, e.g.
|
||||
* "https://github.com/org/*\/blob/master/*.yaml".
|
||||
*/
|
||||
pattern?: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"knex": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.0.2",
|
||||
"prom-client": "^14.0.1",
|
||||
|
||||
@@ -35,6 +35,10 @@ const entity = {
|
||||
};
|
||||
|
||||
const location: Record<string, LocationSpec> = {
|
||||
w: {
|
||||
type: 'url',
|
||||
target: 'https://github.com/b/c/blob/master/w.yaml',
|
||||
},
|
||||
x: {
|
||||
type: 'url',
|
||||
target: 'https://github.com/a/b/blob/master/x.yaml',
|
||||
@@ -50,6 +54,28 @@ const location: Record<string, LocationSpec> = {
|
||||
};
|
||||
|
||||
describe('DefaultCatalogRulesEnforcer', () => {
|
||||
it('should throw an error if both pattern and exact are used', () => {
|
||||
expect(() =>
|
||||
DefaultCatalogRulesEnforcer.fromConfig(
|
||||
new ConfigReader({
|
||||
catalog: {
|
||||
rules: [
|
||||
{
|
||||
allow: ['Component'],
|
||||
locations: [
|
||||
{
|
||||
type: 'url',
|
||||
pattern: 'https://github.com/b/**',
|
||||
exact: 'https://github.com/a/b/blob/master/w.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toThrow(/cannot have both exact and pattern values/i);
|
||||
});
|
||||
it('should deny by default', () => {
|
||||
const enforcer = new DefaultCatalogRulesEnforcer([]);
|
||||
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
|
||||
@@ -205,7 +231,7 @@ describe('DefaultCatalogRulesEnforcer', () => {
|
||||
const enforcer = DefaultCatalogRulesEnforcer.fromConfig(
|
||||
new ConfigReader({
|
||||
catalog: {
|
||||
rules: [{ allow: ['Group'], locations: [{ type: 'url' }] }],
|
||||
rules: [{ allow: ['Group'] }],
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -216,5 +242,25 @@ describe('DefaultCatalogRulesEnforcer', () => {
|
||||
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
|
||||
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
|
||||
});
|
||||
|
||||
it('should only allow locations that match a given pattern', () => {
|
||||
const enforcer = DefaultCatalogRulesEnforcer.fromConfig(
|
||||
new ConfigReader({
|
||||
catalog: {
|
||||
rules: [
|
||||
{
|
||||
allow: ['Component'],
|
||||
locations: [
|
||||
{ type: 'url', pattern: 'https://github.com/b/**' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(enforcer.isAllowed(entity.component, location.w)).toBe(true);
|
||||
expect(enforcer.isAllowed(entity.component, location.y)).toBe(false);
|
||||
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import path from 'path';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
import minimatch from 'minimatch';
|
||||
|
||||
/**
|
||||
* Rules to apply to catalog entities.
|
||||
@@ -29,8 +30,9 @@ export type CatalogRule = {
|
||||
kind: string;
|
||||
}>;
|
||||
locations?: Array<{
|
||||
target?: string;
|
||||
exact?: string;
|
||||
type: string;
|
||||
pattern?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
@@ -76,6 +78,14 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
|
||||
* catalog:
|
||||
* rules:
|
||||
* - allow: [Component, API]
|
||||
* - allow: [Template]
|
||||
* locations:
|
||||
* - type: url
|
||||
* pattern: https://github.com/org/*\/blob/master/template.yaml
|
||||
* - allow: [Location]
|
||||
* locations:
|
||||
* - type: url
|
||||
* pattern: https://github.com/org/repo/blob/master/location.yaml
|
||||
*
|
||||
* locations:
|
||||
* - type: url
|
||||
@@ -92,9 +102,26 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
|
||||
const rules = new Array<CatalogRule>();
|
||||
|
||||
if (config.has('catalog.rules')) {
|
||||
const globalRules = config.getConfigArray('catalog.rules').map(sub => ({
|
||||
allow: sub.getStringArray('allow').map(kind => ({ kind })),
|
||||
}));
|
||||
const globalRules = config
|
||||
.getConfigArray('catalog.rules')
|
||||
.map(ruleConf => ({
|
||||
allow: ruleConf.getStringArray('allow').map(kind => ({ kind })),
|
||||
locations: ruleConf
|
||||
.getOptionalConfigArray('locations')
|
||||
?.map(locationConfig => {
|
||||
const location = {
|
||||
pattern: locationConfig.getOptionalString('pattern'),
|
||||
type: locationConfig.getString('type'),
|
||||
exact: locationConfig.getOptionalString('exact'),
|
||||
};
|
||||
if (location.pattern && location.exact) {
|
||||
throw new Error(
|
||||
'A catalog rule location cannot have both exact and pattern values',
|
||||
);
|
||||
}
|
||||
return location;
|
||||
}),
|
||||
}));
|
||||
rules.push(...globalRules);
|
||||
} else {
|
||||
rules.push(...DefaultCatalogRulesEnforcer.defaultRules);
|
||||
@@ -108,11 +135,11 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
|
||||
return [];
|
||||
}
|
||||
const type = locConf.getString('type');
|
||||
const target = resolveTarget(type, locConf.getString('target'));
|
||||
const exact = resolveTarget(type, locConf.getString('target'));
|
||||
|
||||
return locConf.getConfigArray('rules').map(ruleConf => ({
|
||||
allow: ruleConf.getStringArray('allow').map(kind => ({ kind })),
|
||||
locations: [{ type, target }],
|
||||
locations: [{ type, exact }],
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -144,7 +171,7 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
|
||||
|
||||
private matchLocation(
|
||||
location: LocationSpec,
|
||||
matchers?: { target?: string; type: string }[],
|
||||
matchers?: { exact?: string; type: string; pattern?: string }[],
|
||||
): boolean {
|
||||
if (!matchers) {
|
||||
return true;
|
||||
@@ -154,7 +181,13 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
|
||||
if (matcher.type !== location?.type) {
|
||||
continue;
|
||||
}
|
||||
if (matcher.target && matcher.target !== location?.target) {
|
||||
if (matcher.exact && matcher.exact !== location?.target) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
matcher.pattern &&
|
||||
!minimatch(location?.target, matcher.pattern, { nocase: true })
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user