30

Build, test and deploy Rails application with CircleCI 2.0 and Heroku

 4 years ago
source link: https://www.tuicool.com/articles/22MzA3z
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Currently, I am working on a Rails application that is hosted on Heroku and we have staging environment alongside with production (both production and staging are hosted on Heroku). All the code (our main repo and code base) is hosted on BitBucket and CircleCI is something that was inherited from previous developers (not sure why CircleCI was first chose, but that is some other topic). We have automated CircleCI job that will build, test and deploy code to Heroku staging application, which was working fine, until CircleCI switched from 1.0 to 2.0 on August 31st 2018. Now, up until this date, to be honest, I did not even know how exactly everything is setup, but because I was the only developer around August 2018. I had to learn and switch from 1.0 to 2.0.

At the time, there was not that much documentation on how to deploy to Heroku using CircleCI and thats why I am writing this article, so that anyone else that have common setup for Rails application can understand and implement something like this.

First main difference was that location of CircleCI config file is changed, but I will not talk about that, because there is a pretty good documentation on that on CircleCI website, so I will focus on my config file and Heroku part mainly here.

File location for CircleCI config is .circleci/config.yml , so I am going to got through my current config file, line by line (or couple lines by couple lines) with some explanations.

Basically, first two line are pretty much self explanatory, just specifying version and defining jobs for CircleCI.

version: 2
jobs: ...

Now, fun part is defining jobs. In my setup, I have two jobs basically. First one is called build , which will pull application from BitBucket (this is done automatically, so I am not going to go through setting up integration between BitBucket and CircleCI), then installing all dependencies for my Rails application, loading up database and running tests.

build:
working_directory:
~/my-application
docker:
- image: circleci/ruby:2.5-stretch-node-browsers
environment:
RAILS_ENV:
test
DB_HOST: 127.0.0.1
- image: circleci/mysql:5.6
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MYSQL_ROOT_HOST: "%"
steps:
- checkout
- run:
name:
Install Ruby Dependencies
command: gem install bundler -v 1.16.1 && bundle install --jobs=4 --retry=3 --path vendor/bundle
- run: |-
mkdir -p config && echo 'test:
adapter: mysql2
database: test_db
username: root
host: 127.0.0.1
' > config/database.yml
- run: |-
export RAILS_ENV="test"
export RACK_ENV="test"
bundle exec rake db:create db:schema:load ts:configure ts:index ts:start --trace
- run: bundle exec rake db:migrate
- run: bundle exec rspec spec

New stuff in CircleCI 2.0 is that they are using Docker for building environment, so docker part from this example is specifying docker images we want to use when building application. Next part of this build job is checkout , which is CircleCI’s default command and it will basically checkout code to specific revision (either specific branch or pull request, thats something you can specify in project settings on CircleCI). After checkout, I have couple of run commands, where I am installing all dependencies, setting up database and database config, setting RAILS_ENV and RACK_ENV to “test”, because CircleCI 2.0 does not do that for you automatically anymore (in CircleCI 1.0 these were exported automatically) and after all of this, I am doing migration and running tests using rspec. To sum up, this job will basically build my application on CircleCI and after build, it would run my automated tests.

Second job that I called staging is basically automated deploy from CircleCI to Heroku.

staging:
machine:
true
working_directory: ~/my-application
steps:
- checkout
- run: |-
cat >~/.netrc <<EOF
machine api.heroku.com
login $HEROKU_USERNAME
password $HEROKU_PASSWORD
machine git.heroku.com
login $HEROKU_USERNAME
password $HEROKU_PASSWORD
EOF
- run: chmod 600 ~/.netrc
- run: heroku git:remote -a my-app-staging
- run: git push heroku staging:master
- run: heroku run rake db:migrate --app my-app-staging

Now, in this job, you will notice some difference from build job. First one and the most significant is that I am using machine instead of docker and thats because I do not need my application up and running in this job, I just need machine in order to push my code to Heroku. First step is same as in build job, its a checkout . Next, I am adding api and git Heroku hosts to  .netrc file, in order not to have password or username prompt in this process. If you do not know what  .netrc file is and how to use it, you can check following url . Notice that $HEROKU_USERNAME and $HEROKU_PASSWORD are Environment Variables added directly in CircleCI settings and I can use them during CircleCI jobs where I need them, so if you setting up CircleCI and Heroku, don’t forget to add these in CircleCI. Step with chmod 600 of .netrc file is there because Heroku CLI is complaining if .netrc file does not have this specific chmod (you can search this on Stack Overflow if you want). And finally, deploy to specific Heroku application. First, I am adding git remote for my staging application on Heroku, then I push to that repo and finally, I am running db migrations against that staging application.

Having these two jobs is fine, but the final step for this integration is defining workflow and using these two jobs.

workflows:
version:
2
build_test_deploy:
jobs:
- build
- staging:
requires:
- build
filters:
branches:
only:
staging

Workflows are a separate part of CircleCI config, where you define how and when to call specific jobs defined in first part of config file. In my example, I have only one workflow defined, called build_test_deploy (pretty creative, right?). First job in this workflow is build job (without any specific options) and after build is done, staging job should be run, but with some additional options. First one is requires which is telling staging job not to run if build job is not finished successfully. I also have filter for staging job, which only run only for staging branch of my repo, which in practice means, deploy to staging instance only if code is pushed to staging branch.

Finally, here is a whole .circleci/config.yml content in one piece, so it can be easier to copy and use it.

version: 2
jobs:
  build:
    working_directory: ~/my-application
    docker:
      - image: circleci/ruby:2.5-stretch-node-browsers
        environment:
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
      - image: circleci/mysql:5.6
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: "true"
          MYSQL_ROOT_HOST: "%"
    steps:
      - checkout
      - run:
          name: Install Ruby Dependencies
          command: gem install bundler -v 1.16.1 && bundle install --jobs=4 --retry=3 --path vendor/bundle
      - run: |-
          mkdir -p config && echo 'test:
            adapter: mysql2
            database: test_db
            username: root
            host: 127.0.0.1
          ' > config/database.yml
      - run: |-
          export RAILS_ENV="test"
          export RACK_ENV="test"
          bundle exec rake db:create db:schema:load ts:configure ts:index ts:start --trace
      - run: bundle exec rake db:migrate
      - run: bundle exec rspec spec
  staging:
  machine: true
  working_directory: ~/my-application
  steps:
    - checkout
    - run: |-
        cat >~/.netrc <<EOF
        machine api.heroku.com
          login $HEROKU_USERNAME
          password $HEROKU_PASSWORD
        machine git.heroku.com
          login $HEROKU_USERNAME
          password $HEROKU_PASSWORD
        EOF
    - run: chmod 600 ~/.netrc 
    - run: heroku git:remote -a my-app-staging
    - run: git push heroku staging:master
    - run: heroku run rake db:migrate --app my-app-staging
workflows:
  version: 2
  build_test_deploy:
    jobs:
      - build
      - staging:
          requires:
            - build
          filters:
            branches:
              only: staging

Hope this will help someone not to lose a tone of time figuring out how to solve pretty simple problem (when you know exactly what you are doing).

Cheers!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK