I’ve been updating some old deploy scripts from Capistrano 2 to Capistrano 3 recently. Needless to say, although superficially similar, a lot has changed under the hood (largely for the better. Capistrano 3 is in many ways a simpler, more predictable animal than its predecessor).
One change that bit me today is how filters work. In Capistrano 2, this was handled by environment variables such as
ROLES. Capistrano 3 also add other ways of doing, such as via
set :filter. The big difference is that Capistrano 3 creates its filter set once, whereas Capistrano 2 refiltered the hosts for a role everytime it fetched a list of hosts. This definitely makes things a lot simpler, but interferes with the tasks I have for doing rolling upgrades.
With Capistrano 2 I did this by updating the code on all the servers and then running the symlink and restart tasks on one host at a time, using HOSTS
for each one. One way around it would be to just reimplement the symlink and restart tasks ourselves, call it in our own
on block, and make on process the hosts in sequence. As ever though, I’m loath to reinvent the wheel.
A new concept in Capistrano 3 is that of custom filters, to allow you to expand on the HOSTS and ROLES filtering previously offered. This can be used with the task at hand: although the list of filters is static, Capistrano 3 does re-evaluate the list of servers against the filters with each call to
roles. All that is needed is a filter that is reconfigurable. A filter is just an object with a public filter method. In all the examples an instance of a filter class is created wrapping the particular values that should be filtered, but it works just as well if the object passed to
add_filter is the class itself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
All this is a class with a class instance variable containing a list of hosts that it uses to filter servers. The
with_hosts method allows us to set the filter for the duration of the block. Any new filter is intersected with existing filters and removed at the end of the block, which I think reduces the possibility for surprising behaviour. The rolling restart code is then just
1 2 3 4 5
which is actually a lot nicer than the old Capistrano 2 code it replaced.