Using Sub To Simplify Workflow

At LifeChurch.tv, we’ve spent some time over the last year improving our toolset. Good tools allow us to spend less time thinking about the random details of our processes and focus more on creating great products for church’s around the world.

Before we get into the meat, let’s first talk about setup.

Remotes

We use Heroku for our hosting. Each app has a staging and production environment. The first shortcut we found was to stop referring to them using the --app APP_NAME flag. Instead, each is added as a distinct git remote:

$ git remote add staging OUR_STAGING_GIT_REMOTE
$ git remote add production OUR_PRODUCTION_GIT_REMOTE

That alone simplifies many of our commands. So now instead of heroku run console --app APP_NAME, each app can now be referenced as the remote, like this: heroku run console -r production. This is especially nice if your Heroku app names have strange (or verbose) naming conventions.

Sub

We’ve been using 37Signals “sub” for awhile to extract and generalize some common CLI commands we use every day. We named our sub “c2c” (our team name is “Church to Church”), so each of our commands is c2c <command>.

Because we use Heroku for hosting, it’s easy to run the same commands across each of our apps. For example, we can simplify heroku run console -r production across each app with this little script:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env ruby
# Usage: c2c console [<environment>]
# Summary: Launches rails console

# default to staging
def env
  ARGV[0] == "production" ? "production" : "staging"
end

puts "Getting #{env.upcase} console..."
system("heroku run console -r #{env}")

Now, we can run c2c console production inside any of our 4 apps. While this saves a few keystrokes, it’s not a huge gain. But when you can simplify longer workflows, that’s where you begin to see some of the savings.

Another common process is running migrations. We don’t run anything complicated like read-only modes, etc. We turn on maintenance, migrate, restart, and turn maintenance off. So we can simplify that:

1
2
3
4
5
6
7
8
9
if production?
  system("heroku maintenance:on -r production && heroku ps:scale worker=0 -r production")
end

system("heroku run rake db:migrate -r #{env} && heroku restart -r #{env}")

if production?
  system("heroku maintenance:off -r production && heroku ps:scale worker=2 -r production")
end

For deployments, we use STDIN to get a brief overview of what’s being deployed. This gets posted to a free Redis instance with info about who deployed and when. Our Dashing dashboard pulls from Redis to display the latest two deployments for the rest of the team.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env ruby
# Usage: c2c deploy [<environment>]
# Summary: Deploys the application you're in
require 'redis'
require 'json'

def deploy_production(product)
  puts "===> What are you deploying? "
  message = $stdin.gets().chomp

  success = system("git push production master")

  # post details to dashboard
  if success
    user_email = `git config --global user.email`.chomp()
    puts "NOTIFYING DASHBOARD OF #{message}"
    data = { message: message, at: Time.now.to_i, who: user_email }.to_json
    redis = Redis.new()
    redis.sadd("#{product}_deploys", data)
  end
end

##
# The business
##

product = dir.gsub(/[-.]/, "")

puts "Deploying #{product} to #{env}..."
if production?
  deploy_production(product)
else
  system("git push staging master")
end

The next version of the script I’m working on today will include the c2c migrate task. If the deployment needs to be done with migrations, we can easily ask the user and shell out to run this other task and come back to posting to the dashboard. We can also turn on/off preboot on Heroku to make sure we have a hard restart because of the database changes.

Overall, sub is a great tool for standardizing and simplifying common tasks across your team. Enjoy!

If you see any ways to further simplify any of the above scripts or if you have any questions, let me know!

Hi there, I'm Jon.

Writer. Musician. Adventurer. Nerd.

Purveyor of GIFs and dad jokes.