diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index d0fbf6d8ae..e6c0572ca7 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -16,6 +16,7 @@ import { getGitHubFileFetchUrl, + DefaultGithubCredentialsProvider, GithubCredentialsProvider, GitHubIntegration, ScmIntegrations, @@ -58,7 +59,7 @@ export class GithubUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); return integrations.github.list().map(integration => { - const credentialsProvider = GithubCredentialsProvider.create( + const credentialsProvider = DefaultGithubCredentialsProvider.create( integration.config, ); const reader = new GithubUrlReader(integration, { diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index fcaa39b285..385d337e0d 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -92,6 +92,17 @@ export type BitbucketIntegrationConfig = { appPassword?: string; }; +// @public +export class DefaultGithubCredentialsProvider + implements GithubCredentialsProvider +{ + // (undocumented) + static create( + config: GitHubIntegrationConfig, + ): DefaultGithubCredentialsProvider; + getCredentials(opts: { url: string }): Promise; +} + // @public export function defaultScmResolveUrl(options: { url: string; @@ -198,9 +209,8 @@ export type GithubCredentials = { }; // @public -export class GithubCredentialsProvider { +export interface GithubCredentialsProvider { // (undocumented) - static create(config: GitHubIntegrationConfig): GithubCredentialsProvider; getCredentials(opts: { url: string }): Promise; } diff --git a/packages/integration/src/github/GithubCredentialsProvider.test.ts b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts similarity index 95% rename from packages/integration/src/github/GithubCredentialsProvider.test.ts rename to packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts index 0f51db85be..4ed682aea1 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts @@ -31,11 +31,11 @@ jest.doMock('@octokit/rest', () => { return { Octokit }; }); -import { GithubCredentialsProvider } from './GithubCredentialsProvider'; +import { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; import { RestEndpointMethodTypes } from '@octokit/rest'; import { DateTime } from 'luxon'; -const github = GithubCredentialsProvider.create({ +const github = DefaultGithubCredentialsProvider.create({ host: 'github.com', apps: [ { @@ -49,7 +49,7 @@ const github = GithubCredentialsProvider.create({ token: 'hardcoded_token', }); -describe('GithubCredentialsProvider tests', () => { +describe('DefaultGithubCredentialsProvider tests', () => { beforeEach(() => { jest.resetAllMocks(); }); @@ -204,7 +204,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('should return the default token if no app is configured', async () => { - const githubProvider = GithubCredentialsProvider.create({ + const githubProvider = DefaultGithubCredentialsProvider.create({ host: 'github.com', apps: [], token: 'fallback_token', @@ -218,7 +218,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('should return the configured token if there are no installations', async () => { - const githubProvider = GithubCredentialsProvider.create({ + const githubProvider = DefaultGithubCredentialsProvider.create({ host: 'github.com', apps: [ { @@ -243,7 +243,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('should return undefined if no token or apps are configured', async () => { - const githubProvider = GithubCredentialsProvider.create({ + const githubProvider = DefaultGithubCredentialsProvider.create({ host: 'github.com', }); diff --git a/packages/integration/src/github/DefaultGithubCredentialsProvider.ts b/packages/integration/src/github/DefaultGithubCredentialsProvider.ts new file mode 100644 index 0000000000..6d9944329c --- /dev/null +++ b/packages/integration/src/github/DefaultGithubCredentialsProvider.ts @@ -0,0 +1,287 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import parseGitUrl from 'git-url-parse'; +import { GithubAppConfig, GitHubIntegrationConfig } from './config'; +import { createAppAuth } from '@octokit/auth-app'; +import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; +import { DateTime } from 'luxon'; +import { + GithubCredentials, + GithubCredentialsProvider, + GithubCredentialType, +} from './GithubCredentialsProvider'; + +type InstallationData = { + installationId: number; + suspended: boolean; +}; + +class Cache { + private readonly tokenCache = new Map< + string, + { token: string; expiresAt: DateTime } + >(); + + async getOrCreateToken( + key: string, + supplier: () => Promise<{ token: string; expiresAt: DateTime }>, + ): Promise<{ accessToken: string }> { + const item = this.tokenCache.get(key); + if (item && this.isNotExpired(item.expiresAt)) { + return { accessToken: item.token }; + } + + const result = await supplier(); + this.tokenCache.set(key, result); + return { accessToken: result.token }; + } + + // consider timestamps older than 50 minutes to be expired. + private isNotExpired = (date: DateTime) => + date.diff(DateTime.local(), 'minutes').minutes > 50; +} + +/** + * This accept header is required when calling App APIs in GitHub Enterprise. + * It has no effect on calls to github.com and can probably be removed entirely + * once GitHub Apps is out of preview. + */ +const HEADERS = { + Accept: 'application/vnd.github.machine-man-preview+json', +}; + +/** + * GithubAppManager issues and caches tokens for a specific GitHub App. + */ +class GithubAppManager { + private readonly appClient: Octokit; + private readonly baseUrl?: string; + private readonly baseAuthConfig: { appId: number; privateKey: string }; + private readonly cache = new Cache(); + private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations + + constructor(config: GithubAppConfig, baseUrl?: string) { + this.allowedInstallationOwners = config.allowedInstallationOwners; + this.baseUrl = baseUrl; + this.baseAuthConfig = { + appId: config.appId, + privateKey: config.privateKey.replace(/\\n/gm, '\n'), + }; + this.appClient = new Octokit({ + baseUrl, + headers: HEADERS, + authStrategy: createAppAuth, + auth: this.baseAuthConfig, + }); + } + + async getInstallationCredentials( + owner: string, + repo?: string, + ): Promise<{ accessToken: string }> { + const { installationId, suspended } = await this.getInstallationData(owner); + if (this.allowedInstallationOwners) { + if (!this.allowedInstallationOwners?.includes(owner)) { + throw new Error( + `The GitHub application for ${owner} is not included in the allowed installation list (${installationId}).`, + ); + } + } + if (suspended) { + throw new Error(`The GitHub application for ${owner} is suspended`); + } + + const cacheKey = repo ? `${owner}/${repo}` : owner; + + // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation. + return this.cache.getOrCreateToken(cacheKey, async () => { + const result = await this.appClient.apps.createInstallationAccessToken({ + installation_id: installationId, + headers: HEADERS, + }); + if (repo && result.data.repository_selection === 'selected') { + const installationClient = new Octokit({ + baseUrl: this.baseUrl, + auth: result.data.token, + }); + const repos = await installationClient.paginate( + installationClient.apps.listReposAccessibleToInstallation, + ); + const hasRepo = repos.some(repository => { + return repository.name === repo; + }); + if (!hasRepo) { + throw new Error( + `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`, + ); + } + } + return { + token: result.data.token, + expiresAt: DateTime.fromISO(result.data.expires_at), + }; + }); + } + + getInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + return this.appClient.paginate(this.appClient.apps.listInstallations); + } + + private async getInstallationData(owner: string): Promise { + const allInstallations = await this.getInstallations(); + const installation = allInstallations.find( + inst => + inst.account?.login?.toLocaleLowerCase('en-US') === + owner.toLocaleLowerCase('en-US'), + ); + if (installation) { + return { + installationId: installation.id, + suspended: Boolean(installation.suspended_by), + }; + } + const notFoundError = new Error( + `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`, + ); + notFoundError.name = 'NotFoundError'; + throw notFoundError; + } +} + +/** + * Corresponds to a Github installation which internally could hold several GitHub Apps. + * + * @public + */ +export class GithubAppCredentialsMux { + private readonly apps: GithubAppManager[]; + + constructor(config: GitHubIntegrationConfig) { + this.apps = + config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; + } + + async getAllInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + if (!this.apps.length) { + return []; + } + + const installs = await Promise.all( + this.apps.map(app => app.getInstallations()), + ); + + return installs.flat(); + } + + async getAppToken(owner: string, repo?: string): Promise { + if (this.apps.length === 0) { + return undefined; + } + + const results = await Promise.all( + this.apps.map(app => + app.getInstallationCredentials(owner, repo).then( + credentials => ({ credentials, error: undefined }), + error => ({ credentials: undefined, error }), + ), + ), + ); + + const result = results.find(resultItem => resultItem.credentials); + if (result) { + return result.credentials!.accessToken; + } + + const errors = results.map(r => r.error); + const notNotFoundError = errors.find(err => err.name !== 'NotFoundError'); + if (notNotFoundError) { + throw notNotFoundError; + } + + return undefined; + } +} + +/** + * Handles the creation and caching of credentials for GitHub integrations. + * + * @public + * @remarks + * + * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake + */ +export class DefaultGithubCredentialsProvider + implements GithubCredentialsProvider +{ + static create( + config: GitHubIntegrationConfig, + ): DefaultGithubCredentialsProvider { + return new DefaultGithubCredentialsProvider( + new GithubAppCredentialsMux(config), + config.token, + ); + } + + private constructor( + private readonly githubAppCredentialsMux: GithubAppCredentialsMux, + private readonly token?: string, + ) {} + + /** + * Returns {@link GithubCredentials} for a given URL. + * + * @remarks + * + * Consecutive calls to this method with the same URL will return cached + * credentials. + * + * The shortest lifetime for a token returned is 10 minutes. + * + * @example + * ```ts + * const { token, headers } = await getCredentials({ + * url: 'github.com/backstage/foobar' + * }) + * ``` + * + * @param opts - The organization or repository URL + * @returns A promise of {@link GithubCredentials}. + */ + async getCredentials(opts: { url: string }): Promise { + const parsed = parseGitUrl(opts.url); + + const owner = parsed.owner || parsed.name; + const repo = parsed.owner ? parsed.name : undefined; + + let type: GithubCredentialType = 'app'; + let token = await this.githubAppCredentialsMux.getAppToken(owner, repo); + if (!token) { + type = 'token'; + token = this.token; + } + + return { + headers: token ? { Authorization: `Bearer ${token}` } : undefined, + token, + type, + }; + } +} diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index ece692fecc..15f5375570 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -14,207 +14,6 @@ * limitations under the License. */ -import parseGitUrl from 'git-url-parse'; -import { GithubAppConfig, GitHubIntegrationConfig } from './config'; -import { createAppAuth } from '@octokit/auth-app'; -import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; -import { DateTime } from 'luxon'; - -type InstallationData = { - installationId: number; - suspended: boolean; -}; - -class Cache { - private readonly tokenCache = new Map< - string, - { token: string; expiresAt: DateTime } - >(); - - async getOrCreateToken( - key: string, - supplier: () => Promise<{ token: string; expiresAt: DateTime }>, - ): Promise<{ accessToken: string }> { - const item = this.tokenCache.get(key); - if (item && this.isNotExpired(item.expiresAt)) { - return { accessToken: item.token }; - } - - const result = await supplier(); - this.tokenCache.set(key, result); - return { accessToken: result.token }; - } - - // consider timestamps older than 50 minutes to be expired. - private isNotExpired = (date: DateTime) => - date.diff(DateTime.local(), 'minutes').minutes > 50; -} - -/** - * This accept header is required when calling App APIs in GitHub Enterprise. - * It has no effect on calls to github.com and can probably be removed entirely - * once GitHub Apps is out of preview. - */ -const HEADERS = { - Accept: 'application/vnd.github.machine-man-preview+json', -}; - -/** - * GithubAppManager issues and caches tokens for a specific GitHub App. - */ -class GithubAppManager { - private readonly appClient: Octokit; - private readonly baseUrl?: string; - private readonly baseAuthConfig: { appId: number; privateKey: string }; - private readonly cache = new Cache(); - private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations - - constructor(config: GithubAppConfig, baseUrl?: string) { - this.allowedInstallationOwners = config.allowedInstallationOwners; - this.baseUrl = baseUrl; - this.baseAuthConfig = { - appId: config.appId, - privateKey: config.privateKey.replace(/\\n/gm, '\n'), - }; - this.appClient = new Octokit({ - baseUrl, - headers: HEADERS, - authStrategy: createAppAuth, - auth: this.baseAuthConfig, - }); - } - - async getInstallationCredentials( - owner: string, - repo?: string, - ): Promise<{ accessToken: string }> { - const { installationId, suspended } = await this.getInstallationData(owner); - if (this.allowedInstallationOwners) { - if (!this.allowedInstallationOwners?.includes(owner)) { - throw new Error( - `The GitHub application for ${owner} is not included in the allowed installation list (${installationId}).`, - ); - } - } - if (suspended) { - throw new Error(`The GitHub application for ${owner} is suspended`); - } - - const cacheKey = repo ? `${owner}/${repo}` : owner; - - // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation. - return this.cache.getOrCreateToken(cacheKey, async () => { - const result = await this.appClient.apps.createInstallationAccessToken({ - installation_id: installationId, - headers: HEADERS, - }); - if (repo && result.data.repository_selection === 'selected') { - const installationClient = new Octokit({ - baseUrl: this.baseUrl, - auth: result.data.token, - }); - const repos = await installationClient.paginate( - installationClient.apps.listReposAccessibleToInstallation, - ); - const hasRepo = repos.some(repository => { - return repository.name === repo; - }); - if (!hasRepo) { - throw new Error( - `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`, - ); - } - } - return { - token: result.data.token, - expiresAt: DateTime.fromISO(result.data.expires_at), - }; - }); - } - - getInstallations(): Promise< - RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] - > { - return this.appClient.paginate(this.appClient.apps.listInstallations); - } - - private async getInstallationData(owner: string): Promise { - const allInstallations = await this.getInstallations(); - const installation = allInstallations.find( - inst => - inst.account?.login?.toLocaleLowerCase('en-US') === - owner.toLocaleLowerCase('en-US'), - ); - if (installation) { - return { - installationId: installation.id, - suspended: Boolean(installation.suspended_by), - }; - } - const notFoundError = new Error( - `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`, - ); - notFoundError.name = 'NotFoundError'; - throw notFoundError; - } -} - -/** - * Corresponds to a Github installation which internally could hold several GitHub Apps. - * - * @public - */ -export class GithubAppCredentialsMux { - private readonly apps: GithubAppManager[]; - - constructor(config: GitHubIntegrationConfig) { - this.apps = - config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; - } - - async getAllInstallations(): Promise< - RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] - > { - if (!this.apps.length) { - return []; - } - - const installs = await Promise.all( - this.apps.map(app => app.getInstallations()), - ); - - return installs.flat(); - } - - async getAppToken(owner: string, repo?: string): Promise { - if (this.apps.length === 0) { - return undefined; - } - - const results = await Promise.all( - this.apps.map(app => - app.getInstallationCredentials(owner, repo).then( - credentials => ({ credentials, error: undefined }), - error => ({ credentials: undefined, error }), - ), - ), - ); - - const result = results.find(resultItem => resultItem.credentials); - if (result) { - return result.credentials!.accessToken; - } - - const errors = results.map(r => r.error); - const notNotFoundError = errors.find(err => err.name !== 'NotFoundError'); - if (notNotFoundError) { - throw notNotFoundError; - } - - return undefined; - } -} - /** * The type of credentials produced by the credential provider. * @@ -234,63 +33,11 @@ export type GithubCredentials = { }; /** - * Handles the creation and caching of credentials for GitHub integrations. + * This allows implementations to be provided to retrieve GitHub credentials. * * @public - * @remarks * - * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake */ -export class GithubCredentialsProvider { - static create(config: GitHubIntegrationConfig): GithubCredentialsProvider { - return new GithubCredentialsProvider( - new GithubAppCredentialsMux(config), - config.token, - ); - } - - private constructor( - private readonly githubAppCredentialsMux: GithubAppCredentialsMux, - private readonly token?: string, - ) {} - - /** - * Returns {@link GithubCredentials} for a given URL. - * - * @remarks - * - * Consecutive calls to this method with the same URL will return cached - * credentials. - * - * The shortest lifetime for a token returned is 10 minutes. - * - * @example - * ```ts - * const { token, headers } = await getCredentials({ - * url: 'github.com/backstage/foobar' - * }) - * ``` - * - * @param opts - The organization or repository URL - * @returns A promise of {@link GithubCredentials}. - */ - async getCredentials(opts: { url: string }): Promise { - const parsed = parseGitUrl(opts.url); - - const owner = parsed.owner || parsed.name; - const repo = parsed.owner ? parsed.name : undefined; - - let type: GithubCredentialType = 'app'; - let token = await this.githubAppCredentialsMux.getAppToken(owner, repo); - if (!token) { - type = 'token'; - token = this.token; - } - - return { - headers: token ? { Authorization: `Bearer ${token}` } : undefined, - token, - type, - }; - } +export interface GithubCredentialsProvider { + getCredentials(opts: { url: string }): Promise; } diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index 9c13f13135..440691c436 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -22,10 +22,11 @@ export type { GithubAppConfig, GitHubIntegrationConfig } from './config'; export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; export { GithubAppCredentialsMux, - GithubCredentialsProvider, -} from './GithubCredentialsProvider'; + DefaultGithubCredentialsProvider, +} from './DefaultGithubCredentialsProvider'; export type { GithubCredentials, + GithubCredentialsProvider, GithubCredentialType, } from './GithubCredentialsProvider'; export { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts index 9bd6cc775e..d2498a39d8 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts @@ -17,7 +17,7 @@ import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; @@ -84,7 +84,7 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { // about how to handle the wild card which is special for this processor. const orgUrl = `https://${host}/${org}`; - const { headers } = await GithubCredentialsProvider.create( + const { headers } = await DefaultGithubCredentialsProvider.create( gitHubConfig, ).getCredentials({ url: orgUrl }); diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts index 00b173777d..ba346fa0c6 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts @@ -18,7 +18,7 @@ import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { GithubAppCredentialsMux, - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, GitHubIntegrationConfig, ScmIntegrations, } from '@backstage/integration'; @@ -86,7 +86,8 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { const allUsersMap = new Map(); const baseUrl = new URL(location.target).origin; - const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig); + const credentialsProvider = + DefaultGithubCredentialsProvider.create(gitHubConfig); const orgsToProcess = this.orgs.length ? this.orgs diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts index 87a910ee06..0e1a65df93 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts @@ -17,7 +17,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; @@ -87,7 +87,7 @@ describe('GithubOrgReaderProcessor', () => { (graphql.defaults as jest.Mock).mockReturnValue(mockClient); - jest.spyOn(GithubCredentialsProvider, 'create').mockReturnValue({ + jest.spyOn(DefaultGithubCredentialsProvider, 'create').mockReturnValue({ getCredentials: mockGetCredentials, } as any); @@ -135,7 +135,7 @@ describe('GithubOrgReaderProcessor', () => { (graphql.defaults as jest.Mock).mockReturnValue(mockClient); - jest.spyOn(GithubCredentialsProvider, 'create').mockReturnValue({ + jest.spyOn(DefaultGithubCredentialsProvider, 'create').mockReturnValue({ getCredentials: mockGetCredentials, } as any); diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index 244dbd8104..cb455005fe 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -17,7 +17,7 @@ import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, GithubCredentialType, ScmIntegrations, } from '@backstage/integration'; @@ -107,7 +107,8 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { ); } - const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig); + const credentialsProvider = + DefaultGithubCredentialsProvider.create(gitHubConfig); const { headers, type: tokenType } = await credentialsProvider.getCredentials({ url: orgUrl, diff --git a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.test.ts b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.test.ts index 75edea6909..17c0b7054b 100644 --- a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.test.ts @@ -17,7 +17,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, GitHubIntegrationConfig, } from '@backstage/integration'; import { GitHubOrgEntityProvider } from '.'; @@ -93,7 +93,7 @@ describe('GitHubOrgEntityProvider', () => { type: 'app', }); - jest.spyOn(GithubCredentialsProvider, 'create').mockReturnValue({ + jest.spyOn(DefaultGithubCredentialsProvider, 'create').mockReturnValue({ getCredentials: mockGetCredentials, } as any); diff --git a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts index ecd0d153d1..63d915d817 100644 --- a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts +++ b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts @@ -20,6 +20,7 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { + DefaultGithubCredentialsProvider, GithubCredentialsProvider, GitHubIntegrationConfig, ScmIntegrations, @@ -77,7 +78,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { logger: Logger; }, ) { - this.credentialsProvider = GithubCredentialsProvider.create( + this.credentialsProvider = DefaultGithubCredentialsProvider.create( options.gitHubConfig, ); } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts index bea027a91b..4fbd45c0ee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts @@ -16,6 +16,7 @@ import { InputError } from '@backstage/errors'; import { + DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; @@ -40,7 +41,9 @@ export class OctokitProvider { this.integrations = integrations; this.credentialsProviders = new Map( integrations.github.list().map(integration => { - const provider = GithubCredentialsProvider.create(integration.config); + const provider = DefaultGithubCredentialsProvider.create( + integration.config, + ); return [integration.config.host, provider]; }), ); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 3a9dd01686..79b393a16a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -18,7 +18,7 @@ import fs from 'fs-extra'; import { parseRepoUrl, isExecutable } from './util'; import { - GithubCredentialsProvider, + DefaultGithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; import { zipObject } from 'lodash'; @@ -76,7 +76,7 @@ export const defaultClientFactory = async ({ } const credentialsProvider = - GithubCredentialsProvider.create(integrationConfig); + DefaultGithubCredentialsProvider.create(integrationConfig); if (!credentialsProvider) { throw new InputError(