Merge pull request #30914 from drodil/builtin_sort
fix(catalog): sort default spec fields for stable hash
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Sort built-in relation fields for more stable entity hash in the processing engine
|
||||
@@ -61,6 +61,8 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
|
||||
emit: CatalogProcessorEmit,
|
||||
): Promise<Entity>;
|
||||
// (undocumented)
|
||||
preProcessEntity(entity: Entity): Promise<Entity>;
|
||||
// (undocumented)
|
||||
validateEntityKind(entity: Entity): Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,49 @@ import {
|
||||
import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
|
||||
|
||||
describe('BuiltinKindsEntityProcessor', () => {
|
||||
describe('preProcessEntity', () => {
|
||||
const processor = new BuiltinKindsEntityProcessor();
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('should order relation fields correctly', async () => {
|
||||
const entity: ComponentEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'n' },
|
||||
spec: {
|
||||
type: 'service',
|
||||
owner: 'o',
|
||||
subcomponentOf: 's',
|
||||
lifecycle: 'l',
|
||||
providesApis: ['b', 'a'],
|
||||
consumesApis: ['c', 'x'],
|
||||
dependsOn: ['resource:r', 'component:d'],
|
||||
dependencyOf: ['resource:f', 'component:g'],
|
||||
system: 's',
|
||||
},
|
||||
};
|
||||
|
||||
const ret = await processor.preProcessEntity(entity);
|
||||
|
||||
expect(ret).toEqual({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'n' },
|
||||
spec: {
|
||||
type: 'service',
|
||||
owner: 'o',
|
||||
subcomponentOf: 's',
|
||||
lifecycle: 'l',
|
||||
providesApis: ['a', 'b'],
|
||||
consumesApis: ['c', 'x'],
|
||||
dependsOn: ['component:d', 'resource:r'],
|
||||
dependencyOf: ['component:g', 'resource:f'],
|
||||
system: 's',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('postProcessEntity', () => {
|
||||
const processor = new BuiltinKindsEntityProcessor();
|
||||
const location = { type: 'a', target: 'b' };
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
CatalogProcessorEmit,
|
||||
processingResult,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { get, set } from 'lodash';
|
||||
|
||||
/** @public */
|
||||
export class BuiltinKindsEntityProcessor implements CatalogProcessor {
|
||||
@@ -83,6 +84,30 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
async preProcessEntity(entity: Entity): Promise<Entity> {
|
||||
function sortField(field: string) {
|
||||
const value = get(entity, field);
|
||||
if (
|
||||
value &&
|
||||
Array.isArray(value) &&
|
||||
value.every(v => typeof v === 'string')
|
||||
) {
|
||||
set(entity, field, value.sort());
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the fields of the entity to ensure consistent hash
|
||||
sortField('spec.providesApis');
|
||||
sortField('spec.consumesApis');
|
||||
sortField('spec.dependsOn');
|
||||
sortField('spec.dependencyOf');
|
||||
sortField('spec.memberOf');
|
||||
sortField('spec.children');
|
||||
sortField('spec.members');
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
async postProcessEntity(
|
||||
entity: Entity,
|
||||
_location: LocationSpec,
|
||||
|
||||
Reference in New Issue
Block a user