How to Evolve an Application to Make It More Robust and Maintainable - Setting Up the CICD

Foreword

In the last article we described the tests added to our project. These tests are useful to check our development locally as we use TDD. But they are also useful to check if there are regressions. In this post we will:

  • define what the CICD is
  • How to add the CICD settings to our project

What is the CICD

The acronym CICD stands for Continuous Integration Continuous Delivery. The acronym CICDCD stands for Continuous Integration Continuous Delivery Continuous Deployment

When we talk about CICD (or CICDCD) we often also refer to the pipelines. A CICD pipeline defines a process where certain steps are executed one after the other, all with the aim of building, and verifying the application before delivering it and deploying it.

What CICD tools

There are some tools to execute CICD; we can quote:

  • Gitlab-ci
  • CircleCI
  • GitHub Action
  • Jenkins

For open sources projects as our, it’s easier to use a CICD tool integrated into a web hosting and software development management service such as GitLab or GitHub. It’s why we prefer to use GitLab, CircleCi or GitHub Actions tools as Jenkins need a separate deployment, management and hardware resources.

We also want to use a free service, it is why we don’t use a Gitlab CI; because we can’t no longer push the report from Gitlab to Github withe the free plan.

In this post we will detail these solutions but we will implement only two of them:

  • CircleCI
  • GitHub Action

Implementation of our CICD

Our desired pipeline

Through our first pipeline we want to validate :

  • build the application
  • check if the code is formatted
  • verify the non-regression of the code through the execution of test

Also we want the pipeline to be run on every commit to the develop branch and the ci branches (with prefix ci_). We could add other kind of branches: feature(feat_), fixes (fix_), documentation (doc_)

Implementation with CircleCI

First connection

CircleCI is a CICD platform which has a free plan; To be able to use CircleCI we need to:

  • Create an account

    circleci-create-user.png

  • Set up our code

    img.png

  • Configure

There are tree possibilities to link your project with your future CI:

circleci-create-settings.png

Creation of a job

CircleCI defines “jobs” which are executables tasks; each job can be divided into steps In the first version of our CI (cf. code below) we want to:

  • build the jar file
  • run the unit tests
  • run the integration tests
  • run the system test
  1# Java Gradle CircleCI 2.0 configuration file
  2# See: https://circleci.com/docs/2.0/language-java/
  3version: 2.1
  4
  5
  6# Define a job to be invoked later in a workflow.
  7# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
  8jobs:
  9assemble:
 10# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
 11# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
 12docker:
 13# specify the version you desire here
 14- image: cimg/openjdk:17.0.5
 15
 16      # Specify service dependencies here if necessary
 17      # CircleCI maintains a library of pre-built images
 18      # documented at https://circleci.com/docs/2.0/circleci-images/
 19      # - image: circleci/postgres:9.4
 20
 21    working_directory: ~/happraisal
 22
 23    environment:
 24      # Customize the JVM maximum heap limit
 25      JVM_OPTS: -Xmx3200m
 26      TERM: dumb
 27    # Add steps to the job
 28    # See: https://circleci.com/docs/2.0/configuration-reference/#steps
 29    steps:
 30      - checkout
 31      - attach_workspace:
 32          at: ~/happraisal
 33
 34      # Download and cache dependencies
 35      - restore_cache:
 36          keys:
 37            - v1-dependencies-{{ checksum "build.gradle" }}
 38            # fallback to using the latest cache if no exact match is found
 39            - v1-dependencies-
 40
 41      - run: ./gradlew assemble
 42
 43      - save_cache:
 44          paths:
 45            - ~/.gradle
 46          key: v1-dependencies-{{ checksum "build.gradle" }}
 47      - persist_to_workspace:
 48          root: ~/happraisal
 49          paths:
 50            - ./build
 51      - store_artifacts:
 52          path: ~/happraisal/build/libs
 53
 54unit-tests:
 55docker:
 56- image: cimg/openjdk:17.0.5
 57
 58    working_directory: ~/happraisal
 59    steps:
 60        - checkout
 61        - restore_cache:
 62            keys:
 63              - v1-dependencies-{{ checksum "build.gradle" }}
 64              # fallback to using the latest cache if no exact match is found
 65              - v1-dependencies-
 66        - run: ./gradlew test
 67        - save_cache:
 68            paths:
 69              - ~/.gradle
 70            key: v1-dependencies-{{ checksum "build.gradle" }}
 71        - store_test_results:
 72            path:  ~/happraisal/build/test-results
 73
 74
 75
 76integration-tests:
 77docker:
 78- image: cimg/openjdk:17.0.5
 79
 80    working_directory: ~/happraisal
 81    steps:
 82        - checkout
 83        - restore_cache:
 84            keys:
 85              - v1-dependencies-{{ checksum "build.gradle" }}
 86              # fallback to using the latest cache if no exact match is found
 87              - v1-dependencies-
 88        - run: ./gradlew integrationTest
 89        - save_cache:
 90            paths:
 91              - ~/.gradle
 92            key: v1-dependencies-{{ checksum "build.gradle" }}
 93
 94
 95system-tests:
 96docker:
 97- image: cimg/openjdk:17.0.5
 98
 99    working_directory: ~/happraisal
100    steps:
101        - checkout
102        - restore_cache:
103            keys:
104              - v1-dependencies-{{ checksum "build.gradle" }}
105              # fallback to using the latest cache if no exact match is found
106              - v1-dependencies-
107        - run: ./gradlew systemTest
108        - save_cache:
109            paths:
110              - ~/.gradle
111            key: v1-dependencies-{{ checksum "build.gradle" }}
112
113workflows:
114version: 2.1
115microservice:
116jobs:
117- assemble
118- unit-tests:
119requires:
120- assemble
121- integration-tests:
122requires:
123- assemble
124- system-tests:
125requires:
126- assemble
127

Implementation with GitHub Actions

Terminology

A Workflow: In the official documentation a workflow is defined as “a configurable automated process that will execute one or more jobs. It is set up via a yaml file. cf. https://docs.github.com/fr/actions/using-workflows/about-workflows

Action: “Actions are individual tasks that you can combine to create jobs and customize your workflow.” cf. https://docs.github.com/fr/actions/creating-actions/about-custom-actions

First connection

GitHub Actions is the CI tool of GitHUb. This CI is accessible from the Action button in a toolbar github_action_menu.png

During the first access, GithUb Actions displays a workflow proposal according to your project. In our case we have selected the java with gradle workflow.

github_action_gradle.png

Once we have selected our workflow, we are redirected to the editor proposing us a default file. It is possible to modify it and save it on a specific branch; In order to easily test the CICD without having to push on develop. It is possible to define the name of the working branch in the part push.

github_action_gradle_file.png

Creation of jobs

1on:
2  push:
3    branches:
4    - develop
5    - ci_* 

To implement the 3 steps defined above, we need to create the jobs:

  • assemble: to compile the code and create the jar.
  • spotless: to check the format of new code.
  • unit-tests: to execute the unit tests.
  • integration-tests: to execute the integration tests.
  • system-tests: to execute the system tests.

the code below shows an example of how to create a job

 1jobs:
 2  assemble:
 3    runs-on: ubuntu-latest
 4    steps:
 5    - uses: actions/checkout@v3
 6    - uses: actions/setup-java@v3
 7      with:
 8        java-version: '17'
 9        distribution: 'temurin'
10        cache: gradle
11    - run: ./gradlew assemble --no-daemon
12

In the code above:

  • we create the assemble job which is based on ubuntu-latest
  • we use two actions to check the code and configure the Java compiler.
  • we run the gradle assemble job.

Summary

The first objective of this article was to test three CICD engines (GitLab, CircleCi and GitHub Actions), implementing similar pipelines. While trying to implement the CICD on these engines, we quickly encountered some limitations for the first two CICD:

As a conclusion I think that to set up a free CICD, it is preferable to use a CI integrated with its version manager such as GItHub Actions for GitHub or GitLab CICD for GitLab.

If you have any remarks on the content or the form, you can leave a comment…it is by exchanging that we progress.


CC BY-NC-ND 4.0

What Is Code Formatting and Why Does It Matter
Introduction to APIs

Comments