June 23, 2015

Bamboo, maven-release-plugin, public/private key SSH access to SVN, password access to Nexus

Introduction

Bamboo is great for performing builds.  The maven-release-plugin is great at releasing new versions of your software. SVN is a great source code repository. SSL public/private keys are great for keeping everything secure, and Nexus is great for Maven artifacts.  What's not so great is trying to get all these things working together!  What we'll look at in this blog is how to configure Bamboo build steps for both the repository host and the Maven configuration. Also, we'll look at how to configure your project's pom. Let's start with the Bamboo repository host build step.

Bamboo repository host build step

First, you need to configure the Bamboo repository host build step so Bamboo can checkout your source code:

Figure 1: Repository host build step

Looking at figure 1, the repository host setup is pretty easy: 

(A)
The URL.  It will need to be in the form "svn+ssh" instead of "https". 

(B)
The username.  It's a good idea to create an account specifically for bamboo to use. 

(C)
The authentication type...no big deal. 

(D)
A variable reference to the fully-qualified location of the SSL key.

Next let's take a look at the Maven configuration build step.

Maven configuration build step

Figure 2: Maven configuration build step
Let's take a look at this configuration one field at a time.

(A)
The version of Maven to use for this build. I did this research using 3.0.5.  No guarantees other versions of Maven will work.

(B)
The Maven goal to perform along with some additional attributes.

--batch-mode release:clean release:prepare release:perform
This runs the release plugin in batch mode so there is no user interaction and all defaults are used.

-s /home/bamboo_builder/.m2/bamboo_builder-settings.xml
This configures Maven to use a specific settings file.  In most cases the same Maven settings will be used for all builds in your organization. But, if you have builds which require unique settings, this is how to configure which settings file to use.  The main purpose of this file is to contain the username and encrypted password to access Nexus  (Maven Encryption, 2015, June 21), which will look something like listing 1.

Listing 1: Nexus username & password in settings.xml

  
    nexus
    bamboo_builder
    {lAHiuyjiu4387y5jKLHE29kjlkahyr/=}
  

(C)
The version of Java to use.

(D)
The environment varibles for the build. This is most important field.  After much research, I found these values MUST BE CONFIGURED HERE (not in goals) otherwise the build will fail.

SVN_SSH="ssh -v -l bamboo_builder -i /home/bamboo_builder/.ssh/id_rsa"
This environment variable configures ssh to use the bamboo_builder user and the id_rsa private key when establishing an SSL connection with SVN.  Now, Maven itself has a number of different ways to configure a username and private key both within a settings.xml file and within the pom.xml for a project.  I tried them all and they all failed.  This was the only way I got it to work.

MAVEN_OPTS="-Dsettings.security=/home/bamboo_builder/.m2/bamboo_builder-security-settings.xml"
This configures Maven with a master password used to encrypt the other passwords in the settings.xml file (Maven Encryption, 2015, June 21).  Though not strictly necessary, it's good to have this file.  If you do have it, this is where you put the configuration for the build. Listing 2 shows what this looks like

Listing 2: Maven master password

    {jj498POHIU4HRUhj2rjpu2ajkshdfur/YI=}

(E)
If your project does not have unit tests, uncheck this box so the build won't fail.

Finally, your project's pom.xml needs a bit of special configuration so let's take a look at that next.

Project pom.xml

The project's pom.xml needs a couple configurations: The <scm> tag and the maven-release-plugin version.

First the <scm> tag needs to be configured so Maven knows where the project is located in SVN.  Listing 3 shows what this tag will look like. 

    NOTE: All 3 properties are needed...don't skip any!

Listing 3: The pom <scm> tag 

    scm:svn:svn+ssh://my.subversion.com/data1/svns_repo/path/to/my/project/trunk
    scm:svn:svn+ssh://my.subversion.com/data1/svns_repo/path/to/my/project/trunk
    scm:svn:svn+ssh://my.subversion.com/data1/svns_repo/path/to/my/project/trunk

Next the maven-release-plugin version needs to be configured. The default version of the maven-release-plugin for Maven 3.0.4 has some bugs in it, most notibly when trying to use this configuration to perform mult-module reactor builds.  So your project pom.xml must be configured to use a more recent version of the maven-release-plugin.  Listing 4 shows this.

Listing 4: The maven-release-plugin version

  
    
      
        org.apache.maven.plugins
        maven-release-plugin
        2.5.1
        
          false
        
      
    
  

Hopefully this fully documents everything that's needed.  Enjoy!

References
Maven Encryption. (2015, June 21). maven.apache.org. Retrieved June 23, 2015 from https://maven.apache.org/guides/mini/guide-encryption.html





June 10, 2015

Mocking properties of super classes in different packages for unit testing

Suppose I have the following reusable abstract class.
package org.ferris.io;
import org.apache.log4j.Logger;  
import javax.inject.Inject;
public abstract class AbstractPropertiesFile extends File {
  @Inject
  protected Logger log; 
  // . . .
}
Also suppose my application has a concrete implementation of this abstract class.
package org.ferris.application.preferences;
import  org.ferris.io.AbstractPropertiesFile;
public class PreferencesPropertiesFile extends AbstractPropertiesFile{
// . . .
} 
When unit testing PreferencesPropertiesFile, the challenge is to mock the protected Logger property of the super class.  It's a challenge because:
  1. The concrete class in in a different package than the abstract class
  2. The Logger property is being injected by CDI for you so there is no setter method.
Let's look at the first challenge.  A possible solution is to repackage my application and unit test to use the org.ferris.io package, but this means moving the PreferencesPropertiesFile to a package which doesn't make much sense for it to be in. Now let's look at the second challenge.  Adding a setter method would solve it, but do you really want to add a method just for unit testing purposes?  Neither of these solutions seem right.

A better solution is for the unit test to set the property of the super class using reflection. Though I'm sure many would argue against this solution, I'm OK with it because let's face it, every framework nowadays is using reflection or byte code manipulation, or proxying.  So a little more won't hurt.

Here is how you would setup the unit test.
@Mock
Logger logMock;
// . . .
@Test
public void someTest() {
    PreferencesPropertiesFile props = new PreferencesPropertiesFile ();
    {
        Field logField = AbstractPropertiesFile.class.getDeclaredField("log");
        logField.setAccessible(true);
        logField.set(props, logMock);
    }
    // . . .
} 
Enjoy!