Continuous Integration and Continuous Deployment with Jenkins Pipeline in Grails projects – Visionmate – A full-service digital partner

Continuous Integration and Continuous Deployment with Jenkins Pipeline in Grails projects

Preface

Software development is exciting, but it´s also a long and difficult way of continuous learning and thinking outside the box. Some time ago, software companies had a headache trying to develop and deliver software fast and with quality. At that time, a high percentage of software projects were unsuccessful. In this sense, companies began to think about methodologies and processes to maintain the flow of sustainable development. To be competitive now and in the near future it is important to meet the demand for high quality software and implement technologies which allow the company to do so.

There are many tools that can help the company to take processes to other dimensions, where the speed of deployment and quality are evident. We will present the continuous integration tool called Jenkins, which is an open source tool and has many add-ons that can help us. We intend to show how to create a pipeline (Jenkins Declarative Pipeline) in order to have flexible and powerful stages to build, test and deploy pieces of software.

Goals

Obtain general knowledge about continuous integration tools in the software process, being able to automate the integration and deployment of software pieces.

Requirements

Jenkins 2.73.3 ( If you are using Linux OS you need replace batch scripts by shell scripts)

Grails 3.*

1.0. Continuous integration and deployment approach

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily – leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible (Martin Fowler, 2006).

The development team should focus on development, not on deployment process. The developer’s need is to deliver quality code and pieces of software, once the code is in the repository, the developer is not responsible for more, then how to do it? It is not a solution to do that manually, because anyway you need to establish a local environment to build and deploy the software and if the software is more complex more complex will be the build and deployment process. The best approach is to make the whole process automated, in this case the responsible to do it could be a member of the DevOps team.

Once the code is in the repository, it will be automatically compiled and deployed in our different environments using various technologies and tools. Developers will be concentrating on the development.

2.0. Jenkins approach

Jenkins is a well-known continuous integration tool. It is open source and free, also the developer community is quite large. With Jenkins you can build your project, test it and deploy it in your different environments.

In Jenkins it is possible to create different types of Jobs or Items, among them you can find the “Freestyle project” in which you can use different modules to build and deploy your software and “Jenkins pipeline” is a very easy way to take advantage of all the characteristics of Jenkins. Jenkins Pipeline is much more flexible and customizable. In this article we want to show an example of Jenkins Pipeline.

2.1. Jenkins pipeline

Jenkins pipeline is a Groovy script (Groovy is a dynamic and modern language that is interpreted by Java Virtual Machine (JVM)). Pipeline can be declarative or scripted, the difference between them is that declarative must follow a specific syntax and scripted should not. In this case, we will use the Declarative Pipeline. Declarative Pipeline basically has a simple and useful structure where you can divide your work into Stages and Steps.

Below you can see a Declarative Pipeline structure:

pipeline {
    agent any 
    stages {
        stage('Stage name') {
            steps{ 
            }
        } 
    }
}

The agent syntax specifies where and how pipeline will execute. You can describe many stages and each of them contains steps, in the step syntax you will describe the action you want to do. The Declarative Pipeline is much richer and contains many useful syntax (see: https://jenkins.io/doc/book/pipeline).

Pipelines can be implemented in a Jenkins Job or as a Jenkinsfile in the source of your project, the last one is just a file that contains your declarative pipeline (see: https://jenkins.io/doc/book/pipeline/jenkinsfile/). In this article we will use Pipeline within a Jenkins Job.

2.1.1. Updating from version control repository

In this stage, we will update our source code. We are using a Git module to manage it, by default it is installed. We only need to specify which branch we will use, the URL of the repository and the access Id. We can configure our access Id in the “Credentials” section.

stage('Pull') {
    steps{
        git(branch: 'myBranch', 
            credentialsId: 'myId', url: 'https://github.com/myCompany/MyProject')
    }
}

2.1.2. Build your project

In this stage we will build the project, running a Grails command with batch module. As we use Grails in our project we only have to run a command to build it and specify where we will create our .war file. Later we need to archive our artifact in order to have it available to deploy it or do something else, we use wildcards to specify the path to the artifact.

stage('Build') {
    steps{
        bat(script: 'grails war', returnStatus: true)
        archiveArtifacts '**/build/libs/*.war'
    }
}

2.1.3. Testing your project

Grails 3.* uses Gradle as build tool, and like with other such as Ant and Maven we can implement tasks to be executed calling them directly or within different tasks. When .war artifact is created we can test our piece of software automatically. In this case you can run your test in the build process. In this article we will run the tests after building our project using a new stage in our Jenkins Pipeline.

stage('Test') { 
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'grails test-app my.package.MyTestClass.myTestMethod | findstr BUILD')
            
            if(status.contains('BUILD') && status.contains('FAILED'))
                return true
               else 
                return false
        }
    }
    steps{
        error("TEST FAILED")
    }

}

In this step we used conditional step to make the whole process fail if the test failed.

2.1.4. Deploy

In this case as we have Jenkins running in the same server than as our application server is enough to copy our artifact.

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

With “selector: specific(‘${BUILD_NUMBER}’)” we will be sure to deploy the artifact generated by the current build.

2.2. Hook triggers

In “Build Triggers” section you can check “GitHub hook trigger for GITScm polling” option and in your Github project setting you need to add new webhook with a secret key that you need to configure also in Jenkins. After that when Github detects some change in your branch it will send a notification to Jenkins and it will start your Job.

2.3. Additional configurations and tips

If you are using Tomcat and you have some trouble auto-deploying .war files you can add new stages to your pipeline:

stage('PreDeploy') {
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'sc query Tomcat8 | findstr RUNNING || echo STOPPED')
           
            if(status.contains('STATE') && status.contains('RUNNING'))
                return true
               else 
                return false
        }
    }
    steps {
        bat(script:'net stop Tomcat8')
    } 
}

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

stage('PostDeploy') {
    steps {
        bat(script:'net start Tomcat8')
    }
}

Conclusion

The time dedicated to improve our development process, such as to implement continuous integration and continuous deployment is never a wasted time, and it will ensure faster releases, also a team focus on development processes, and of course, a satisfied customer. Jenkins Pipeline gives us a powerful and easy way to do it.

References

Martin Fowler, 2006: Continuous Integration, https://www.martinfowler.com/articles/continuousIntegration.html

Origin: www.linkedin.com

All articles