by Emma

Automating apps' booting with Babushka


The humble command

When you are in your local environment and you want to start a rails application bundle exec rails server is the only command you (normally) need. We love that comfort. Yet our dev team deals with a number of web apps, api apps, daemons and background services and not all of them are done with RoR. So we decided to standardize the way we boot them up with a shell script.

As a development rule, all our apps have a shell script with standardized name and parameters. This is quite handy as you do not need to think whether the app you want to start is a rails app, a sinatra app or some other exotic framework in order to boot it.

We also faced the challenge of interdependence: app A needs to have app B and C up, while some of the features of B need to connect with app D. We initially solved this by having a super command that would start apps based on scenarios. We would have a command for booting up apps that cover a basic experience of our main services; should you need a special scenario, you would then start those apps and services with the standardized shell script.

This solution is good enough when the number of apps are small and all the knowledge is shared by the team. However, we found some weak points:

  • Discerning which features are part of the basic experience and which are not was difficult for new hires.
  • Our scripts were merely starting the app, but they did not provide all the necessary steps for a good boot up. For instance, we would start an app to quickly find out when trying to use it that we needed to run migrations on it.

The vitaminized command

We made a list of things we would like the booting of an app to take into account:

  • that would boot all dependent apps if not already running
  • that would install or update gems if needed
  • that would run database migrations if needed
  • that would create symbolic links if needed
  • [insert here any task your app needs in order to execute correctly]
  • that would start the app with our standardized shell script

The decision was to implement this with Babushka, a humble tool for automating computing chores written in Ruby. We particularly like its test-driven approach when running commands and its easiness to declare dependencies.

You can install it with a shell command to try it, or with a Chef cookbook to automate its installation.

Skeleton of a basic babuska command (called “name”):

dep 'name', :argument do
  requires 'other deps'.with('args'), 'whatever they might be'
  met? {
    # is this dependency already met?
  }
  meet {
    # this code gets run if it isn't.
  }
end

Example of a babushka command:

dep 'admin group' do
  # Returns a bool (i.e. "is this dep met?")
  met? { '/etc/group'.p.grep(/^admin\:/) }

  # Blindly do whatever is required to meet the dep.
  meet { shell "groupadd admin" }
end

Tweaking Babushka

In order to interact with the shell, Babushka comes with a bunch of handy methods:

  • shell cmd, &block: Basic one, runs the command on a shell
  • log_shell message, cmd, &block: Run a shell command, logging before and after and using a spinner while the command runs.
  • login_shell cmd, opt, &block: Run a shell command in a separate interactive shell

As we are using rbenv as our ruby version management, a shell method that loads RBENV_VERSION is necessary to execute the commands with the proper ruby version.

This is our own version of rbenv_shell:

def rbenv_shell message, cmd, opts = {}, &block
  Babushka::LogHelpers.log_block message do
    rbenv_cmd = "bash -l -c 'RBENV_VERSION=`cat $PWD/.ruby-version` #{cmd}'"
    shell rbenv_cmd, opts.merge(:spinner => true), &block
  end
end

We use babushka to start, shutdown and restart our apps in development environments in a smarter way that has less friction with our flow.

Checking out a branch and restart all apps is a childsplay now.

Emma Lopez.
Developer at peerTransfer.