Merge pull request #17597 from tsoslow/jenkins-customize-action
Configuration for action column in Jenkins table
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"jenkins": "^1.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -19,9 +19,10 @@ import jenkins from 'jenkins';
|
||||
import { JenkinsInfo } from './jenkinsInfoProvider';
|
||||
import { JenkinsBuild, JenkinsProject } from '../types';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
|
||||
jest.mock('jenkins');
|
||||
jest.mock('node-fetch');
|
||||
const mockedJenkinsClient = {
|
||||
job: {
|
||||
get: jest.fn(),
|
||||
@@ -54,6 +55,11 @@ const fakePermissionApi = {
|
||||
|
||||
describe('JenkinsApi', () => {
|
||||
const jenkinsApi = new JenkinsApiImpl(fakePermissionApi);
|
||||
const mockFetch = fetch as jest.MockedFunction<typeof fetch>;
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getProjects', () => {
|
||||
const project: JenkinsProject = {
|
||||
@@ -689,55 +695,63 @@ describe('JenkinsApi', () => {
|
||||
buildNumber,
|
||||
);
|
||||
});
|
||||
it('buildProject', async () => {
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef);
|
||||
it('getBuildUrl', async () => {
|
||||
const jenkinsApiProto = Object.getPrototypeOf(jenkinsApi);
|
||||
const buildUrl = jenkinsApiProto.getBuildUrl(
|
||||
jenkinsInfo,
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
);
|
||||
expect(buildUrl).toEqual(
|
||||
'https://jenkins.example.com/job/example-jobName/job/foo/19',
|
||||
);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
headers: jenkinsInfo.headers,
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
|
||||
const buildUrlTriple = jenkinsApiProto.getBuildUrl(
|
||||
jenkinsInfo,
|
||||
'example-jobName/foo/bar',
|
||||
buildNumber,
|
||||
);
|
||||
expect(buildUrlTriple).toEqual(
|
||||
'https://jenkins.example.com/job/example-jobName/job/foo/job/bar/19',
|
||||
);
|
||||
});
|
||||
|
||||
it('buildProject should fail if it does not have required permissions', async () => {
|
||||
fakePermissionApi.authorize.mockResolvedValueOnce([
|
||||
{
|
||||
result: AuthorizeResult.DENY,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(() =>
|
||||
jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef),
|
||||
).rejects.toThrow(NotAllowedError);
|
||||
});
|
||||
|
||||
it('buildProject should succeed if it have required permissions', async () => {
|
||||
fakePermissionApi.authorize.mockResolvedValueOnce([
|
||||
{
|
||||
result: AuthorizeResult.ALLOW,
|
||||
},
|
||||
]);
|
||||
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef);
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
headers: jenkinsInfo.headers,
|
||||
promisify: true,
|
||||
describe('rebuildProject', () => {
|
||||
it('successfully rebuilds', async () => {
|
||||
mockFetch.mockResolvedValueOnce({ status: 200 } as Response);
|
||||
const status = await jenkinsApi.rebuildProject(
|
||||
jenkinsInfo,
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
resourceRef,
|
||||
);
|
||||
expect(status).toEqual(200);
|
||||
});
|
||||
expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
|
||||
});
|
||||
|
||||
it('buildProject with crumbIssuer option', async () => {
|
||||
const info: JenkinsInfo = { ...jenkinsInfo, crumbIssuer: true };
|
||||
await jenkinsApi.buildProject(info, jobFullName, resourceRef);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
headers: jenkinsInfo.headers,
|
||||
promisify: true,
|
||||
crumbIssuer: true,
|
||||
it('fails to rebuild', async () => {
|
||||
mockFetch.mockResolvedValueOnce({ status: 401 } as Response);
|
||||
const status = await jenkinsApi.rebuildProject(
|
||||
jenkinsInfo,
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
resourceRef,
|
||||
);
|
||||
expect(status).toEqual(401);
|
||||
});
|
||||
|
||||
it('should fail if it does not have required permissions', async () => {
|
||||
fakePermissionApi.authorize.mockResolvedValueOnce([
|
||||
{
|
||||
result: AuthorizeResult.DENY,
|
||||
},
|
||||
]);
|
||||
|
||||
mockFetch.mockResolvedValueOnce({ status: 200 } as Response);
|
||||
const status = await jenkinsApi.rebuildProject(
|
||||
jenkinsInfo,
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
resourceRef,
|
||||
);
|
||||
expect(status).toEqual(401);
|
||||
});
|
||||
expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
PermissionEvaluator,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { jenkinsExecutePermission } from '@backstage/plugin-jenkins-common';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
import fetch, { HeaderInit } from 'node-fetch';
|
||||
|
||||
export class JenkinsApiImpl {
|
||||
private static readonly lastBuildTreeSpec = `lastBuild[
|
||||
@@ -144,14 +144,13 @@ export class JenkinsApiImpl {
|
||||
* Trigger a build of a project
|
||||
* @see ../../../jenkins/src/api/JenkinsApi.ts#retry
|
||||
*/
|
||||
async buildProject(
|
||||
async rebuildProject(
|
||||
jenkinsInfo: JenkinsInfo,
|
||||
jobFullName: string,
|
||||
buildNumber: number,
|
||||
resourceRef: string,
|
||||
options?: { token?: string },
|
||||
) {
|
||||
const client = await JenkinsApiImpl.getClient(jenkinsInfo);
|
||||
|
||||
): Promise<number> {
|
||||
if (this.permissionApi) {
|
||||
const response = await this.permissionApi.authorize(
|
||||
[{ permission: jenkinsExecutePermission, resourceRef }],
|
||||
@@ -160,16 +159,19 @@ export class JenkinsApiImpl {
|
||||
// permission api returns always at least one item, we need to check only one result since we do not expect any additional results
|
||||
const { result } = response[0];
|
||||
if (result === AuthorizeResult.DENY) {
|
||||
throw new NotAllowedError();
|
||||
return 401;
|
||||
}
|
||||
}
|
||||
|
||||
// looks like the current SDK only supports triggering a new build
|
||||
// can't see any support for replay (re-running the specific build with the same SCM info)
|
||||
const buildUrl = this.getBuildUrl(jenkinsInfo, jobFullName, buildNumber);
|
||||
|
||||
// Note Jenkins itself has concepts of rebuild and replay on a job.
|
||||
// The latter should be possible to trigger with a POST to /replay/rebuild
|
||||
await client.job.build(jobFullName);
|
||||
// the current SDK only supports triggering a new build
|
||||
// replay the job by triggering request directly from Jenkins api
|
||||
const response = await fetch(`${buildUrl}/replay/rebuild`, {
|
||||
method: 'post',
|
||||
headers: jenkinsInfo.headers as HeaderInit,
|
||||
});
|
||||
return response.status;
|
||||
}
|
||||
|
||||
// private helper methods
|
||||
@@ -318,4 +320,13 @@ export class JenkinsApiImpl {
|
||||
})
|
||||
.pop();
|
||||
}
|
||||
|
||||
private getBuildUrl(
|
||||
jenkinsInfo: JenkinsInfo,
|
||||
jobFullName: string,
|
||||
buildId: number,
|
||||
): string {
|
||||
const jobs = jobFullName.split('/');
|
||||
return `${jenkinsInfo.baseUrl}/job/${jobs.join('/job/')}/${buildId}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,9 +145,10 @@ export async function createRouter(
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber::rebuild',
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber',
|
||||
async (request, response) => {
|
||||
const { namespace, kind, name, jobFullName } = request.params;
|
||||
const { namespace, kind, name, jobFullName, buildNumber } =
|
||||
request.params;
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
request.header('authorization'),
|
||||
);
|
||||
@@ -162,10 +163,16 @@ export async function createRouter(
|
||||
});
|
||||
|
||||
const resourceRef = stringifyEntityRef({ kind, namespace, name });
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef, {
|
||||
token,
|
||||
});
|
||||
response.json({});
|
||||
const status = await jenkinsApi.rebuildProject(
|
||||
jenkinsInfo,
|
||||
jobFullName,
|
||||
parseInt(buildNumber, 10),
|
||||
resourceRef,
|
||||
{
|
||||
token,
|
||||
},
|
||||
);
|
||||
response.json({}).status(status);
|
||||
},
|
||||
);
|
||||
router.use(errorHandler());
|
||||
|
||||
Reference in New Issue
Block a user