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:
Ben Lambert
2022-01-11 10:30:44 +01:00
committed by GitHub
32 changed files with 608 additions and 165 deletions
@@ -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,
+11
View File
@@ -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({
+1
View File
@@ -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,