refactor: replace getOctokit with OctokitProvider.getOctokit to allow credentails caching
Signed-off-by: @pawelmitka <pawel.mitka@brainly.com>
This commit is contained in:
+3
-5
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { getOctokit } from './helpers';
|
||||
import { OctokitProvider } from './helpers';
|
||||
|
||||
export function createGithubActionsDispatchAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
const { integrations } = options;
|
||||
const octokitProvider = new OctokitProvider(integrations);
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
@@ -61,10 +62,7 @@ export function createGithubActionsDispatchAction(options: {
|
||||
`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`,
|
||||
);
|
||||
|
||||
const { client, owner, repo } = await getOctokit({
|
||||
integrations,
|
||||
repoUrl,
|
||||
});
|
||||
const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl);
|
||||
|
||||
await client.rest.actions.createWorkflowDispatch({
|
||||
owner,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { getOctokit } from './helpers';
|
||||
import { OctokitProvider } from './helpers';
|
||||
|
||||
type ContentType = 'form' | 'json';
|
||||
|
||||
@@ -23,6 +23,7 @@ export function createGithubWebhookAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
const { integrations } = options;
|
||||
const octokitProvider = new OctokitProvider(integrations);
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
@@ -96,10 +97,7 @@ export function createGithubWebhookAction(options: {
|
||||
|
||||
ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`);
|
||||
|
||||
const { client, owner, repo } = await getOctokit({
|
||||
integrations,
|
||||
repoUrl,
|
||||
});
|
||||
const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl);
|
||||
|
||||
try {
|
||||
const insecure_ssl = insecureSsl ? '1' : '0';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getOctokit } from './helpers';
|
||||
import { OctokitProvider } from './helpers';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('getOctokit', () => {
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const octokitProvider = new OctokitProvider(integrations);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
@@ -36,43 +37,30 @@ describe('getOctokit', () => {
|
||||
|
||||
it('should throw an error when the repoUrl is not well formed', async () => {
|
||||
await expect(
|
||||
getOctokit({
|
||||
integrations,
|
||||
repoUrl: 'github.com?repo=bob',
|
||||
}),
|
||||
octokitProvider.getOctokit('github.com?repo=bob'),
|
||||
).rejects.toThrow(/missing owner/);
|
||||
|
||||
await expect(
|
||||
getOctokit({
|
||||
integrations,
|
||||
repoUrl: 'github.com?owner=owner',
|
||||
}),
|
||||
octokitProvider.getOctokit('github.com?owner=owner'),
|
||||
).rejects.toThrow(/missing repo/);
|
||||
});
|
||||
|
||||
it('should throw if there is no integration config provided', async () => {
|
||||
await expect(
|
||||
getOctokit({
|
||||
integrations,
|
||||
repoUrl: 'missing.com?repo=bob&owner=owner',
|
||||
}),
|
||||
octokitProvider.getOctokit('missing.com?repo=bob&owner=owner'),
|
||||
).rejects.toThrow(/No matching integration configuration/);
|
||||
});
|
||||
|
||||
it('should throw if there is no token in the integration config that is returned', async () => {
|
||||
await expect(
|
||||
getOctokit({
|
||||
integrations,
|
||||
repoUrl: 'ghe.github.com?repo=bob&owner=owner',
|
||||
}),
|
||||
octokitProvider.getOctokit('ghe.github.com?repo=bob&owner=owner'),
|
||||
).rejects.toThrow(/No token available for host/);
|
||||
});
|
||||
|
||||
it('should return proper Octokit', async () => {
|
||||
const { client, token, owner, repo } = await getOctokit({
|
||||
integrations,
|
||||
repoUrl: 'github.com?repo=bob&owner=owner',
|
||||
});
|
||||
const { client, token, owner, repo } = await octokitProvider.getOctokit(
|
||||
'github.com?repo=bob&owner=owner',
|
||||
);
|
||||
expect(client).toBeDefined();
|
||||
expect(token).toBe('tokenlols');
|
||||
expect(owner).toBe('owner');
|
||||
|
||||
@@ -22,62 +22,68 @@ import {
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
|
||||
type OctokitOptions = {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
repoUrl: string;
|
||||
};
|
||||
|
||||
type OctokitIntegration = {
|
||||
export type OctokitIntegration = {
|
||||
client: Octokit;
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
};
|
||||
|
||||
export const getOctokit = async ({
|
||||
integrations,
|
||||
repoUrl,
|
||||
}: OctokitOptions): Promise<OctokitIntegration> => {
|
||||
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
|
||||
export class OctokitProvider {
|
||||
private readonly integrations: ScmIntegrationRegistry;
|
||||
private readonly credentialsProviders: Map<string, GithubCredentialsProvider>;
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError(`No owner provided for repo ${repoUrl}`);
|
||||
}
|
||||
|
||||
const integrationConfig = integrations.github.byHost(host)?.config;
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(`No integration for host ${host}`);
|
||||
}
|
||||
|
||||
const credentialsProvider =
|
||||
GithubCredentialsProvider.create(integrationConfig);
|
||||
|
||||
if (!credentialsProvider) {
|
||||
throw new InputError(
|
||||
`No matching credentials for host ${host}, please check your integrations config`,
|
||||
constructor(integrations: ScmIntegrationRegistry) {
|
||||
this.integrations = integrations;
|
||||
this.credentialsProviders = new Map(
|
||||
integrations.github.list().map(integration => {
|
||||
const provider = GithubCredentialsProvider.create(integration.config);
|
||||
return [integration.config.host, provider];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's
|
||||
// needless to create URL and then parse again the other side.
|
||||
const { token } = await credentialsProvider.getCredentials({
|
||||
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
|
||||
repo,
|
||||
)}`,
|
||||
});
|
||||
async getOctokit(repoUrl: string): Promise<OctokitIntegration> {
|
||||
const { owner, repo, host } = parseRepoUrl(repoUrl, this.integrations);
|
||||
|
||||
if (!token) {
|
||||
throw new InputError(
|
||||
`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`,
|
||||
);
|
||||
if (!owner) {
|
||||
throw new InputError(`No owner provided for repo ${repoUrl}`);
|
||||
}
|
||||
|
||||
const integrationConfig = this.integrations.github.byHost(host)?.config;
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(`No integration for host ${host}`);
|
||||
}
|
||||
|
||||
const credentialsProvider = this.credentialsProviders.get(host);
|
||||
|
||||
if (!credentialsProvider) {
|
||||
throw new InputError(
|
||||
`No matching credentials for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's
|
||||
// needless to create URL and then parse again the other side.
|
||||
const { token } = await credentialsProvider.getCredentials({
|
||||
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
|
||||
repo,
|
||||
)}`,
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
throw new InputError(
|
||||
`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`,
|
||||
);
|
||||
}
|
||||
|
||||
const client = new Octokit({
|
||||
auth: token,
|
||||
baseUrl: integrationConfig.apiBaseUrl,
|
||||
previews: ['nebula-preview'],
|
||||
});
|
||||
|
||||
return { client, token, owner, repo };
|
||||
}
|
||||
|
||||
const client = new Octokit({
|
||||
auth: token,
|
||||
baseUrl: integrationConfig.apiBaseUrl,
|
||||
previews: ['nebula-preview'],
|
||||
});
|
||||
|
||||
return { client, token, owner, repo };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { getRepoSourceDirectory } from './util';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getOctokit } from '../github/helpers';
|
||||
import { OctokitProvider } from '../github/helpers';
|
||||
|
||||
type Permission = 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
type Collaborator = { access: Permission; username: string };
|
||||
@@ -31,6 +31,7 @@ export function createPublishGithubAction(options: {
|
||||
config: Config;
|
||||
}) {
|
||||
const { integrations, config } = options;
|
||||
const octokitProvider = new OctokitProvider(integrations);
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
@@ -140,10 +141,9 @@ export function createPublishGithubAction(options: {
|
||||
topics,
|
||||
} = ctx.input;
|
||||
|
||||
const { client, token, owner, repo } = await getOctokit({
|
||||
integrations,
|
||||
const { client, token, owner, repo } = await octokitProvider.getOctokit(
|
||||
repoUrl,
|
||||
});
|
||||
);
|
||||
|
||||
const user = await client.users.getByUsername({
|
||||
username: owner,
|
||||
|
||||
Reference in New Issue
Block a user