From 7228b18c0ffdde85aefb73786902156ce820a47b Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Fri, 17 Dec 2021 11:25:01 +0000 Subject: [PATCH] create an interface for gh creds provider We plan to build on this later to allow the credentials provider to be passed into the scaffolder tasks, the processors, and the url readers. Signed-off-by: Brian Fletcher --- .../src/reading/GithubUrlReader.ts | 3 +- packages/integration/api-report.md | 14 +- ... DefaultGithubCredentialsProvider.test.ts} | 12 +- .../DefaultGithubCredentialsProvider.ts | 287 ++++++++++++++++++ .../src/github/GithubCredentialsProvider.ts | 259 +--------------- packages/integration/src/github/index.ts | 5 +- .../processors/GithubDiscoveryProcessor.ts | 4 +- .../GithubMultiOrgReaderProcessor.ts | 5 +- .../GithubOrgReaderProcessor.test.ts | 6 +- .../processors/GithubOrgReaderProcessor.ts | 5 +- .../providers/GitHubOrgEntityProvider.test.ts | 4 +- .../providers/GitHubOrgEntityProvider.ts | 3 +- .../actions/builtin/github/OctokitProvider.ts | 5 +- .../builtin/publish/githubPullRequest.ts | 4 +- 14 files changed, 334 insertions(+), 282 deletions(-) rename packages/integration/src/github/{GithubCredentialsProvider.test.ts => DefaultGithubCredentialsProvider.test.ts} (95%) create mode 100644 packages/integration/src/github/DefaultGithubCredentialsProvider.ts 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(