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)',