Merge pull request #8619 from RoadieHQ/demo-of-processor-using-creds-provider-interface
processor, actions and readers to use gh creds interface
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
import {
|
||||
getGitHubFileFetchUrl,
|
||||
SingleInstanceGithubCredentialsProvider,
|
||||
DefaultGithubCredentialsProvider,
|
||||
GithubCredentialsProvider,
|
||||
GitHubIntegration,
|
||||
ScmIntegrations,
|
||||
@@ -58,9 +58,9 @@ export type GhBlobResponse =
|
||||
export class GithubUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const credentialsProvider =
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
||||
return integrations.github.list().map(integration => {
|
||||
const credentialsProvider =
|
||||
SingleInstanceGithubCredentialsProvider.create(integration.config);
|
||||
const reader = new GithubUrlReader(integration, {
|
||||
treeResponseFactory,
|
||||
credentialsProvider,
|
||||
|
||||
@@ -94,6 +94,17 @@ export type BitbucketIntegrationConfig = {
|
||||
appPassword?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class DefaultGithubCredentialsProvider
|
||||
implements GithubCredentialsProvider
|
||||
{
|
||||
// (undocumented)
|
||||
static fromIntegrations(
|
||||
integrations: ScmIntegrationRegistry,
|
||||
): DefaultGithubCredentialsProvider;
|
||||
getCredentials(opts: { url: string }): Promise<GithubCredentials>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function defaultScmResolveUrl(options: {
|
||||
url: string;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2020 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 { ScmIntegrations } from '../ScmIntegrations';
|
||||
import { GitHubIntegrationConfig } from './config';
|
||||
import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';
|
||||
|
||||
import { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { GithubCredentials } from './types';
|
||||
|
||||
const resultBuilder = (host: string): GithubCredentials => {
|
||||
return {
|
||||
type: 'token',
|
||||
token: `${host}-token`,
|
||||
headers: {
|
||||
token: `${host}-token`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock('./SingleInstanceGithubCredentialsProvider');
|
||||
|
||||
let integrations: ScmIntegrations;
|
||||
|
||||
describe('DefaultGithubCredentialsProvider tests', () => {
|
||||
beforeEach(() => {
|
||||
integrations = ScmIntegrations.fromConfig(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{
|
||||
host: 'github.com',
|
||||
apps: [
|
||||
{
|
||||
appId: 1,
|
||||
privateKey: 'privateKey',
|
||||
webhookSecret: '123',
|
||||
clientId: 'CLIENT_ID',
|
||||
clientSecret: 'CLIENT_SECRET',
|
||||
},
|
||||
],
|
||||
token: 'hardcoded_token',
|
||||
},
|
||||
{
|
||||
host: 'grithub.com',
|
||||
token: 'hardcoded_token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
jest.resetAllMocks();
|
||||
SingleInstanceGithubCredentialsProvider.create = (
|
||||
config: GitHubIntegrationConfig,
|
||||
) => {
|
||||
return {
|
||||
getCredentials: (_opts: { url: string }) => {
|
||||
return Promise.resolve(resultBuilder(config.host));
|
||||
},
|
||||
};
|
||||
};
|
||||
jest.spyOn(SingleInstanceGithubCredentialsProvider, 'create');
|
||||
});
|
||||
|
||||
describe('.create', () => {
|
||||
it('passes the config through to the single provider', () => {
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
||||
const githubIntegration =
|
||||
integrations.github.byHost('github.com')?.config;
|
||||
const grithubIntegration =
|
||||
integrations.github.byHost('grithub.com')?.config;
|
||||
expect(
|
||||
SingleInstanceGithubCredentialsProvider.create,
|
||||
).toHaveBeenCalledWith(githubIntegration);
|
||||
expect(
|
||||
SingleInstanceGithubCredentialsProvider.create,
|
||||
).toHaveBeenCalledWith(grithubIntegration);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getCredentials', () => {
|
||||
it('returns the data verbatim from the creds provider', async () => {
|
||||
const provider =
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
||||
const gitHubCredentials = await provider.getCredentials({
|
||||
url: 'https://github.com/blah',
|
||||
});
|
||||
const gritHubCredentials = await provider.getCredentials({
|
||||
url: 'https://grithub.com/blah',
|
||||
});
|
||||
|
||||
expect(gitHubCredentials).toEqual({
|
||||
type: 'token',
|
||||
token: 'github.com-token',
|
||||
headers: {
|
||||
token: 'github.com-token',
|
||||
},
|
||||
});
|
||||
|
||||
expect(gritHubCredentials).toEqual({
|
||||
type: 'token',
|
||||
token: 'grithub.com-token',
|
||||
headers: {
|
||||
token: 'grithub.com-token',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 { GithubCredentials, GithubCredentialsProvider } from './types';
|
||||
import { ScmIntegrationRegistry } from '../registry';
|
||||
import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';
|
||||
|
||||
/**
|
||||
* 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 fromIntegrations(integrations: ScmIntegrationRegistry) {
|
||||
const credentialsProviders: Map<string, GithubCredentialsProvider> =
|
||||
new Map<string, GithubCredentialsProvider>();
|
||||
|
||||
integrations.github.list().forEach(integration => {
|
||||
const credentialsProvider =
|
||||
SingleInstanceGithubCredentialsProvider.create(integration.config);
|
||||
credentialsProviders.set(integration.config.host, credentialsProvider);
|
||||
});
|
||||
return new DefaultGithubCredentialsProvider(credentialsProviders);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly providers: Map<string, GithubCredentialsProvider>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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: 'https://github.com/backstage/foobar'
|
||||
* })
|
||||
*
|
||||
* const { token, headers } = await getCredentials({
|
||||
* url: 'https://github.com/backstage'
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param opts - The organization or repository URL
|
||||
* @returns A promise of {@link GithubCredentials}.
|
||||
*/
|
||||
async getCredentials(opts: { url: string }): Promise<GithubCredentials> {
|
||||
const parsed = new URL(opts.url);
|
||||
const provider = this.providers.get(parsed.host);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(
|
||||
`There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,
|
||||
);
|
||||
}
|
||||
|
||||
return provider.getCredentials(opts);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { GithubCredentialsProvider } from './types';
|
||||
|
||||
const octokit = {
|
||||
paginate: async (fn: any) => (await fn()).data,
|
||||
apps: {
|
||||
@@ -35,23 +37,24 @@ import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubC
|
||||
import { RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
const github = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
apps: [
|
||||
{
|
||||
appId: 1,
|
||||
privateKey: 'privateKey',
|
||||
webhookSecret: '123',
|
||||
clientId: 'CLIENT_ID',
|
||||
clientSecret: 'CLIENT_SECRET',
|
||||
},
|
||||
],
|
||||
token: 'hardcoded_token',
|
||||
});
|
||||
describe('SingleInstanceGithubCredentialsProvider tests', () => {
|
||||
let github: GithubCredentialsProvider;
|
||||
|
||||
describe('DefaultGithubCredentialsProvider tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
github = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
apps: [
|
||||
{
|
||||
appId: 1,
|
||||
privateKey: 'privateKey',
|
||||
webhookSecret: '123',
|
||||
clientId: 'CLIENT_ID',
|
||||
clientSecret: 'CLIENT_SECRET',
|
||||
},
|
||||
],
|
||||
token: 'hardcoded_token',
|
||||
});
|
||||
});
|
||||
it('create repository specific tokens', async () => {
|
||||
octokit.apps.listInstallations.mockResolvedValue({
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
} from './config';
|
||||
export type { GithubAppConfig, GitHubIntegrationConfig } from './config';
|
||||
export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core';
|
||||
export { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider';
|
||||
export {
|
||||
GithubAppCredentialsMux,
|
||||
SingleInstanceGithubCredentialsProvider,
|
||||
|
||||
Reference in New Issue
Block a user