This blog post will demonstrate how to access an external system using HTTP with form-based security. This will show how you can use the following features:
Jenkins Workflow plugin
Groovy built-in libraries such as HttpClient
Git-based Workflow Global Library repository
Getting Started
JDK 1.7+
Jenkins 1.609+
An external system secured with form-based authentication - e.g., a Jenkins server with security enabled and anonymous access rescinded
Installation
Download and install JDK 1.7 or higher
Setup Jenkins
Update plugins - Make sure you have the latest Workflow plugins by going to Manage Jenkins –> Manage Plugins -> Updates and selecting any Workflow-related updates. Restart Jenkins after the updates are complete. As of this writing the latest version is 1.8.
Global libraries repo - Jenkins exposes a Git repository for hosting global libraries meant to be reused across multiple CD pipelines managed on the controller. We will setup this repository so you can build on it to create your own custom libraries. If this is a fresh Jenkins install and you haven’t setup this Git repository, follow these instructions to setup.
Important - Before proceeding to the next steps, make sure your Jenkins instance is running |
See Workflow Global Library for details on how to set up the shared library - note that if security is enabled, the ssh format works best.
If using ssh ensure that your public key has been configured in Jenkins.
To initialise the git repository:
git clone ssh://<user>@<jenkins_host>:<ssh_port>/workflowLibs.git </ssh_port> </jenkins_host> </user>
Where
USER is a user a valid user that can authenticate
JENKINS_HOST is the DNS name of the Jenkins server you will be running the workflow on. If running on the CloudBees Jenkins platform, this is the relevant client controller not the Jenkins Operations Center node.
SSH_PORT is the ssh port defined in Jenkins configuration:
Note the repository is initially empty.
To set things up after cloning, start with:
git checkout -b master
Now you may add and commit files normally. For your first push to Jenkins you will need to set up a tracking branch:
git push --set-upstream origin master Thereafter it should suffice to run:
git push
Creating a Shared Class to Access Jenkins
cd workflowLibs mkdir -p src/net/harniman/workflow/jenkins curl -O \ https://gist.githubusercontent.com/harniman/36a004ddd5e1c0635edd/raw/3997ddab0ab571c902068afad60cbc56eeda07cb/Server.groovy git add * git commit git push
This will make the following class
net.harniman.workflow.jenkins.Server
available for use by workflow scripts using this syntax:
def jenkins=new net.harniman.workflow.jenkins.Server(<parameters>) </parameters>
and methods accessed using:
jenkins.logon() jenkins.getURL(<supplied url="">) </supplied>
Note:
Classes follow the usual Groovy package naming formats and thus need to be in the appropriate directory structure
It is unnecessary to go out of process to access this Jenkins instance - it can be accessed from Groovy via the Jenkins model, however, this is to show how a client for form-based access could be built
The below script is built to access Jenkins as an example for demonstration
This is the actual contents of Server.groovy:
package net.harniman.workflow.jenkins import org.apache.commons.httpclient.Header import org.apache.commons.httpclient.HostConfiguration import org.apache.commons.httpclient.HttpClient import org.apache.commons.httpclient.NameValuePair import org.apache.commons.httpclient.methods.GetMethod import org.apache.commons.httpclient.methods.PostMethod import org.apache.commons.httpclient.cookie.CookiePolicy import org.apache.commons.httpclient.params.HttpClientParams class Server { String user String password String host Integer port String proto HttpClient client = new HttpClient() Server(String user, String password, String host, Integer port, String proto) { this.user = user this.password=password this.host=host this.port=port this.proto=proto client.getHostConfiguration().setHost(host, port, proto); client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); } HttpClient getHttpClient() { return client } Integer logon() { String logonInitiationURL = proto + "://" + host + ":" + port + "/login?from=%2F" String logonSubmitURL = proto + "://" + host + ":" + port + "/j_acegi_security_check" // We need to make a call first to set up the session // HttpClient will automatically handle cookies based on the // CookiePolicy set above GetMethod get = new GetMethod(logonInitiationURL) client.executeMethod(get); get.releaseConnection() PostMethod authpost = new PostMethod(logonSubmitURL) def json='{"j_username": "'+user+'", "j_password": "'+password+'", "remember_me": false, "from": "/"}' def param1=new NameValuePair("j_username", user) def param2=new NameValuePair("j_password", password) def param3=new NameValuePair("from", "/") def param4=new NameValuePair("json", json) def param5=new NameValuePair("Submit", "log in") authpost.addParameters(param1) authpost.addParameters(param2) authpost.addParameters(param3) authpost.addParameters(param4) authpost.addParameters(param5) client.executeMethod(authpost) // We need to follow the redirect to understand whether authentication // was successful // 200 = Success // 401 = Credentials failure Header header = authpost.getResponseHeader("location"); def response if (header != null) { String newuri = header.getValue(); if ((newuri == null) || (newuri.equals(""))) { newuri = "/"; } GetMethod redirect = new GetMethod(newuri); client.executeMethod(redirect); response=redirect.getStatusCode() redirect.releaseConnection() } authpost.releaseConnection() return response } String getURL(String URL) { GetMethod get = new GetMethod(URL) client.executeMethod(get) def body = get.getResponseBodyAsString() get.releaseConnection() return body } }
Creating the workflow
stage "Initialise" def jenkins = new net.harniman.workflow.jenkins.Server("annie.admin","password", "jenkins.beedemo.local", 80, "http" ) def response stage "Logon" response = jenkins.logon() echo "Response = $response" stage "Query" response = jenkins.getURL("http://jenkins.beedemo.local/roles/whoAmI") echo "Response = $response"
Ensure you substitute in real values for:
You can substitute in the required URL for the query - for instance, it could be/config.xml to retrieve a job’s config.
Run the Job
If it works successfully you should see the raw body of the response printed in the console log.
A successful authentication results in
Response = 200
being printed in the console log. Anything else indicates a failure with the Logon step.
ConclusionDRY.
In this instance, we have also shown how the built-in Groovy libraries can be used to simplify making calls to external systems. These could be configuration databases, ticketing platforms or custom deployment tools. The underlying Groovy libraries can be leveraged to implement complex capabilities such as handling authentication, following redirects, etc. The advantage of using groovy rather than using command line tools from say an `sh` step, is that you have the option perform the logic outside of a `node` block. When executing inside a `node` block, an executor is allocated/consumed, whereas code outside the node block runs as a flyweight task and does not consume an executor.
ReferencesHttpClient Examples
Jenkins Workflow - Getting Started by Udaypal Aarkoti