From b0ff0d25798c219c4e3e31050250d69fdddaa509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Val=C3=A9rio=20Val=C3=A9rio?= Date: Tue, 7 Oct 2025 14:47:42 +0300 Subject: [PATCH] Introduce new option in the GH catalog plugin to exclude suspended users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce an optional setting to exclude suspended users from GitHub Enterprise instances. When it’s set to true, suspended users won’t be emitted by the default transform. If a custom transformer is used, it should check if the property `suspendedAt` from the `GithubUser` is defined, in order to exclude such users. This logic was not introduced in the `GithubMultiOrgReaderProcessor.ts`, since the usage there is marked as deprecated. To note that this setting should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, adding it will cause a schema validation error and the syncing of users will fail. Signed-off-by: Valério Valério --- .../src/module.ts | 4 ++++ plugins/catalog-backend-module-github/config.d.ts | 14 ++++++++++++++ .../src/lib/defaultTransformers.ts | 3 +++ .../src/lib/github.ts | 4 ++++ .../processors/GithubMultiOrgReaderProcessor.ts | 1 + .../src/providers/GithubMultiOrgEntityProvider.ts | 13 +++++++++++++ .../src/providers/GithubOrgEntityProvider.ts | 13 +++++++++++++ 7 files changed, 52 insertions(+) diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 2d349cb873..e6179e07f1 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -121,6 +121,7 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ alwaysUseDefaultNamespace: definitions.length === 1 && definition.orgs?.length === 1, pageSizes: definition.pageSizes, + excludeSuspendedUsers: definition.excludeSuspendedUsers, }), ); } @@ -133,6 +134,7 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: string; githubUrl: string; orgs?: string[]; + excludeSuspendedUsers?: boolean; schedule: SchedulerServiceTaskScheduleDefinition; pageSizes?: { teams?: number; @@ -154,6 +156,8 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: c.getString('id'), githubUrl: c.getString('githubUrl'), orgs: c.getOptionalStringArray('orgs'), + excludeSuspendedUsers: + c.getOptionalBoolean('excludeSuspendedUsers') ?? false, schedule: readSchedulerServiceTaskScheduleDefinitionFromConfig( c.getConfig('schedule'), ), diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index f3cbb15e06..6277f7aa2e 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -264,6 +264,13 @@ export interface Config { */ orgs?: string[]; + /** + * (Optional) Only for GitHub Enterprise. Whether to exclude suspended users when querying organization users. + * If true, the defaultTransformer will not return suspended users. + * Default: `false`. + */ + excludeSuspendedUsers?: boolean; + /** * The refresh schedule to use. */ @@ -315,6 +322,13 @@ export interface Config { */ orgs?: string[]; + /** + * (Optional) Only for GitHub Enterprise. Whether to exclude suspended users when querying organization users. + * If true, the defaultTransformer will not return suspended users. + * Default: `false`. + */ + excludeSuspendedUsers?: boolean; + /** * The refresh schedule to use. */ diff --git a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts index 6befd32056..02f5052ae1 100644 --- a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts +++ b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts @@ -62,6 +62,9 @@ export const defaultUserTransformer = async ( item: GithubUser, _ctx: TransformerContext, ): Promise => { + if (item.suspendedAt) { + return undefined; + } const entity: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 625fb57bba..b614b4be64 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -117,6 +117,7 @@ export type GithubUser = { email?: string; name?: string; organizationVerifiedDomainEmails?: string[]; + suspendedAt?: string; }; /** @@ -186,9 +187,11 @@ export async function getOrganizationUsers( client: typeof graphql, org: string, tokenType: GithubCredentialType, + excludeSuspendedUsers: boolean = false, userTransformer: UserTransformer = defaultUserTransformer, pageSizes: GithubPageSizes = DEFAULT_PAGE_SIZES, ): Promise<{ users: Entity[] }> { + const suspendedAtField = excludeSuspendedUsers ? 'suspendedAt,' : ''; const query = ` query users($org: String!, $email: Boolean!, $cursor: String, $organizationMembersPageSize: Int!) { organization(login: $org) { @@ -200,6 +203,7 @@ export async function getOrganizationUsers( email @include(if: $email), login, name, + ${suspendedAtField} organizationVerifiedDomainEmails(login: $org) } } diff --git a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts index 8af528dfcf..058ce2cb6c 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts @@ -148,6 +148,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { client, orgConfig.name, tokenType, + false, async (githubUser, ctx): Promise => { const result = this.options.userTransformer ? await this.options.userTransformer(githubUser, ctx) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 839ca66e40..bb0dd84c6f 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -174,6 +174,14 @@ export interface GithubMultiOrgEntityProviderOptions { * Reduce these values if hitting RESOURCE_LIMITS_EXCEEDED errors. */ pageSizes?: Partial; + + /** + * Optionally exclude suspended users when querying organization users. + * @defaultValue false + * @remarks + * Only for GitHub Enterprise instances. Will error if used against GitHub.com API. + */ + excludeSuspendedUsers?: boolean; } type CreateDeltaOperation = (entities: Entity[]) => { @@ -221,6 +229,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { events: options.events, alwaysUseDefaultNamespace: options.alwaysUseDefaultNamespace, pageSizes: options.pageSizes, + excludeSuspendedUsers: options.excludeSuspendedUsers, }); provider.schedule(options.schedule); @@ -241,6 +250,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { teamTransformer?: TeamTransformer; alwaysUseDefaultNamespace?: boolean; pageSizes?: Partial; + excludeSuspendedUsers?: boolean; }, ) {} @@ -304,6 +314,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, pageSizes, ); @@ -456,6 +467,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, pageSizes, ); @@ -690,6 +702,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, pageSizes, ); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index 6dfa8a07f8..31b6dc338b 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -130,6 +130,14 @@ export interface GithubOrgEntityProviderOptions { * Optionally include a team transformer for transforming from GitHub teams to Group Entities */ teamTransformer?: TeamTransformer; + + /** + * Optionally exclude suspended users when querying organization users. + * @defaultValue false + * @remarks + * Only for GitHub Enterprise instances. Will error if used against GitHub.com API. + */ + excludeSuspendedUsers?: boolean; } /** @@ -167,6 +175,7 @@ export class GithubOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, teamTransformer: options.teamTransformer, events: options.events, + excludeSuspendedUsers: options.excludeSuspendedUsers, }); provider.schedule(options.schedule); @@ -184,6 +193,7 @@ export class GithubOrgEntityProvider implements EntityProvider { githubCredentialsProvider?: GithubCredentialsProvider; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; + excludeSuspendedUsers?: boolean; }, ) { this.credentialsProvider = @@ -235,6 +245,7 @@ export class GithubOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, ); const { teams } = await getOrganizationTeams( @@ -363,6 +374,7 @@ export class GithubOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, ); @@ -454,6 +466,7 @@ export class GithubOrgEntityProvider implements EntityProvider { client, org, tokenType, + this.options.excludeSuspendedUsers, this.options.userTransformer, );