Saturday, 8 August 2015

Deployment Automation using Jenkins API and Shell Script

If you are using Jenkins as continuous integration system and looking forward to automate deployment activities using Jenkins API and Shell script, this blog may help you out. 

What could be the deployment activities?
In my project, I experienced below manual activities those are required as part of deployment. 
  1. Send Notification before Deployment: Before triggering deployment, developer has to notify team that he is going to trigger deployment. Each developer has to wait for SVN check-ins so that new Jenkins build is not triggered due to their check-ins.
  2. Connect to Remote Linux Boxes: Login to Linux boxes through SSH client and trigger the deployment through shell script which used to deploy application into WebSphere. The responsibility of this script is to download EAR from Jenkins and trigger deployment through WebSphere commands.
  3. Apply DB Changes: Execute database changes into common dev environment's DB schema.
  4. Deploy UI Static Contents: After EAR deployment, UI contents need to be deployed into Node.js HTTP server.
  5. Application Health Check: Once, deployment is done then developer has to verify whether applications are deployed successfully and these are working fine.
  6. Send Notification after Deployment: Finally, notify the team that deployment is done and now common development environment is up and running.

All these manual steps are time consuming and impact development cost up to some extent. Why can't all these steps can be automated? Is there any way where you can automate these activities through script? I know there are different tools in market for automatic deployment. But if you don't want to use other tools and looking for simple automation script, this blog can help you out for sure. 

Deployment Activity Flow Diagram
Before automating any steps you need to understand what are the steps and what should be the flow. Let's see the flow of deployment activities first and then try to automate through different unix commands in script. 



How to achieve it?
Unix commands and shell script is one of the best option to automate all aforementioned activities. I explored Jenkins API and Jenkins-cli library  which can be accessed through unix command.

1. Send notification before deployment

First declare below variables in your shell script:
senderEmailId="xyz@xyz.com"
recipients="abc@xyz.com"
Subject= "Deployment is started"
body="<html> <head> <title>HTML E-mail</title> </head> <body>Deployment is started<body>
</head></html>"

Use below command to send mail notification from your script:
( echo "From: $senderEmailId"; echo "To: $recipients"; echo "Subject: $subject "; echo "Content-Type: text/html"; echo "MIME-Version: 1.0"; echo ""; echo "$body";)| /usr/sbin/sendmail -t
2. Check whether current build is failed or success, if failed send deploy failed notification

First declare below variables in your shell script:
user="jenkins-user"
pass="jenkins-pass"
IP="jenkins-ip"
port="jenkins-port"
job="jenkins-job"
subject= "Build is in failed state"  
Use below command to check whether build is failed:
lastBuildNumber=$(curl -s  --insecure       https://$user:$pass@$IP:$port/jenkins/job/$job/lastBuild/buildNumber)
lastSuccessfullBuild=$(curl -s  --insecure https://$user:$pass@$IP:$port/jenkins/job/$job/lastSuccessfulBuild/buildNumber)

#Check if build is stable
if [ "$lastBuildNumber" == "$lastSuccessfullBuild" ]; then
        echo "Latest build is stable. Build $lastBuildNumber can be deployed."
else
       failedBuildLogs=$(curl -s --insecure https://$user:$pass@$IP:$port/jenkins/job/$job/$lastBuildNumber/consoleText | tail -n 100)
      ( echo "From: $senderEmailId"; echo "To: $recipients"; echo "Subject: $subject "; echo "Content-Type: text/html"; echo "MIME-Version: 1.0"; echo ""; echo "$body";)| /usr/sbin/sendmail -t
      echo "Exit from script"
      exit
fi
3. Check whether current build is not in-progress, if so send deploy failed notification

Use below commands to check whether build is in progress :
lastStableBuild=$(curl -s --insecure https://$user:$pass@$IP:$port/jenkins/job/$job/lastStableBuild/buildNumber)
if [ "$lastBuildNumber" == "$lastStableBuild" ]; then
        echo "Latest build is stable. Build $lastBuildNumber can be deployed."
else
      ( echo "From: $senderEmailId"; echo "To: $recipients"; echo "Subject: $subject "; echo "Content-Type: text/html"; echo "MIME-Version: 1.0"; echo ""; echo "$body";)| /usr/sbin/sendmail -t
      echo "Exit from script"
      exit
fi

4. If build is stable then disable it

Before starting deployment its better to disable build so that other check-in during deployment may not trigger another build. To disable build use below command.
curl -X POST --insecure https://$user:$pass@$IP:$port/jenkins/job/$job/disable

5. Execute your deployment script 

At this point, execute your deployment script.

6. After deployment enable the build
curl -X POST --insecure https://$user:$pass@$IP:$port/jenkins/job/$job/enable
7. Perform application health check

After deployment is done, it's better to check whether all applications are accessible. How you can check it automatically? In our application we used log4j configuration that facilitates changing log level dynamically. I used the same log4j URLs to perform application health check. Use below commands for application health check.

healthCheckMailContentTableGrid="<h3>Applications Health Check</h3><table><tr><th>Application</th><th>Instance</th><th>Status</tr>"
declare -a appArr=("app1-context" "app2-context")
declare -a instanceArr=("127.0.0.1:9080" "127.0.0.1:9082")
log4jPackage=com.xxx
isAnyApplicationNotWorking=1
for i in "${appArr[@]}"
do
   echo "Checking health of $i application"
   for instance in "${instanceArr[@]}"
   do
              #Application specific log4j URL 
     healthCheckUrl="http://$instance/$i/Log4JControl/"
     healthCheckStr=$(curl -s --insecure --connect-timeout 5  $healthCheckUrl)
               # If log4jPackage is found in the returned string, consider that app is working
     found=$(echo $healthCheckStr | grep -c $log4jPackage)
     healthCheckHtml="<tr><td>$i</td><td>$instance</td>"
     if [ $found -eq 1 ]; then
          echo "---> App $i is accessible [ $instance ]"

          healthCheckMailContentTableGrid=$healthCheckMailContentTableGrid$healthCheckHtml"<td bgcolor=\"green\">Accessible</td></tr>"
     else
          isAnyApplicationNotWorking=0
          echo "---> App $i is not accessible [ $instance ]"

          healthCheckMailContentTableGrid=$healthCheckMailContentTableGrid$healthCheckHtml"<td bgcolor=\"red\">Not Accessible</td></tr>"
     fi
     echo $found
    done
done
8. Prepare build summary for notification and send successful deployment notification.

In this step you can collect all information like deployed build number, application health check details, changes list which is made in last deployed build and current deployed build etc.
lastBuildNumber=$(curl -s  --insecure  https://$user:$pass@$IP:$port/jenkins/job/$job/lastBuild/buildNumber)
jreLocation=/opt/java/bin 
latestBuildChanges=$($jreLocation/java -jar jenkins-cli.jar -noCertificateCheck -s https://@$IP:$port/jenkins  list-changes  $job $lastBuildNumber --username $user --password $pass) 
lastDeployedBuildNumber ="Set your last deployed build number"
changesBetweenLastAndLatestDeployedBuild=$($jreLocation/java -jar jenkins-cli.jar -noCertificateCheck -s https://@$IP:$port/jenkins  list-changes  $job $lastBuildNumber-$lastDeployedBuildNumber --username $user --password $pass)
At last consolidate deployment summary and send successful deployment notification along with detail.