Migrating from Travis-CI to GitLab-CI

This guide describes how to migrate from Travis-CI to GitLab-CI with a Ruby gem project.

Migrating from Travis-CI to GitLab-CI

After Microsoft took over GitHub little more than a week ago I moved all my active repositories from GitHub to GitLab.

While moving the repositories incl. issues and pull-requests was super easy, migrating the CI pipeline was more difficult to get working. Up until the migration I used Travis-CI but since the lack of integration into GitLab I decided to go with the build-in CI from GitLab.

Following I'm using rails-menu-manager as an example on how I migrated over to GitLab-CI.

The old .travis.yml looked like this:

sudo: false

language: ruby

rvm:
  - 2.2.9
  - 2.3.6
  - 2.4.3
  - 2.5.0

gemfile:
  - gemfiles/rails40
  - gemfiles/rails41
  - gemfiles/rails42
  - gemfiles/rails50
  - gemfiles/rails51

matrix:
  exclude:
    - rvm: 2.2.9
      gemfile: gemfiles/rails50
    - rvm: 2.2.9
      gemfile: gemfiles/rails51

script: bundle exec rake spec

before_install:
  - gem install bundler -v '~> 1.16'

To get started I tried to get a single test within this build matrix to work. My first simple working version of .gitlab-ci.yml look like this:

before_script:
  - ruby -v
  - gem install bundler --no-ri --no-rdoc
  - bundle install --jobs=$(nproc)
  - bundle list

rspec:
  image: ruby:2.5.1
  script:
    - bundle exec rake spec

Sadly GitLab does currently not support to use a build matrix so the only solution is to define every desiered build constellation manually:

.rspec_default: &rspec_default
  before_script:
    - ruby -v
    - gem install bundler --no-ri --no-rdoc
    - export BUNDLE_GEMFILE="spec/gemfiles/${RSPEC_GEMFILE}"
    - echo $BUNDLE_GEMFILE
    - bundle install --jobs=$(nproc)
    - bundle list
  script:
    - bundle exec rake spec

rspec-ruby2.4.4-rails4.0:
  <<: *rspec_default
  image: ruby:2.4.4
  variables:
    RSPEC_GEMFILE: rails40

rspec-ruby2.5.1-rails5.1:
  <<: *rspec_default
  image: ruby:2.5.1
  variables:
    RSPEC_GEMFILE: rails51
    
 ...

This took me an hour to get right because the tricky part was to figure out to set the BUNDLE_GEMFILE environment variable so bundler will pick up the right gems when running rspec after installing them.

To speed things I also introduced caching for the installed gems so e.g. nokogiri does not need to be compiled on every execution:

.rspec_default: &rspec_default
  before_script:
    - ruby -v
    - gem install bundler --no-ri --no-rdoc
    - export BUNDLE_GEMFILE="spec/gemfiles/${RSPEC_GEMFILE}"
    - echo $BUNDLE_GEMFILE
    - bundle install --jobs=$(nproc) --path="../../cache/bundle"
    - bundle list
  script:
    - bundle exec rake spec
  cache:
    key: "$CI_JOB_NAME"
    paths:
      - cache/

rspec-ruby2.4.4-rails4.0:
  <<: *rspec_default
  image: ruby:2.4.4
  variables:
    RSPEC_GEMFILE: rails40

rspec-ruby2.5.1-rails5.1:
  <<: *rspec_default
  image: ruby:2.5.1
  variables:
    RSPEC_GEMFILE: rails51
    
...

Setting the cache key to "$CI_JOB_NAME" will ensure that the cache is bound to the name of the job e.g. "rspec-ruby2.5.1-rails5.1". This ensures caches between Ruby and Rails versions don't get mixed up.

After spending the time to get the build matrix working I thought why not add rubocop as a job as well and so I endend up with the following, now complete, CI config:

rubocop:
  image: ruby:2.5
  script:
    - gem install rubocop --no-ri --no-rdoc
    - rubocop

.rspec_default: &rspec_default
  before_script:
    - ruby -v
    - gem install bundler --no-ri --no-rdoc
    - export BUNDLE_GEMFILE="spec/gemfiles/${RSPEC_GEMFILE}"
    - echo $BUNDLE_GEMFILE
    - bundle install --jobs=$(nproc) --path="../../cache/bundle"
    - bundle list
  script:
    - bundle exec rake spec
  cache:
    key: "$CI_JOB_NAME"
    paths:
      - cache/

rspec-ruby2.4.4-rails4.0:
  <<: *rspec_default
  image: ruby:2.4.4
  variables:
    RSPEC_GEMFILE: rails40

rspec-ruby2.5.1-rails5.1:
  <<: *rspec_default
  image: ruby:2.5.1
  variables:
    RSPEC_GEMFILE: rails51

...

Since Gemnasium was recently acquired by GitLab I'm missing a solution for dependency monitoring. I have not yet made any attempts to replace it with some other service. But because it was bought by GitLab integrating the new services integrated into GitLab seems like the obvious way to go. Having this said one of the next topics I'd like to dive into is to add a job for dependency scanning.

I'll write another blog post as soon as I had the time to get this working.