Continuous Integration

Steven J Zeil

Last modified: Dec 27, 2023
Contents:

Abstract

In continuous integration, the practices of version control, automated building, automated configuration, and automated testing are combined so that, as changes are checked in to the version control repository, the system is automatically rebuilt, tested, reports generated, and the results posted to a project website.

1 Big Builds

Think of everything we have started to put into our automated builds:


and, coming up, we will want to expand our testing to include

There’s a danger of the builds becoming so unwieldy and slow that programmers will start to look for ways to circumvent steps,


Do We Need to do All of Those Steps, All of the Time?

One possible breakdown:

Every build Occasional
fetching and setup of 3rd party libraries documentation generation
static analysis static analysis reports
compilation deployment/publication of artifacts
unit testing updating of project website
packaging of artifacts integration testing
test coverage reporting
system testing

This should provide someone actively working on a specific module/story the info they need, deferring some of the more time-consuming build activities.


How do we divide these steps in the build?

2 Continuous Integration

When we combine

we can rebuild and retest automatically as developers check in changes.

2.1 Key Ideas

Our project should have the characteristics:

2.1.1 Advantages

2.1.2 Disadvantages

2.2 Continuous Integration Systems

A CI system consists of a server/manager and one or more runners…

2.2.1 The Continuous Integration Server

 

A continuous integration server is a network-accessible machine that

2.2.2 Continuous Integration Runners

A CI runner (a.k.a., nodes) is a process on a machine that

When notified by the server, the runner

  1. Clones a designated branch of a project from its version control system.

  2. Runs the build.

  3. Publishes reports on the results of the build(s).

    • It may also ship “artifacts”, constructed files being made available for download.
      • Examples would be the latest version of a library or an executable.

3 Case study: GitHub Actions

GitHub provides an integrated CI service called “Actions”.

3.1 GitHub Actions setup

  1. In the root directory of your project, create a .github/workflows/ directory.

    This can hold one or more action scripts, written in YAML.

  2. Create a file with a .yml extension in that directory describing your desired CI actions.

    This will have a mixture of metadata describing how you want this run and run blocks containing scripted commands.

  3. Commit and push.


An example of .github/workflows/gradle.yml

# This workflow will build a Java project with Gradle

name: CI - build and test

on:                                
  push:
    branches: [ main, CI-setup ]

jobs:
  build:                           

    runs-on: ubuntu-latest         

    steps:                         
      ⋮

3.1.1 The steps dissected

Now let’s look at the steps:

      ⋮
    steps:                         
    - uses: actions/checkout@v2                   ➀
      with:
        fetch-depth: 0
    - name: Set up JDK 11                         ➁
      uses: actions/setup-java@v2        
      with:
        java-version: '11'
        distribution: 'adopt'
    - name: Grant execute permission for gradlew  ➂
      run: chmod +x gradlew              
    - name: Build and test the plugin             ➃
      working-directory: ./cowem-plugin
      timeout-minutes: 20
      run: ../gradlew build

Each step is introduced by the leading hyphen (‘-’).

The components of a step that you can see are:

With that understanding, here’s what’s happening:

3.1.2 GitHub Actions and GitHub Pages

In an earlier lesson, we saw that GitHub uses a potentially complicated series of git actions to publish web content in a separate repository branch (usually, gh-pages).

Not surprisingly, they’ve packaged this up into a pre-defined action that can be invoked in the steps instructions:

- name: Deploy
        uses: JamesIves/github-pages-deploy-action@v4.2.5  ➀
        with:
          branch: gh-pages                                 ➁
          folder: build/reports                            ➂

Run this after the steps in which you build your project (including the website content).

3.2 Supplying Secrets to Github Actions

Sometimes an automated build will require secret or private information such as passwords or SSH private keys.

When running such a build from the command line, we can prompt the user or acquire such secret information in other ways, such as via an SSH key agent.

That won’t work during continuous integration, because the programmer is not present to supply the secret information. And, obviously, if this information is supposed to be kept private, the last thing we want to do is to code it into a file that we commit to our repository, where it becomes part of the historical record of the project for all time.

So Github provides a mechanism for providing secret information that can be used in CI scripts but not viewed by anyone afterwards.

For example, in one of our website deployment case studies, we developed Gradle tasks that cloned a GitHub repository. That would likely work if you ran it from the command line, because presumably you would already have an SSH key set up to give you access to GitHub. But it won’t work when dropped into a CI script, because the CI runner will not already have those credentials.

The secrets are added from the Secrets tab of a Github repository’s Settings page. Click the New repository secret button to add one.

For example, suppose we wanted to provide an SSH key that could be used from within an action.


  1. Create a new SSH key pair. Make this one passphrase-free, as possession of the private key is going to be restricted to this single use.
  2. Add the public half of the key to your (or some other team member’s) GitHub account.
  3. Go to your repository’s Settings page on Github. Select the Secrets tab and click the New repository secret button.
  4. Give this secret a name, for example, REPORTS_SSH_KEY. Paste the text of the private key into the “Value” box and click Add secret..

You can now use that name like a variable in your Github actions via the rather arcane syntax:

${{ secrets.secret-name }}

For example, to set up an SSH agent that can supply that key, you could add this to your CI script:

- name: Deploy reports using an SSH agent
      run: |
        eval $(ssh-agent -s -t 600)                           ➀
        ssh-add <(echo "${{ secrets.REPORTS_SSH_KEY }}")      ➁
        git config --global user.email "you@email.address"      ➂
        git config --global user.name "Project Actions"
        ./gradlew deployWebsite

Any later steps in the CI script will now be able to access the Github account to which the public half of that key is registered.

4 Hosting a GitHub Actions Runner

If the GitHub-supplied runners are unsuitable, you can provide your own, e.g., on AWS. GitHub refers to this as “self-hosting”.

There are several steps involved, with lots of details, but not overly complicated.

  1. You install the runner application on your own machine and use your GitHub project settings to add that self-hosted runner to the project/repository.
  2. On your hosting machine, either start the runner application manually or configure the machine to run the application automatically on startup.
  3. Instead of saying that your CI scripts runs-on a GitHub machine, you specify one or more of the descriptive labels for your runner application.

5 Related ideas