catalog-backend: add support for type in catalog.rules.allow

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2025-09-05 19:10:55 +02:00
parent 8a6393a43e
commit 15575563f7
2 changed files with 60 additions and 7 deletions
@@ -27,8 +27,14 @@ const entity = {
kind: 'Group',
} as Entity,
component: {
kind: 'component',
kind: 'Component',
} as Entity,
componentWithType: {
kind: 'Component',
spec: {
type: 'service',
},
} as unknown as Entity,
location: {
kind: 'Location',
} as Entity,
@@ -164,6 +170,9 @@ describe('DefaultCatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(true);
expect(enforcer.isAllowed(entity.componentWithType, location.z)).toBe(
true,
);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(true);
});
@@ -268,6 +277,33 @@ describe('DefaultCatalogRulesEnforcer', () => {
});
});
it('should only allow components with a specific type', () => {
const enforcer = DefaultCatalogRulesEnforcer.fromConfig(
new ConfigReader({
catalog: {
rules: [
{
allow: [{ kind: 'Component', type: 'service' }],
locations: [{ type: 'url', pattern: 'https://github.com/b/**' }],
},
],
},
}),
);
expect(enforcer.isAllowed(entity.component, location.w)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.componentWithType, location.w)).toBe(true);
expect(enforcer.isAllowed(entity.componentWithType, location.y)).toBe(
false,
);
expect(enforcer.isAllowed(entity.componentWithType, location.z)).toBe(
false,
);
});
it('should allow locations with a hidden folder', () => {
const enforcer = DefaultCatalogRulesEnforcer.fromConfig(
new ConfigReader({
@@ -26,9 +26,7 @@ import { minimatch } from 'minimatch';
* An undefined list of matchers means match all, an empty list of matchers means match none.
*/
export type CatalogRule = {
allow: Array<{
kind: string;
}>;
allow: CatalogRuleAllow[];
locations?: Array<{
exact?: string;
type: string;
@@ -36,6 +34,11 @@ export type CatalogRule = {
}>;
};
type CatalogRuleAllow = {
kind: string;
type?: string;
};
/**
* Decides whether an entity from a given location is allowed to enter the
* catalog, according to some rule set.
@@ -78,6 +81,9 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
* catalog:
* rules:
* - allow: [Component, API]
* - allow:
* - kind: Resource
* type: database
* - allow: [Template]
* locations:
* - type: url
@@ -105,7 +111,14 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
const globalRules = config
.getConfigArray('catalog.rules')
.map(ruleConf => ({
allow: ruleConf.getStringArray('allow').map(kind => ({ kind })),
allow: ruleConf
.get<Array<string | CatalogRuleAllow>>('allow')
.map(kindOrObject => {
if (typeof kindOrObject === 'string') {
return { kind: kindOrObject };
}
return kindOrObject;
}),
locations: ruleConf
.getOptionalConfigArray('locations')
?.map(locationConfig => {
@@ -199,13 +212,17 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer {
return false;
}
private matchEntity(entity: Entity, matchers?: { kind: string }[]): boolean {
private matchEntity(entity: Entity, matchers?: CatalogRuleAllow[]): boolean {
if (!matchers) {
return true;
}
for (const matcher of matchers) {
if (entity?.kind?.toLowerCase() !== matcher.kind.toLowerCase()) {
if (entity.kind?.toLowerCase() !== matcher.kind.toLowerCase()) {
continue;
}
if (matcher.type && matcher.type !== entity.spec?.type) {
continue;
}