Managing Tasks with Jenkins CI

Jenkins could be implemented in practically any company that has to automatically deploy applications and infrastructures, as well as for just comfortably managing various types of tasks.

There are lots of different tools on the market today, both proprietary and free, that make continuous integration as easy as possible.

Jenkins is a free tool that offers countless capabilities thanks to the availability of thousands of plugins, which are constantly being upgraded and released.

The most basic approach to managing tasks is the actual Jenkins web interface, where we can configure whatever we need. Unfortunately, the more tasks we have and the more varied they are, the harder it is to maintain.

Today we’ll be looking at how we can make creating tasks in Jenkins easier and faster.

Our Tools

Jenkins Job Builder

This is a python script that lets us describe tasks in YAML and JSON format and then uploads them to the server via HTTP API. We just have to install it on our local machine, write the configuration file with connections, and describe the tasks.
Current project documentation

DSL Plugin

This is a Jenkins plugin, which we can use to describe tasks in a special language (DSL, Domain Specific Language) and then generate them from those descriptions. It runs locally on the actual Jenkins server.
Plugin page

Jenkins Pipeline

This is a type of Jenkins task that we can use to describe deployment or application build processes in stages. We’ll be describing our pipeline in a special Jenkinsfile, which will be saved in the project’s repository.

Gitlab and Gitlab CI

This is the most popular and actively developing self hosted project, which is also used by many companies. There are separate community and enterprise versions.

Gitlab has its own CI written in Go, which lets us execute various build and deploy stages from Runners.

To get started, we have to create a .gitlab-ci.yml file in the project’s repository. This is where we’ll describe our execution sequence. Docker would work excellently in this case.

Let’s Get Started

To start off, we’ll install the necessary tools.

In our case, Jenkins Job Builder has be be installed on both the local machine and the machine that will run the gitlab agent (although this can be the local machine as well).

To install the latest available version on Linux and Mac:

pip install jenkins-job-builder
brew install jenkins-job-builder

We’ll be able to test it locally to describe our seed job.

I should add that “seed job” in DSL Plugin terminology is a free-style jenkins job that creates other jobs from DSL scripts.

The installation process for the gitlab server is the same as for the local Linux machine:

pip install jenkins-job-builder

To be safe, we launch the tool just to make sure it works:

➜  ~ jenkins-jobs
usage: jenkins-jobs [-h] [--conf CONF] [-l LOG_LEVEL] [--ignore-cache]
                    [--flush-cache] [--version] [--allow-empty-variables]
                    [--user USER] [--password PASSWORD]
                    {update,test,delete,delete-all} ...

Installing Plugins

Now we’ll install our Jenkins plugins. We can use the classic UpdateCenter from the Manage Jenkins → Manage Plugins → Available section, or curl/wget by downloading the plugin from the official plugin page:

  • gitlab — 9.4.4-ce.0
  • gitlab-ci-multi-runner — 9.4.2

Creating a Repository

We’ll create a repository for our code with the task description.

We’ll start by preparing our seed job (seed-job). The repository will contain the following files:

jenkins.ini

[job_builder]
ignore_cache=True
keep_descriptions=False
include_path=.
recursive=False
allow_duplicates=False
 
[jenkins]
user=username
password=secret-pass
url=http://example-ci.org

job-generator.yml

---
- job:
    name: job-generator
    description: 'Do not edit this job through the web!'
    scm:
    - git:
        url: git@gitlab-selfhosted.org:group/repo-dsl.git
        branches:
            - origin/master
        credentials-id: some-credentials-id
        timeout: 20
        basedir: dsl
    triggers:
    - gitlab:
        trigger-push: true
        trigger-merge-request: false
        trigger-open-merge-request-push: both
        ci-skip: false
        set-build-description: false
        add-note-merge-request: false
        add-vote-merge-request: false
        add-ci-message: true
        allow-all-branches: true
        branch-filter-type: RegexBasedFilter
        target-branch-regex: '.*master*.'
    builders:
    - dsl:
        target: "dsl/*.groovy"
        ignore-existing: "false"
        removed-job-action: "DISABLE"
        removed-view-action: "DELETE"
        lookup-strategy: "SEED_JOB"

.gitlab-ci.yml

job:
  script: 'jenkins-jobs --conf jenkins.ini update job-generator.yml'

Now we’ll connect gitlab-runner to our project. Since the gitlab interface has seen a lot of frequent changes recently, we recommend referencing the official documentation.

After connecting, it should look something like this:

Now we commit the changes, and in the Jobs section of our project, we’ll see the following:

The process is represented in the flowchart below:

Now we’ll prepare the repository with our task description. We’ll give it the name repo-dsl and a flat structure. No files have to be moved.
We add a file with our pipline:

pipelineJob('testpipeline-build') {
    description('Build test docker image, test and push it to local registry')
    definition {
        cpsScm {
        scm {
            git {
            branch('origin/master')
            remote {
                url('git@gitlab-selfhosted.org:test-group/sample-project.git')
                credentials('gitlab-creds')
            }
            }
        }
        scriptPath('Jenkinsfile')
        }
    }
}

We’ll configure a webhook to launch the seed-job we created earlier.

In this version of gitlab, webhooks are located in the Settings → Integrations section of the project.

We create a webhook to push (gitlab:pass@ci.org/projects/job-generator). The process looks like this:

We now have two tasks in our Jenkins:

  1. The seed-job task generator
  2. The testpipeline-build pipeline task

In the test pipeline, we’ll build a Docker image, which will be loaded to the local Docker registry afterwards. The image file will be taken from another project.

In the sample-project project, we create a Jenkinsfile containing the following:

node {
    def app
 
    stage('Clone repository') {
 
        checkout([$class: 'GitSCM', branches: [[name: '*/master']],
        doGenerateSubmoduleConfigurations: false, extensions: [],
        submoduleCfg: [], userRemoteConfigs:
        [[credentialsId: 'gitlab-repo-creds',
        url: 'git@gitlab-selfhosted.org:test-group/docker-image-file.git']]])
    }
    stage('Build image') {
        app = docker.build("docker-image")
    }
 
    stage('Test image') {
        sh '''
        if ! docker inspect docker-image &> /dev/null; then
            echo 'docker-image does not exist!'
            exit 1
        fi
        '''
    }
 
    stage('Push image') {
 
        echo 'Push image'
        docker.withRegistry('https://local-registry:9666', 'registry-creds') {
            app.push("${env.BUILD_NUMBER}")
            app.push("latest")
        }
    }
 
    stage('Clean existing image') {
    sh "docker rmi docker-image"
    }
}

When all is said and done, the pipeline will look as follows in the Jenkins blueocean interface:

Or in the classic interface:

To automatically launch this task, we can add a webhook to the project with our Docker image, where the task will be launched after changes are sent to the repository.

In conclusion, we’d like to note that this article describes just one of the many possible approaches to managing tasks in Jenkins. Thanks to its flexibility and functionality, you can use whatever approach makes managing tasks as easy and comfortable as possible for you (based on your infrastructure and requirements).