module.exports = () => ({ method: 'DELETE', path: '/commands/{namespace}/{name}', config: { description: 'Delete a command', notes: 'Returns null if successful', tags: ['api', 'commands'], auth: { strategies: ['token'], scope: ['build', 'user', '!guest'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const { namespace, name } = request.params; const { credentials } = request.auth; const { commandFactory, commandTagFactory } = request.server.app; const { canRemove } = request.server.plugins.commands; const storeUrl = request.server.app.ecosystem.store; const authToken = request.headers.authorization; return Promise.all([ commandFactory.list({ params: { namespace, name } }), commandTagFactory.list({ params: { namespace, name } }) ]).then(([commands, tags]) => { if (commands.length === 0) { throw boom.notFound(`Command ${namespace}/${name} does not exist`); } return canRemove(credentials, commands[0], 'admin').then(() => { // eslint-disable-next-line max-len const commandPromises = commands.map(command => removeCommand(command, storeUrl, authToken)); const tagPromises = tags.map(tag => tag.remove()); return Promise.all(commandPromises.concat(tagPromises)); }) .then(() => reply().code(204)); }) .catch(err => reply(boom.boomify(err))); }, validate: { params: { namespace: joi.reach(baseSchema, 'namespace'), name: joi.reach(baseSchema, 'name') } } } });
module.exports = () => ({ method: 'GET', path: '/pipelines/{id}/events', config: { description: 'Get pipeline type events for this pipeline', notes: 'Returns pipeline events for the given pipeline', tags: ['api', 'pipelines', 'events'], auth: { strategies: ['token'], scope: ['user', 'build', 'pipeline'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const factory = request.server.app.pipelineFactory; return factory.get(request.params.id) .then((pipeline) => { if (!pipeline) { throw boom.notFound('Pipeline does not exist'); } const eventType = request.query.type || 'pipeline'; const config = { params: { type: eventType } }; if (request.query.page || request.query.count) { config.paginate = { page: request.query.page, count: request.query.count }; } if (request.query.prNum) { config.params.type = 'pr'; config.params.prNum = request.query.prNum; } return pipeline.getEvents(config); }) .then(events => reply(events.map(e => e.toJson()))) .catch(err => reply(boom.boomify(err))); }, response: { schema: listSchema }, validate: { query: schema.api.pagination.concat(joi.object({ type: joi.string(), prNum: joi.reach(schema.models.event.base, 'prNum') })) } } });
getDefault(key) { const schemaKey = Array.isArray(key) ? key.join('.') : key; const subSchema = Joi.reach(this.getSchema(), schemaKey); if (!subSchema) { throw new Error(`Unknown config key: ${key}.`); } return clone(_.get(Joi.describe(subSchema), 'flags.default')); }
validateField(field, value) { let subSchema = Joi.reach(this.schema, field) try{ Joi.assert(value, subSchema) if(this._errors[field]) delete this._errors[field] } catch(error) { if(!this._errors[field]) this._errors[field] = {} for(let detail of error.details) { this._errors[field] = { message: detail.message } } } }
module.exports = () => ({ method: 'DELETE', path: '/templates/{name}', config: { description: 'Delete a template', notes: 'Returns null if successful', tags: ['api', 'templates'], auth: { strategies: ['token'], scope: ['build', 'user', '!guest'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const { name } = request.params; const { credentials } = request.auth; const { templateFactory, templateTagFactory } = request.server.app; const { canRemove } = request.server.plugins.templates; return Promise.all([ templateFactory.list({ params: { name } }), templateTagFactory.list({ params: { name } }) ]).then(([templates, tags]) => { if (templates.length === 0) { throw boom.notFound(`Template ${name} does not exist`); } return canRemove(credentials, templates[0], 'admin').then(() => { const templatePromises = templates.map(template => template.remove()); const tagPromises = tags.map(tag => tag.remove()); return Promise.all(templatePromises.concat(tagPromises)); }) .then(() => reply().code(204)); }) .catch(err => reply(boom.boomify(err))); }, validate: { params: { name: joi.reach(baseSchema, 'name') } } } });
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const getSchema = schema.models.event.get; const idSchema = joi.reach(schema.models.event.base, 'id'); module.exports = () => ({ method: 'GET', path: '/events/{id}', config: { description: 'Get a single event', notes: 'Returns a event record', tags: ['api', 'events'], handler: (request, reply) => { const factory = request.server.app.eventFactory; return factory.get(request.params.id) .then((model) => { if (!model) { throw boom.notFound('Event does not exist'); } return reply(model.toJson()); }) .catch(err => reply(boom.wrap(err))); }, response: { schema: getSchema },
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const idSchema = joi.reach(schema.models.banner.base, 'id'); module.exports = () => ({ method: 'PUT', path: '/banners/{id}', config: { description: 'Update a banner', notes: 'Update a banner', tags: ['api', 'banners'], auth: { strategies: ['token'], scope: ['user', '!guest'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const { bannerFactory } = request.server.app; const id = request.params.id; // id of banner to update const username = request.auth.credentials.username; const scmContext = request.auth.credentials.scmContext; // lookup whether user is admin const adminDetails = request.server.plugins.banners
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const idSchema = joi.reach(schema.models.job.base, 'id'); module.exports = () => ({ method: 'PUT', path: '/jobs/{id}', config: { description: 'Update a job', notes: 'Update a specific job', tags: ['api', 'jobs'], auth: { strategies: ['token'], scope: ['user', '!guest'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const { jobFactory, pipelineFactory, userFactory } = request.server.app; const id = request.params.id; const username = request.auth.credentials.username; const scmContext = request.auth.credentials.scmContext; return jobFactory.get(id) .then((job) => {
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const winston = require('winston'); const getSchema = schema.models.collection.get; const idSchema = joi.reach(schema.models.collection.base, 'id'); /** * Helper function to get PR info of pipeline * * @param {Pipeline} pipeline Pipeline model to get PR info for * @returns {Object} */ function getPipelinePRInfo(pipeline) { const prs = { open: 0, failing: 0 }; // Get all the PR jobs return pipeline.getJobs({ type: 'pr' }) .then((prJobs) => { if (!prJobs) { return prs; } // Return array of prJobs' builds return Promise.all(prJobs.map(job => job.getBuilds()
exports.register = (server, options, next) => { server.route({ method: 'GET', path: '/isAdmin', config: { description: 'Check if a user is admin of a pipeline, event, or job', notes: 'Returns true or false', tags: ['api'], auth: { strategies: ['token'], scope: ['user'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => Promise.resolve().then(() => { const { pipelineId, eventId, jobId } = request.query; if (eventId) { const eventFactory = request.server.app.eventFactory; return eventFactory.get(eventId).then(e => e.pipelineId); } if (jobId) { const jobFactory = request.server.app.jobFactory; return jobFactory.get(jobId).then(j => j.pipelineId); } return pipelineId; }).then((pid) => { const pipelineFactory = request.server.app.pipelineFactory; const userFactory = request.server.app.userFactory; const username = request.auth.credentials.username; const scmContext = request.auth.credentials.scmContext; return Promise.all([ pipelineFactory.get(pid), userFactory.get({ username, scmContext }) ]).then(([pipeline, user]) => { if (!pipeline) { throw boom.notFound(`Pipeline ${pid} does not exist`); } // ask the user for permissions on this repo return user.getPermissions(pipeline.scmUri) .then(permissions => reply(permissions.admin)); }); }).catch(err => reply(boom.boomify(err))), validate: { query: joi.object().keys({ pipelineId: joi.reach(schema.models.pipeline.base, 'id'), eventId: joi.reach(schema.models.event.base, 'id'), jobId: joi.reach(schema.models.job.base, 'id') }).max(1).min(1) } } }); next(); };
'use strict'; const Joi = require('joi'); const Build = require('../models/build'); const SCHEMA_PARAMS = Joi.object().keys({ id: Joi.reach(Build.base, 'id'), name: Joi.reach(Build.getStep, 'name') }).label('URL Parameters'); const SCHEMA_QUERY = Joi.object().keys({ from: Joi.number().integer() .min(0) .default(0) .description('Starting line Number'), pages: Joi.number().integer() .min(1) .default(10) .description('Max pages sent per request'), sort: Joi .string().lowercase() .valid(['ascending', 'descending']) .default('ascending') .description('Sorting option for lines') }).label('Query Parameters'); const SCHEMA_LOGLINE = Joi.object().keys({ n: Joi .number().integer() .description('Numbered line number since the start of the step') .example(15),
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const idSchema = joi.reach(schema.models.token.base, 'id'); module.exports = () => ({ method: 'PUT', path: '/tokens/{id}/refresh', config: { description: 'Refresh a token', notes: 'Update the value of a token while preserving its other metadata', tags: ['api', 'tokens'], auth: { strategies: ['token'], scope: ['user', '!guest'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const tokenFactory = request.server.app.tokenFactory; const credentials = request.auth.credentials; const canAccess = request.server.plugins.tokens.canAccess; return tokenFactory.get(request.params.id) .then((token) => { if (!token) {
stateChangeMessage: Joi .string() .max(512) .description('Reason why disabling or enabling job') .example('Testing out new feature change in beta only'), archived: Joi .boolean() .description('Flag if the job is archived') .example(true) .default(false) }; const EXTENDED_MODEL = { title: Joi.reach(SCM_PR_SCHEMA, 'title'), createTime: Joi.reach(SCM_PR_SCHEMA, 'createTime'), username: Joi.reach(SCM_PR_SCHEMA, 'username'), userProfile: Joi.reach(SCM_PR_SCHEMA, 'userProfile'), url: Joi.reach(SCM_PR_SCHEMA, 'url'), ...MODEL }; module.exports = { /** * All the available properties of Job * * @property base * @type {Joi} */ base: Joi.object(MODEL).label('Job'),
'use strict'; const Joi = require('joi'); const mutate = require('../lib/mutate'); const Command = require('../config/command'); const scmContext = Joi.reach(require('./pipeline').base, 'scmContext'); const scmOrganization = Joi .string().max(100) .description('SCM organization name') .example('screwdriver-cd'); const MODEL = { id: Joi .number().integer().positive() .description('Identifier of this Job') .example(123345), name: Joi .string().regex(/^[\w-]+$/) .max(50) .description('Name of the build cluster') .example('iOS'), description: Joi .string().max(100) .description('Description of the build cluster') .example('Build cluster for iOS team'), scmContext, scmOrganizations: Joi.array().items(scmOrganization),
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const getSchema = schema.models.buildCluster.get; const nameSchema = joi.reach(schema.models.buildCluster.base, 'name'); module.exports = () => ({ method: 'GET', path: '/buildclusters/{name}', config: { description: 'Get a single build cluster', notes: 'Returns a build cluster record', tags: ['api', 'buildclusters'], auth: { strategies: ['token'], scope: ['user', 'build'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const name = request.params.name; const factory = request.server.app.buildClusterFactory; const config = { params: { name }
module.exports = config => ({ method: 'GET', path: '/pipelines/{id}/{jobName}/badge', config: { description: 'Get a badge for a job', notes: 'Redirects to the badge service', tags: ['api', 'job', 'badge'], handler: (request, reply) => { const jobFactory = request.server.app.jobFactory; const pipelineFactory = request.server.app.pipelineFactory; const { id, jobName } = request.params; const badgeService = request.server.app.ecosystem.badges; const encodeBadgeSubject = request.server.plugins.pipelines.encodeBadgeSubject; const { statusColor } = config; const badgeConfig = { badgeService, statusColor, encodeBadgeSubject }; return Promise.all([ jobFactory.get({ pipelineId: id, name: jobName }), pipelineFactory.get(id) ]).then(([job, pipeline]) => { if (!job) { return reply.redirect(getUrl(badgeConfig)); } if (job.state === 'DISABLED') { return reply.redirect(getUrl(Object.assign( badgeConfig, { builds: [{ status: 'DISABLED' }], subject: `${pipeline.name}:${jobName}` }))); } const listConfig = { paginate: { page: 1, count: 1 } }; return job.getBuilds(listConfig) .then(builds => reply.redirect(getUrl(Object.assign( badgeConfig, { builds, subject: `${pipeline.name}:${jobName}` })))); }).catch(() => reply.redirect(getUrl(badgeConfig))); }, validate: { params: { id: idSchema, jobName: joi.reach(schema.models.job.base, 'name') } } } });
scmContext: Joi .string().max(128) .required() .description('The SCM in which the repository exists') .example('github:github.com'), sha: Joi.string().hex() .when('action', { is: ['release', 'tag'], then: Joi.allow('').optional() }) .when('type', { is: 'ping', then: Joi.allow('').optional(), otherwise: Joi.required() }) .label('Commit SHA') .example('ccc49349d3cffbd12ea9e3d41521480b4aa5de5f'), type: Joi.string() .valid(['pr', 'repo', 'ping']) .required() .label('Type of the event'), username: Joi.reach(SCHEMA_USER, 'username') }).label('SCM Hook'); module.exports = { command: SCHEMA_COMMAND, commit: SCHEMA_COMMIT, repo: SCHEMA_REPO, repoName: REPO_NAME, user: SCHEMA_USER, hook: SCHEMA_HOOK, pr: SCHEMA_PR };
module.exports = () => ({ method: 'PUT', path: '/templates/{templateName}/tags/{tagName}', config: { description: 'Add or update a template tag', notes: 'Add or update a specific template', tags: ['api', 'templates'], auth: { strategies: ['token', 'session'], scope: ['build'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const pipelineFactory = request.server.app.pipelineFactory; const templateFactory = request.server.app.templateFactory; const templateTagFactory = request.server.app.templateTagFactory; const pipelineId = request.auth.credentials.pipelineId; const name = request.params.templateName; const tag = request.params.tagName; const version = request.payload.version; return Promise.all([ pipelineFactory.get(pipelineId), templateFactory.get({ name, version }), templateTagFactory.get({ name, tag }) ]).then(([pipeline, template, templateTag]) => { // If template doesn't exist, throw error if (!template) { throw boom.notFound(`Template ${name}@${version} not found`); } // If template exists, but this build's pipelineId is not the same as template's pipelineId // Then this build does not have permission to tag the template if (pipeline.id !== template.pipelineId) { throw boom.unauthorized('Not allowed to tag this template'); } // If template tag exists, then the only thing it can update is the version if (templateTag) { templateTag.version = version; return templateTag.update().then(newTag => reply(newTag.toJson()).code(200)); } // If template exists, then create the tag return templateTagFactory.create({ name, tag, version }) .then((newTag) => { const location = urlLib.format({ host: request.headers.host, port: request.headers.port, protocol: request.server.info.protocol, pathname: `${request.path}/${newTag.id}` }); return reply(newTag.toJson()).header('Location', location).code(201); }); }).catch(err => reply(boom.wrap(err))); }, validate: { params: { templateName: joi.reach(baseSchema, 'name'), tagName: joi.reach(baseSchema, 'tag') }, payload: { version: joi.reach(baseSchema, 'version') } } } });
'use strict'; const Joi = require('joi'); const mutate = require('../lib/mutate'); const Template = require('../config/template'); const pipelineId = Joi.reach(require('./pipeline').base, 'id'); const MODEL = { id: Joi .number().integer().positive() .description('Identifier of this template') .example(123345), labels: Joi .array() .items(Joi.string()) .description('Labels for template') .example(['stable', 'latest', 'beta']), config: Template.config, name: Joi .string() .max(64) .description('Template name') .example('nodejs/lib'), version: Template.version, description: Template.description, maintainer: Template.maintainer, pipelineId, namespace: Template.namespace, images: Template.images, createTime: Joi .string()
'use strict'; const boom = require('boom'); const joi = require('joi'); const schema = require('screwdriver-data-schema'); const getSchema = schema.models.template.get; const nameSchema = joi.reach(schema.models.template.base, 'name'); const versionSchema = joi.reach(schema.models.template.base, 'version'); const tagSchema = joi.reach(schema.models.templateTag.base, 'tag'); module.exports = () => ({ method: 'GET', path: '/templates/{name}/{versionOrTag}', config: { description: 'Get a single template given template name and version or tag', notes: 'Returns a template record', tags: ['api', 'templates'], auth: { strategies: ['token'], scope: ['user', 'build'] }, plugins: { 'hapi-swagger': { security: [{ token: [] }] } }, handler: (request, reply) => { const templateFactory = request.server.app.templateFactory; const { name, versionOrTag } = request.params; return templateFactory.getTemplate(`${name}@${versionOrTag}`)
'use strict'; const joi = require('joi'); const schema = require('screwdriver-data-schema'); const workflowParser = require('screwdriver-workflow-parser'); const tinytim = require('tinytim'); const idSchema = joi.reach(schema.models.pipeline.base, 'id'); /** * Generate Badge URL * @method getUrl * @param {String} badgeService Badge service url * @param {Object} statusColor Mapping for status and color * @param {Function} encodeBadgeSubject Function to encode subject * @param {Array} [buildsStatus=[]] An array of builds * @param {String} [subject='job'] Subject of the badge * @return {String} */ function getUrl({ badgeService, statusColor, encodeBadgeSubject, buildsStatus = [], subject = 'pipeline' }) { const counts = {}; const parts = []; let worst = 'lightgrey'; const levels = Object.keys(statusColor); buildsStatus.forEach((status) => {
'use strict'; const Annotations = require('../config/annotations'); const Job = require('../config/job'); const Joi = require('joi'); const models = require('../models'); const buildId = Joi.reach(models.build.base, 'id').required(); const eventId = Joi.reach(models.event.base, 'id'); const jobId = Joi.reach(models.job.base, 'id'); const jobName = Joi.reach(models.job.base, 'name'); const SCHEMA_PIPELINE = Joi.object().keys({ id: Joi.reach(models.pipeline.base, 'id').required(), scmContext: Joi.reach(models.pipeline.base, 'scmContext').required() }).unknown(); const SCHEMA_START = Joi.object().keys({ build: Joi.object(), jobId, jobName, annotations: Annotations.annotations, blockedBy: Joi.array().items(jobId), freezeWindows: Job.freezeWindows, buildId, eventId, tokenGen: Joi.func(), pipeline: SCHEMA_PIPELINE, buildClusterName: Joi.reach(models.buildCluster.base, 'name'), container: Joi.reach(models.build.base, 'container').required(), apiUri: Joi.string().uri().required() .label('API URI'),