fix(GithubLocationAnalyzer): support github app auth & authenticated backends
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': minor
|
||||
---
|
||||
|
||||
BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied:
|
||||
|
||||
```diff
|
||||
...
|
||||
builder.addLocationAnalyzers(
|
||||
new GitHubLocationAnalyzer({
|
||||
discovery: env.discovery,
|
||||
config: env.config,
|
||||
+ tokenManager: env.tokenManager,
|
||||
}),
|
||||
);
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
---
|
||||
|
||||
Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication
|
||||
@@ -20,6 +20,7 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend';
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
export class GithubDiscoveryProcessor implements CatalogProcessor {
|
||||
@@ -111,6 +112,7 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
|
||||
export type GithubLocationAnalyzerOptions = {
|
||||
config: Config;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -32,7 +32,10 @@ jest.mock('@octokit/rest', () => {
|
||||
return { Octokit };
|
||||
});
|
||||
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { GithubLocationAnalyzer } from './GithubLocationAnalyzer';
|
||||
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
@@ -46,6 +49,10 @@ describe('GithubLocationAnalyzer', () => {
|
||||
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'),
|
||||
getExternalBaseUrl: jest.fn(),
|
||||
};
|
||||
const mockTokenManager: jest.Mocked<TokenManager> = {
|
||||
authenticate: jest.fn(),
|
||||
getToken: jest.fn().mockResolvedValue('abc123'),
|
||||
};
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
@@ -117,6 +124,7 @@ describe('GithubLocationAnalyzer', () => {
|
||||
const analyzer = new GithubLocationAnalyzer({
|
||||
discovery: mockDiscoveryApi,
|
||||
config,
|
||||
tokenManager: mockTokenManager,
|
||||
});
|
||||
const result = await analyzer.analyze({
|
||||
url: 'https://github.com/foo/bar',
|
||||
@@ -142,6 +150,7 @@ describe('GithubLocationAnalyzer', () => {
|
||||
const analyzer = new GithubLocationAnalyzer({
|
||||
discovery: mockDiscoveryApi,
|
||||
config,
|
||||
tokenManager: mockTokenManager,
|
||||
});
|
||||
const result = await analyzer.analyze({
|
||||
url: 'https://github.com/foo/bar',
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
*/
|
||||
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import {
|
||||
DefaultGithubCredentialsProvider,
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { trimEnd } from 'lodash';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
@@ -23,28 +28,36 @@ import {
|
||||
AnalyzeOptions,
|
||||
ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
/** @public */
|
||||
export type GithubLocationAnalyzerOptions = {
|
||||
config: Config;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
|
||||
private readonly catalogClient: CatalogApi;
|
||||
private readonly config: Config;
|
||||
private readonly githubCredentialsProvider: GithubCredentialsProvider;
|
||||
private readonly integrations: ScmIntegrationRegistry;
|
||||
private readonly tokenManager: TokenManager;
|
||||
|
||||
constructor(options: GithubLocationAnalyzerOptions) {
|
||||
this.config = options.config;
|
||||
this.catalogClient = new CatalogClient({ discoveryApi: options.discovery });
|
||||
this.integrations = ScmIntegrations.fromConfig(options.config);
|
||||
this.githubCredentialsProvider =
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
|
||||
this.tokenManager = options.tokenManager;
|
||||
}
|
||||
|
||||
supports(url: string) {
|
||||
const integrations = ScmIntegrations.fromConfig(this.config);
|
||||
const integration = integrations.byUrl(url);
|
||||
const integration = this.integrations.byUrl(url);
|
||||
return integration?.type === 'github';
|
||||
}
|
||||
|
||||
@@ -55,15 +68,18 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
|
||||
|
||||
const query = `filename:${catalogFile} repo:${owner}/${repo}`;
|
||||
|
||||
const integration = ScmIntegrations.fromConfig(this.config).github.byUrl(
|
||||
url,
|
||||
);
|
||||
const integration = this.integrations.github.byUrl(url);
|
||||
if (!integration) {
|
||||
throw new Error('Make sure you have a GitHub integration configured');
|
||||
}
|
||||
|
||||
const { token: githubToken } =
|
||||
await this.githubCredentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
const octokitClient = new Octokit({
|
||||
auth: integration.config.token,
|
||||
auth: githubToken,
|
||||
baseUrl: integration.config.apiBaseUrl,
|
||||
});
|
||||
|
||||
@@ -82,15 +98,20 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
|
||||
});
|
||||
const defaultBranch = repoInformation.data.default_branch;
|
||||
|
||||
const { token: serviceToken } = await this.tokenManager.getToken();
|
||||
|
||||
const result = await Promise.all(
|
||||
searchResult.data.items
|
||||
.map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`)
|
||||
.map(async target => {
|
||||
const addLocationResult = await this.catalogClient.addLocation({
|
||||
type: 'url',
|
||||
target,
|
||||
dryRun: true,
|
||||
});
|
||||
const addLocationResult = await this.catalogClient.addLocation(
|
||||
{
|
||||
type: 'url',
|
||||
target,
|
||||
dryRun: true,
|
||||
},
|
||||
{ token: serviceToken },
|
||||
);
|
||||
return addLocationResult.entities.map(e => ({
|
||||
location: { type: 'url', target },
|
||||
isRegistered: !!addLocationResult.exists,
|
||||
|
||||
Reference in New Issue
Block a user