Support rebuilding a build

Note that like the previous implementation, this actually triggers a build of the project, not a re-build of a given build.

Signed-off-by: Andrew Shirley <andrew.shirley@sainsburys.co.uk>
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
Andrew Shirley
2021-06-02 18:55:25 +01:00
committed by blam
parent 64b279a55b
commit 79593909c9
5 changed files with 67 additions and 26 deletions
+32 -3
View File
@@ -52,8 +52,10 @@ const jobTreeSpec = `actions[*],
${lastBuildTreeSpec}
jobs{0,1},
name,
fullName,
displayName,
fullDisplayName`;
fullDisplayName,
inQueue`;
const jobsTreeSpec = `jobs[
${jobTreeSpec}
@@ -67,8 +69,7 @@ export interface RouterOptions {
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
// @ts-ignore keeping unused logger for future use
const { logger, jenkinsInfoProvider } = options;
const { jenkinsInfoProvider } = options;
const router = Router();
router.use(express.json());
@@ -169,6 +170,34 @@ export async function createRouter(
},
);
router.post(
'/v1/entity/:namespace/:kind/:name/job/:jobName/:buildNumber::rebuild',
async (request, response) => {
const { namespace, kind, name, jobName } = request.params;
const jenkinsInfo = await jenkinsInfoProvider.getInstance({
entityRef: {
kind,
namespace,
name,
},
jobName,
});
const client = await getClient(jenkinsInfo);
// 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)
// 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(jobName);
// TODO: return the buildNumber which was started.
response.send({});
},
);
router.use(errorHandler());
return router;
}
+29 -9
View File
@@ -62,7 +62,7 @@ export interface Project {
inQueue: string;
// added by us
status: string; // == inQueue ? 'queued' : lastBuild.building ? 'running' : lastBuild.result,
onRestartClick: () => void; // TODO handle.* ?
onRestartClick: () => Promise<void>; // TODO rename to handle.* ? also, should this be on lastBuild?
}
export interface JenkinsApi {
@@ -98,7 +98,11 @@ export interface JenkinsApi {
buildNumber: string,
): Promise<Build>;
retry(entity: EntityName, jobName: string, buildNumber: string): Promise<any>;
retry(
entity: EntityName,
jobName: string,
buildNumber: string,
): Promise<void>;
}
export class JenkinsApiImpl implements JenkinsApi {
@@ -135,7 +139,14 @@ export class JenkinsApiImpl implements JenkinsApi {
},
});
return (await response.json()).projects;
return (
(await response.json()).projects?.map((p: Project) => ({
...p,
onRestartClick: async () => {
await this.retry(entity, p.fullName, String(p.lastBuild.number));
},
})) || []
);
}
async getBuild(
@@ -160,14 +171,23 @@ export class JenkinsApiImpl implements JenkinsApi {
return (await response.json()).build;
}
retry(
// @ts-ignore unused because unimplemented
async retry(
entity: EntityName,
// @ts-ignore unused because unimplemented
jobName: string,
// @ts-ignore unused because unimplemented
buildNumber: string,
): Promise<any> {
return Promise.resolve(undefined);
): Promise<void> {
const url = `${await this.discoveryApi.getBaseUrl('jenkins')}/v1/entity/${
entity.namespace
}/${entity.kind}/${entity.name}/job/${encodeURIComponent(
jobName,
)}/${buildNumber}:rebuild`;
const idToken = await this.identityApi.getIdToken();
await fetch(url, {
method: 'POST',
headers: {
...(idToken && { Authorization: `Bearer ${idToken}` }),
},
});
}
}
@@ -48,6 +48,7 @@ const useStyles = makeStyles(theme => ({
}));
const BuildWithStepsView = () => {
// TODO: Add a test that react-router decodes this (even though `generatePath` doesn't encode it for you!)
const { jobName, buildNumber } = useRouteRefParams(buildRouteRef);
const classes = useStyles();
@@ -100,11 +100,11 @@ const generatedColumns: TableColumn[] = [
<Link
component={RouterLink}
to={generatePath(buildRouteRef.path, {
jobName: row.fullName,
buildNumber: row.lastBuild?.number.toString(),
jobName: encodeURIComponent(row.fullName),
buildNumber: String(row.lastBuild?.number),
})}
>
{row.fullName}
{row.fullDisplayName}
</Link>
);
},
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useAsyncRetry } from 'react-use';
import { jenkinsApiRef } from '../api';
import { useAsyncPolling } from './useAsyncPolling';
@@ -32,7 +32,7 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) {
const api = useApi(jenkinsApiRef);
const errorApi = useApi(errorApiRef);
const { entity } = useEntity();
const entityName = getEntityName(entity);
const entityName = useMemo(() => getEntityName(entity), [entity]);
const getBuildWithSteps = useCallback(async () => {
try {
@@ -43,14 +43,6 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) {
}
}, [buildNumber, jobName, entityName, api, errorApi]);
const restartBuild = async () => {
try {
await api.retry(entityName, jobName, buildNumber);
} catch (e) {
errorApi.post(e);
}
};
const { loading, value, retry } = useAsyncRetry(() => getBuildWithSteps(), [
getBuildWithSteps,
]);
@@ -63,7 +55,6 @@ export function useBuildWithSteps(jobName: string, buildNumber: string) {
return [
{ loading, value, retry },
{
restartBuild,
getBuildWithSteps,
startPolling,
stopPolling,