Lab: Continuous Integration

Steven J Zeil

Last modified: Dec 21, 2019
Contents:

This is a self-assessment activity to give you practice in working with the gitlab-ci continuous integration system. Feel free to share your problems, experiences, and choices in the Forum.

1 Setting up CI

In a previous assignment, you set up a project on GitLab with Java code and some unit tests. We’ll use that as the starting point for this lab.

an automated build and some unit tests, all controlled by gradle.

Running continuous integration on gitlab-ci requires a gitlab project with

  1. Make sure that you have a fresh copy of your GitLab1 project from GitLab.

    If you have not already done so, add a gradle wrapper and a ssetings.gradle' andbuild.gradle` for a []simple java build](doc:gradle#simple-java-build) to your project. Commit and push those changes.

  2. Visit your GitLab1 project on GitLab. Go to the “Settings” page, then select “CI/CD Pipelines” from the toolbar. Scroll down to the “CI/CD Pipelines” section (yes, same phrase again). Set the Timeout to 5, so that a “stuck” or looping build will terminate after a few minutes.

    Save your changes on this page.

  3. Near the top of that page is a section devoted to runners.

    On this page, you can see runners that are available (“shared”) for general use or that have been registered to work specifically for this project. A specific runner requires installing special software on a machine that can function as a server and is available whenever you are likely to be committing changes to your project. Since not everyone may be in a position to set something like that up, we will used a shared runner instead.

    The shared runners are identified by various tags, which you will see listed under each one. In your project work, you have been assigned to a team identified by a color and a number. For this lab, use the runner matching your team name, e.g., “red1”, “green4”, “blue2”, etc.

  4. Return to your local copy of your project. Find the root directory of your project.

    • If you followed the guidelines for project directory structure carefully, the root directory of your project contains a README file, a .git directory containing your git repository, and a sub-directory with your build files and your src/ directory.

      If so, your Eclipse project should actually be pointing at that sub-directory. You might find it useful to create a second Eclipse project, GitLab1-root, this one a “General Project” instead of a Java or Gradle project, pointed at the true root directory. This will make it easier to edit files in your root directory.

    • If you were a bit more loose in your structuring, your project’s root directory may have your build files and your src directory in the same directory as your README and .git repo.

  5. In that root directory, create a .gitlab-ci.yml text file. The overall structure of this file should be

    stages:
      - build
      - deploy
    
    
    build-job:
      tags:
        - # runner tag name
      stage: build
      script:
        - # script line 1
        - # script line 2
        - # ...
      only:
        - master
    

    In the tags area, insert the identifying tag of the shared runner you selected earlier.

    In the script area, you replace the # lines with shell script statements to actually run your build. Typically, this should be short and simple, because all of the complicated stuff is already set up to be handled by your build manager.

    • The script will be started in your project root directory, so if your build files and src directory are actually in a sub-directory, you may want to cd into that sub-directory.

    • After that, something like ./gradlew build should actually run your build.

  6. When you have finished editing that file, commit your changes and push it to your GitLab project.

  7. On GitLab, go to your “Commits” page and you should see your new commit. On the right, you may see a checkmark or other symbol indicating that a build was attempted for this commit.

    On your “Files” page you should see your new .gitlab-ci.yml file.

  8. Go to your Pipelines page. You should see a line indicating that a “build” has been added. Depending on how quickly you got here, it may have a status of “Pending”, “Running”, “Passed”, or “Failed”. If you don’t see a build listed, you probably have failed to commit a .gitlab-ci.yml file to your root directory. Go back and check your Files listing carefully. If that looks OK, check to be sure that you really did assign a runner to your project.

    Click on the status symbol to be taken to a listing of what has happened or, if it is running, is currently happened with your running build. If it’s still running, you can watch it in more-or-less real time.

  9. If your build failed, try to figure out why and fix it. Common issues include:

    • Errors in the script portion of the .gitlab-ci.yml file.

      • Look particularly at whether you have cd’d to the wrong place.
    • Errors in your build file. Do your builds actually work when invoked from the command line?

      • A common mistake is to embed absolute paths to libraries and other files within your build instructions. Those paths are specific to your own machine and will likely not match anything on the runner machine.
    • Missing files in your repository. You may have files in your local copy of your project that have not been getting committed to your git repository. Compare your local project listing carefully against the Files page of your project on GitLab. Make sure that your all of your build files (including the gradlew wrappers), your source code, and, if you have any, test data input files are being uploaded to the repository.

    • The build runs as expected, but stops/fails because your code fails one or more unit tests.

      When running builds manually, it’s common to stop if any tests fail.

      When running in a continuous integration setting, we often want to continue on and collect statistics and reports for the project website.

      You can tell your Gradle build to keep going even if some unit tests fail by adding

      test {
          ignoreFailures = true
      }
      

      to your build.gradle file.

2 Deploying Artifacts

“Artifacts” are the intended final products of a build. For a java project, the artifacts are generally jar files.

When we studied configuration management we saw that projects often deploy their “milestone” releases to large distributed repositories such as Maven Central, JCenter, or the various Linux distribution repositories.

You may have encountered some projects, however, that have less official releases, e.g., daily builds. Continuous deployment is the practice of providing a copy of the project artifacts as a side effect of the coninuous integration build.

Examine the build directory for your project. You should find that a Jar file has been created in build/libs/. When your build is done on the gitlab-ci runner, that same file is built, but then sits un the runner, unavailable to anyone. We’re going to set that up to be released on each build of your project.

  1. If you don’t have one, add a README.txt or README.md file to your project.

  2. Then edit your .gitlab-ci.yml file to add an “artifacts” section:

    build-job: tags: /*…*/ only: - master artifacts: paths: - path-to-your-README - path-to-your-Jar-file

    The paths that you add should be relative paths starting from your project root directory.

    We currently have 2 shared runners for use with gitlab-ci. Both the “linux” and “shell” runners actually run on Linux systems. However, only the shell runner can publish artifacts.

  3. Commit your changes.

    Your project should build on the runner as before. When it is done, however, you should find a download button/link has been added alongside the new build on the PipeLines page. That provides access to a .zip archive holding the two files we named as artifacts.

  4. Try downloading that file and unpacking it. You can see that the original directory structure separating the two files is preserved.

    Something to think about: suppose you wanted to have both files in the top level of the .zip instead of buried in a directory. How could you do that?

3 Deploying Reports

Examine your project again. in build/reports/tests/, you should find a nice little collection of files providing a web-ready summary of your unit test performance. Suppose that we wanted to put that up on a project website?

The artifact mechanism doesn’t help here, because it would bury the web pages inside a .zip file, and you can’t browse to the interior of an archive.

There is a gradle-ssh-plugin that can be used to copy files. It would be a tad cumbersome to copy the files to the web server one at a time, but we could add a command to our .gitlab-ci.yml script to zip up the whole report, then use the gradle-ssh-plugin to copy that zip file to our web server, then use it again to issue an unzip command remotely to that web server.

Of course, to use ssh we need to supply a login and password for the remote machine. And we don’t want to put that kind of into our repository where anyone might read it.

gitLab has a mechanism of “secret variables” that we could use to store a login name and password. We would need to be careful how we make use of those variables, to make sure that they don’t have their values listed in the build transcripts where anyone could read them, but it’s definitely possible.

Secret variables can only be examined by the “master” of a GitLab project. But what if a project has more than one “master”? We can improve on this a bit. Remember those command-limited ssh keys you created a long time ago to allow files to be uploaded to your website?

  1. In the Settings area of your GitLab project, go to Variables.

    Create a new variable named RSYNC_KEY and, for the value, insert the private key that you created for this purpose.

  2. Edit your .gitLab-ci.yml file. We’re going to augment the script for the build-job:

    build-job:
         ⋮
      script:
         ⋮
        - eval $(ssh-agent -t 5m -s)
        - ssh-add <(echo "$RSYNC_KEY")
        - rsync -auvz -e "ssh" build/reports/tests/ yourLoginName@atria.cs.odu.edu:GitLab1/
        - ssh-agent -k
    

    making an appropriate substitution for the tags and for yourLoginName.

    In the script area,

    • The first line starts an ssh agent.

      The “-t 5m” means that, after you add a key, it will continue to offer that key for up to 5 minutes, which should be more than enought time for what we are going to do.

    • The second line registers the private key stored in your RSYNC_KEY secret variable with that agent.

    • The third line should use that key to publish your test report to http://www.cs.odu.edu/~yourLoginName/cs350/sshAsst/GitLab1/

    • The final line shuts down your ssh agent. Otherwise the process tends to hang around forever.