DEV Community

Cover image for CI/CD #03. Jenkins: using Pipeline and proper Bash script to run Pytest.
Be Hai Nguyen
Be Hai Nguyen

Posted on • Edited on

CI/CD #03. Jenkins: using Pipeline and proper Bash script to run Pytest.

We write a proper and generic Bash script file to ⓵ create a virtual environment, ⓶ run editable install, and ⓷ run Pytest for all tests. Then we write a generic Jenkins Pipeline which will ⓵ clone a Python project GitHub repository, ⓶ call the Bash script file to do all the works.

To recap, I am running Jenkins 2.388 LTS on my Ubuntu 22.10, the name of this machine is HP-Pavilion-15. I am accessing Jenkins and HP-Pavilion-15 from my Windows 10 machine using FireFox and ssh, respectively. And Pipeline is the Jenkins documentation page which I have been working through.

I'm discussing converting the “Free style shell script” in CI/CD #01. Jenkins: manually clone a Python GitHub repo and run Pytest to a proper Bash script (file), and calling this Bash script file in a Jenkins Pipeline using the sh step.

Jenkins is running under user jenkins, and the group is also named jenkins. This is apparent if we list contents of Jenkins' work directory, which is /var/lib/jenkins/workspace/.

To make managing permissions easier, I will create the Bash script file under user jenkins. To do that, we need to be able to log in, and ssh to HP-Pavilion-15 under user jenkins. We need to set its password:

$ sudo passwd jenkins
Enter fullscreen mode Exit fullscreen mode

Once the password is set, we can use it to log in, via:

C:\>ssh jenkins@hp-pavilion-15
Enter fullscreen mode Exit fullscreen mode

The original “Free style shell script” code we are converting to proper Bash script code is:

PYENV_HOME=$WORKSPACE/venv

# Delete previously built virtualenv
if [ -d $PYENV_HOME ]; then
    rm -rf $PYENV_HOME
fi

# Create virtualenv and install necessary packages
virtualenv $PYENV_HOME
. $PYENV_HOME/bin/activate
$PYENV_HOME/bin/pip install -e .
$PYENV_HOME/bin/pytest
Enter fullscreen mode Exit fullscreen mode

ssh into the Ubuntu machine with:

C:\>ssh jenkins@hp-pavilion-15
Enter fullscreen mode Exit fullscreen mode

The home directory of user jenkins is /var/lib/jenkins. Create the new sub-directory scripts/ under home directory, and create the pytest.sh Bash script file under this /var/lib/jenkins/scripts.

Content of /var/lib/jenkins/scripts/pytest.sh:
Enter fullscreen mode Exit fullscreen mode
#!/bin/bash

#
# 04/01/2023.
#
# A generic script which create a clean virtual environment and run pytest.
#
# Intended to be used in a Jenkins GitHub project Pipeline as:
#
#    sh("${JENKINS_HOME}/scripts/pytest.sh ${WORKSPACE}")
#
# The repo is cloned automatically by Jenkins. This script does the followings:
#
#   1. If virtual directory ${WORKSPACE}/venv exists, remove all of it.
#
#   2. Create virtual environment ${WORKSPACE}/venv, then activate it.
#
#   3. Run editable install for the project.
#
#   4. Finally run pytest.
#

if [ -z $1 ]; then
    echo "Usage: ${0##*/} <dir>"
    exit 1
fi

if [ ! -d $1 ]; then
    echo "$1 does not exist."
    exit 1
fi

virtual_dir=$1/venv

echo "Virtual directory $virtual_dir."

if [ -d $virtual_dir ]; then
    rm -rf $virtual_dir
    echo "$virtual_dir removed."
fi

virtualenv $virtual_dir

cd $1

. $virtual_dir/bin/activate
$virtual_dir/bin/pip install -e .
$virtual_dir/bin/pytest
Enter fullscreen mode Exit fullscreen mode

Param $1 to pytest.sh is basically the project directory: Jenkins environment variable ${WORKSPACE}. Let's walk through the code:

  • Lines 23-26 -- there must be a directory parameter supplied, otherwise exit with error code 1.
  • Lines 28-31 -- the directory passed in must exist, otherwise exit with error code 1.
  • Line 33 -- assemble the virtual directory absolute path under the passed in project directory. The name of the actual virtual directory is venv.
  • Line 35 -- print out the absolute virtual directory path.
  • Lines 37-40 -- if the virtual directory exists already, remove it.
  • Line 42 -- create the virtual environment using the absolute virtual directory.
  • Line 44 -- move to the passed in directory. This directory is the parent of the actual virtual directory venv.
  • Line 46 -- activate the virtual environment.
  • Line 47 -- run editable install.
  • Line 48 -- run Pytest.

We have to set the Execute permission for the owner:

$ chmod u+x pytest.sh
Enter fullscreen mode Exit fullscreen mode

057-01.png

This script is generic enough to do Pytest for any Python project via a Jenkins Pipeline. The documentation also states that, an advantage of this approach is that we can test, such as this Bash script, in isolation. And also, reducing the Pipeline code, making it easier to manage and follow.

Following is a generic Jenkins Pipeline code, which uses the above script:

pipeline {
    agent any

    stages {
        stage('Pytest') {
            steps {
                sh("${JENKINS_HOME}/scripts/pytest.sh ${WORKSPACE}")
            }
        }

        stage('Email Notification') {
            steps {
                mail(body: "${JOB_NAME}, build ${BUILD_NUMBER} Pytest completed.", subject: 'Pytest completed.', to: 'behai_nguyen@hotmail.com')
            }
        }       
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Line 7 -- environment variable ${JENKINS_HOME} is user jenkins home directory, ${WORKSPACE} is the project directory.
  • Lines 13 -- send out a simple email whose body has the project name and the build number. This is just a simple email, and is discussed in CI/CD #02. Jenkins: basic email using your Gmail account.

Updated on 07/06/2023 -- Starts

The above Jenkins Pipeline code will work ONLY with Pipeline script from SCM, discussed later. It does NOT ALWAYS work with GitHub project, also discussed later.

GitHub project will not clone the repo automatically! I made the mistake of thinking that it does: possibly because I already had the repo cloned on the local Jenkins workspace.

We need to include the stage to clone the repo Clone Git repo. The Jenkins Pipeline code for GitHub project, hence, is:

pipeline {
    agent any

    stages {
        stage('Clone Git repo') {
            steps {
                git(
                    url: "https://github.com/behai-nguyen/bh_apistatus.git",
                    branch: "main",
                    changelog: false,
                    poll: false
                )
            }
        }

        stage('Pytest') {
            steps {
                sh("${JENKINS_HOME}/scripts/pytest.sh ${WORKSPACE}")
            }
        }

        stage('Email Notification') {
            steps {
                mail(body: "${JOB_NAME}, build ${BUILD_NUMBER} Pytest completed.", subject: 'Pytest completed.', to: 'behai_nguyen@hotmail.com')
            }
        }       
    }
}
Enter fullscreen mode Exit fullscreen mode

Also, in /var/lib/jenkins/scripts/pytest.sh, the comment:

...
# The repo is cloned automatically by Jenkins. This script does the followings:
...
Enter fullscreen mode Exit fullscreen mode

is no longer true. It should be changed to:

...
# This script does the followings:
...
Enter fullscreen mode Exit fullscreen mode

Updated on 07/06/2023 -- Ends

On double quotes ("") and single quotes ('')

In the above Pipeline script, line 7 uses double quotes (""). The first part of line 13 uses double quotes (""), while the rest uses single quotes (''). In Jenkins, this is known as String interpolation. There are security implications when it comes to credentials, the document recommends using single quotes. But if I used single quotes, the value of the environment variables would not come out, instead everything within the single quotes comes out as a literal string. I.e.:

'${JOB_NAME}, build ${BUILD_NUMBER} Pytest completed.'

The output is a literal string:

${JOB_NAME}, build ${BUILD_NUMBER} Pytest completed.

I don't quite understand this yet. I need to keep this in mind.

Let's do a Jenkins Pipeline to test this. The repo I am using is https://github.com/behai-nguyen/bh_apistatus.git/. The name of the new Pipeline project should be bh_apistatus, on the Configure page, check GitHub project, then fill in the address of the repo, as seen:

057-02.png

Scroll down to Pipeline, and select Pipeline script, the content is the Pipeline script code above:

057-03.png

Please note that, if we save the above Pipeline script code to a file at the root level of the project, for example Jenkinsfile, then check in this file as part of the source code, then we could select Pipeline script from SCM, and fill in the rest of the fields as:

057-04.png
057-05.png

Script Path is a free type field, and is defaulted to Jenkinsfile, if we use some other name, ensure to enter it correctly.

I have tested both implementations on more than one repo. I did not have any problem.

It's clearly better to store the Pipeline script code in a file, so that we can use our preference IDE to edit it, and also keeping a history of bug fixings and etc.

This post is yet another smaller step to understand Jenkins better. I did enjoy investigating this. I hope you find the information useful. Thank you for reading and stay safe as always.

Top comments (0)