Wednesday, May 27, 2015

Deploying Spring Boot App using SCP

        In this article Iam going to cover how you could deploy a spring boot application jar into remote server.
I would assume that you have spring boot application is all set up and you could build and generate a deployable (executable jar) artifact using maven. In boot once we have this jar we could simply issue a java command on this jar to start the application inside an embedded tomcat.
So technically deploying the jar artifact means simply uploading the jar to specified location on remote server.We will be using maven-wagon plugin and wagon-ssh extension connector in this article to upload this jar artifact into specified directory on remote linux server as part of maven deploy phase.

In order for this plugin to work we will need to configure the remote server information in the maven settings.xml like below,

<server>
   <id>my.remote.server</id>
   <username>myname</username>
   <password>mypassword</password>
</server>

In the application pom file add the wagon-ssh extension as shown below into the build section,
<extensions>
 <extension>
  <groupId>org.apache.maven.wagon</groupId>
  <artifactId>wagon-ssh</artifactId>
  <version>2.10</version>
 </extension>
</extensions>

Make sure the wagon version is 2.9 and above.
Now add the plugin into your application pom as shown below,
<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>wagon-maven-plugin</artifactId>
 <version>1.0</version>
 <executions>
  <execution>
   <id>upload</id>
   <phase>deploy</phase>
   <goals>
    <goal>upload-single</goal>
   </goals>
   <configuration>
    <serverId>my.remote.server.id</serverId>
    <fromFile>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</fromFile>
    <url>scp://my.remote.server/opt/apps</url>
   </configuration>
  </execution>
 </executions>
</plugin>

We just added a wagon plugin and defined a new goal into the maven's deploy phase.
We will use upload-single goal from wagon plugin since we want to upload only one file which is the application jar file.
Notice the configuration piece for this execution goal upload.

In the url you notice that it is trying to upload into /opt/apps directory. Instead you could add toDir attribute if you want.

It is very important to use the serverId that matches the id of server configuration you did in your maven settings earlier. If you don't specify couple of issues you will run into,
First it will pick up default maven user and forms the default url as "mavenUser@my.remote.server"
If the user is same on remote host then no issues but it would be different in most cases.
So now if you try to modify your url in the configuration as "remoteUser@my.remote.server" then it will end up prompting for the password in the middle of the build that you don't want. So simply point your plugin's configuration to maven server settings using serverId attribute.

Now in order to run maven deploy command you will need to provide the repository distribution url like as shown below,
<distributionManagement>
   <repository>
 <id>my.remote.server.id</id>
 <url>scp://my.remote.server/opt/apps</url>
   </repository>
</distributionManagement>

That's it, now you can run command

 mvn deploy

Your application should run through all phases of build cycle and then finish up successfully uploading the jar file onto remote host.

At this point you should be able to login to your remote server and issue a boot start command.
java -jar mySpringBootApp.jar

While you are in the remote server, if you list the contents under /opt/apps at this point
you will see application jar file and also a folder (example com or org) based on whatever the package structure you used in your application's maven groupId coordinate. You wonder how?, what happened here was maven's default deploy plugin kicked in, generated maven meta data, pom with the deployed application's coordinates and it uploaded it to remote location along with the jar.
In case you really don't want this additional folder in your remote host created by maven's default deploy plugin you simply turn the default deploy plugin off, I mean skip it as shown below,

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <configuration>
 <skip>true</skip>
    </configuration>
</plugin>

If you decide to keep this additional info folder then make sure you are using wagon-ssh version 2.9 and above but if you are using older versions then you will run into issues with redeploy. Build will simply hangs while uploading the meta data. This is due to some glitches either in the maven version or wagon-ssh connection version.
If you are using maven 3.3.x and wagon-ssh version 2.9 and above but problem still persist then you should look into switching to wagon-ssh-external.
Here is how you can replace the extension connector,

<extension>
     <groupId>org.apache.maven.wagon</groupId>
     <artifactId>wagon-ssh-external</artifactId>
     <version>2.6</version>
</extension>

Then you will have to change the URL in your plugin configuration as well as repository distribution section to something like this,
scp://my.remote.server

to 

scpexe://my.remote.server

Lastly if you want to just run the command,
>mvn wagon:upload-single

You can't at this time since the configuration for the plugin is inside the execution so we need to add the same configuration outside of execution as well,
<plugin>
        <groupId>org.codehaus.mojo</groupId>
 <artifactId>wagon-maven-plugin</artifactId>
 <version>1.0</version>
 <configuration>
  <serverId>192.168.88.128</serverId>
  <fromFile>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</fromFile>
  <url>scp://192.168.88.128/opt/apps</url>
 </configuration>
 <executions>
  <execution>
   <id>upload</id>
   <phase>deploy</phase>
   <goals>
    <goal>upload-single</goal>
   </goals>
   <configuration>
    <serverId>192.168.88.128</serverId>
    <fromFile>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</fromFile>
    <url>scp://192.168.88.128/opt/apps</url>
   </configuration>
  </execution>
 </executions>
</plugin>

Now you could run both,
>mvn clean deploy

or

>mvn clean install wagon:upload-single

Here is the complete pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>org.test</groupId>
 <artifactId>demoboot</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>demoBoot</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.2.3.RELEASE</version>
  <relativePath /> 
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <start-class>demoboot.DemoBootApplication</start-class>
  <java.version>1.7</java.version>
 </properties>
 
 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <extensions>
   <extension>
    <groupId>org.apache.maven.wagon</groupId>
    <artifactId>wagon-ssh</artifactId>
    <version>2.10</version>
   </extension>
  </extensions>

  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <configuration>
     <skip>true</skip>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>wagon-maven-plugin</artifactId>
    <version>1.0</version>
    <configuration>
     <serverId>192.168.88.128</serverId>
     <fromFile>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</fromFile>
     <url>scp://192.168.88.128/opt/apps</url>
    </configuration>
    <executions>
     <execution>
      <id>upload</id>
      <phase>deploy</phase>
      <goals>
       <goal>upload-single</goal>
      </goals>
      <configuration>
       <serverId>192.168.88.128</serverId>
       <fromFile>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</fromFile>
       <url>scp://192.168.88.128/opt/apps</url>
      </configuration>
     </execution>
    </executions>
   </plugin>
  </plugins>

 </build>

 <distributionManagement>
  <repository>
   <id>192.168.88.128</id>
   <url>scp://192.168.88.128/opt/apps</url>
  </repository>
 </distributionManagement>


</project>

That's it for now, happy deploying spring boot.

References

Learn more about wagon-ssh
Learn more about maven deploy plugin.
Here is stackoverflow thread talks about fixing hanging issue with wagon-ssh older version