From 878feed47c64561b5483ba1e4f7f72c4004d5daf Mon Sep 17 00:00:00 2001 From: Danny Eck Date: Wed, 4 Dec 2019 19:16:09 -0800 Subject: [PATCH 1/2] Add validateJenkinsfile to jenkins namespace Adds a method to validate a stringified Jenkinsfile by POST'ing to the `/pipeline-model-converter/validate` path. The remote API responds with HTTP 200 whether there are lints reported or not, so we check the if the response body matches the canned reponse for successful validation. --- lib/jenkins.js | 30 ++++++++++++++ test/Dockerfile | 1 + .../invalid/Jenkinsfile.txt | 1 + .../validateJenkinsfile/invalid/response.txt | 5 +++ .../validateJenkinsfile/valid/Jenkinsfile.txt | 40 +++++++++++++++++++ .../validateJenkinsfile/valid/response.txt | 1 + test/jenkins.js | 34 ++++++++++++++++ 7 files changed, 112 insertions(+) create mode 100644 test/fixtures/validateJenkinsfile/invalid/Jenkinsfile.txt create mode 100644 test/fixtures/validateJenkinsfile/invalid/response.txt create mode 100644 test/fixtures/validateJenkinsfile/valid/Jenkinsfile.txt create mode 100644 test/fixtures/validateJenkinsfile/valid/response.txt diff --git a/lib/jenkins.js b/lib/jenkins.js index adfdaca..b78ca67 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,35 @@ 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/validate', + body: formData, + headers: formData.getHeaders() + }; + + var expected = 'Jenkinsfile successfully validated.'; + + return this._post( + req, + function validateJenkinsfileMiddleware(request, next) { + var received = request.res.body.toString().trim(); + request.res.body = received; + if (received === expected) return next(); + next(received); + }, + 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.txt b/test/fixtures/validateJenkinsfile/invalid/response.txt new file mode 100644 index 0000000..0b86851 --- /dev/null +++ b/test/fixtures/validateJenkinsfile/invalid/response.txt @@ -0,0 +1,5 @@ +Errors encountered validating Jenkinsfile: +WorkflowScript: 1: Ambiguous expression could be either a parameterless closure expression or an isolated open code block; + 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.txt b/test/fixtures/validateJenkinsfile/valid/response.txt new file mode 100644 index 0000000..251f93d --- /dev/null +++ b/test/fixtures/validateJenkinsfile/valid/response.txt @@ -0,0 +1 @@ +Jenkinsfile successfully validated. diff --git a/test/jenkins.js b/test/jenkins.js index ffd21ac..c3bee7c 100644 --- a/test/jenkins.js +++ b/test/jenkins.js @@ -59,6 +59,39 @@ describe('jenkins', function() { }); }); + describe('validateJenkinsfile', function() { + it('should return validation results', function(done) { + var self = this; + + self.nock + .post('/pipeline-model-converter/validate') + .reply(200, fixtures.validateJenkinsfile.valid.response); + + self.jenkins.validateJenkinsfile( + fixtures.validateJenkinsfile.valid.Jenkinsfile, + function(err, res) { + should.not.exist(err); + should.equal(res.body, fixtures.validateJenkinsfile.valid.response); + done(); + } + ); + }); + + it('should determine if linting errors are present', function(done) { + var self = this; + + self.nock + .post('/pipeline-model-converter/validate') + .reply(200, fixtures.validateJenkinsfile.invalid.response); + + self.jenkins.validateJenkinsfile('{}', 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 +1767,7 @@ describe('jenkins', function() { 'Jenkins', ' - info (callback)', ' - get (callback)', + ' - validateJenkinsfile (callback)', ' - walk (sync)', ' Build', ' - get (callback)', From afdb4af52c100d8bbf668771166300fe915926e9 Mon Sep 17 00:00:00 2001 From: Danny Eck Date: Wed, 4 Dec 2019 22:12:23 -0800 Subject: [PATCH 2/2] Use json api instead --- README.md | 46 ++++++++++++++++++- lib/jenkins.js | 15 +++--- .../validateJenkinsfile/invalid/response.json | 11 +++++ .../validateJenkinsfile/invalid/response.txt | 5 -- .../validateJenkinsfile/valid/response.json | 6 +++ .../validateJenkinsfile/valid/response.txt | 1 - test/jenkins.js | 22 +++++---- 7 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 test/fixtures/validateJenkinsfile/invalid/response.json delete mode 100644 test/fixtures/validateJenkinsfile/invalid/response.txt create mode 100644 test/fixtures/validateJenkinsfile/valid/response.json delete mode 100644 test/fixtures/validateJenkinsfile/valid/response.txt 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 b78ca67..dc0af83 100644 --- a/lib/jenkins.js +++ b/lib/jenkins.js @@ -188,20 +188,17 @@ Jenkins.prototype.validateJenkinsfile = function(jenkinsfile, callback) { var req = { name: 'validateJenkinsfile', - path: '/pipeline-model-converter/validate', + path: '/pipeline-model-converter/validateJenkinsfile', body: formData, - headers: formData.getHeaders() + headers: formData.getHeaders(), }; - var expected = 'Jenkinsfile successfully validated.'; - return this._post( req, - function validateJenkinsfileMiddleware(request, next) { - var received = request.res.body.toString().trim(); - request.res.body = received; - if (received === expected) return next(); - next(received); + function validateJenkinsfileMiddleware(req, next) { + var res = req.res.body; + if (res.data.result === 'success') return next(); + next(res); }, callback ); 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/invalid/response.txt b/test/fixtures/validateJenkinsfile/invalid/response.txt deleted file mode 100644 index 0b86851..0000000 --- a/test/fixtures/validateJenkinsfile/invalid/response.txt +++ /dev/null @@ -1,5 +0,0 @@ -Errors encountered validating Jenkinsfile: -WorkflowScript: 1: Ambiguous expression could be either a parameterless closure expression or an isolated open code block; - 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/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/fixtures/validateJenkinsfile/valid/response.txt b/test/fixtures/validateJenkinsfile/valid/response.txt deleted file mode 100644 index 251f93d..0000000 --- a/test/fixtures/validateJenkinsfile/valid/response.txt +++ /dev/null @@ -1 +0,0 @@ -Jenkinsfile successfully validated. diff --git a/test/jenkins.js b/test/jenkins.js index c3bee7c..0cbe350 100644 --- a/test/jenkins.js +++ b/test/jenkins.js @@ -64,14 +64,17 @@ describe('jenkins', function() { var self = this; self.nock - .post('/pipeline-model-converter/validate') + .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(res.body, fixtures.validateJenkinsfile.valid.response); + should.equal(actual.status, expected.status); + should.equal(actual.data.result, expected.data.result); done(); } ); @@ -81,14 +84,17 @@ describe('jenkins', function() { var self = this; self.nock - .post('/pipeline-model-converter/validate') + .post('/pipeline-model-converter/validateJenkinsfile') .reply(200, fixtures.validateJenkinsfile.invalid.response); - self.jenkins.validateJenkinsfile('{}', function(err) { - should.exist(err); - err.message.should.containEql(fixtures.validateJenkinsfile.invalid.response); - done(); - }); + self.jenkins.validateJenkinsfile( + fixtures.validateJenkinsfile.invalid.Jenkinsfile, + function(err) { + should.exist(err); + err.message.should.containEql(fixtures.validateJenkinsfile.invalid.response); + done(); + } + ); }); });