Introduce new option in the GH catalog plugin to exclude suspended users

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 <vdv100@gmail.com>
This commit is contained in:
Valério Valério
2025-10-07 14:47:42 +03:00
parent a6d5e3018a
commit b0ff0d2579
7 changed files with 52 additions and 0 deletions
@@ -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'),
),
+14
View File
@@ -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.
*/
@@ -62,6 +62,9 @@ export const defaultUserTransformer = async (
item: GithubUser,
_ctx: TransformerContext,
): Promise<UserEntity | undefined> => {
if (item.suspendedAt) {
return undefined;
}
const entity: UserEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'User',
@@ -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)
}
}
@@ -148,6 +148,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
client,
orgConfig.name,
tokenType,
false,
async (githubUser, ctx): Promise<Entity | undefined> => {
const result = this.options.userTransformer
? await this.options.userTransformer(githubUser, ctx)
@@ -174,6 +174,14 @@ export interface GithubMultiOrgEntityProviderOptions {
* Reduce these values if hitting RESOURCE_LIMITS_EXCEEDED errors.
*/
pageSizes?: Partial<GithubPageSizes>;
/**
* 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<GithubPageSizes>;
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,
);
@@ -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,
);