YAML configuration everywhere and your sanity

by Michal Cichra

If you are using Rails, you are probably familiar with at least one YAML configuration: config/database.yml. Many people took this example and ran with it, creating their own YAML configurations for Redis, Memcache and other services.

Here at 3scale we were pretty happy with this workflow, except for a few small things. Like for example what happens when you split your application into two? You have to load the configuration somehow in both. There is no code to load YAML files, you have to roll your own. What if you are using Sinatra? Or just plain Ruby without any framework? How do you handle deployment? Using Capistrano, right? But how do you share this YAML generation between projects?

Rails 4.2 will bring some sanity and provide config_for for loading arbitrary YAML files. That is great if you’re running Rails 4.2. Unfortunately we’re not.

So we created a common framework for loading/uploading YAML configuration. Enter config_for.

config_for gem

It provides the following components:

  • Rails integration
  • Sinatra integration
  • Capistrano 3 task
  • ConfigFor.load_config and ConfigFor.load_config!

Let’s see them, one by one.

load_config and load_config!

You won’t need to use these if you are using any of the integrations below. They are “low level” methods for loading, processing and parsing YAML configuration. The common workflow for both is following: Read YAML file -> Process it by ERB -> Parse. The bang version (load_config!) will raise exception when your environment does not exist in the parsed hash. Being low level, these methods need a full path to the folder with config files as well as the name of the config to load and the environment name.

module MyProject
  class App
    @@redis = Redis.new ConfigFor.load_config!('config', 'redis', 'production')
  end
end

Rails integration

This is probably only useful for applications using Rails < 4.2.

It hooks into the same place as the config_for from Rails 4.2. So it provides an easy migration path. It will use your Rails config folder and environment (Rails.env). Just like the other integrations, it is using load_config! so it will raise exception when the environment is not found in the config.

Rails.application.configure do
  config.redis = config_for(:redis) # will load config/redis.yml and get key 'production'
end

Sinatra integration

Sinatra does not provide any method for loading config files by default, so this integration is especially handy. It is automatically loaded for classic apps, but for modular ones you have to register the plugin.

class MyApp < Sinatra::Base
  register ConfigFor::Sinatra

  # loads root + 'config/redis.yml' and gets key of correct 'environment'
  set :redis, Redis.new(config_for(:redis))
end

Capistrano Task

Now the juiciest part. If you have ever deployed a Rails app using Capistrano, you probably had to generate some YAML configuration. It probably was not a very nice experience.

Lets take a look on how easy it is to use a capistrano task for handing configuration. First it has to be loaded in Capfile:

require 'config_for/capistrano'

Now, you can use the ConfigFor::Capistrano::Task to generate tasks for you.

set :database_yml, {
  production: {
    host: 'localhost',
    port: 3306
  }
}
ConfigFor::Capistrano::Task.new(:database)

That will generate several tasks in Capistrano, which you can use:

cap database                       # Generate config/database.yml
cap database:remove                # Remove config/database.yml from current and shared path
cap database:reset                 # Reset database config
cap database:upload                # Upload config/database.yml to remote servers

To persist the file between deployments, you have to add it to the linked_files like:

set :linked_files, %w[ config/database.yml ]

Advanced Capistrano patterns

You probably have other environments than production as we do. To make code reuse easier, we have a pattern for sharing some parts of configuration.

When you initialize ConfigFor::Capistrano::Task.new(:name) it expects you to provide a variable named name_yml. To reuse that variable between environments, we do:

# config/deploy.rb
set :database_yml, -> do
  { fetch(:rails_env) -> fetch(:database_configuration) }
end
ConfigFor::Capistrano::Task.new(:database)

# config/deploy/production.rb
set :database_configuration, -> do
 {
   host: '...'
 }
end

That way you can have some defaults for all the environments and have less code in environment specific files.

And that’s it. Now whenever you cap production deploy it will upload the database.yml if missing. If you want to reset the configuration just run: cap production database:reset and the configuration will be reset on all servers.

Cool, huh?

Summary

Hope you like the gem and it will make your deployment workflow a bit saner. Check the README for more examples.

We have been using this gem in production for about a month without any issues so far. If you have any feedback, please open an issue on Github.

Stay tuned, we will have some more goodies coming.

Published: November 06 2014

  • category:
blog comments powered by Disqus