diff --git a/README.md b/README.md index 91561d6..1e95724 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a Node.js client for [Jenkins](http://jenkins-ci.org/). ## Documentation - * jenkins: [init](#init), [info](#info) + * jenkins: [init](#init), [info](#info), [validateJenkinsfile](#validate-jenkinsfile) * build: [get](#build-get), [log](#build-log), [logStream](#build-log-stream), [stop](#build-stop), [term](#build-term) * job: [build](#job-build), [get config](#job-config-get), [set config](#job-config-set), [copy](#job-config-copy), [create](#job-create), [destroy](#job-destroy), [disable](#job-disable), [enable](#job-enable), [exists](#job-exists), [get](#job-get), [list](#job-list) * label: [get](#label-get) @@ -98,6 +98,50 @@ Result } ``` + +### jenkins.validateJenkinsfile(Jenkinsfile, callback) + +Validates a Jenkinsfile. + +Usage + +``` javascript +var jenkinsfile = getStringifiedJenkinsfileSomehow(); + +jenkins.validateJenkinsfile(jenkinsfile, function(err, data) { + if (err) throw err; + + console.log('info', data); +}); +``` + +Result + +```json +{ + "status": "ok", + "data": { + "result": "success" + } +} +``` + +_or_ + +```json +{ + "status": "ok", + "data": { + "result": "failure", + "errors": [ + { + "error": "some-error-message" + } + ] + } +} +``` + ### jenkins.build.get(options, callback) diff --git a/lib/jenkins.js b/lib/jenkins.js index adfdaca..dc0af83 100644 --- a/lib/jenkins.js +++ b/lib/jenkins.js @@ -10,6 +10,7 @@ var papi = require('papi'); var util = require('util'); +var FormData = require('form-data'); var Build = require('./build').Build; var CrumbIssuer = require('./crumb_issuer').CrumbIssuer; @@ -177,6 +178,32 @@ Jenkins.prototype.info = function(opts, callback) { Jenkins.prototype.get = Jenkins.prototype.info; +/** + * Validate Jenkinsfile + */ +Jenkins.prototype.validateJenkinsfile = function(jenkinsfile, callback) { + + var formData = new FormData(); + formData.append('jenkinsfile', jenkinsfile); + + var req = { + name: 'validateJenkinsfile', + path: '/pipeline-model-converter/validateJenkinsfile', + body: formData, + headers: formData.getHeaders(), + }; + + return this._post( + req, + function validateJenkinsfileMiddleware(req, next) { + var res = req.res.body; + if (res.data.result === 'success') return next(); + next(res); + }, + callback + ); +}; + /** * Walk methods */ diff --git a/test/Dockerfile b/test/Dockerfile index 438b07a..d1c884a 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -31,5 +31,6 @@ jdk-tool\n\ script-security\n\ command-launcher\n\ cloudbees-folder\n\ +pipeline-model-definition\n\ ' > /usr/share/jenkins/ref/plugins.txt RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt diff --git a/test/fixtures/validateJenkinsfile/invalid/Jenkinsfile.txt b/test/fixtures/validateJenkinsfile/invalid/Jenkinsfile.txt new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/fixtures/validateJenkinsfile/invalid/Jenkinsfile.txt @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/validateJenkinsfile/invalid/response.json b/test/fixtures/validateJenkinsfile/invalid/response.json new file mode 100644 index 0000000..d79b556 --- /dev/null +++ b/test/fixtures/validateJenkinsfile/invalid/response.json @@ -0,0 +1,11 @@ +{ + "status": "ok", + "data": { + "result": "failure", + "errors": [ + { + "error": "Ambiguous expression could be either a parameterless closure expression or an isolated open code block;\n solution: Add an explicit closure parameter list, e.g. {it -> ...}, or force it to be treated as an open block by giving it a label, e.g. L:{...} @ line 1, column 1." + } + ] + } +} diff --git a/test/fixtures/validateJenkinsfile/valid/Jenkinsfile.txt b/test/fixtures/validateJenkinsfile/valid/Jenkinsfile.txt new file mode 100644 index 0000000..efa4627 --- /dev/null +++ b/test/fixtures/validateJenkinsfile/valid/Jenkinsfile.txt @@ -0,0 +1,40 @@ +#!/usr/bin/env groovy + +pipeline { + agent { + docker { + image 'image:tag' + } + } + stages { + stage('Initialize') { + steps { + sh 'some shell script' + } + } + + stage('Lint') { + steps { + sh 'some shell script' + } + } + + stage('Test') { + steps { + sh 'some shell script' + } + } + + stage('Build') { + steps { + sh 'some shell script' + } + } + + stage('Deliver') { + steps { + sh 'some shell script' + } + } + } +} diff --git a/test/fixtures/validateJenkinsfile/valid/response.json b/test/fixtures/validateJenkinsfile/valid/response.json new file mode 100644 index 0000000..0f1ad49 --- /dev/null +++ b/test/fixtures/validateJenkinsfile/valid/response.json @@ -0,0 +1,6 @@ +{ + "status": "ok", + "data": { + "result": "success" + } +} diff --git a/test/jenkins.js b/test/jenkins.js index ffd21ac..0cbe350 100644 --- a/test/jenkins.js +++ b/test/jenkins.js @@ -59,6 +59,45 @@ describe('jenkins', function() { }); }); + describe('validateJenkinsfile', function() { + it('should return validation results', function(done) { + var self = this; + + self.nock + .post('/pipeline-model-converter/validateJenkinsfile') + .reply(200, fixtures.validateJenkinsfile.valid.response); + + self.jenkins.validateJenkinsfile( + fixtures.validateJenkinsfile.valid.Jenkinsfile, + function(err, res) { + var actual = res.body; + var expected = fixtures.validateJenkinsfile.valid.response; + should.not.exist(err); + should.equal(actual.status, expected.status); + should.equal(actual.data.result, expected.data.result); + done(); + } + ); + }); + + it('should determine if linting errors are present', function(done) { + var self = this; + + self.nock + .post('/pipeline-model-converter/validateJenkinsfile') + .reply(200, fixtures.validateJenkinsfile.invalid.response); + + self.jenkins.validateJenkinsfile( + fixtures.validateJenkinsfile.invalid.Jenkinsfile, + function(err) { + should.exist(err); + err.message.should.containEql(fixtures.validateJenkinsfile.invalid.response); + done(); + } + ); + }); + }); + describe('build', function() { beforeEach(function(done) { helper.setup({ job: true, test: this }, done); @@ -1734,6 +1773,7 @@ describe('jenkins', function() { 'Jenkins', ' - info (callback)', ' - get (callback)', + ' - validateJenkinsfile (callback)', ' - walk (sync)', ' Build', ' - get (callback)',