Merge pull request #3373 from backstage/freben/remove-ancestors-descendants
catalog: remove group ancestors and descendants
This commit is contained in:
@@ -23,34 +23,6 @@ import {
|
||||
import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
|
||||
|
||||
describe('BuiltinKindsEntityProcessor', () => {
|
||||
it('fills in fields for #3049', async () => {
|
||||
const p = new BuiltinKindsEntityProcessor();
|
||||
const result = await p.preProcessEntity({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'n',
|
||||
},
|
||||
spec: {
|
||||
type: 't',
|
||||
children: [],
|
||||
} as any,
|
||||
});
|
||||
expect(result).toEqual({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'n',
|
||||
},
|
||||
spec: {
|
||||
type: 't',
|
||||
children: [],
|
||||
ancestors: [],
|
||||
descendants: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('postProcessEntity', () => {
|
||||
const processor = new BuiltinKindsEntityProcessor();
|
||||
const location = { type: 'a', target: 'b' };
|
||||
@@ -215,9 +187,7 @@ describe('BuiltinKindsEntityProcessor', () => {
|
||||
spec: {
|
||||
type: 't',
|
||||
parent: 'p',
|
||||
ancestors: [],
|
||||
children: ['c'],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -53,25 +53,6 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
|
||||
userEntityV1alpha1Validator,
|
||||
];
|
||||
|
||||
async preProcessEntity(entity: Entity): Promise<Entity> {
|
||||
// NOTE(freben): Part of Group field deprecation on Nov 22nd, 2020. Fields
|
||||
// scheduled for removal Dec 6th, 2020. This code can be deleted after that
|
||||
// point. See https://github.com/backstage/backstage/issues/3049
|
||||
if (
|
||||
entity.apiVersion === 'backstage.io/v1alpha1' &&
|
||||
entity.kind === 'Group' &&
|
||||
entity.spec
|
||||
) {
|
||||
if (!entity.spec.ancestors) {
|
||||
entity.spec.ancestors = [];
|
||||
}
|
||||
if (!entity.spec.descendants) {
|
||||
entity.spec.descendants = [];
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
async validateEntityKind(entity: Entity): Promise<boolean> {
|
||||
for (const validator of this.validators) {
|
||||
const result = await validator.check(entity);
|
||||
|
||||
@@ -47,7 +47,7 @@ function group(data: RecursivePartial<GroupEntity>): GroupEntity {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'name' },
|
||||
spec: { type: 'type', ancestors: [], children: [], descendants: [] },
|
||||
spec: { type: 'type', children: [] },
|
||||
} as GroupEntity,
|
||||
data,
|
||||
);
|
||||
@@ -173,9 +173,7 @@ describe('readLdapGroups', () => {
|
||||
},
|
||||
spec: {
|
||||
type: 'type-value',
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -150,9 +150,7 @@ export async function readLdapGroups(
|
||||
},
|
||||
spec: {
|
||||
type: 'unknown',
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -49,9 +49,7 @@ function group(data: RecursivePartial<GroupEntity>): GroupEntity {
|
||||
name: 'name',
|
||||
},
|
||||
spec: {
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
type: 'team',
|
||||
},
|
||||
} as GroupEntity,
|
||||
@@ -306,33 +304,20 @@ describe('read microsoft graph', () => {
|
||||
resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);
|
||||
|
||||
expect(rootGroup.spec.parent).toBeUndefined();
|
||||
expect(rootGroup.spec.ancestors).toEqual(expect.arrayContaining([]));
|
||||
expect(rootGroup.spec.children).toEqual(
|
||||
expect.arrayContaining(['a', 'b']),
|
||||
);
|
||||
expect(rootGroup.spec.descendants).toEqual(
|
||||
expect.arrayContaining(['a', 'b', 'c']),
|
||||
);
|
||||
|
||||
expect(groupA.spec.parent).toEqual('root');
|
||||
expect(groupA.spec.ancestors).toEqual(expect.arrayContaining(['root']));
|
||||
expect(groupA.spec.children).toEqual(expect.arrayContaining([]));
|
||||
expect(groupA.spec.descendants).toEqual(expect.arrayContaining([]));
|
||||
|
||||
expect(groupB.spec.parent).toEqual('root');
|
||||
expect(groupB.spec.ancestors).toEqual(expect.arrayContaining(['root']));
|
||||
expect(groupB.spec.children).toEqual(expect.arrayContaining(['c']));
|
||||
expect(groupB.spec.descendants).toEqual(expect.arrayContaining(['c']));
|
||||
|
||||
expect(groupC.spec.parent).toEqual('b');
|
||||
expect(groupC.spec.ancestors).toEqual(
|
||||
expect.arrayContaining(['root', 'b']),
|
||||
);
|
||||
expect(groupC.spec.children).toEqual(expect.arrayContaining([]));
|
||||
expect(groupC.spec.descendants).toEqual(expect.arrayContaining([]));
|
||||
|
||||
expect(user1.spec.memberOf).toEqual(expect.arrayContaining(['a']));
|
||||
|
||||
expect(user2.spec.memberOf).toEqual(expect.arrayContaining(['b', 'c']));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
|
||||
import limiterFactory from 'p-limit';
|
||||
import { buildMemberOf, buildOrgHierarchy } from '../util/org';
|
||||
import { MicrosoftGraphClient } from './client';
|
||||
import {
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,
|
||||
MICROSOFT_GRAPH_USER_ID_ANNOTATION,
|
||||
} from './constants';
|
||||
import limiterFactory from 'p-limit';
|
||||
|
||||
export function normalizeEntityName(name: string): string {
|
||||
return name
|
||||
@@ -98,7 +98,7 @@ export async function readMicrosoftGraphOrganization(
|
||||
): Promise<{
|
||||
rootGroup: GroupEntity; // With all relations empty
|
||||
}> {
|
||||
// For now we expect a single root orgranization
|
||||
// For now we expect a single root organization
|
||||
const organization = await client.getOrganization(tenantId);
|
||||
const name = normalizeEntityName(organization.displayName!);
|
||||
const rootGroup: GroupEntity = {
|
||||
@@ -113,9 +113,7 @@ export async function readMicrosoftGraphOrganization(
|
||||
},
|
||||
spec: {
|
||||
type: 'root',
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -165,9 +163,7 @@ export async function readMicrosoftGraphGroups(
|
||||
spec: {
|
||||
type: 'team',
|
||||
// TODO: We could include a group email and picture
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -278,7 +274,7 @@ export function resolveRelations(
|
||||
});
|
||||
});
|
||||
|
||||
// Make sure that all groups have proper ancestors and descendants
|
||||
// Make sure that all groups have proper parents and children
|
||||
buildOrgHierarchy(groups);
|
||||
|
||||
// Set relations for all users
|
||||
|
||||
@@ -100,9 +100,7 @@ describe('github', () => {
|
||||
spec: {
|
||||
type: 'team',
|
||||
parent: 'parent',
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -162,9 +162,7 @@ export async function getOrganizationTeams(
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
ancestors: [],
|
||||
children: [],
|
||||
descendants: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function g(
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name },
|
||||
spec: { type: 'team', parent, children, ancestors: [], descendants: [] },
|
||||
spec: { type: 'team', parent, children },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,28 +43,16 @@ describe('buildOrgHierarchy', () => {
|
||||
expect(d.spec.children).toEqual([]);
|
||||
});
|
||||
|
||||
it('fills out descendants', () => {
|
||||
const a = g('a', undefined, []);
|
||||
const b = g('b', 'a', []);
|
||||
const c = g('c', 'b', []);
|
||||
const d = g('d', 'a', []);
|
||||
it('sets parent of groups children', () => {
|
||||
const a = g('a', undefined, ['b', 'd']);
|
||||
const b = g('b', undefined, ['c']);
|
||||
const c = g('c', undefined, []);
|
||||
const d = g('d', undefined, []);
|
||||
buildOrgHierarchy([a, b, c, d]);
|
||||
expect(a.spec.descendants).toEqual(expect.arrayContaining(['b', 'c', 'd']));
|
||||
expect(b.spec.descendants).toEqual(expect.arrayContaining(['c']));
|
||||
expect(c.spec.descendants).toEqual([]);
|
||||
expect(d.spec.descendants).toEqual([]);
|
||||
});
|
||||
|
||||
it('fills out ancestors', () => {
|
||||
const a = g('a', undefined, []);
|
||||
const b = g('b', 'a', []);
|
||||
const c = g('c', 'b', []);
|
||||
const d = g('d', 'a', []);
|
||||
buildOrgHierarchy([a, b, c, d]);
|
||||
expect(a.spec.ancestors).toEqual([]);
|
||||
expect(b.spec.ancestors).toEqual(expect.arrayContaining(['a']));
|
||||
expect(c.spec.ancestors).toEqual(expect.arrayContaining(['a', 'b']));
|
||||
expect(d.spec.ancestors).toEqual(expect.arrayContaining(['a']));
|
||||
expect(a.spec.parent).toBeUndefined();
|
||||
expect(b.spec.parent).toBe('a');
|
||||
expect(c.spec.parent).toBe('b');
|
||||
expect(d.spec.parent).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -35,62 +35,17 @@ export function buildOrgHierarchy(groups: GroupEntity[]) {
|
||||
}
|
||||
|
||||
//
|
||||
// Make sure that g.descendants is complete
|
||||
// Make sure that g.children.parent is g
|
||||
//
|
||||
|
||||
function visitDescendants(current: GroupEntity): string[] {
|
||||
if (current.spec.descendants.length) {
|
||||
return current.spec.descendants;
|
||||
}
|
||||
|
||||
const accumulator = new Set<string>();
|
||||
for (const childName of current.spec.children) {
|
||||
accumulator.add(childName);
|
||||
for (const group of groups) {
|
||||
const selfName = group.metadata.name;
|
||||
for (const childName of group.spec.children) {
|
||||
const child = groupsByName.get(childName);
|
||||
if (child) {
|
||||
for (const d of visitDescendants(child)) {
|
||||
accumulator.add(d);
|
||||
}
|
||||
if (child && !child.spec.parent) {
|
||||
child.spec.parent = selfName;
|
||||
}
|
||||
}
|
||||
|
||||
const descendants = Array.from(accumulator);
|
||||
current.spec.descendants = descendants;
|
||||
return descendants;
|
||||
}
|
||||
|
||||
for (const group of groups) {
|
||||
visitDescendants(group);
|
||||
}
|
||||
|
||||
//
|
||||
// Make sure that g.ancestors is complete
|
||||
//
|
||||
|
||||
function visitAncestors(current: GroupEntity): string[] {
|
||||
if (current.spec.ancestors.length) {
|
||||
return current.spec.ancestors;
|
||||
}
|
||||
|
||||
let ancestors: string[];
|
||||
const parentName = current.spec.parent;
|
||||
if (!parentName) {
|
||||
ancestors = [];
|
||||
} else {
|
||||
const parent = groupsByName.get(parentName);
|
||||
if (parent) {
|
||||
ancestors = [parentName, ...visitAncestors(parent)];
|
||||
} else {
|
||||
ancestors = [parentName];
|
||||
}
|
||||
}
|
||||
|
||||
current.spec.ancestors = ancestors;
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
for (const group of groups) {
|
||||
visitAncestors(group);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +55,24 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) {
|
||||
const groupsByName = new Map(groups.map(g => [g.metadata.name, g]));
|
||||
|
||||
users.forEach(user => {
|
||||
const transitiveMemberOf = new Set([...user.spec.memberOf]);
|
||||
const transitiveMemberOf = new Set<string>();
|
||||
|
||||
user.spec.memberOf.forEach(groupName => {
|
||||
const group = groupsByName.get(groupName);
|
||||
|
||||
if (group) {
|
||||
group.spec.ancestors.forEach(g => transitiveMemberOf.add(g));
|
||||
const todo = [...user.spec.memberOf];
|
||||
for (;;) {
|
||||
const current = todo.pop();
|
||||
if (!current) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (!transitiveMemberOf.has(current)) {
|
||||
transitiveMemberOf.add(current);
|
||||
const group = groupsByName.get(current);
|
||||
if (group?.spec.parent) {
|
||||
todo.push(group.spec.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.spec.memberOf = [...transitiveMemberOf];
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user