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
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:
- Ruby Tests
- JS Tests
- Linting (Maybe split into Ruby/JS)
- Building
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.
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.
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.
mkdir .github
mkdir .github/workflows
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.
Setting up linting first, as its quick and has few dependencies. An example from Sequencescape is shown in examples/lint.yml.
touch lint.yml
- Bring in the example config above, and give it a sensible name
- Replace run tests with:
- name: Run rubocop
run: bundle exec rubocop
Looking at rails example:
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.
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.
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
- 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 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 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
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
- 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
Headless chrome no longer requires xvfb
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:
- 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.
- 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.
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