Continuous Integration with Jenkins and Tools: a complete build and release automation

Continuous Integration with Jenkins and Tools: a complete build and release automation

Jenkins, Git, Nexus, Check Style & Sonarqube, Maven, Slack, AWS

Project Overview

Developers will make code commits to the source code management tool Git which will trigger the build.

The build tool(Maven) starts the code build and performs unit testing automatically and sends the report to the Slack channel.

In the next phase, it will perform a code analysis with code analysis tools and a notification will be sent to Slack on passing or failing the Quality Gates.

On passing the code analysis phase, a software artifact will be built and a notification will be sent.

Once artifacts are built they will be deployed to Artifact Repository and a notification will be sent.

If any failure occurs at any of the stages, developers will be notified via the Slack channel and can be able to fix the code. The entire process repeats until successful deployment.

Tools to be Implemented

  • AWS EC2: Compute resources for setting up servers

  • Jenkins: Continuous Integration server

  • Git: Version Control System

  • Maven: Build tool

  • Check Style & Sonarqube: A code analysis tool

  • Slack: Notification

  • Nexus/Sonar Type: Artifacts/Software repository and maven dependency repository.

Flow of Execution

  1. Create a security group for Jenkins, SonarQube, and Nexus server

  2. Create EC2 instances with user data for Jenkins, Nexus, and SonarQube

  3. Post Installation

    • Jenkins setup and plugins

    • Nexus setup and repository setup

    • SonarQube login test

  4. Git integration with VSCode

  5. Build Job with Nexus integration

  6. GitHub Webhook

  7. SonarQube server integration stage

  8. Nexus Artifact upload stage

  9. Slack Notification

Step-1(Creating Security Group for 3 Instances)

Security group for Jenkins(sg-07cd5bd61506852ef - Jenkins-SG)

Jenkins service runs on port 8080 so we have allowed SSH from My IP to port 8080 which will allow SSH requests only from me.

We have allowed access from anywhere to the Jenkins port so that other related services can access Jenkins.

We have also explicitly allowed Sonar-SG to connect to Jenkins at port 8080 so that it can send the result back to Jenkins.

Security Group for Nexus(sg-0dee2f9ee96fec50d - Nexus SG)

We have taken SSH from my IP only to connect to the Nexus EC2 instance via SSH.

Nexus runs on port 8081 so, we have given inbound assess to the Jenkins Security group so that Jenkins can download project dependencies and upload project artifacts to the Nexus server.

We have also taken access to Nexus port 8081 so that we can access Nexus from our web browser itself from My IP only.

Security Group for SonarQube(sg-04ab95b12c15f13f6 - Sonar-SG)

Allowing SSH connection from My IP.

SonarQube server usually runs on port 9000 however, we are configuring a Nexus server in the SonarQube server which will listen to port 80 so we are allowing port 80 from My IP.

Also, port 80 is allowed from Jenkins Security Group so that Jenkins can upload test results to SonarQube.

Step-2(Setting up EC2 Instances from user data for tools)

Jenkins Server

User data for Jenkins:

#!/bin/bash
sudo apt update
sudo apt install openjdk-11-jdk -y
sudo apt install maven -y
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null

echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null

sudo apt-get update
sudo apt-get install jenkins -y
###

Once the server is up and installation is done we have set up Jenkins however we need some plugins to enable Jenkins to interact with our other services such as Nexus, Sonar, Maven, Git, and Slack. To do so we need the below listed plugins from the plugin section of Jenkins:

Nexus Server

Nexus User data

You can refer to this for flawless nexus setup

#!/bin/bash
sudo dnf install java-1.8.0-amazon-corretto -y #Jave installation for amazon linux only
sudo yum update -y
sudo yum install wget -y
sudo mkdir /app && cd /app
sudo wget -O nexus.tar.gz https://download.sonatype.com/nexus/3/latest-unix.tar.gz
sudo tar -xvf nexus.tar.gz
sudo mv nexus-3* nexus
sudo adduser nexus
sudo chown -R nexus:nexus /app/nexus
sudo chown -R nexus:nexus /app/sonatype-work
sudo echo run_as_user="nexus">> /app/nexus/bin/nexus.rc

sudo echo "[Unit]
Description=nexus service
After=network.target

[Service]
Type=forking
LimitNOFILE=65536
User=nexus
Group=nexus
ExecStart=/app/nexus/bin/nexus start
ExecStop=/app/nexus/bin/nexus stop
User=nexus
Restart=on-abort

[Install]
WantedBy=multi-user.target">> /etc/systemd/system/nexus.service
sudo chkconfig nexus on
sudo systemctl start nexus
sudo systemctl enable nexus

Once the installation is done, we can access the Nexus server on the browser and do the further setup at the http://serverPublicIP:8081/.

So, in our project, we have to use Nexus for downloading the dependencies and uploading the artifacts*.* To do so, we have to create several repositories and club them in a group repository.

Setting->Repositories->Create Repository

We'll be creating 4 repositories here:

  1. Maven2 Hosted for Artifact Release

    Here we'll release or host our project artifacts so we have to make the configuration as below,

    What type of artifacts does this repository store? should be "Release":

  2. Maven2 Proxy for Dependancy

    The purpose of this repository is to store the dependencies, so the maven build tool of our application will download all the required project dependencies from this repository. This is called a Proxy repository for Maven.

    Here we have to define remote storage which means when Nexus doesn't find a dependency that our project needs it will connect to this remote storage and download from there.

  3. Maven2 Hosted for Snapshot

    Here the version policy has to be Snapshot and the purpose of this repository is to store snapshots by definition, snapshots are mutable, and releases are immutable. This is why Nexus makes you store them separately because usually, you don't care if you lose snapshots, but you will care if you lose releases.

  4. Maven2 Group

    This will be the Group repository and the purpose of this is to club or group all our above-created repositories in one place:

Finally, we have created all 4 repositories which are VProfile Release, VProfile-Maven-Central, VProfile-Snapshot, and VProfile-maven-Group. With this, our Nexus setup has been completed.

Release vs. Snapshot Versions: Version policies often distinguish between release versions and snapshot versions. Release versions are considered stable and immutable, while snapshot versions represent works in progress and can be overwritten with newer snapshots.

Sonar Server User data

#!/bin/bash
cp /etc/sysctl.conf /root/sysctl.conf_backup
cat <<EOT> /etc/sysctl.conf
vm.max_map_count=262144
fs.file-max=65536
ulimit -n 65536
ulimit -u 4096
EOT
cp /etc/security/limits.conf /root/sec_limit.conf_backup
cat <<EOT> /etc/security/limits.conf
sonarqube   -   nofile   65536
sonarqube   -   nproc    409
EOT

sudo apt-get update -y
sudo apt-get install openjdk-11-jdk -y
sudo update-alternatives --config java

java -version

sudo apt update
wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -

sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
sudo apt install postgresql postgresql-contrib -y
#sudo -u postgres psql -c "SELECT version();"
sudo systemctl enable postgresql.service
sudo systemctl start  postgresql.service
sudo echo "postgres:admin123" | chpasswd
runuser -l postgres -c "createuser sonar"
sudo -i -u postgres psql -c "ALTER USER sonar WITH ENCRYPTED PASSWORD 'admin123';"
sudo -i -u postgres psql -c "CREATE DATABASE sonarqube OWNER sonar;"
sudo -i -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE sonarqube to sonar;"
systemctl restart  postgresql
#systemctl status -l   postgresql
netstat -tulpena | grep postgres
sudo mkdir -p /sonarqube/
cd /sonarqube/
sudo curl -O https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.3.0.34182.zip
sudo apt-get install zip -y
sudo unzip -o sonarqube-8.3.0.34182.zip -d /opt/
sudo mv /opt/sonarqube-8.3.0.34182/ /opt/sonarqube
sudo groupadd sonar
sudo useradd -c "SonarQube - User" -d /opt/sonarqube/ -g sonar sonar
sudo chown sonar:sonar /opt/sonarqube/ -R
cp /opt/sonarqube/conf/sonar.properties /root/sonar.properties_backup
cat <<EOT> /opt/sonarqube/conf/sonar.properties
sonar.jdbc.username=sonar
sonar.jdbc.password=admin123
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube
sonar.web.host=0.0.0.0
sonar.web.port=9000
sonar.web.javaAdditionalOpts=-server
sonar.search.javaOpts=-Xmx512m -Xms512m -XX:+HeapDumpOnOutOfMemoryError
sonar.log.level=INFO
sonar.path.logs=logs
EOT

cat <<EOT> /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube service
After=syslog.target network.target

[Service]
Type=forking

ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop

User=sonar
Group=sonar
Restart=always

LimitNOFILE=65536
LimitNPROC=4096


[Install]
WantedBy=multi-user.target
EOT

systemctl daemon-reload
systemctl enable sonarqube.service
#systemctl start sonarqube.service
#systemctl status -l sonarqube.service
apt-get install nginx -y
rm -rf /etc/nginx/sites-enabled/default
rm -rf /etc/nginx/sites-available/default
cat <<EOT> /etc/nginx/sites-available/sonarqube
server{
    listen      80;
    server_name sonarqube.groophy.in;

    access_log  /var/log/nginx/sonar.access.log;
    error_log   /var/log/nginx/sonar.error.log;

    proxy_buffers 16 64k;
    proxy_buffer_size 128k;

    location / {
        proxy_pass  http://127.0.0.1:9000;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect off;

        proxy_set_header    Host            \$host;
        proxy_set_header    X-Real-IP       \$remote_addr;
        proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto http;
    }
}
EOT
ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/sonarqube
systemctl enable nginx.service
#systemctl restart nginx.service
sudo ufw allow 80,9000,9001/tcp

echo "System reboot in 30 sec"
sleep 30
reboot

SonarQube is up and running:

Source code migration to Git

GitHub Authentication

As we know we have 2 ways of accessing the GitHub repository i.e. SSH and HTTPS. In this project, we'll be using an SSH connection because we are dealing with a private repository this time. And to do so we need to setup authentication between our local machine and the GitHub account.

To establish the connection we need a public key and a private key by the command: $ ssh-keygen

Now this command has created a public key and private key for authentication as below:

The private key will stay in our local machine and the public key has to be sent to the git hub SSH keys as below:

After adding the SSH key in GitHub our connection is successfully established:

Pushing the Code to the Private repository

We have the source code ready in our system to be sent to the remote repository:

To send the code to our remote repository we need to establish the connection between the repository and our local directory by : $ git remote set-url origin git@github.com:rkn1999/VPro_CI_Project.git .

Now, the origin has been changed to git@github.com:rkn1999/VPro_CI_Project.git repository:

As the remote origin has been set up, we can push the source code to the remote repository by $ git push --all origin as below:

And now our code is successfully pushed to our private GitHub repository:

VS Code Integration with GitHub Repo

To integrate with VSCode simply use the command . code and it will open the VS Code editor in the local project folder with all its files.

And now the git hub is opened and it's allowing us to commit:

Once we hit commit it's asking for the commit message:

NB: We have been doing git operations from git bash itself however in this project we have introduced VS Code integration for complete real-time simulation.

Build Job with Nexus

Before setting up the pipeline just make sure you have added JDK and Maven path in Jenkins properly as our Project requires JDK-8 and maven.

Adding Nexus credentials in Jenkins

Writing Jenkins script file for pipeline

Overall, this pipeline script sets up the environment, specifies the required tools, and defines a single stage for building a Maven project.

pipeline{
    agent any
    tools{
        maven "Maven3"
        jdk "OracleJDK8"
    }

    environment{
      SNAP_REPO = 'VProfile-Snapshot'
      NEXUS_USER = 'admin'
      NEXUS_PASSWORD = 'Test@123'
      RELEASE_REPO = 'VProfile-Release'
      CENTRAL_REPO = 'VProfile-maven-central'
      NEXUSIP = '172.31.88.158'
      NEXUSPORT = '8081'
      NEXUS_GRP_REPO = 'VProfile-maven-Group'
      NEXUS_LOGIN = 'nexuslogin' 

    }

    stages{
        stage('Build'){
            steps{
                sh 'mvn -s settings.xml -DskipTests install'
            }
        }
    }
}

We have pushed the above script to our GitHub repository so that we can use that SCM itself.

Creating the Job with the Script

Go to Jenkins dashboard --> create job and select Pipeline

Importing pipeline script to Jenkins from SCM

Initially, we'll get the above error and to resolve this we have to add the Git Private Key here as credentials because now Jenkins will connect to Git through SSH so, it needs the private key as we have already configured the public key with Git in the above section. Click on the Add

And then add the SSH credentials with the below configuration:

Bring the private key from your local machine by $ cat ~/.ssh/id_rsa and paste in the Key section below:

The issue still persists even after adding the credential because Jenkins is trying to do SSH in the backend and we know whenever we try to SSH it asks a Yes/No question and Jenkins is not able to answer that automatically. So, to further resolve it we have to do that on behalf of Jenkins users. And to do so we'll have to SSH to the Jenkins server and accept that request using the command: $ git ls-remote -h git@github.com:rkn1999/VPro_CI_Project.git HEAD . And now you can see it asks the question as usual so it can store the identity of GitHub into the Jenkins User.

And now you can see that the GitHub host has been saved by the command cat .ssh/known-hosts :

And now you can see the connection issue has been resolved and Jenkins is able to connect to the GitHub repository:

And after that make sure you change the branch to ci-jenkins, by default it staus master:

Now Jenkins is pointing to the ci-jenkins branch and Jenkins file. Now the pipeline will be built from the Jenkins File:

Now also you can see that the dependencies are also stored in Nexus Repository:

Git WebHook

Git WebHook helps to start the build automatically whenever there is a code change happens in the repository.

So to add the WebHook we simply have to paste the Jenkins server's public IP in the repository webhook just shown below:

Once this green check mark is shown, it indicates that WebHook is successfully able to establish the connection with Jenkins:

After setting up the GitWebHook we just need to configure our pipeline :

Now the webhook configuration is done. Going forward whenever there are any changes, Jenkins will make start the build execution.

Adding a post-build action, test and code analysis stage in the pipeline script

Now we'll make some changes to the source code to test the webhook functionality. In our Jenkins file, we are going to add another step as post-build action, unit test step, and check style code analysis stage and we'll push the code to the git hub. Now our Jenkins file will look like the below where a new code block post{} and 2 more stages are added:

pipeline{
    agent any
    tools{
        maven "Maven3"
        jdk "OracleJDK8"
    }

    environment{
      SNAP_REPO = 'VProfile-Snapshot'
      NEXUS_USER = 'admin'
      NEXUS_PASS = 'Test@123'
      RELEASE_REPO = 'VProfile-Release'
      CENTRAL_REPO = 'VProfile-maven-central'
      NEXUSIP = '172.31.88.158'
      NEXUSPORT = '8081'
      NEXUS_GRP_REPO = 'VProfile-maven-Group'
      NEXUS_LOGIN = 'nexuslogin' 



    }

    stages{
        stage('Build'){
            steps{
                sh 'mvn -s settings.xml -DskipTests install'
            }
            post{
                success{
                    echo "Now archiving"
                    archiveArtifacts artifacts: '**/*.war'
                }
            }
        }

        stage('Test'){
            steps{
                sh 'mvn -s settings.xml test'
            }
        }

        stage('Checkstyle analysis'){
            steps{
                sh 'mvn -s settings.xml checkstyle:checkstyle'
            }
        }
    }
}

The newly added post block will archive the .war file using the extension archiveArtifacts. The test stage will perform the unit test and send the report and the Checkstyle Analysis stage will perform the code analysis.

Commit and Push the changes to the repo

Now you can see the build has triggered automatically:

And now you can see 2 new stages have been created and the build has succeeded:

Code Analysis with SonarQube

Our build has been successful and through the check style analysis phase some reports have been generated based on the code quality as below:

However, these reports are not in human understandable format now. These are just XML reports. To make these reports and to represent it in human understandable format we need to use SonarQube.

So to implement SonarQube, we need 2 things in Jenkins to set up:

  • A sonar scanner tool which will scan the result and publish to SonarQube just like we have Maven tool, JDK tool, Nexus etc.

  • We need SonarQube server information in Jenkins so, Jenkins will know where to upload.

Setting up sonar scanner tool

It's simple, in the Global Tool Configuration we can add the Soncar scanner just the way we did for JDK:

Configuring SonarQube Server Information in Jenkins

Generating Sonar Token for Jenkins authentication

Adding Sonar Server in Jenkins

Manage Jenkins --> Configure System --> SonarQube server :

Adding sonar token as Secret Text :

Code changes in the Jenkins file for SonarQube

We are adding another stage for SonarQube in Jenkins file:

stage('Sonar Analysis') {
            environment {
                scannerHome = tool "${SONARSCANNER}"
            }
            steps {
               withSonarQubeEnv("${SONARSERVER}") {
                   sh '''${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=vprofile \
                   -Dsonar.projectName=vprofile \
                   -Dsonar.projectVersion=1.0 \
                   -Dsonar.sources=src/ \
                   -Dsonar.java.binaries=target/test-classes/com/visualpathit/account/controllerTest/ \
                   -Dsonar.junit.reportsPath=target/surefire-reports/ \
                   -Dsonar.jacoco.reportsPath=target/jacoco.exec \
                   -Dsonar.java.checkstyle.reportPaths=target/checkstyle-result.xml'''
              }
            }
        }

Now the entire Jenkins file looks something like with 2 additional variables SONARSERVER and SONARSCANNER:

pipeline{
    agent any
    tools{
        maven "Maven3"
        jdk "OracleJDK8"
    }

    environment{
      SNAP_REPO = 'VProfile-Snapshot'
      NEXUS_USER = 'admin'
      NEXUS_PASS = 'Test@123'
      RELEASE_REPO = 'VProfile-Release'
      CENTRAL_REPO = 'VProfile-maven-central'
      NEXUSIP = '172.31.88.158'
      NEXUSPORT = '8081'
      NEXUS_GRP_REPO = 'VProfile-maven-Group'
      NEXUS_LOGIN = 'nexuslogin' 
      SONARSERVER = 'sonarserver'
      SONARSCANNER = 'sonarscanner'
    }

    stages{
        stage('Build'){
            steps{
                sh 'mvn -s settings.xml -DskipTests install'
            }
            post{
                success{
                    echo "Now archiving"
                    archiveArtifacts artifacts: '**/*.war'
                }
            }
        }

        stage('Test'){
            steps{
                sh 'mvn -s settings.xml test' 
            }
        }

        stage('Checkstyle analysis'){
            steps{
                sh 'mvn -s settings.xml checkstyle:checkstyle'
            }
        }
        stage('Sonar Analysis') {
            environment {
                scannerHome = tool "${SONARSCANNER}"
            }
            steps {
               withSonarQubeEnv("${SONARSERVER}") {
                   sh '''${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=vprofile \
                   -Dsonar.projectName=vprofile \
                   -Dsonar.projectVersion=1.0 \
                   -Dsonar.sources=src/ \
                   -Dsonar.java.binaries=target/test-classes/com/visualpathit/account/controllerTest/ \
                   -Dsonar.junit.reportsPath=target/surefire-reports/ \
                   -Dsonar.jacoco.reportsPath=target/jacoco.exec \
                   -Dsonar.java.checkstyle.reportPaths=target/checkstyle-result.xml'''
              }
            }
        }
    }
}

Now let's save the changes and push the code to the repository so that it can create another stage for Code Analysis.

Result on SonarQube

Now you can see that the result has been published to the SonarQube dashboard.

SonarQube WebHook

Now as the code analysis has been completed, Jenkins will be waiting for the report back from SonarQube. Just like GitHub webhook, SonarQube has its WebHook for sending and receiving information. To set the webhook we have to go to project --> Project Setting--> WebHooks just like below:

On the webhook page, click on Create and give the URL of your Jenkins Server. Here I have given the private IP of my Jenkins Server followed by a port number(8080) followed by sonarqube-webhook exactly like we did for Git webhook:

Adding QualityGate stage in Jenkins Pipeline

stage("Quality Gate") {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    // Parameter indicates whether to set pipeline to UNSTABLE if Quality Gate fails
                    // true = set pipeline to UNSTABLE, false = don't
                    waitForQualityGate abortPipeline: true
                }
            }
        }

This stage will make Jenkins wait for the SonarQube result to receive from WebHook. The above snippet says that wait for 1 hour and if no response is received abort the pipeline.

Now I have pushed the changes and the pipeline has failed with another stage added as Quality Gate

The above failure is made intentionally by modifying quality gates to see if Jenkins is receiving the response back from SonarQube.

Now I have updated the quality gate condition previously it was 20 and bug count was 32 hence it was failing.

Now with the updated quality gate, the build should success:

Now you can see the log quality gate is ok

Publish Artifact to Nexus Repo

Until now, our code test and code analysis have been completed and our next step is to publish the artifact to the Nexus repository we have created as VProfile-Release. Currently, the repository is empty.

We already have installed a plugin called build time stamp in Jenkins. That will help us versioning the artifact name with a pattern as yy-MM-dd_HH:mm

Adding code to Jenkins file for publishing artifact

Now using the code we'll create another stage called UploadArtifact

stage("UploadArtifact"){
            steps{
                nexusArtifactUploader(
                  nexusVersion: 'nexus3',
                  protocol: 'http',
                  nexusUrl: "${NEXUSIP}:${NEXUSPORT}",
                  groupId: 'QA',
                  version: "${env.BUILD_ID}-${env.BUILD_TIMESTAMP}",
                  repository: "${RELEASE_REPO}",
                  credentialsId: "${NEXUS_LOGIN}",
                  artifacts: [
                    [artifactId: 'vproapp',
                     classifier: '',
                     file: 'target/vprofile-v2.war',
                     type: 'war']
                  ]
                )
            }
        }

Now let's push this file and see the changes. And now you can see another stage named UploadArtifact:

And now we can see the artifact has been uploaded to our VProfie-Release:

Slack Notification

Log in to Slack with EmailID and create a channel. I have logged in with my Gmail and created a channel jenkins-ci-cd

Integrating with Jenkins

We have to add an app to Slack. So, go to Slack app and add Jenkins and choose the

And choose the workspace where you want to integrate Slack:

Get the token from the bottom of this Slack page.

Now we have to configure it with Jenkins:

Adding Token:

You can test the connection by the below button. I have received a notification on my mobile as well:

Adding Post-build action for Slack in the Jenkins file

 post {
        always {
            echo 'Slack Notifications.'
            slackSend channel: '#jenkins-ci-cd',
                color: COLOR_MAP[currentBuild.currentResult],
                message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} \n More info at: ${env.BUILD_URL}"
        }
    }

The post block in the Jenkins pipeline script you provided specifies actions to be performed after the stages of the pipeline have been completed, regardless of the build result (success or failure).

bashCopy codeslackSend channel: '#jenkins-ci-cd',
    color: COLOR_MAP[currentBuild.currentResult],
    message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} \n More info at: ${env.BUILD_URL}"

This step sends a notification to a Slack channel. It includes the following information in the notification message:

  • The build result (currentBuild.currentResult) is displayed in bold, surrounded by asterisks (*).

  • The job name (env.JOB_NAME) and build number (env.BUILD_NUMBER) are included.

  • The build URL (env.BUILD_URL) is provided as a clickable link to access more information about the build.

The always block ensures that the Slack notification is sent regardless of the build outcome. This allows you to receive notifications in your specified Slack channel for every build, whether it is successful or not.

Now, let's push the newly modified Jenkins file and we'll see a new stage as Post action which sends the Slack notification.

Above you can see a new stage as Declarative: Post Actions. This step has enabled Slack to send post-build notifications as below:

Final End-to-End Jenkins Pipeline code

def COLOR_MAP = [
    'SUCCESS': 'good', 
    'FAILURE': 'danger',
]
pipeline{
    agent any
    tools{
        maven "Maven3"
        jdk "OracleJDK8"
    }

    environment{
      SNAP_REPO = 'VProfile-Snapshot'
      NEXUS_USER = 'admin'
      NEXUS_PASS = 'Test@123'
      RELEASE_REPO = 'VProfile-Release'
      CENTRAL_REPO = 'VProfile-maven-central'
      NEXUSIP = '172.31.88.158'
      NEXUSPORT = '8081'
      NEXUS_GRP_REPO = 'VProfile-maven-Group'
      NEXUS_LOGIN = 'nexuslogin' 
      SONARSERVER = 'sonarserver'
      SONARSCANNER = 'sonarscanner'



    }

    stages{
        stage('Build'){
            steps{
                sh 'mvn -s settings.xml -DskipTests install'
            }
            post{
                success{
                    echo "Now archiving"
                    archiveArtifacts artifacts: '**/*.war'
                }
            }
        }

        stage('Test'){
            steps{
                sh 'mvn -s settings.xml test' 
            }
        }

        stage('Checkstyle analysis'){
            steps{
                sh 'mvn -s settings.xml checkstyle:checkstyle'
            }
        }
        stage('Sonar Analysis') {
            environment {
                scannerHome = tool "${SONARSCANNER}"
            }
            steps {
               withSonarQubeEnv("${SONARSERVER}") {
                   sh '''${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=vprofile \
                   -Dsonar.projectName=vprofile \
                   -Dsonar.projectVersion=1.0 \
                   -Dsonar.sources=src/ \
                   -Dsonar.java.binaries=target/test-classes/com/visualpathit/account/controllerTest/ \
                   -Dsonar.junit.reportsPath=target/surefire-reports/ \
                   -Dsonar.jacoco.reportsPath=target/jacoco.exec \
                   -Dsonar.java.checkstyle.reportPaths=target/checkstyle-result.xml'''
              }
            }
        }

        stage("Quality Gate") {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    // Parameter indicates whether to set pipeline to UNSTABLE if Quality Gate fails
                    // true = set pipeline to UNSTABLE, false = don't
                    waitForQualityGate abortPipeline: true
                }
            }
        }

        stage("UploadArtifact"){
            steps{
                nexusArtifactUploader(
                  nexusVersion: 'nexus3',
                  protocol: 'http',
                  nexusUrl: "${NEXUSIP}:${NEXUSPORT}",
                  groupId: 'QA',
                  version: "${env.BUILD_ID}-${env.BUILD_TIMESTAMP}",
                  repository: "${RELEASE_REPO}",
                  credentialsId: "${NEXUS_LOGIN}",
                  artifacts: [
                    [artifactId: 'vproapp',
                     classifier: '',
                     file: 'target/vprofile-v2.war',
                     type: 'war']
                  ]
                )
            }
        }
    }
    post {
        always {
            echo 'Slack Notifications.'
            slackSend channel: '#jenkins-ci-cd',
                color: COLOR_MAP[currentBuild.currentResult],
                message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} \n More info at: ${env.BUILD_URL}"
        }
    }
}

Conclusion

This is a complete end-to-end continuous Integration project. In this project, we have implemented Jenkins, Git, Maven, Nexus, CheckStyle, SonarQube, and Slack for integrating, source code managing, building, testing, code analysis, artifact deployment, and notification system respectively.

We have done all these with Jenkins Pipeline Script which is given above.