mirror of
https://github.com/capistrano/capistrano
synced 2023-03-27 23:21:18 -04:00
Add support for adding custom filters
Users of the API may now add their own 'on'-filters by calling Configuration#add_filter, e.g: `Capistrano::Configuration.env.add_filter(RegionFilter.new('NE'))`. An filter object passed into this method must respond to .filter, and this method should accept an array of Server objects and return a subset of this array. Tests have been added that checks that: * A filter can be added without throwing an error * Trying to add a filter without a filter method throws a TypeError * An added filter is used to filter a list of servers * add_filter accepts a block, and raises an ArgumentError if passed both a block and an object.
This commit is contained in:
parent
199b7854ac
commit
f76fb81fe2
6 changed files with 157 additions and 2 deletions
|
@ -9,6 +9,8 @@ https://github.com/capistrano/capistrano/compare/v3.7.0.beta1...HEAD
|
||||||
* Your contribution here!
|
* Your contribution here!
|
||||||
|
|
||||||
* Fix the removal of old releases `deploy:cleanup`. Logic is changed because of unreliable modification times on folders. Removal of directories is now decided by sorting on folder names (name is generated from current datetime format YmdHis). Cleanup is skipped, and a warning is given when a folder name is in a different format.
|
* Fix the removal of old releases `deploy:cleanup`. Logic is changed because of unreliable modification times on folders. Removal of directories is now decided by sorting on folder names (name is generated from current datetime format YmdHis). Cleanup is skipped, and a warning is given when a folder name is in a different format.
|
||||||
|
* Add support for custom on-filters
|
||||||
|
[#1776](https://github.com/capistrano/capistrano/issues/1776)
|
||||||
|
|
||||||
## `3.7.0.beta1` (2016-11-02)
|
## `3.7.0.beta1` (2016-11-02)
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
<li><a href="/documentation/advanced-features/property-filtering/">Property Filtering</a></li>
|
<li><a href="/documentation/advanced-features/property-filtering/">Property Filtering</a></li>
|
||||||
<li><a href="/documentation/advanced-features/host-filtering/">Host filtering</a></li>
|
<li><a href="/documentation/advanced-features/host-filtering/">Host filtering</a></li>
|
||||||
<li><a href="/documentation/advanced-features/role-filtering/">Role Filtering</a></li>
|
<li><a href="/documentation/advanced-features/role-filtering/">Role Filtering</a></li>
|
||||||
|
<li><a href="/documentation/advanced-features/custom-filters/">Custom Filters</a></li>
|
||||||
<li><a href="/documentation/advanced-features/overriding-capistrano-tasks/">Overriding Capistrano Tasks</a></li>
|
<li><a href="/documentation/advanced-features/overriding-capistrano-tasks/">Overriding Capistrano Tasks</a></li>
|
||||||
<li><a href="/documentation/advanced-features/remote-file/">Remote File Task</a></li>
|
<li><a href="/documentation/advanced-features/remote-file/">Remote File Task</a></li>
|
||||||
<li><a href="/documentation/advanced-features/ssh-kit">Remote Commands with SSHKit</a></li>
|
<li><a href="/documentation/advanced-features/ssh-kit">Remote Commands with SSHKit</a></li>
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
---
|
||||||
|
title: Custom Filters
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
|
||||||
|
Custom filters (specifically, Custom On-Filters) limit the hosts that are being
|
||||||
|
deployed to, in the same way as
|
||||||
|
[Host](/documentation/advanced-features/host-filtering/) and
|
||||||
|
[Role](/documentation/advanced-features/role-filtering/) filters, but the exact
|
||||||
|
method used to filter servers is up to the user of Capistrano.
|
||||||
|
|
||||||
|
Filters may be added to Capistrano's list of filters by using the
|
||||||
|
`Configuration#add_filter` method. Filters must respond to a `filter` method,
|
||||||
|
which will be given an array of Servers, and should return a subset of that
|
||||||
|
array (the servers which passed the filter).
|
||||||
|
|
||||||
|
`Configuration#add_filter` may also take a block, in which case the block is
|
||||||
|
expected to be unary. The block will be passed an array of servers, and is
|
||||||
|
expected to return a subset of that array.
|
||||||
|
|
||||||
|
Either a block or object may be passed to `add_filter`, but not both.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
You may have a large group of servers that are partitioned into separate regions
|
||||||
|
that correspond to actual geographic regions. Usually, you deploy to all of
|
||||||
|
them, but there are cases where you want to deploy to a specific region.
|
||||||
|
|
||||||
|
Capistrano recognizes the concept of a server's *role* and *hostname*, but has
|
||||||
|
no concept of a *region*. In this case, you can construct your own filter that
|
||||||
|
selects servers based on their region. When defining servers, you may provide
|
||||||
|
them with a `region` property, and use that property in your filter.
|
||||||
|
|
||||||
|
|
||||||
|
The filter could look like this:
|
||||||
|
|
||||||
|
`config/deploy.rb`
|
||||||
|
|
||||||
|
class RegionFilter
|
||||||
|
|
||||||
|
def initialize(regions)
|
||||||
|
@regions = Array(regions)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(servers)
|
||||||
|
servers.select {|server|
|
||||||
|
region = server.fetch(:region)
|
||||||
|
region && @regions.include?(region)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
You would add servers like this:
|
||||||
|
|
||||||
|
`config/deploy/production.rb`
|
||||||
|
|
||||||
|
server('123.123.123.123', region: 'north-east')
|
||||||
|
server('12.12.12.12', region: 'south-west')
|
||||||
|
server('4.5.6.7', region: 'mid-west')
|
||||||
|
|
||||||
|
To tell Capistrano to use this filter, you would use the
|
||||||
|
`Configuration#add_filter` method. In this example, we look at the `REGIONS`
|
||||||
|
environment variable, and take it to be a comma-separated list of regions that
|
||||||
|
we're interested in:
|
||||||
|
|
||||||
|
`config/deploy.rb`
|
||||||
|
|
||||||
|
if ENV['REGIONS']
|
||||||
|
regions = ENV['REGIONS'].split(',')
|
||||||
|
filter = RegionFilter.new(regions)
|
||||||
|
Capistrano::Configuration.env.add_filter(filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
We obtain a list of regions to deploy to from the environment variable,
|
||||||
|
construct a new filter with those regions, and add it to Capistrano's list of
|
||||||
|
filters.
|
||||||
|
|
||||||
|
Of course, we're not limited to regions. Any time you can classify or partition
|
||||||
|
a list of servers in a way that you only want to deploy to some of them, you can
|
||||||
|
use a custom filter. For another example, you might arbitrarily assign your
|
||||||
|
servers to either an *A* group or a *B* group, and deploy a new version only to
|
||||||
|
the *B* group as a simple variant of A/B Testing.
|
|
@ -31,7 +31,7 @@ and the configuration ones which are _Property-Filters_
|
||||||
|
|
||||||
### On-Filtering
|
### On-Filtering
|
||||||
|
|
||||||
On-filters apply only to the `on()` method that invokes SSH. There are two types:
|
On-filters apply only to the `on()` method that invokes SSH. There are two default types:
|
||||||
|
|
||||||
* [Host Filters](/documentation/advanced-features/host-filtering/), and
|
* [Host Filters](/documentation/advanced-features/host-filtering/), and
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ to look for servers with the hostname `server1` or `server2`. If only `server2`
|
||||||
had the role `app` (`server1` has some other role), then in this situation your
|
had the role `app` (`server1` has some other role), then in this situation your
|
||||||
task would only run on `server2`.
|
task would only run on `server2`.
|
||||||
|
|
||||||
|
Custom filters may also be added; see
|
||||||
|
[Custom Filters](/documentation/advanced-features/custom-filters/).
|
||||||
|
|
||||||
### Property-Filtering
|
### Property-Filtering
|
||||||
|
|
||||||
Property-filters select servers based on the value of their properties alone and
|
Property-filters select servers based on the value of their properties alone and
|
||||||
|
|
|
@ -110,8 +110,25 @@ module Capistrano
|
||||||
@timestamp ||= Time.now.utc
|
@timestamp ||= Time.now.utc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_filter(filter=nil, &block)
|
||||||
|
if block
|
||||||
|
raise ArgumentError, "Both a block and an object were given" if filter
|
||||||
|
|
||||||
|
filter = Object.new
|
||||||
|
def filter.filter(servers)
|
||||||
|
block.call(servers)
|
||||||
|
end
|
||||||
|
elsif !filter.respond_to? :filter
|
||||||
|
raise TypeError, "Provided custom filter <#{filter.inspect}> does " \
|
||||||
|
"not have a public 'filter' method"
|
||||||
|
end
|
||||||
|
@custom_filters ||= []
|
||||||
|
@custom_filters << filter
|
||||||
|
end
|
||||||
|
|
||||||
def setup_filters
|
def setup_filters
|
||||||
@filters = cmdline_filters.clone
|
@filters = cmdline_filters
|
||||||
|
@filters += @custom_filters if @custom_filters
|
||||||
@filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
|
@filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
|
||||||
@filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
|
@filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
|
||||||
fh = fetch_for(:filter, {}) || {}
|
fh = fetch_for(:filter, {}) || {}
|
||||||
|
|
|
@ -304,5 +304,54 @@ module Capistrano
|
||||||
expect(config.dry_run?).to eq(true)
|
expect(config.dry_run?).to eq(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "custom filtering" do
|
||||||
|
it "accepts a custom filter object" do
|
||||||
|
filter = Object.new
|
||||||
|
def filter.filter(servers)
|
||||||
|
servers
|
||||||
|
end
|
||||||
|
config.add_filter(filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts a custom filter as a block" do
|
||||||
|
config.add_filter { |servers| servers }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if passed a block and an object" do
|
||||||
|
filter = Object.new
|
||||||
|
def filter.filter(servers)
|
||||||
|
servers
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { config.add_filter(filter) { |servers| servers } }.to raise_error(ArgumentError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the filter lacks a filter method" do
|
||||||
|
filter = Object.new
|
||||||
|
expect { config.add_filter(filter) }.to raise_error(TypeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls the filter method of a custom filter" do
|
||||||
|
ENV.delete "ROLES"
|
||||||
|
ENV.delete "HOSTS"
|
||||||
|
|
||||||
|
servers = Configuration::Servers.new
|
||||||
|
|
||||||
|
servers.add_host("test1")
|
||||||
|
servers.add_host("test2")
|
||||||
|
servers.add_host("test3")
|
||||||
|
|
||||||
|
filtered_servers = servers.take(2)
|
||||||
|
|
||||||
|
filter = mock("custom filter")
|
||||||
|
filter.expects(:filter)
|
||||||
|
.with { |subset| subset.is_a? Configuration::Servers }
|
||||||
|
.returns(filtered_servers)
|
||||||
|
|
||||||
|
config.add_filter(filter)
|
||||||
|
expect(config.filter(servers)).to eq(filtered_servers)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue