The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Using SwitchTower with multiple deployment stages

3 January 2006 — 2-minute read

Back in August I blogged briefly about having a conditional SwitchTower configuration, which would allow you to specify a different configuration for various stages of deployment. Recently, people have been asking for “deploy_stage” and “deploy_production” in SwitchTower itself, but I’m pushing back. It seems to me that it is easy enough to do in SwitchTower that there doesn’t really need to be a specific feature set in ST for it (but you tell me).

Revisiting that entry, I wouldn’t do it much differently today. The deploy.rb would still look more or less like this:

1
2
3
4
5
6
7
8
9
10
11
set :application, "example"
if ENV["STAGE"] == "stage"
  set :deploy_to, "/u/apps/stage/#{application}"
  role :app, "staging.example.com"
  role :web, "staging.example.com"
  role :db,  "staging.example.com", :primary => true
else
  role :app, "foo.example.com", "bar.example.com"
  role :web, "123.example.com",
  role :db,  "tiger.example.com", :primary => true
end

It is as simple as an if statement, and it lets you configure multiple deployment stages (if you need them). If using environment variables is less than tasteful to you, you can also use ST variables:

1
2
3
4
5
6
7
...
if stage == "stage"
  ...
else
  ...
end
...

You would then invoke the task like:

  switchtower -vvvv -S stage=production -r config/deploy -a deploy

(Note the use of a capital -S—this causes the variable to be set before the recipe file is loaded, which is necessary since the variable is referenced as the file is being read.)

Note that using a variable complicates things a little bit if you are using rake, because you can’t pass the variable to the ST task from the rake command-line. Instead, you’ll need to create a new rake task for each value you want to pass to the variable (i.e., deploy_stage and deploy_production). For this reason, I prefer to use environment variables, but your mileage may vary.

So, from those of you who need multiple deployment stages, is this sufficient for you? If not, why?

Reader Comments

This is how we do it with odeo too. Although we envoke it with direct environment variables.... $ STAGE=development rake deploy
wow, I wanna use this at work with our j2ee crap...
I was writing code for staging/production deployments this very evening and this recipe works great. Had me going in no time with 2 deployment targets in 1 deploy.rb.
That's great, thanks. I'm still looking for a clean way to check if a file exists on the server and other simple tasks, is there a nice way of "plugging in" this functionality to create methods similar to 'delete'
Jamie, that's a great question, and a good topic for another blog entry. Here's a quick answer, though: First, you can load multiple recipes via the switchtower commandline, using the @-r@ switch multiple times:
  switchtower -r /path/to/my/recipes -r config/deploy -a deploy
Armed with that, you can put reusable chunks of recipes into a file and use them in multiple places. Here's an example of a method that tests for the existence of a file on the remote server:
def exists?(path)
  exists = false
  run "test -e #{path} && echo exists", :once => true do |ch, str, out|
    exists ||= out == :out && str.chomp == "exists"
    raise str if out == :err
  end
  exists
end
(Warning: I haven't actually tested the above code--but the theory is sound... ;))
Jamis, I think the current mechanism works well, except for one small deficiency. I typically want a different RAILS_ENV on the staging server -- not quite as chatty and slow as development, but not as streamlined as production, either. Rails makes it so easy to add a new environment, but SwitchTower has some built-in assumptions in places (e.g., migrate) that you want RAILS_ENV=production on the deployment servers, and you have to jump through hoops to override that. It would be nice if there were an easy way to change RAILS_ENV for the remote executions based on deployment stage.
Glenn, thanks for the suggestion. Others have suggested it as well, and the next version of ST will definitely allow you to specify which RAILS_ENV to use (for those tasks that require one, line @migrate@).
Jamis, your solution for the file_exists? won't work as the run method is only available inside a task block as far as I can see.
Jamie, you're absolutely right. You can pass @self@ (which is the actor reference) to the method, and then do @actor.run@ instead, which should work:
def file_exists?(actor, file)
  ...
  actor.run(...)
  ...
end
I also use the env variable approach but define tasks that set the environment vars. I then use ST rake to stack the tasks. For example this command deploys to staging rake staging deploy and this to production rake production deploy