Skip to content

Some notes from my attempts to migrate from travis to GH actions

Notifications You must be signed in to change notification settings

JamesGlover/travis_to_gh_actions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 

Repository files navigation

Migrating Travis to GH Actions

Examples

Sequencescape https://github.com/sanger/sequencescape/tree/develop/.github/workflows PR: sanger/sequencescape#2978

Record Loader (Been using GH actions for a while) https://github.com/sanger/record_loader/tree/master/.github/workflows

Warren (Very simple, but example of version testing matrix) PR: sanger/warren#1

Basics

GH actions are defined in yml files which live in .github/workflows Unlike travis, gh actions can be split into multiple files. Current plan is to not go overboard here, for Sequencescape for instance I'm planning:

  1. Ruby Tests
  2. JS Tests
  3. Linting (Maybe split into Ruby/JS)
  4. Building

Converting

Travis sets a load of rules for the whole file, some of which are redundant for some jobs

  • Been trying to get bin/setup fulfilling all pre-installation steps. This means we can use the same script for CI, as developer on-boarding.

A note on docker

I'm avoiding docker for the time being, but its something we could consider in future, as it will give us more control over the process.

Basic Ruby template

THis outline was based on the template generated by github templates. However I've made a few adjustments to better mean out needs:

  • Trigger on push / PR to any branch
  • Remove the specified ruby-version to use the .ruby-version file instead
  • Simplifies the bundle install and caching.

Getting started: Boring generators can help template out the ruby test action https://github.com/abhaynikam/boring_generators This got added literally just after I had finished. But on the plus side, it let me pop in a couple of pull requests to improve their templates.

Steps

  1. mkdir .github
  2. mkdir .github/workflows
  3. cd .github/workflows
# .github/workflows/ruby_tests.yml
name: Ruby

on:
  - push
  - pull_request

jobs:
  test:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        bundler-cache: true # Runs bundle install and caches gems. See the ruby_test.yml
                            # example if you need more control over bundler.
    - name: Run tests
      run: bundle exec rake

This is a very basic outline, which will run the tests for a ruby application. It doesn't handle linting, or database generation.

Linting

Setting up linting first, as its quick and has few dependencies. An example from Sequencescape is shown in examples/lint.yml.

  1. touch lint.yml
  2. Bring in the example config above, and give it a sensible name
  3. Replace run tests with:
    - name: Run rubocop
      run: bundle exec rubocop

Looking at rails example:

https://github.com/rails/rails/blob/4a78dcb326e57b5035c4df322e31875bfa74ae23/.github/workflows/rubocop.yml#L1

Referenced from here: https://github.com/andrewmcodes/rubocop-linter-action Which itself isn't usable in out case, and has a big warning about its suitability, which is a shame as the inline errors look great.

Note: Not using the https://github.com/github/super-linter as want control over rubocop versions to ensure same versions used in dev and CI. Otherwise its just a pain keeping the two in sync.

Installing only some gems

Ran into issues with Oracle gems, when it came to Sequencescape, plus for linting I only really needed the rubocop gems.

Add the env: BUNDLE_WITHOUT: 'groups to exclude'

To your particular job (or the whole file iff applicable).

Note: Earlier versions of this document suggested rolling your own caching. Since then ruby-setup documented tha above, and promises to provide a more robust caching system. We ran into some issues on the master branch on the transition to ubuntu 20.04 from 18.04, where the 20.04 builds were trying to use gems built against 18.04.

Caching

I advise against caching your bundler directory, and instead use the inbuilt caching in ruby-setup. However you may wish to cache other directories.

Note: If a number of your jobs share the same setup, consider replacing ${{ github.job }} with a shared string. eg. ${{ runner.os }}-tests-${{ hashFiles('**/Gemfile.lock') }}

    - name: Cache gems and cops
      uses: actions/cache@v2
      with:
        path: |
          public/assets
          tmp/cache/assets/sprockets
          node_modules
        key: ${{ runner.os }}-${{ github.job }}-${{ hashFiles('**/yarn.lock') }}
        # If we don't find the specific cache we want, fallback to the last rubocop
        # cache, then finally any cache for this repo.
        # Github looks for the newest cache beginning with the first entry, before
        # falling back the the second if none is present.
        restore-keys: |
          ${{ runner.os }}-${{ github.job }}-
          ${{ runner.os }}-
    # Install only the gems needed for linting, keep things nice and fast
    # Keep an eye on https://github.com/rubygems/bundler-features/issues/59
    # in case bundler add an only flag
    # We also set the install path to vendor/bundle to assist with out caching
    - name: Install dependencies
      run: |
        bundle config path vendor/bundle
        bundle config set without 'warehouse cucumber deployment profile development default test'
        bundle install --jobs 4 --retry 3

Meanwhile I moved the linting gems into a separate group.

Artifacts

Artifacts let you store files from your job. This can be useful for aggregating coverage of multiple tests (see example/ruby_test.yml). However currently artifacts are bundled up in zip files, so while they can be used for storing things like screenshots and test output, the UX isn't great.

Databases

Databases are spun up as docker containers. First, add a service:

Setup a database service:

jobs:
  rake_tests:

    # ...

    # Services
    # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idservices
    services:
      mysql:
        # Use the Mysql docker image https://hub.docker.com/_/mysql
        image: mysql:5.7 # Using 5.7 to map to what is used in production.
        ports:
         - 3306 # Default port mappings
         # Monitor the health of the container to mesaure when it is ready
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
        env:
          MYSQL_ROOT_PASSWORD: '' # Set root PW to nothing
          MYSQL_ALLOW_EMPTY_PASSWORD: yes

    # ...

Need to then update the database.yml file to take custom port numbers, and then populate those with the appropriate port:

# database.yml
mysql: &MYSQL
  adapter: mysql2
  username: <%= ENV.fetch('DBUSERNAME','root') %>
  password: <%= ENV['DBPASSWORD'] %>
  encoding: utf8
  properties:
    characterSetResults: utf8
  pool: 5
  timeout: 5000
  reaping_frequency: 600
  host: 127.0.0.1
  port: <%= ENV.fetch('DBPORT','3306') %>
  variables:
    sql_mode: TRADITIONAL
    # This improbably large value mimics the global option for production
    # Without this things fall back to 1024 (At least with my setup) which
    # is too small for larger pools.
    group_concat_max_len: 67108864
    - name: Setup environment
      env:
        DBPORT: ${{ job.services.mysql.ports[3306] }}
      run: |
        bundle config path vendor/bundle
        bundle config set without 'warehouse deployment profile development'
        bin/setup
    # Actually run our tests
    - name: Run rake tests
      env:
        DBPORT: ${{ job.services.mysql.ports[3306] }}
      run: bundle exec rake test

Environmental variables

Travis envs - Fairly easy to bring across

env:
  global:
  - TZ=Europe/London
  - CUCUMBER_FORMAT=summary

Becomes:

env:
  TZ: Europe/London
  CUCUMBER_FORMAT: summary

Top level and is shared across all steps/jobs Meanwhile I also added some of the job specific envs:

jobs:
  rake_tests:
    env:
      RAILS_ENV: test
  1. Migrating before scripts:

In most cases I've been making a few tweaks to the way these work, but in theory migrating is as simple as

    before_script:
    - RAILS_ENV=test bundle exec rails webdrivers:chromedriver:update
    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
    - chmod +x ./cc-test-reporter
    - "./cc-test-reporter before-build"

Would become:

  steps:
    - name: Setup environment
      env:
        DBPORT: ${{ job.services.mysql.ports[3306] }}
      run: |
        bundle exec rails webdrivers:chromedriver:update
        curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
        chmod +x ./cc-test-reporter
        ./cc-test-reporter before-build

xvfb-run

Headless chrome no longer requires xvfb

Code coverage

Code coverage with code climate looks to be a pain.

This action seems popular: https://github.com/marketplace/actions/code-climate-coverage-action

Getting parallel testing working is actually somewhat easier than it may seem:

  1. To each test suite hob add the following step last:
    - name: Upload coverage artifact
      uses: actions/upload-artifact@v2
      with:
        name: codeclimate-${{ github.job }}-${{ matrix.ci_node_index }}
        path: coverage/.resultset.json # This is simple cov, you may need to change for other tools
        retention-days: 1

This will upload the coverage stats

Note: CodeClimate simple cov support is changing: codeclimate/test-reporter#413 The simple cov changes have been made, and we're just waiting for code climate to update their side. Essentially rather than using the internal .resultset.json the new version will use a proper formatter.

  1. Add the final json, dependent on the previous ones
  end_coverage:
    runs-on: ubuntu-latest
    needs: [rake_tests, rspec_tests, cucumber_tests]
    steps:
    - uses: actions/checkout@v2
    - name: Fetch coverage results
      uses: actions/download-artifact@v2
      with:
        path: tmp/
    - name: Publish code coverage
      uses: paambaati/codeclimate-action@v2.7.5
      with:
        coverageLocations: |
          ${{github.workspace}}/tmp/codeclimate-*/coverage.json:simplecov

This downloads all the files uploaded in the previous steps, and then formats, sums and uploads them.

Building

Here the trickiest aspect is working out how to trigger a release build. In travis we:

  • Detected a tag
  • Pushed up the release files

And this seemed to work fine. However, the official action requires a release url, which isn't available (as far as I can tell) for an action triggered by a tag.

Instead I've opted to use the release publish action. This does mean it only works for proper github releases, not just tags.

on:
  release:
    types: published

If we want the latter, I believe we can create another action to generate a release from a tag event.

Actual upload bit:

    # https://github.com/marketplace/actions/upload-a-release-asset
    - name: Upload release.gz
      id: upload-release-gz
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ github.event.release.upload_url }} # Pull the URL from the event
        asset_path: ./release.tar.gz
        asset_name: gh-release.tar.gz
        asset_content_type: application/gzip

About

Some notes from my attempts to migrate from travis to GH actions

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published