Skip to content

Commit

Permalink
Add an extension point for credentials resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
ScottGuymer committed Sep 19, 2024
1 parent 4245054 commit cfd99fd
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/

import { ResponseError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { Metric } from '@backstage-community/plugin-copilot-common';
import fetch from 'node-fetch';
import { getGithubInfo, GithubInfo } from '../utils/GithubUtils';
import {
CopilotCredentialsProvider,
GithubInfo,
} from '../utils/CopilotCredentialsProvider';

interface GithubApi {
getCopilotUsageDataForEnterprise: () => Promise<Metric[]>;
Expand All @@ -27,8 +29,8 @@ interface GithubApi {
export class GithubClient implements GithubApi {
constructor(private readonly props: GithubInfo) {}

static async fromConfig(config: Config) {
const info = await getGithubInfo(config);
static async fromConfig(credentialsProvider: CopilotCredentialsProvider) {
const info = await credentialsProvider.getCredentials();
return new GithubClient(info);
}

Expand Down
6 changes: 5 additions & 1 deletion workspaces/copilot/plugins/copilot-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@
*/

export * from './service/router';
export { copilotPlugin as default } from './plugin';
export { copilotPlugin as default, copilotExtensionPoint } from './plugin';
export {
type CopilotCredentialsProvider,
type GithubInfo,
} from './utils/CopilotCredentialsProvider';
24 changes: 24 additions & 0 deletions workspaces/copilot/plugins/copilot-backend/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,22 @@
import {
coreServices,
createBackendPlugin,
createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { createRouterFromConfig } from './service/router';
import {
CopilotCredentialsProvider,
DefaultCopilotCredentialsProvider,
} from './utils/CopilotCredentialsProvider';

export interface CopilotExtensionPoint {
useCredentialsProvider(provider: CopilotCredentialsProvider): void;
}

export const copilotExtensionPoint =
createExtensionPoint<CopilotExtensionPoint>({
id: 'copliot.credentials',
});

/**
* Backend plugin for Copilot.
Expand All @@ -27,6 +41,13 @@ import { createRouterFromConfig } from './service/router';
export const copilotPlugin = createBackendPlugin({
pluginId: 'copilot',
register(env) {
let credentialsProvider: CopilotCredentialsProvider;
env.registerExtensionPoint(copilotExtensionPoint, {
useCredentialsProvider(provider: CopilotCredentialsProvider) {
credentialsProvider = provider;
},
});

env.registerInit({
deps: {
httpRouter: coreServices.httpRouter,
Expand All @@ -42,6 +63,9 @@ export const copilotPlugin = createBackendPlugin({
database,
scheduler,
config,
credentialsProvider:
credentialsProvider ??
new DefaultCopilotCredentialsProvider({ config }),
}),
);
httpRouter.addAuthPolicy({
Expand Down
11 changes: 9 additions & 2 deletions workspaces/copilot/plugins/copilot-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { DatabaseHandler } from '../db/DatabaseHandler';
import Scheduler from '../task/Scheduler';
import { GithubClient } from '../client/GithubClient';
import { DateTime } from 'luxon';
import { CopilotCredentialsProvider } from '../utils/CopilotCredentialsProvider';

/**
* Options for configuring the Copilot plugin.
Expand Down Expand Up @@ -67,6 +68,11 @@ export interface RouterOptions {
* Configuration for the router.
*/
config: Config;

/**
* Credentials provider for the router.
*/
credentialsProvider: CopilotCredentialsProvider;
}

const defaultSchedule: SchedulerServiceTaskScheduleDefinition = {
Expand Down Expand Up @@ -106,11 +112,12 @@ async function createRouter(
routerOptions: RouterOptions,
pluginOptions: PluginOptions,
): Promise<express.Router> {
const { logger, database, scheduler, config } = routerOptions;
const { logger, database, scheduler, config, credentialsProvider } =
routerOptions;
const { schedule } = pluginOptions;

const db = await DatabaseHandler.create({ database });
const api = await GithubClient.fromConfig(config);
const api = await GithubClient.fromConfig(credentialsProvider);

await scheduler.scheduleTask({
id: 'copilot-metrics',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2024 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 { Config } from '@backstage/config';
import {
DefaultGithubCredentialsProvider,
GithubCredentials,
ScmIntegrations,
} from '@backstage/integration';

export type GithubInfo = {
credentials: GithubCredentials;
apiBaseUrl: string;
enterprise: string;
};

export interface CopilotCredentialsProvider {
getCredentials(): Promise<GithubInfo>;
}

export class DefaultCopilotCredentialsProvider
implements CopilotCredentialsProvider
{
private readonly host: string;
private readonly enterprise: string;
private readonly integrations: ScmIntegrations;
private readonly credentialsProvider: DefaultGithubCredentialsProvider;

constructor(options: { config: Config }) {
const { config } = options;

this.integrations = ScmIntegrations.fromConfig(config);
this.credentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);

this.host = config.getString('copilot.host');
this.enterprise = config.getString('copilot.enterprise');

if (!this.host) {
throw new Error('The host configuration is missing from the config.');
}

if (!this.enterprise) {
throw new Error(
'The enterprise configuration is missing from the config.',
);
}
}

async getCredentials(): Promise<GithubInfo> {
const githubConfig = this.integrations.github.byHost(this.host)?.config;

if (!githubConfig) {
throw new Error(
`GitHub configuration for host "${this.host}" is missing or incomplete.`,
);
}

const apiBaseUrl = githubConfig.apiBaseUrl ?? 'https://api.github.com';

const credentials = await this.credentialsProvider.getCredentials({
url: apiBaseUrl,
});

if (!credentials.headers) {
throw new Error('Failed to retrieve credentials headers.');
}

return {
apiBaseUrl,
credentials,
enterprise: this.enterprise,
};
}
}

This file was deleted.

0 comments on commit cfd99fd

Please sign in to comment.