Merge pull request #21595 from awanlin/topic/use-host-org-everywhere
Added multi-org support
This commit is contained in:
@@ -8,7 +8,7 @@ The following sections will help you get the Azure DevOps Backend plugin setup a
|
||||
|
||||
### Configuration
|
||||
|
||||
The Azure DevOps plugin requires the following YAML to be added to your app-config.yaml:
|
||||
The Azure DevOps plugin requires the following YAML to be added to your `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
azureDevOps:
|
||||
@@ -23,6 +23,12 @@ Configuration Details:
|
||||
- `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build
|
||||
- `organization` is your Azure DevOps Services (cloud) Organization name or for Azure DevOps Server (on-premise) this will be your Collection name
|
||||
|
||||
#### Multi Organization
|
||||
|
||||
To support cases where you have multiple Azure DevOps organizations you will want to make sure to configure them in the `integrations.azure` section of your `app-config.yaml` as detailed in the [Azure DevOps Locations](https://backstage.io/docs/integrations/azure/locations) documentation.
|
||||
|
||||
**Note:** You will still need to define the [configuration above](#configuration).
|
||||
|
||||
### Up and Running
|
||||
|
||||
Here's how to get the backend up and running:
|
||||
@@ -112,6 +118,39 @@ The Azure DevOps backend plugin includes the `AzureDevOpsAnnotatorProcessor` whi
|
||||
}
|
||||
```
|
||||
|
||||
To use this with the New Backend System you'll want to create a [backend module extension for the Catalog](https://backstage.io/docs/backend-system/building-backends/migrating#other-catalog-extensions) if you haven't already. Here's a basic example of this assuming you are only adding the `AzureDevOpsAnnotatorProcessor`, this would go in your `packages/backend/index.ts`:
|
||||
|
||||
```diff
|
||||
import { createBackend } from '@backstage/backend-defaults';
|
||||
+ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
+ import { coreServices, createBackendModule } from '@backstage/backend-plugin-api';
|
||||
+ import { AzureDevOpsAnnotatorProcessor } from '@backstage/plugin-azure-devops-backend';
|
||||
|
||||
+ const catalogModuleCustomExtensions = createBackendModule({
|
||||
+ pluginId: 'catalog', // name of the plugin that the module is targeting
|
||||
+ moduleId: 'custom-extensions',
|
||||
+ register(env) {
|
||||
+ env.registerInit({
|
||||
+ deps: {
|
||||
+ catalog: catalogProcessingExtensionPoint,
|
||||
+ config: coreServices.rootConfig,
|
||||
+ },
|
||||
+ async init({ catalog, config }) {
|
||||
+ catalog.addProcessor(AzureDevOpsAnnotatorProcessor.fromConfig(config));
|
||||
+ },
|
||||
+ });
|
||||
+ },
|
||||
+ });
|
||||
|
||||
const backend = createBackend();
|
||||
|
||||
// ... other feature additions
|
||||
|
||||
+ backend.add(catalogModuleCustomExtensions());
|
||||
|
||||
backend.start();
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops)
|
||||
|
||||
@@ -60,12 +60,16 @@ export class AzureDevOpsApi {
|
||||
getBuildDefinitions(
|
||||
projectName: string,
|
||||
definitionName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<BuildDefinitionReference[]>;
|
||||
// (undocumented)
|
||||
getBuildList(
|
||||
projectName: string,
|
||||
repoId: string,
|
||||
top: number,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<Build[]>;
|
||||
// (undocumented)
|
||||
getBuildRuns(
|
||||
@@ -73,6 +77,8 @@ export class AzureDevOpsApi {
|
||||
top: number,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<BuildRun[]>;
|
||||
// (undocumented)
|
||||
getBuilds(
|
||||
@@ -80,6 +86,8 @@ export class AzureDevOpsApi {
|
||||
top: number,
|
||||
repoId?: string,
|
||||
definitions?: number[],
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<Build[]>;
|
||||
// (undocumented)
|
||||
getDashboardPullRequests(
|
||||
@@ -90,16 +98,25 @@ export class AzureDevOpsApi {
|
||||
getGitRepository(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<GitRepository>;
|
||||
// (undocumented)
|
||||
getGitTags(projectName: string, repoName: string): Promise<GitTag[]>;
|
||||
getGitTags(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<GitTag[]>;
|
||||
// (undocumented)
|
||||
getProjects(): Promise<Project[]>;
|
||||
getProjects(host?: string, org?: string): Promise<Project[]>;
|
||||
// (undocumented)
|
||||
getPullRequests(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options: PullRequestOptions,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<PullRequest[]>;
|
||||
// (undocumented)
|
||||
getReadme(
|
||||
@@ -116,6 +133,8 @@ export class AzureDevOpsApi {
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
top: number,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<RepoBuild[]>;
|
||||
// (undocumented)
|
||||
getTeamMembers(options: {
|
||||
|
||||
@@ -130,8 +130,8 @@ export class AzureDevOpsApi {
|
||||
return webApi;
|
||||
}
|
||||
|
||||
public async getProjects(): Promise<Project[]> {
|
||||
const webApi = await this.getWebApi();
|
||||
public async getProjects(host?: string, org?: string): Promise<Project[]> {
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getCoreApi();
|
||||
const projectList: TeamProjectReference[] = await client.getProjects();
|
||||
|
||||
@@ -149,12 +149,14 @@ export class AzureDevOpsApi {
|
||||
public async getGitRepository(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<GitRepository> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const webApi = await this.getWebApi();
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getGitApi();
|
||||
return client.getRepository(repoName, projectName);
|
||||
}
|
||||
@@ -163,12 +165,14 @@ export class AzureDevOpsApi {
|
||||
projectName: string,
|
||||
repoId: string,
|
||||
top: number,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<Build[]> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const webApi = await this.getWebApi();
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getBuilds(
|
||||
projectName,
|
||||
@@ -199,16 +203,25 @@ export class AzureDevOpsApi {
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
top: number,
|
||||
host?: string,
|
||||
org?: string,
|
||||
) {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const gitRepository = await this.getGitRepository(
|
||||
projectName,
|
||||
repoName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
const buildList = await this.getBuildList(
|
||||
projectName,
|
||||
gitRepository.id as string,
|
||||
top,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
|
||||
const repoBuilds: RepoBuild[] = buildList.map(build => {
|
||||
@@ -221,13 +234,20 @@ export class AzureDevOpsApi {
|
||||
public async getGitTags(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<GitTag[]> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting Git Tags for Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const webApi = await this.getWebApi();
|
||||
const gitRepository = await this.getGitRepository(
|
||||
projectName,
|
||||
repoName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getGitApi();
|
||||
const tagRefs: GitRef[] = await client.getRefs(
|
||||
gitRepository.id as string,
|
||||
@@ -256,13 +276,20 @@ export class AzureDevOpsApi {
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options: PullRequestOptions,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<PullRequest[]> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting up to ${options.top} Pull Requests for Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const webApi = await this.getWebApi();
|
||||
const gitRepository = await this.getGitRepository(
|
||||
projectName,
|
||||
repoName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getGitApi();
|
||||
const searchCriteria: GitPullRequestSearchCriteria = {
|
||||
status: options.status,
|
||||
@@ -397,12 +424,14 @@ export class AzureDevOpsApi {
|
||||
public async getBuildDefinitions(
|
||||
projectName: string,
|
||||
definitionName: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<BuildDefinitionReference[]> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`,
|
||||
);
|
||||
|
||||
const webApi = await this.getWebApi();
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getDefinitions(projectName, definitionName);
|
||||
}
|
||||
@@ -412,12 +441,14 @@ export class AzureDevOpsApi {
|
||||
top: number,
|
||||
repoId?: string,
|
||||
definitions?: number[],
|
||||
host?: string,
|
||||
org?: string,
|
||||
): Promise<Build[]> {
|
||||
this.logger?.debug(
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const webApi = await this.getWebApi();
|
||||
const webApi = await this.getWebApi(host, org);
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getBuilds(
|
||||
projectName,
|
||||
@@ -449,12 +480,19 @@ export class AzureDevOpsApi {
|
||||
top: number,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
host?: string,
|
||||
org?: string,
|
||||
) {
|
||||
let repoId: string | undefined;
|
||||
let definitions: number[] | undefined;
|
||||
|
||||
if (repoName) {
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const gitRepository = await this.getGitRepository(
|
||||
projectName,
|
||||
repoName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
repoId = gitRepository.id;
|
||||
}
|
||||
|
||||
@@ -462,13 +500,22 @@ export class AzureDevOpsApi {
|
||||
const buildDefinitions = await this.getBuildDefinitions(
|
||||
projectName,
|
||||
definitionName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
definitions = buildDefinitions
|
||||
.map(bd => bd.id)
|
||||
.filter((bd): bd is number => Boolean(bd));
|
||||
}
|
||||
|
||||
const builds = await this.getBuilds(projectName, top, repoId, definitions);
|
||||
const builds = await this.getBuilds(
|
||||
projectName,
|
||||
top,
|
||||
repoId,
|
||||
definitions,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
|
||||
const buildRuns: BuildRun[] = builds.map(mappedBuildRun);
|
||||
|
||||
|
||||
@@ -161,6 +161,8 @@ describe('createRouter', () => {
|
||||
'myProject',
|
||||
'af4ae3af-e747-4129-9bbc-d1329f6b0998',
|
||||
40,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(builds);
|
||||
@@ -215,6 +217,8 @@ describe('createRouter', () => {
|
||||
'myProject',
|
||||
'myRepo',
|
||||
50,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(repoBuilds);
|
||||
@@ -252,6 +256,8 @@ describe('createRouter', () => {
|
||||
expect(azureDevOpsApi.getGitTags).toHaveBeenCalledWith(
|
||||
'myProject',
|
||||
'myRepo',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(gitTags);
|
||||
@@ -315,6 +321,8 @@ describe('createRouter', () => {
|
||||
'myProject',
|
||||
'myRepo',
|
||||
{ status: 1, top: 50 },
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(pullRequests);
|
||||
@@ -341,6 +349,8 @@ describe('createRouter', () => {
|
||||
expect(azureDevOpsApi.getBuildDefinitions).toHaveBeenCalledWith(
|
||||
'myProject',
|
||||
'myBuildDefinition',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(inputDefinitions);
|
||||
@@ -397,6 +407,8 @@ describe('createRouter', () => {
|
||||
50,
|
||||
'myRepo',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(buildRuns);
|
||||
@@ -452,6 +464,8 @@ describe('createRouter', () => {
|
||||
50,
|
||||
undefined,
|
||||
'myDefinition',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(buildRuns);
|
||||
|
||||
@@ -75,10 +75,14 @@ export async function createRouter(
|
||||
router.get('/builds/:projectName/:repoId', async (req, res) => {
|
||||
const { projectName, repoId } = req.params;
|
||||
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const buildList = await azureDevOpsApi.getBuildList(
|
||||
projectName,
|
||||
repoId,
|
||||
top,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
res.status(200).json(buildList);
|
||||
});
|
||||
@@ -87,11 +91,14 @@ export async function createRouter(
|
||||
const { projectName, repoName } = req.params;
|
||||
|
||||
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
|
||||
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const gitRepository = await azureDevOpsApi.getRepoBuilds(
|
||||
projectName,
|
||||
repoName,
|
||||
top,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
|
||||
res.status(200).json(gitRepository);
|
||||
@@ -99,7 +106,14 @@ export async function createRouter(
|
||||
|
||||
router.get('/git-tags/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const gitTags = await azureDevOpsApi.getGitTags(projectName, repoName);
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const gitTags = await azureDevOpsApi.getGitTags(
|
||||
projectName,
|
||||
repoName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
res.status(200).json(gitTags);
|
||||
});
|
||||
|
||||
@@ -107,7 +121,8 @@ export async function createRouter(
|
||||
const { projectName, repoName } = req.params;
|
||||
|
||||
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
|
||||
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const status = req.query.status
|
||||
? Number(req.query.status)
|
||||
: PullRequestStatus.Active;
|
||||
@@ -121,6 +136,8 @@ export async function createRouter(
|
||||
projectName,
|
||||
repoName,
|
||||
pullRequestOptions,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
|
||||
res.status(200).json(gitPullRequest);
|
||||
@@ -158,9 +175,13 @@ export async function createRouter(
|
||||
'/build-definitions/:projectName/:definitionName',
|
||||
async (req, res) => {
|
||||
const { projectName, definitionName } = req.params;
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const buildDefinitionList = await azureDevOpsApi.getBuildDefinitions(
|
||||
projectName,
|
||||
definitionName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
res.status(200).json(buildDefinitionList);
|
||||
},
|
||||
@@ -171,11 +192,15 @@ export async function createRouter(
|
||||
const repoName = req.query.repoName?.toString();
|
||||
const definitionName = req.query.definitionName?.toString();
|
||||
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
|
||||
const host = req.query.host?.toString();
|
||||
const org = req.query.org?.toString();
|
||||
const builds = await azureDevOpsApi.getBuildRuns(
|
||||
projectName,
|
||||
top,
|
||||
repoName,
|
||||
definitionName,
|
||||
host,
|
||||
org,
|
||||
);
|
||||
res.status(200).json(builds);
|
||||
});
|
||||
@@ -187,12 +212,14 @@ 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 host =
|
||||
req.query.host?.toString() ?? config.getString('azureDevOps.host');
|
||||
const org =
|
||||
req.query.org?.toString() ?? config.getString('azureDevOps.organization');
|
||||
const { projectName, repoName } = req.params;
|
||||
const readme = await azureDevOpsApi.getReadme(
|
||||
host,
|
||||
organization,
|
||||
org,
|
||||
projectName,
|
||||
repoName,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user