Merge pull request #14898 from backstage/freben/validate-some-more

enable multi-processor validation of entity kinds
This commit is contained in:
Fredrik Adelöw
2022-11-29 12:05:34 +01:00
committed by GitHub
6 changed files with 102 additions and 11 deletions
+2 -1
View File
@@ -133,7 +133,7 @@ export class CatalogBuilder {
...permissionRules: Array<
CatalogPermissionRule | Array<CatalogPermissionRule>
>
): void;
): this;
addProcessor(
...processors: Array<CatalogProcessor | Array<CatalogProcessor>>
): CatalogBuilder;
@@ -164,6 +164,7 @@ export class CatalogBuilder {
errors: Error[];
}) => Promise<void> | void;
}): void;
useLegacySingleProcessorValidation(): this;
}
// @alpha
@@ -245,6 +245,7 @@ class TestHarness {
logger,
parser: defaultEntityDataParser,
policy: EntityPolicies.allOf([]),
legacySingleProcessorValidation: false,
});
const stitcher = new Stitcher(db, logger);
const catalog = new DefaultEntitiesCatalog(db, stitcher);
@@ -97,6 +97,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => {
parser: defaultEntityDataParser,
policy: EntityPolicies.allOf([]),
rulesEnforcer: { isAllowed: () => true },
legacySingleProcessorValidation: false,
});
it('runs a minimal processing', async () => {
@@ -190,6 +191,54 @@ describe('DefaultCatalogProcessingOrchestrator', () => {
ok: true,
});
});
it('runs all processor validations when asked to', async () => {
const validate = jest.fn(async () => true);
const processor1: Partial<CatalogProcessor> = {
validateEntityKind: validate,
};
const processor2: Partial<CatalogProcessor> = {
validateEntityKind: validate,
};
const legacy = new DefaultCatalogProcessingOrchestrator({
processors: [
processor1 as CatalogProcessor,
processor2 as CatalogProcessor,
],
integrations: ScmIntegrations.fromConfig(new ConfigReader({})),
logger: getVoidLogger(),
parser: defaultEntityDataParser,
policy: EntityPolicies.allOf([]),
rulesEnforcer: { isAllowed: () => true },
legacySingleProcessorValidation: true,
});
const modern = new DefaultCatalogProcessingOrchestrator({
processors: [
processor1 as CatalogProcessor,
processor2 as CatalogProcessor,
],
integrations: ScmIntegrations.fromConfig(new ConfigReader({})),
logger: getVoidLogger(),
parser: defaultEntityDataParser,
policy: EntityPolicies.allOf([]),
rulesEnforcer: { isAllowed: () => true },
legacySingleProcessorValidation: false,
});
await expect(legacy.process({ entity })).resolves.toMatchObject({
ok: true,
});
expect(validate).toHaveBeenCalledTimes(1);
validate.mockClear();
await expect(modern.process({ entity })).resolves.toMatchObject({
ok: true,
});
expect(validate).toHaveBeenCalledTimes(2);
});
});
describe('rules', () => {
@@ -231,6 +280,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => {
parser,
policy: EntityPolicies.allOf([]),
rulesEnforcer,
legacySingleProcessorValidation: false,
});
rulesEnforcer.isAllowed.mockReturnValueOnce(true);
@@ -272,6 +322,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => {
parser,
policy: EntityPolicies.allOf([new FailingEntityPolicy()]),
rulesEnforcer,
legacySingleProcessorValidation: false,
});
await expect(
@@ -76,6 +76,7 @@ export class DefaultCatalogProcessingOrchestrator
parser: CatalogProcessorParser;
policy: EntityPolicy;
rulesEnforcer: CatalogRulesEnforcer;
legacySingleProcessorValidation: boolean;
},
) {}
@@ -253,19 +254,17 @@ export class DefaultCatalogProcessingOrchestrator
);
}
let foundKind = false;
let valid = false;
for (const processor of this.options.processors) {
if (processor.validateEntityKind) {
try {
foundKind = await processor.validateEntityKind(entity);
if (foundKind) {
// TODO(freben): It would make sense to keep running, so that
// multiple processors could have a go at making checks. For
// example, an org may want to add additional rules on top of the
// provided ones. But that would be a breaking change, so we'll
// postpone that to a future processors rewrite.
break;
const thisValid = await processor.validateEntityKind(entity);
if (thisValid) {
valid = true;
if (this.options.legacySingleProcessorValidation) {
break;
}
}
} catch (e) {
throw new InputError(
@@ -276,7 +275,7 @@ export class DefaultCatalogProcessingOrchestrator
}
}
if (!foundKind) {
if (!valid) {
throw new InputError(
`No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`,
);
@@ -154,6 +154,7 @@ export class CatalogBuilder {
private locationAnalyzer: LocationAnalyzer | undefined = undefined;
private readonly permissionRules: CatalogPermissionRule[];
private allowedLocationType: string[];
private legacySingleProcessorValidation = false;
/**
* Creates a catalog builder.
@@ -384,6 +385,7 @@ export class CatalogBuilder {
>
) {
this.permissionRules.push(...permissionRules.flat());
return this;
}
/**
@@ -396,6 +398,15 @@ export class CatalogBuilder {
return this;
}
/**
* Enables the legacy behaviour of canceling validation early whenever only a
* single processor declares an entity kind to be valid.
*/
useLegacySingleProcessorValidation(): this {
this.legacySingleProcessorValidation = true;
return this;
}
/**
* Wires up and returns all of the component parts of the catalog
*/
@@ -429,6 +440,7 @@ export class CatalogBuilder {
logger,
parser,
policy,
legacySingleProcessorValidation: this.legacySingleProcessorValidation,
});
const stitcher = new Stitcher(dbClient, logger);
const unauthorizedEntitiesCatalog = new DefaultEntitiesCatalog(