From cca9fc2bb62ad5a908c484ca89f8201b960ddf59 Mon Sep 17 00:00:00 2001 From: wpessers Date: Fri, 27 Mar 2026 14:54:37 +0100 Subject: [PATCH] feat(catalog): add retries to octokit client Signed-off-by: wpessers --- .changeset/true-groups-slide.md | 5 +++++ plugins/catalog-backend-module-github/package.json | 1 + .../catalog-backend-module-github/src/lib/github.test.ts | 3 ++- plugins/catalog-backend-module-github/src/lib/github.ts | 5 +++-- .../src/providers/GithubEntityProvider.test.ts | 1 + .../src/providers/GithubEntityProvider.ts | 7 ++++--- yarn.lock | 1 + 7 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 .changeset/true-groups-slide.md diff --git a/.changeset/true-groups-slide.md b/.changeset/true-groups-slide.md new file mode 100644 index 0000000000..0bf2bdb233 --- /dev/null +++ b/.changeset/true-groups-slide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Added automatic retry on temporary errors (like 5XX) to the shared GitHub GraphQL client used by `GithubOrgEntityProvider` and replaced the GraphQL client in `GithubEntityProvider` by this one as well, improving resilience against intermittent GitHub API failures. diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index e42357d60c..08c20fd0aa 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -63,6 +63,7 @@ "@octokit/auth-callback": "^5.0.0", "@octokit/core": "^5.2.0", "@octokit/graphql": "^7.0.2", + "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.1.3", "@octokit/rest": "^19.0.3", "@octokit/webhooks-types": "^7.6.1", diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index 554a482422..81dc77ab5f 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -41,6 +41,7 @@ import { } from './github'; import { Octokit } from '@octokit/core'; import { throttling } from '@octokit/plugin-throttling'; +import { retry } from '@octokit/plugin-retry'; jest.mock('@octokit/core', () => ({ ...jest.requireActual('@octokit/core'), @@ -1011,7 +1012,7 @@ describe('github', () => { }); it('should return a graphql client with throttling', async () => { expect(client).toBeDefined(); - expect(Octokit.plugin).toHaveBeenCalledWith(throttling); + expect(Octokit.plugin).toHaveBeenCalledWith(throttling, retry); }); it('should return a graphql client with the correct options', async () => { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 200c7d260d..fc72004d9c 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -30,6 +30,7 @@ import { DeferredEntity } from '@backstage/plugin-catalog-node'; import { Octokit } from '@octokit/core'; import { LoggerService } from '@backstage/backend-plugin-api'; import { throttling } from '@octokit/plugin-throttling'; +import { retry } from '@octokit/plugin-retry'; /** * Configuration for GitHub GraphQL API page sizes. @@ -874,7 +875,7 @@ export const createReplaceEntitiesOperation = }; /** - * Creates a GraphQL Client with Throttling + * Creates a GraphQL Client with Throttling and Retries */ export const createGraphqlClient = (args: { headers: @@ -886,7 +887,7 @@ export const createGraphqlClient = (args: { logger: LoggerService; }): typeof graphql => { const { headers, baseUrl, logger } = args; - const ThrottledOctokit = Octokit.plugin(throttling); + const ThrottledOctokit = Octokit.plugin(throttling, retry); const octokit = new ThrottledOctokit({ throttle: { onRateLimit: (retryAfter, rateLimitData, _, retryCount) => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index e9e106cce9..dc61d8ecd1 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -47,6 +47,7 @@ type PartialDeep = T extends (...args: unknown[]) => unknown jest.mock('../lib/github', () => { return { getOrganizationRepositories: jest.fn(), + createGraphqlClient: jest.fn().mockReturnValue(jest.fn()), }; }); class PersistingTaskRunner implements SchedulerServiceTaskRunner { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index e215337d8c..5a830c20b5 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -32,13 +32,13 @@ import { import { LocationSpec } from '@backstage/plugin-catalog-common'; -import { graphql } from '@octokit/graphql'; import * as uuid from 'uuid'; import { GithubEntityProviderConfig, readProviderConfigs, } from './GithubEntityProviderConfig'; import { + createGraphqlClient, getOrganizationRepositories, getOrganizationRepository, RepositoryResponse, @@ -249,9 +249,10 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { url: orgUrl, }); - return graphql.defaults({ - baseUrl: this.integration.apiBaseUrl, + return createGraphqlClient({ headers, + baseUrl: this.integration.apiBaseUrl!, + logger: this.logger, }); } diff --git a/yarn.lock b/yarn.lock index 13b51ed309..6cc6525165 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4916,6 +4916,7 @@ __metadata: "@octokit/auth-callback": "npm:^5.0.0" "@octokit/core": "npm:^5.2.0" "@octokit/graphql": "npm:^7.0.2" + "@octokit/plugin-retry": "npm:^6.0.0" "@octokit/plugin-throttling": "npm:^8.1.3" "@octokit/rest": "npm:^19.0.3" "@octokit/webhooks-types": "npm:^7.6.1"