diff --git a/.changeset/unlucky-clouds-build.md b/.changeset/unlucky-clouds-build.md new file mode 100644 index 0000000000..7bd1cb0644 --- /dev/null +++ b/.changeset/unlucky-clouds-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops-backend': patch +--- + +Added support for using `AzureDevOpsCredentialsProvider` and deprecated `azureDevOps.token` configuration value diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index 8c916a1495..4cdccc6ffc 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -20,11 +20,10 @@ import { RepoBuild } from '@backstage/plugin-azure-devops-common'; import { Team } from '@backstage/plugin-azure-devops-common'; import { TeamMember } from '@backstage/plugin-azure-devops-common'; import { UrlReader } from '@backstage/backend-common'; -import { WebApi } from 'azure-devops-node-api'; // @public (undocumented) export class AzureDevOpsApi { - constructor(logger: Logger, webApi: WebApi, urlReader: UrlReader); + constructor(logger: Logger, urlReader: UrlReader, config: Config); // (undocumented) getAllTeams(): Promise; // (undocumented) diff --git a/plugins/azure-devops-backend/config.d.ts b/plugins/azure-devops-backend/config.d.ts index 433e4df22f..39523a7a05 100644 --- a/plugins/azure-devops-backend/config.d.ts +++ b/plugins/azure-devops-backend/config.d.ts @@ -24,6 +24,8 @@ export interface Config { /** * Token used to authenticate requests. * @visibility secret + * @deprecated Use `integrations.azure` instead. + * @see https://backstage.io/docs/integrations/azure/locations */ token: string; /** diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 030ac45088..69c278f491 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -31,6 +31,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-azure-devops-common": "workspace:^", "@types/express": "^4.17.6", "azure-devops-node-api": "^11.0.1", diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 87ded2f0e9..ecd0e4704a 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -49,23 +49,51 @@ import { import { TeamMember as AdoTeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; import { Logger } from 'winston'; import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces'; -import { WebApi } from 'azure-devops-node-api'; +import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api'; import { TeamProjectReference, WebApiTeam, } from 'azure-devops-node-api/interfaces/CoreInterfaces'; import { UrlReader } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { + DefaultAzureDevOpsCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; /** @public */ export class AzureDevOpsApi { public constructor( private readonly logger: Logger, - private readonly webApi: WebApi, private readonly urlReader: UrlReader, + private readonly config: Config, ) {} + private async getWebApi(host?: string, org?: string): Promise { + const validHost = host ?? this.config.getString('azureDevOps.host'); + const validOrg = org ?? this.config.getString('azureDevOps.organization'); + const scmIntegrations = ScmIntegrations.fromConfig(this.config); + const credentialsProvider = + DefaultAzureDevOpsCredentialsProvider.fromIntegrations(scmIntegrations); + const credentials = await credentialsProvider.getCredentials({ + url: `https://${validHost}/${validOrg}`, + }); + + let validToken: string; + if (credentials && credentials.token) { + validToken = credentials.token; + } else { + validToken = this.config.getString('azureDevOps.token'); + } + + const authHandler = getPersonalAccessTokenHandler(validToken); + const webApi = new WebApi(`https://${validHost}/${validOrg}`, authHandler); + return webApi; + } + public async getProjects(): Promise { - const client = await this.webApi.getCoreApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getCoreApi(); const projectList: TeamProjectReference[] = await client.getProjects(); const projects: Project[] = projectList.map(project => ({ @@ -87,7 +115,8 @@ export class AzureDevOpsApi { `Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`, ); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getGitApi(); return client.getRepository(repoName, projectName); } @@ -100,7 +129,8 @@ export class AzureDevOpsApi { `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getBuildApi(); return client.getBuilds( projectName, undefined, @@ -158,7 +188,8 @@ export class AzureDevOpsApi { ); const gitRepository = await this.getGitRepository(projectName, repoName); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getGitApi(); const tagRefs: GitRef[] = await client.getRefs( gitRepository.id as string, projectName, @@ -169,10 +200,10 @@ export class AzureDevOpsApi { false, true, ); - const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}?version=GT`; - const commitBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const commitBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}/commit`; const gitTags: GitTag[] = tagRefs.map(tagRef => { @@ -192,7 +223,8 @@ export class AzureDevOpsApi { ); const gitRepository = await this.getGitRepository(projectName, repoName); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { status: options.status, }; @@ -204,7 +236,7 @@ export class AzureDevOpsApi { undefined, options.top, ); - const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}/pullrequest`; const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => { @@ -222,7 +254,8 @@ export class AzureDevOpsApi { `Getting dashboard pull requests for project '${projectName}'.`, ); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { status: options.status, @@ -254,7 +287,7 @@ export class AzureDevOpsApi { return convertDashboardPullRequest( gitPullRequest, - this.webApi.serverUrl, + webApi.serverUrl, policies, ); }), @@ -270,7 +303,8 @@ export class AzureDevOpsApi { `Getting pull request policies for pull request id '${pullRequestId}'.`, ); - const client = await this.webApi.getPolicyApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getPolicyApi(); const artifactId = getArtifactId(projectId, pullRequestId); @@ -285,7 +319,8 @@ export class AzureDevOpsApi { public async getAllTeams(): Promise { this.logger?.debug('Getting all teams.'); - const client = await this.webApi.getCoreApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getCoreApi(); const webApiTeams: WebApiTeam[] = await client.getAllTeams(); const teams: Team[] = webApiTeams.map(team => ({ @@ -307,7 +342,8 @@ export class AzureDevOpsApi { const { projectId, teamId } = options; this.logger?.debug(`Getting team member ids for team '${teamId}'.`); - const client = await this.webApi.getCoreApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getCoreApi(); const teamMembers: AdoTeamMember[] = await client.getTeamMembersWithExtendedProperties(projectId, teamId); @@ -327,7 +363,8 @@ export class AzureDevOpsApi { `Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getBuildApi(); return client.getDefinitions(projectName, definitionName); } @@ -341,7 +378,8 @@ export class AzureDevOpsApi { `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getBuildApi(); return client.getBuilds( projectName, definitions, diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index b6b8a6fdea..13d8a78d96 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -19,7 +19,6 @@ import { PullRequestOptions, PullRequestStatus, } from '@backstage/plugin-azure-devops-common'; -import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api'; import { AzureDevOpsApi } from '../api'; import { Config } from '@backstage/config'; @@ -43,18 +42,10 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger, reader } = options; - const config = options.config.getConfig('azureDevOps'); - - const token = config.getString('token'); - const host = config.getString('host'); - const organization = config.getString('organization'); - - const authHandler = getPersonalAccessTokenHandler(token); - const webApi = new WebApi(`https://${host}/${organization}`, authHandler); + const { logger, reader, config } = options; const azureDevOpsApi = - options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, reader); + options.azureDevOpsApi || new AzureDevOpsApi(logger, reader, config); const pullRequestsDashboardProvider = await PullRequestsDashboardProvider.create(logger, azureDevOpsApi); @@ -195,6 +186,8 @@ export async function createRouter( }); router.get('/readme/:projectName/:repoName', async (req, res) => { + const host = config.getString('azureDevOps.host'); + const organization = config.getString('azureDevOps.organization'); const { projectName, repoName } = req.params; const readme = await azureDevOpsApi.getReadme( host, diff --git a/yarn.lock b/yarn.lock index 0a5ad1d720..89923ecd97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5077,6 +5077,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/integration": "workspace:^" "@backstage/plugin-azure-devops-common": "workspace:^" "@types/express": ^4.17.6 "@types/supertest": ^2.0.8