It is hard to go back, once you integrate CI/CD flow into your workflow. Luckily, tools like GitLab are great, as they provide not only a nice GIT hosting platform, but a robust continues integration and delivery platform.
Let’s setup our
.gitlab-ci.yml file, so it meets our needs.
We will use the official Ruby docker image as a start and add PostgreSQL service. It is good idea to cache our dependencies as this will remove the need to download them if there are no changes.
image: ruby:2.4.3 cache: paths: - vendor/bundle - node_modules services: - postgres:10.1 variables: BUNDLE_PATH: vendor/bundle DISABLE_SPRING: 1 DB_HOST: postgres
before_script is the place, where you can put your setup steps that will run before every stage. Here we will put the installation of additional software that we need and the initial setup of the project, like running migration, compiling assets, etc.
before_script: # Install node and some other deps - curl -sL https://deb.nodesource.com/setup_8.x | bash - - apt-get update -yq - apt-get install -y apt-transport-https build-essential cmake nodejs python-software-properties software-properties-common unzip # Install yarn - wget -q -O - https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - - echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list - apt-get update -yq - apt-get install -y yarn# Project setup # Check if the dependencies are ok, if not install what is missing - bundle check || bundle install --jobs $(nproc) - yarn install# database.yml.ci file contains the configurations for the CI # server, so let's copy to the configuration file - cp config/database.yml.ci config/database.yml- bundle exec rails db:create RAILS_ENV=test - bundle exec rails db:schema:load RAILS_ENV=test - bundle exec webpack
Take a note that we copy our CI specific database configuration file, which contains the correct values for the runner. It should have the following content:
default: &default adapter: postgresql encoding: unicode username: postgres password: postgres host: <%= ENV['DB_HOST'] %> pool: 5 database: ci_db
Defining the stages
stages block of the config file defines all the stages of the build process of the app. We, at Evermore, usually go with three: test, lint and deploy.
stages: - test - lint - deploy
GitLab allows to have as many task per stage as you like, which makes perfect sense for our unit and system tests. But to be able to run tests using headless Chrome, we need to install it and install
chromedriver as well. As this is kinda expensive task, we only do it when we actually need it.
Tests: stage: test script: - bundle exec rails test -dSystem Tests: stage: test script: - ./bin/setup_chrome - bundle exec rails test:system
Here is the
#!/bin/bashset -e# Install Chrome wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list apt-get update -yqqq apt-get install -y google-chrome-stable > /dev/null 2>&1 sed -i 's/"$@"/--no-sandbox "$@"/g' /opt/google/chrome/google-chrome# Install chromedriver wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip unzip /tmp/chromedriver.zip chromedriver -d /usr/bin/ rm /tmp/chromedriver.zip chmod ugo+rx /usr/bin/chromedriver
At Evermore we have a coding style guide and one thing that we like to do is to automate the review process of lint violations. It is always a better idea if they come from a machine than a person :) To do this we use this awesome gem called pronto. It has various runners and rubocop is one of them.
Pronto: stage: lint allow_failure: true except: - master script: - bundle exec pronto run -f gitlab -c origin/master
As this is not curtail part of our build, we allow failures for this task. And, of course, there is no need to run this task on the master branch, because we want to lint only the change in a pull (merge) request.
Deploy stage can be different depending on the needs. We usually use Heroku and have two environments — staging and production. We deploy to staging on successful build on branches (pull requests), so it is easy to review and deploy the master branch to production.
The easiest way to deploy your app to Heroku, is using the
dpl package. You just need to install it and setup an API key.
Do not forget to add `$HEROKU_API_KEY` to your secret variables in GitLab.
Deploy Production: stage: deploy retry: 2 only: - master script: - ./bin/setup_heroku - dpl --provider=heroku --app=awesome-app --api-key=$HEROKU_API_KEY - heroku run rake db:migrate --exit-code --app awesome-appDeploy Staging: stage: deploy allow_failure: true retry: 2 except: - master script: - ./bin/setup_heroku - dpl --provider=heroku --app=awesome-app-staging --api-key=$HEROKU_API_KEY - heroku run rake db:migrate --exit-code --app awesome-app-staging - heroku run rake db:seed --exit-code --app awesome-app-staging
Here is the
#!/bin/bashset -eapt-get update -yq apt-get install apt-transport-https software-properties-common python-software-properties -y add-apt-repository "deb https://cli-assets.heroku.com/branches/stable/apt ./" curl -L https://cli-assets.heroku.com/apt/release.key | apt-key add - apt-get update -yq apt-get install heroku -y gem install dpl
We install Heroku’s CLI tool, so we can run tasks like migrations, seeding the database and so on.
And we are done
Of course, we can go a step further and create a custom docker image. This way we will cache some of the setup steps and speed up the builds. Strangely enough, I was not able to find an image that fits my needs for this specific project, so I created one — https://hub.docker.com/r/mupkoo/rails-ci/. Give it a try if you like.
Here is the full script. Tweak it and make it yours. Good luck!