Alexito's World

A world of coding 💻, by Alejandro Martinez

Running Danger on GitHub Actions

In the past I've written about the setup I typically use with Danger. It worked quite well given the limitations but there was always the annoying restriction that to update the checks you had to push a commit so the CI could run Danger again.

This is something that Peril was designed to solve, but I never had time to look into it and probably I wouldn't have used it since it required self-hosting (at least the last time I checked).

But time has passed and GitHub has now a feature that can help with this: GitHub actions. Let's take a look at how they improve the situation!

Checkout GitHub Actions for your Swift Package for another great usage of this GitHub feature.

Many Dangers

One thing to take into account is that by now there are many different versions of Danger in different languages and ecosystems. The one I'm talking about is Danger Ruby.

You may wonder why I'm still using Ruby instead of Swift, since Danger accepts that now (although I'm not fond that it needs JS under the hood for that, even if it makes sense for the project). The answer is two folded.

For one, it's already setup and working and I don't see the need of migrating the rules for not much gain. Writing them in Swift would be cool but then we have he other reason: Android.

The majority of the rules on our Danger file are about PR etiquette and that is shared between iOS and Android. If wouldn't be nice to make my Android team switch from Ruby to Swift just because I like it more ^^.

Configure the action

To configure an action you need to make a new yaml file in your repo at the path .github/workflows/file.yml. You can do that manually but I usually just do it from GitHub's own actions interface.

actions

That page will show you relevant actions for your repo but, at least at the time of writing this, the suggestions for iOS/Swift are quite poor. You may find some predefined action for Danger but I was not able to make any work so I just rolled with my own. This gives me more control about what's happening so it's my preferred option anyway, at least for now.

make your own action

Let's take a look at the configuration needed to run Danger.

name: 🚧 Danger

The first thing you want to do is give it a name. It may seem a trivial thing but in my case I will have another Danger instance running so is important that the names help differentiate them.

on:
  pull_request:
    types: [opened, reopened, edited, synchronize]

This is probably the most important setting of the action, since it's what allow us run Danger when we need it (when a PR is opened or modified), not like before that we were stuck on running it only on commit (called synchronize in the action config).

jobs:
  build:

Now let's describe the work that needs to be done. Starting with the machine that needs to run it.

     runs-on: ubuntu-latest

We pick a linux distribution since is usually the standard and we don't have any specific requirements for this job as long as it can run Ruby.

Let's move on to the steps:

     steps:
    - uses: actions/checkout@v2

The first thing you need to do is checkout your git repo. For it you can use an existing action that will automatically pull the correct commit.

As many modern CI systems GitHub actions use containers that start from scratch every time they run. This ensures that there is no stale state but also means we can't rely on previous files being there.

     - name: Set up Ruby
      uses: actions/setup-ruby@v1
      with:
        ruby-version: '2.6'

The ubuntu image we defined above doesn't have the specific Ruby version we need, so we must install it first.

For that we use another existing action that installs a specific ruby version just by passing it as a parameter.

I was not able to get it to install the specific patch version we use so I'm just asking for the latest version matching the minor we require.

     - uses: actions/cache@v1
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gems-

Adding a cache is an optional step but really useful. There is no need to run trough the gem installation every single time since they don't change that often.

To ensure that when we change a gem version the gems are refreshed we use the Gemfile.lock hash as the key for the cache.

The cache action uses a very interesting feature that allows it to install a post action. This means that the action will run first at the point that is defined, to restore, but also at the end of the steps, to save, automatically! This is such an improvement compared to other CI systems.

     - name: Bundle install
      run: |
        gem install bundler
        bundle config path vendor/bundle
        bundle install --jobs 4 --retry 3

Now it's time to install bundler and the specific gems that we need. This ensures that we're running the specific version of Danger that we want.

Note that we also configure the path to store the gems, this needs to match the path specific previously on the caching step.

     - name: Run danger
      run: |
        bundle exec danger
      env:
        DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

And finally we run 🚧 Danger! Note how we run it using bundle, to ensure it runs the known version.

We also pass the environment variable that it expects to access the GitHub API. And this is another great advantage that GitHub actions have. Because they are a first party service you can just use the secrets to get a token for the action, without having to configure anything else.

See the entire config

name: 🚧 Danger

on:
  pull_request:
    types: [opened, reopened, edited, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby 2.6.3
      uses: actions/setup-ruby@v1
      with:
        ruby-version: '2.6'
    - uses: actions/cache@v1
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gems-
    - name: Bundle install
      run: |
        gem install bundler
        bundle config path vendor/bundle
        bundle install --jobs 4 --retry 3
    - name: Run danger
      run: |
        bundle exec danger
      env:
        DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Finishing touches

I recommend you go into your branch setings and select this new Danger check as a requirement, and disable the old one if you were running it in another CI.

You can now also cleanup your previous Danger setup from Fastlane or wherever you had it before.

Conclusion

You can see how easy is to use GitHub actions and how convenient they are since they are fully integrated into GitHub.

Go and check them out if you haven't yet!