screwdriver-api
Version:
API server for the Screwdriver.cd service
146 lines (120 loc) • 5.58 kB
JavaScript
;
const urlLib = require('url');
const boom = require('@hapi/boom');
const hoek = require('@hapi/hoek');
const joi = require('joi');
const schema = require('screwdriver-data-schema');
const getSchema = schema.models.event.get;
const idSchema = schema.models.event.base.extract('id');
const { deriveEventStatusFromBuildStatuses, stopBuilds } = require('../builds/helper/updateBuild');
const { emitBuildStatusEvent } = require('../builds/triggers/helpers');
const nonTerminatedStatus = ['CREATED', 'RUNNING', 'QUEUED', 'BLOCKED', 'FROZEN'];
module.exports = () => ({
method: 'PUT',
path: '/events/{id}/stop',
options: {
description: 'Stop all builds in an event',
notes: 'Stop all builds in a specific event',
tags: ['api', 'events'],
auth: {
strategies: ['token'],
scope: ['user', '!guest', 'pipeline']
},
handler: async (request, h) => {
const { eventFactory, pipelineFactory, userFactory, bannerFactory } = request.server.app;
const { username, scmContext, scmUserId } = request.auth.credentials;
const { isValidToken } = request.server.plugins.pipelines;
const eventId = request.params.id;
const { updateAdmins } = request.server.plugins.events;
const event = await eventFactory.get(eventId);
// Check if event exists
if (!event) {
throw boom.notFound(`Event ${eventId} does not exist`);
}
// Fetch the pipeline and user models
const [pipeline, user] = await Promise.all([
pipelineFactory.get(event.pipelineId),
userFactory.get({ username, scmContext })
]);
// In pipeline scope, check if the token is allowed to the pipeline
if (!isValidToken(pipeline.id, request.auth.credentials)) {
throw boom.unauthorized('Token does not have permission to this pipeline');
}
// Check permissions
let permissions;
const scmDisplayName = bannerFactory.scm.getDisplayName({ scmContext });
const adminDetails = request.server.plugins.banners.screwdriverAdminDetails(
username,
scmDisplayName,
scmUserId
);
try {
permissions = await user.getPermissions(pipeline.scmUri);
} catch (err) {
// Screwdriver admin can stop all events
if (!adminDetails.isAdmin) {
if (err.statusCode === 403 && pipeline.scmRepo && pipeline.scmRepo.private) {
throw boom.notFound();
}
throw boom.boomify(err, { statusCode: err.statusCode });
}
}
const isPrOwner = hoek.reach(event, 'commit.author.username') === username;
// PR author should be able to stop their own PR event
// Screwdriver admin can also stop events
if (!((event.prNum && isPrOwner) || adminDetails.isAdmin)) {
// Check permissions and update user in admins list
await updateAdmins({
permissions,
pipeline,
user
});
}
// User has good permissions, get event builds
const builds = await event.getBuilds();
// Update endtime and stop running builds
// Note: COLLAPSED builds will never run
const statusMessage = `Aborted event by ${username}`;
const { unchangedBuilds, changedBuilds } = stopBuilds(builds, statusMessage);
const updatedBuilds = await Promise.all(changedBuilds.map(b => b.update()));
const updatedAllBuilds = [...unchangedBuilds, ...updatedBuilds];
const newEventStatus = deriveEventStatusFromBuildStatuses(updatedAllBuilds);
if (newEventStatus && event.status !== newEventStatus) {
event.status = newEventStatus;
await event.update();
}
const abortedBuilds = updatedBuilds.filter(build => build.status === 'ABORTED');
const buildNotifies = abortedBuilds.map(async build => {
const job = await build.job;
return emitBuildStatusEvent({ server: request.server, build, pipeline, event, job });
});
await Promise.all(buildNotifies);
// Update stageBuild status to ABORTED
const stageBuilds = await event.getStageBuilds();
const toUpdateStageBuilds = [];
stageBuilds.forEach(sb => {
if (nonTerminatedStatus.includes(sb.status)) {
sb.status = 'ABORTED';
toUpdateStageBuilds.push(sb.update());
}
});
await Promise.all(toUpdateStageBuilds);
// everything succeeded, inform the user
const location = urlLib.format({
host: request.headers.host,
port: request.headers.port,
protocol: request.server.info.protocol,
pathname: `${request.path}/${event.id}`
});
return h.response(event.toJson()).header('Location', location).code(200);
},
response: {
schema: getSchema
},
validate: {
params: joi.object({
id: idSchema
})
}
}
});