UNPKG

screwdriver-api

Version:

API server for the Screwdriver.cd service

138 lines (115 loc) 4.8 kB
'use strict'; const boom = require('@hapi/boom'); const schema = require('screwdriver-data-schema'); const joi = require('joi'); const idSchema = schema.models.build.base.extract('id'); const { getScmUri, getUserPermissions } = require('../helper'); const { updateBuildAndTriggerDownstreamJobs } = require('./helper/updateBuild'); /** * Validate if build status can be updated * @method validateBuildStatus * @param {String} id Build Id * @param {Object} buildFactory Build factory object to quey build store */ async function getBuildToUpdate(id, buildFactory) { const build = await buildFactory.get(id); if (!build) { throw boom.notFound(`Build ${id} does not exist`); } // Check build status if (!['RUNNING', 'QUEUED', 'BLOCKED', 'UNSTABLE', 'FROZEN'].includes(build.status)) { throw boom.forbidden('Can only update RUNNING, QUEUED, BLOCKED, FROZEN, or UNSTABLE builds'); } return build; } /** * * @param {Object} build Build object * @param {Object} request hapi request object * @throws boom.badRequest on validation error */ async function validateUserPermission(build, request) { const { jobFactory, userFactory, bannerFactory, pipelineFactory } = request.server.app; const { username, scmContext, scmUserId } = request.auth.credentials; const { status: desiredStatus } = request.payload; const scmDisplayName = bannerFactory.scm.getDisplayName({ scmContext }); // Check if Screwdriver admin const adminDetails = request.server.plugins.banners.screwdriverAdminDetails(username, scmDisplayName, scmUserId); // Check desired status if (adminDetails.isAdmin) { if (desiredStatus !== 'ABORTED' && desiredStatus !== 'FAILURE') { throw boom.badRequest('Admin can only update builds to ABORTED or FAILURE'); } } else if (desiredStatus !== 'ABORTED') { throw boom.badRequest('User can only update builds to ABORTED'); } // Check permission against the pipeline // Fetch the job and user models const [job, user] = await Promise.all([jobFactory.get(build.jobId), userFactory.get({ username, scmContext })]); const pipeline = await job.pipeline; // Use parent's scmUri if pipeline is child pipeline and using read-only SCM const scmUri = await getScmUri({ pipeline, pipelineFactory }); // Check the user's permission await getUserPermissions({ user, scmUri, level: 'push', isAdmin: adminDetails.isAdmin }); } /** * Prevent duplicate builds with the same build ID * @param {Object} build Build object * @param {Object} request hapi request object * @throws boom.forbidden error */ async function preventDuplicateBuild(build, request) { const { status: desiredStatus } = request.payload; if (build.status === 'RUNNING' && desiredStatus === 'RUNNING') { throw boom.forbidden(`Can not update status to RUNNING. Build Id: ${build.id} is still running.`); } } module.exports = () => ({ method: 'PUT', path: '/builds/{id}', options: { description: 'Update a build', notes: 'Update a specific build', tags: ['api', 'builds'], auth: { strategies: ['token'], scope: ['build', 'pipeline', 'user', '!guest', 'temporal'] }, handler: async (request, h) => { const { buildFactory } = request.server.app; const { id } = request.params; const { username, scmContext, scope } = request.auth.credentials; const isBuild = scope.includes('build') || scope.includes('temporal'); // Check token permissions if (isBuild && username !== id) { return boom.forbidden(`Credential only valid for ${username}`); } const build = await getBuildToUpdate(id, buildFactory); if (isBuild) { await preventDuplicateBuild(build, request); } else { await validateUserPermission(build, request); } if (request.payload.status && request.payload.status === 'FAILURE') { request.log( ['PUT', 'builds', id], `Build failed. Received payload: ${JSON.stringify(request.payload)}` ); } const newBuild = await updateBuildAndTriggerDownstreamJobs( request.payload, build, request.server, username, scmContext ); return h.response(await newBuild.toJsonWithSteps()).code(200); }, validate: { params: joi.object({ id: idSchema }), payload: schema.models.build.update } } });