Adding Azure DevOps backend plugin
Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
@@ -400,3 +400,9 @@ jenkins:
|
||||
baseUrl: https://jenkins.example.com
|
||||
username: backstage-bot
|
||||
apiKey: 123456789abcdef0123456789abcedf012
|
||||
|
||||
azureDevOps:
|
||||
host: dev.azure.com
|
||||
token: ${AZURE_TOKEN}
|
||||
organization: my-company
|
||||
top: 50
|
||||
@@ -30,10 +30,11 @@
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/integration": "^0.6.5",
|
||||
"@backstage/plugin-app-backend": "^0.3.16",
|
||||
"@backstage/plugin-auth-backend": "^0.4.1",
|
||||
"@backstage/plugin-badges-backend": "^0.1.10",
|
||||
"@backstage/plugin-catalog-backend": "^0.14.0",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.11",
|
||||
"@backstage/plugin-auth-backend": "^0.4.0",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.1.0",
|
||||
"@backstage/plugin-badges-backend": "^0.1.9",
|
||||
"@backstage/plugin-catalog-backend": "^0.13.8",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.10",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.9",
|
||||
"@backstage/plugin-jenkins-backend": "^0.1.5",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.3.16",
|
||||
|
||||
@@ -38,6 +38,7 @@ import { Config } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import { metricsInit, metricsHandler } from './metrics';
|
||||
import auth from './plugins/auth';
|
||||
import azureDevOps from './plugins/azuredevops';
|
||||
import catalog from './plugins/catalog';
|
||||
import codeCoverage from './plugins/codecoverage';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
@@ -94,6 +95,7 @@ async function main() {
|
||||
);
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar'));
|
||||
const searchEnv = useHotMemoize(module, () => createEnv('search'));
|
||||
@@ -112,6 +114,7 @@ async function main() {
|
||||
apiRouter.use('/rollbar', await rollbar(rollbarEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/auth', await auth(authEnv));
|
||||
apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv));
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/todo', await todo(todoEnv));
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-azure-devops-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
return await createRouter({ logger, config });
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
# Azure DevOps Backend
|
||||
|
||||
Simple plugin that proxies requests to the [Azure DevOps](https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-6.1) API.
|
||||
|
||||
## Setup
|
||||
|
||||
The following values are read from the configuration file:
|
||||
|
||||
```yaml
|
||||
azureDevOps:
|
||||
host: dev.azure.com
|
||||
token: ${AZURE_TOKEN}
|
||||
organization: my-company
|
||||
top: 50
|
||||
```
|
||||
|
||||
Configuration Details:
|
||||
|
||||
- `host` and `token` can be the same as the ones used for the `integration` section
|
||||
- `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 Organization name or for Azure DevOps Server (on-premise) this will be your Collection name
|
||||
- `top` sets the max number of items to return - currently only applies to builds
|
||||
|
||||
## Links
|
||||
|
||||
- [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
/** Configuration options for the azure-devops-backend plugin */
|
||||
azureDevOps?: {
|
||||
/**
|
||||
* The hostname of the given Azure instance
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* Token used to authenticate requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* The organization of the given Azure instance
|
||||
*/
|
||||
organization: string;
|
||||
/**
|
||||
* The max number of items to return - applies to builds
|
||||
*/
|
||||
top: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-devops-backend",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli backend:dev",
|
||||
"build": "backstage-cli backend:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.2",
|
||||
"@backstage/config": "^0.1.9",
|
||||
"@types/express": "^4.17.6",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.11",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 { Logger } from 'winston';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
import { RepoBuild } from './types';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
export class AzureDevOpsApi {
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly webApi: WebApi,
|
||||
private readonly top: number,
|
||||
) {}
|
||||
|
||||
async getGitRepository(projectName: string, repoName: string) {
|
||||
if (this.logger) {
|
||||
this.logger.info(
|
||||
`Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const client = await this.webApi.getGitApi();
|
||||
return client.getRepository(repoName, projectName);
|
||||
}
|
||||
|
||||
async getBuildList(projectName: string, repoId: string) {
|
||||
if (this.logger) {
|
||||
this.logger.info(
|
||||
`Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository Id ${repoId} for Project ${projectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const client = await this.webApi.getBuildApi();
|
||||
return client.getBuilds(
|
||||
projectName,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
this.top,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
repoId,
|
||||
'TfsGit',
|
||||
);
|
||||
}
|
||||
|
||||
async getRepoBuilds(projectName: string, repoName: string) {
|
||||
if (this.logger) {
|
||||
this.logger.info(
|
||||
`Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const buildList = await this.getBuildList(
|
||||
projectName,
|
||||
gitRepository.id as string,
|
||||
);
|
||||
|
||||
const repoBuilds = buildList.map(build => {
|
||||
const repoBuild: RepoBuild = {
|
||||
id: build.id as number,
|
||||
title: `${build.definition?.name} - ${build.buildNumber}`,
|
||||
link: build._links?.web.href,
|
||||
status: BuildStatus[build.status as BuildStatus],
|
||||
result: BuildResult[build.result as BuildResult],
|
||||
queueTime: build.queueTime as Date,
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
|
||||
};
|
||||
return repoBuild;
|
||||
});
|
||||
|
||||
return repoBuilds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type RepoBuild = {
|
||||
id: number;
|
||||
title: string;
|
||||
link: string;
|
||||
status: string;
|
||||
result: string;
|
||||
queueTime: Date;
|
||||
source: string;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './service/router';
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { getRootLogger } from '@backstage/backend-common';
|
||||
import yn from 'yn';
|
||||
import { startStandaloneServer } from './service/standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { createRouter } from './router';
|
||||
import { RepoBuild } from '../api/types';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
describe('createRouter', () => {
|
||||
let azureDevOpsApi: jest.Mocked<AzureDevOpsApi>;
|
||||
let app: express.Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
azureDevOpsApi = {
|
||||
getGitRepository: jest.fn(),
|
||||
getBuildList: jest.fn(),
|
||||
getRepoBuilds: jest.fn(),
|
||||
} as any;
|
||||
const router = await createRouter({
|
||||
azureDevOpsApi,
|
||||
logger: getVoidLogger(),
|
||||
config: new ConfigReader({
|
||||
azureDevOps: {
|
||||
token: 'foo',
|
||||
host: 'host.com',
|
||||
organization: 'myOrg',
|
||||
top: 5,
|
||||
},
|
||||
}),
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns ok', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
status: 'Healthy',
|
||||
details: 'All required config has been provided',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /repository/:projectName/:repoName', () => {
|
||||
it('fetches a single repository', async () => {
|
||||
const gitRepository: GitRepository = {
|
||||
id: 'af4ae3af-e747-4129-9bbc-d1329f6b0998',
|
||||
name: 'myRepo',
|
||||
url: 'https://host.com/repo',
|
||||
defaultBranch: 'refs/heads/develop',
|
||||
sshUrl: 'ssh://host.com/repo',
|
||||
webUrl: 'https://host.com/webRepo',
|
||||
};
|
||||
|
||||
azureDevOpsApi.getGitRepository.mockResolvedValueOnce(gitRepository);
|
||||
|
||||
const response = await request(app).get(
|
||||
`/repository/${'myProject'}/${'myRepo'}`,
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(gitRepository);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /builds/:projectName/:repoId', () => {
|
||||
it('fetches a list of builds', async () => {
|
||||
const firstBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date,
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: '9bedf67800b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
};
|
||||
|
||||
const secondBuild: Build = {
|
||||
id: 2,
|
||||
buildNumber: 'Build-2',
|
||||
status: BuildStatus.InProgress,
|
||||
result: BuildResult.None,
|
||||
queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date,
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: '13c988d4f15e06bcdd0b0af290086a3079cdadb0',
|
||||
};
|
||||
|
||||
const thirdBuild: Build = {
|
||||
id: 3,
|
||||
buildNumber: 'Build-3',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.PartiallySucceeded,
|
||||
queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date,
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b319c308600eab015a5d6529add21660dc1',
|
||||
};
|
||||
|
||||
const builds: Build[] = [firstBuild, secondBuild, thirdBuild];
|
||||
|
||||
azureDevOpsApi.getBuildList.mockResolvedValueOnce(builds);
|
||||
|
||||
const response = await request(app).get(
|
||||
`/builds/${'myProject'}/${'af4ae3af-e747-4129-9bbc-d1329f6b0998'}`,
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(builds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /repo-builds/:projectName/:repoName', () => {
|
||||
it('fetches a list of repo builds', async () => {
|
||||
const firstRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'My Build Definition - Build 1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: 'completed',
|
||||
result: 'partiallySucceeded',
|
||||
queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date,
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
const secondRepoBuild: RepoBuild = {
|
||||
id: 2,
|
||||
title: 'My Build Definition - Build 2',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2',
|
||||
status: 'inProgress',
|
||||
result: 'none',
|
||||
queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date,
|
||||
source: 'refs/heads/develop (13c988d4)',
|
||||
};
|
||||
|
||||
const thirdRepoBuild: RepoBuild = {
|
||||
id: 3,
|
||||
title: 'My Build Definition - Build 3',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3',
|
||||
status: 'completed',
|
||||
result: 'succeeded',
|
||||
queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date,
|
||||
source: 'refs/heads/develop (9bedf678)',
|
||||
};
|
||||
|
||||
const repoBuilds: RepoBuild[] = [
|
||||
firstRepoBuild,
|
||||
secondRepoBuild,
|
||||
thirdRepoBuild,
|
||||
];
|
||||
|
||||
azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds);
|
||||
|
||||
const response = await request(app).get(
|
||||
`/repo-builds/${'myProject'}/${'myRepo'}`,
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(repoBuilds);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 { errorHandler } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api';
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
|
||||
export interface RouterOptions {
|
||||
azureDevOpsApi?: AzureDevOpsApi;
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger } = options;
|
||||
const config = options.config.getConfig('azureDevOps');
|
||||
|
||||
const token: string = config.getString('token');
|
||||
const host: string = config.getString('host');
|
||||
const organization: string = config.getString('organization');
|
||||
const top: number = config.getOptionalNumber('top') || 10;
|
||||
|
||||
const authHandler = getPersonalAccessTokenHandler(token);
|
||||
const webApi = new WebApi(`https://${host}/${organization}`, authHandler);
|
||||
|
||||
const azureDevOpsApi =
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, top);
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/health', (_, response) => {
|
||||
let code: number = 200;
|
||||
let status: string = '';
|
||||
let details: string = '';
|
||||
|
||||
if (token && host && organization) {
|
||||
code = 200;
|
||||
status = 'Healthy';
|
||||
details = 'All required config has been provided';
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
code = 500;
|
||||
status = 'Unhealthy';
|
||||
details = 'Token is missing';
|
||||
}
|
||||
|
||||
if (!host) {
|
||||
code = 500;
|
||||
status = 'Unhealthy';
|
||||
details = 'Host is missing';
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
code = 500;
|
||||
status = 'Unhealthy';
|
||||
details = 'Organization is missing';
|
||||
}
|
||||
|
||||
logger.info('PONG!');
|
||||
response.status(code).send({ status: status, details: details });
|
||||
});
|
||||
|
||||
router.get('/repository/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const gitRepository = await azureDevOpsApi.getGitRepository(
|
||||
projectName,
|
||||
repoName,
|
||||
);
|
||||
res.status(200).send(gitRepository);
|
||||
});
|
||||
|
||||
router.get('/builds/:projectName/:repoId', async (req, res) => {
|
||||
const { projectName, repoId } = req.params;
|
||||
const buildList = await azureDevOpsApi.getBuildList(projectName, repoId);
|
||||
res.status(200).send(buildList);
|
||||
});
|
||||
|
||||
router.get('/repo-builds/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const gitRepository = await azureDevOpsApi.getRepoBuilds(
|
||||
projectName,
|
||||
repoName,
|
||||
);
|
||||
res.status(200).send(gitRepository);
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'azure-devops-backend' });
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
config,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/azure-devops', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user