Pages

Monday, August 3, 2015

Pull request build automation for Stash with Jenkins


Intro


Moving toward the frequent releasing is a challenging goal for all organizations. It needs well crafted delivery pipelines, devops mindset to take care about the whole software delivery lifecycle and also needs efficient tooling and methodology from the beginning. The starting point is the definition of a supportive workflow for your SCM. We are using Atlassian Stash as Git server, therefore I looked after for Git based workflows. Click on the links for the detailed description of these workflows, the Atlassian guys made a great job to explain them.




Workflow comparison


The centralized workflow is an entry level usage of Git and works exactly as a Subversion server. Not efficiently supporting the distributed team structure, the daily releasing and the prototyping. Not really uses DVCS features.

The feature branch workflow is just a bit more complex method where we are starting to use the powerful branching feature in Git that is more convenient than Subversion's own. Unfortunately we are highly dependent from the master branch, the repository is centralized and all of our experiments, unfinished branches are sitting in the central repo to blame us for the our laziness. This is the first workflow where we could use pull requests for review.

The gitflow workflow by Vincent Driessen is quite famous and easy to understand. This is a bigger step up from the feature branch workflow, but still uses Git on basic level, so definitely recommended for the fresh adopters to sharpen their Git skills. It uses one central repository too, but separates the work streams by feature branches and uses central development and releasing branches. The major problem is the administrative overhead of mergings and the centralized repository. When you finished the feature implementation you should merge it back first to the development branch, then to the release preparation branch, then to the release branch itself. The central repo could be filled with never finished/proof-of-concept branches quickly and you should regularly clean-up them and asking around your colleagues what branches are still in use. If we want to use it for frequent releasing we are sacrificing some capacity for the extra administration and housekeeping.

Last but not least we have the forking workflow for Git where every developer makes a forked copy about the application's repository and works on this copy locally, then pushes the changes into the forked repository. The finished and tested feature is delivered back to the main repository as pull request. The release manager(s) are reviewing the PR then merging back to the application's repository as a release candidate feature.


Finally I compared the workflows from the frequent releasing perspective to see what fits to my needs. I had measured them by 4 attributes.

Centralization is considered as a bad thing, because not giving the maximum independence to the developers, to work on their on tasks without disturbed by others accidentally.

Administrative overhead means how much extra effort needed to keep the code clean, coherent and releasable and also how much work needed, to prepare the source for releasing

Housekeeping is the extra work needed to clean-up some previous activities and make the code ready for the upcoming development needs and stopping the devs to work on the code during the activity.

Complexity tells us, how hard is to learn and understand the workflow until it turns to be natural for everyone.


As you could see the Forking workflow is the perfect candidate because the developers can work on their own copy without disturbed by others,  they could get updates form anyone without the need to merge unfinished code (even in a separated branch) into the application repository. Also the administration is low, because the access to the application repository is limited and controlled. Technically almost zero housekeeping needed on the applications repository and you could sync easily the recent changes or fork it again from scratch. The only weak point is the complexity, because it definitely built on the best Git features, but using Git is a long term decision, so the management should reserve time for the devs to get familiar with this great tool.

Testing pull requests


In the forking workflow process the first point where the newly developed code could get publicly available is the pull request made by the developer. This is not just a simple entry point for our private separated work, but the best place to add some automated checks before the delivery manager starts the reviewing. It could save time and protect our application's codebase from our own mistakes. Implementing automated pull request testing could greatly improve our reliability and confidence in our work. For practicing we could setup a small test environment first with Docker.

Setting up the test environment


The test environment made from the following elements:

First round: manual setup


Preparation


The very first step is to download and install Stash on your machine. I'm using the Atlassian's Docker image for Stash to the test drive and I strongly recommend to use Docker for your test drivings, because you could skip a lot of configuration and dependency resolution steps. Just follow the steps described there and create a trial key for your Stash instance, add your SSH keys to the server and that's it. Stash is ready for test driving. 

Next step to configure your Jenkins with the listed plugins (this is the minimum set of required plugins) and create a demo project. My demo project is a simple text file called testfile.txt and I'm just adding one extra line before a commit.

Setup the repos


As a start let's create a Project in Stash called PullRequestTest with PRT key and create a repository within the project as Main project.


Create a folder for your main project (not for the fork) and create the test file. When you click on your repository Stash shows you how to add this folder to Stash under the My code is ready to be pushed section:



Now we have the "application repository". Click on the Fork at side menu and generate your forked repo. Make a clone of the forked repo to your machine then you have the "forked repository".

Create Jenkins job


Before we issue a pull request a Jenkins job should be made to handle it. The pull request triggered job will be a freestyle job where we are configuring the Git repo, Pull Request plugin and a fake "testing" step to verify it works as expected.

Create a Freestyle job named StashPRTest 



In the Source Code Management section configure the Git


The next step to add a build trigger "Stash Pull Request Builder". As you can see the Pull Request builder is based on cron scheduling, not post commit hooks. That's sad, but....



Then create a fake "build" step as Execute shell as:


Now the Jenkins job is ready to server our pull requests.

First we need a test commit into our forked repo. Run the following from the cloned fork repository folder



Now let's create a pull-request from Stash

If done everything well the Jenkins server will checks out the pull request, runs our fake build against it and adds a status comment to the pull request


Overview


Finally here's a simple sequence diagram, how the PR automation works and checks the quality.




Configuring the plugin with our script

After the manual process, let's implement the same solution in the DSL way. We have two options

First we need to check the generated XML on the Jenkins server to see what elements should be created. Let's see them at the http://localhost:8080/job/StashPRTest/config.xml (or something like this ;) ) address.

Be careful with using HTTPS based user/password authentication, because the plugin stores the password in unencrypted format!

Configure block for pull request testing


Let's define a job using configure block to reimplement the pull request automation job:


I'm not a big friend of specifying username and password, so when I found a better idea, I'll surely publish it. You could do some optimizations of course, for example the Stash host url can be stored in an environment variable. This configure block is working very well at the moment and let us to setup every aspects of the pull-request triggering.

Creating library function for pull request testing


If you want a reusable library function, just try this "little" piece of of code :) Maybe you noticed the argument variable names are a bit different from the xml elements. If you choose the same naming, the plugin drops an error message, so I had to alter them a bit.



Summary


Now I shared my overview about the Git workflows and made a code snippet to configure Jenkins via the DSL plugin to test all submitted pull request automatically on the application's code. This is the first line of defense of your code to keep it clean, runnable and build trust in the quality and reliability. You just need to implement all required tests for the PR checks to make sure, no low quality code could get released.

And never forget!


5 comments:

  1. HI I performed the same steps but i face this error hudson.plugins.git.GitException: Failed to fetch from ssh://git@stash-eng.example.com:7999//${projectCode}/${repositoryName}.git
    at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:766)
    It seems that no environt variables like ${projectCode} are there..Help to resolve the same .

    ReplyDelete
  2. Yep, some extra tips could be handy. Where do those environment variables come from?
    How can we check if the polling works?

    ReplyDelete
  3. I keep getting this exception for some reason.

    java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.net.ConnectException: Connection refused
    at stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient.getRequest(StashApiClient.java:241)
    at stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient.getPullRequests(StashApiClient.java:79)
    at stashpullrequestbuilder.stashpullrequestbuilder.StashRepository.getTargetPullRequests(StashRepository.java:71)
    at stashpullrequestbuilder.stashpullrequestbuilder.StashPullRequestsBuilder.run(StashPullRequestsBuilder.java:32)
    at stashpullrequestbuilder.stashpullrequestbuilder.StashBuildTrigger.run(StashBuildTrigger.java:223)
    at hudson.triggers.Trigger.checkTriggers(Trigger.java:273)
    at hudson.triggers.Trigger$Cron.doRun(Trigger.java:222)
    at hudson.triggers.SafeTimerTask.run(SafeTimerTask.java:50)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
    Caused by: java.util.concurrent.ExecutionException: java.net.ConnectException: Connection refused
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:206)
    at stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient.getRequest(StashApiClient.java:233)
    ... 14 more
    Caused by: java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at hudson.util.NoClientBindProtocolSocketFactory.createSocket(NoClientBindProtocolSocketFactory.java:93)
    at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707)
    at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:387)
    at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
    at stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient$1.call(StashApiClient.java:209)
    at stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient$1.call(StashApiClient.java:200)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    ... 1 more

    ReplyDelete
  4. Hi, we are using bitbucket cloud and have requirement - triggering jenkins job once we merge to Master branch in bitbucket manually. How can we achieve this using bitbucket pull request builder plugin? Please help. Thank you

    ReplyDelete
  5. is it possible to add multiple repos in the stash PR builder plugin ?

    ReplyDelete

Note: Only a member of this blog may post a comment.