Continuous Integration


Honors Dojo.

An introduction to the CI part of CI/CD.

  • We'll be using Github Actions to teach some of the basics.
  • This is a very broad skill in the world of software engineering and can be customized to fit almost any project.

Credits:

  • This project makes heavy use of Nektos's act to run challenges in the dojo.
  • This module was created by Eli Sells as an honors project for CSE 365.


Getting Started

Let's try something really simple to start.

As you'll recall from the videos, the .yml functions almost like a bash script. For this challenge (and only this challenge), /flag will be owned by the runner. The output from the challenge runner will be displayed in stdout. Can you get the flag?

The checker script for the whole module is at /challenge/check. The directory with the workflows is /challenge/repository/.github/workflows. Remember what happens when you try to run ls on a directory with a . in front of it? Don't worry, it's definitely there!

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

Integrate your Build System

In the first challenge, your workflow ran as root. From now on, we'll be protecting the flag and the only (intended) way for you to complete these challenges will be to pass the provided conditions.

One of the most common ways to utilize a continuous integration system is to build an application in some kind of reproducible environment. For example, Sun Devil Rocketry builds its flight firmware apps in a CI pipeline that follows these steps:

  1. Check out the repository's code, and recursively check out the submodules
  2. Install the required dependencies (a version of GCC for bare-metal ARM targets called arm-none-eabi-gcc)
  3. Invoke all flight firmware makefiles for legacy apps
  4. Invoke the current app's makefile in debug mode
  5. Invoke the current app's makefile in release mode

By doing this all in a workflow, we're able to verify that someone's changes won't break the build on any application that is actively supported by the team.

Because of my own familiarity with C and C++ build systems, that's what we'll have you use here. We have provided a Makefile and a source file. You don't need to know how either of them work, all you need to do is invoke the Makefile in your workflow! The program it builds will be run as root, and it will print the flag to stdout.

This challenge also has one way to simplify things: instead of simulating a remote run location, this will run in /challenge/repository. This is necessary to make sure your compiled program exists after the workflow cleans itself up. In future challenges, this will not be the case.

P.S: If you aren't familiar with Makefiles, they also work kinda like an extended version of a shell script. All you have to do is run the make command in the same directory as any file designated as a Makefile.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

In the last challenge, your workflow ran in /challenge/repository. Obviously, on a real remote runner, you would need to be able to set everything up yourself. This might include compilers, SDKs, other external libraries, etc. Fortunately, since this project is built in C and runs on an ubuntu-latest runner, GCC comes pre-installed (both on our dojo CI run simulator and on actual Ubuntu runners)!

If you're curious about what tools come pre-installed on each runner for your own projects, check out the following resources:

However, nothing new gets downloaded onto your runner without you telling it to, including the code from the repository that triggered the run. There's a pretty easy solution for this, luckily! GitHub packages its own set of steps to do this for you.

- name: Check out repository code
  uses: actions/checkout@v4

This step checks out your repository to the root of the runner (the runner's root directory == the repository's root directory). From there, you can proceed as you did previously -- almost. Since we're no longer in /challenge/repository, we can't see your compiled binary once you're done! The runner cleans up after itself. The fix for this is just to run it (remember the binary is called program and lives in the build directory).

P.S: If you aren't familiar with Makefiles, they also work kinda like an extended version of a shell script. All you have to do is run the make command in the same directory as any file designated as a Makefile.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

Automated Testing

In the last challenge, you built your program on a remote runner! This is a great first step towards unlocking CI/CD in your own projects, and you've hit the first of the three prongs I mentioned in the lectures. Now, it's time to look at the second prong.

It's really useful to be able to run automated tests in your CI pipelines. This is a huge step towards promoting code quality, since every change will verify that core functionality hasn't been broken. The tests themselves are out of scope for this module, since there's so much to cover in the world of SQA and testing. Depending on your language or environment, there are so many different tools for this and we definitely couldn't cover it all as a tangent. If you don't have any experience with software testing coming into this, I highly recommend you go out and do some research to see what it's all about.

For this module, we'll keep it simple and just simulate the invocation of a test using a bash script. You can use your imagination and fill in the blanks based on whatever framework you might be used to (if this were Sun Devil Rocketry, the command would be "make test").

You'll want to perform two steps in your workflow:

  1. Compile the program, same as before.
  2. Invoke tst/check-sha.sh

The checker script will verify that you ran the test correctly. Good luck!

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

Great job! Being able to run tests like that is a huge reason why I love CI so much.

Now, let's expand upon this a little bit. Most projects won't just have one test to run. There are a bunch of ways to run multiple tests, but my favorite is built in to Github Actions for exactly this purpose. In your .yaml file, you can specify a list of jobs that use the same specification with keywords swapped out. It's hard to explain in text, but I'll show you what I mean here:

jobs:
  test-runner:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        tests: [test-1, test-2]
    steps:
      - name: Build and run the app/${{ matrix.tests }} tests
        run: |
          cd ${{ github.workspace }}/test/app/${{ matrix.tests }}
          make test

Note the "strategy: matrix: tests: [...]" here, that's doing a lot of heavy lifting. What happens is the list under "steps" runs twice, once for each item in the list. When it runs, it'll fill in this ${{ matrix.tests }} variable with every value enumerated in the list, letting you re-use your specification with only the replacements you need.

For this challenge, you'll do the same thing. In addition to the tst/check-sha.sh from the last challenge, there's now a tst/check-sha2.sh. You'll invoke it the same way, and the checker script will look for both in your output.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

Continuous Delivery

You're nearly there! The last thing we're gonna look at is the basics of CD, or more accurately: how to link your CI pipeline up to a delivery mechanism. Now, there are limits to what we can simulate in the dojo, but luckily, our tools allow us to set up a fake artifact server. So, we're gonna do Build a Binary 2 again, but with one little extra step this time.

- name: "Upload Artifacts"
  uses: actions/upload-artifact@v3
  id: promote-results
  with:
    name: program
    path: ${{ github.workspace }}/build/program

In an actual environment, this would upload a file from your workflow run to Github! I use this to preserve test artifacts after a run, including results and coverage reports. I'm also starting to use it for CD, building binaries and making pre-releases for me on Github automatically. You're going to use it to upload your compiled program, and then the checker script is gonna run it. Simple!

--

Note: Due to the internet access restriction in the dojo, we can't actually use the real upload artifacts action. We're gonna get around it by using the following step:

- name: "Upload Artifacts"
  uses: ./.github/actions/upload-artifacts
  with:
    file: [file-name]

This is a bit different from what you'd do for an actual project, but work with me here, Github Actions wasn't built to be an offline tool.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score