diff --git a/.changeset/good-papayas-fetch.md b/.changeset/good-papayas-fetch.md new file mode 100644 index 0000000000..6cd74ce064 --- /dev/null +++ b/.changeset/good-papayas-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Adds optional namespacing for users in the GitHub Multi Org Plugin diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 7da9cad139..5701c9df48 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -163,6 +163,13 @@ export interface Config { * Defaults to org name if omitted. */ groupNamespace?: string; + + /** + * The namespace of the users created from this org. + * + * Defaults to empty string if omitted. + */ + userNamespace?: string; }>; }; diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts index ebfcaf5b5d..00b173777d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts @@ -111,6 +111,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { client, orgConfig.name, tokenType, + orgConfig.userNamespace, ); const { groups, groupMemberUsers } = await getOrganizationTeams( client, @@ -123,15 +124,18 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { `Read ${users.length} GitHub users and ${groups.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`, ); + let prefix: string = orgConfig.userNamespace ?? ''; + if (prefix.length > 0) prefix += '/'; + users.forEach(u => { - if (!allUsersMap.has(u.metadata.name)) { - allUsersMap.set(u.metadata.name, u); + if (!allUsersMap.has(prefix + u.metadata.name)) { + allUsersMap.set(prefix + u.metadata.name, u); } }); for (const [groupName, userNames] of groupMemberUsers.entries()) { for (const userName of userNames) { - const user = allUsersMap.get(userName); + const user = allUsersMap.get(prefix + userName); if (user && !user.spec.memberOf.includes(groupName)) { user.spec.memberOf.push(groupName); } diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts index 7bdc28e6dd..a67d2378d0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts @@ -78,32 +78,48 @@ describe('config', () => { }); describe('readGithubMultiOrgConfig', () => { - function config(orgs: { name: string; groupNamespace?: string }[]) { + function config( + orgs: { name: string; groupNamespace?: string; userNamespace?: string }[], + ) { return new ConfigReader({ orgs }); } it('reads org configs', () => { const output = readGithubMultiOrgConfig( config([ - { name: 'foo', groupNamespace: 'apple' }, - { name: 'bar', groupNamespace: 'Orange' }, + { name: 'foo', groupNamespace: 'apple', userNamespace: 'red' }, + { name: 'bar', groupNamespace: 'Orange', userNamespace: 'blue' }, ]), ); expect(output).toEqual([ - { name: 'foo', groupNamespace: 'apple' }, - { name: 'bar', groupNamespace: 'orange' }, + { name: 'foo', groupNamespace: 'apple', userNamespace: 'red' }, + { name: 'bar', groupNamespace: 'orange', userNamespace: 'blue' }, ]); }); it('defaults groupNamespace to org name if undefined', () => { + const output = readGithubMultiOrgConfig( + config([ + { name: 'foo', userNamespace: 'red' }, + { name: 'bar', userNamespace: 'blue' }, + ]), + ); + + expect(output).toEqual([ + { name: 'foo', groupNamespace: 'foo', userNamespace: 'red' }, + { name: 'bar', groupNamespace: 'bar', userNamespace: 'blue' }, + ]); + }); + + it('defaults userNamespace to undefined if unspecified', () => { const output = readGithubMultiOrgConfig( config([{ name: 'foo' }, { name: 'bar' }]), ); expect(output).toEqual([ - { name: 'foo', groupNamespace: 'foo' }, - { name: 'bar', groupNamespace: 'bar' }, + { name: 'foo', groupNamespace: 'foo', userNamespace: undefined }, + { name: 'bar', groupNamespace: 'bar', userNamespace: undefined }, ]); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.ts index 40305ff1f2..df9e19ff10 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.ts @@ -95,6 +95,10 @@ export type GithubMultiOrgConfig = Array<{ * The namespace of the group created for this org. */ groupNamespace: string; + /** + * The namespace of the users created for this org. If not specified defaults to undefined. + */ + userNamespace: string | undefined; }>; export function readGithubMultiOrgConfig(config: Config): GithubMultiOrgConfig { @@ -104,5 +108,6 @@ export function readGithubMultiOrgConfig(config: Config): GithubMultiOrgConfig { groupNamespace: ( c.getOptionalString('groupNamespace') ?? c.getString('name') ).toLowerCase(), + userNamespace: c.getOptionalString('userNamespace') ?? undefined, })); } diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.ts index fbbe9b3470..547568d8d2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.ts @@ -82,6 +82,7 @@ export async function getOrganizationUsers( client: typeof graphql, org: string, tokenType: GithubCredentialType, + userNamespace?: string, ): Promise<{ users: UserEntity[] }> { const query = ` query users($org: String!, $email: Boolean!, $cursor: String) { @@ -117,6 +118,7 @@ export async function getOrganizationUsers( }, }; + if (userNamespace) entity.metadata.namespace = userNamespace; if (user.bio) entity.metadata.description = user.bio; if (user.name) entity.spec.profile!.displayName = user.name; if (user.email) entity.spec.profile!.email = user.email;