diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f34930..aaf87e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Reverse Chronological Order: ## master + * Add ability to filter tasks to specific servers (host filtering). (@andytinycat) * Add a command line option to control role filter (`--roles`) (@andytinycat) * Use an SCM object with a pluggable strategy diff --git a/lib/capistrano/application.rb b/lib/capistrano/application.rb index 7ebf5034..14d3c7c8 100644 --- a/lib/capistrano/application.rb +++ b/lib/capistrano/application.rb @@ -16,7 +16,7 @@ module Capistrano end def sort_options(options) - options.push(version, dry_run, roles) + options.push(version, roles, dry_run, hostfilter) super end @@ -74,6 +74,16 @@ module Capistrano } ] end + + def hostfilter + ['--hosts HOSTS', '-z', + "Filter command to only apply to these hosts (separate multiple hosts with a comma)", + lambda { |value| + Configuration.env.set(:filter, :hosts => value.split(",")) + } + ] + end + end end diff --git a/lib/capistrano/configuration/servers.rb b/lib/capistrano/configuration/servers.rb index d52e65ef..54f8d4a7 100644 --- a/lib/capistrano/configuration/servers.rb +++ b/lib/capistrano/configuration/servers.rb @@ -1,5 +1,6 @@ require 'set' require_relative 'servers/role_filter' +require_relative 'servers/host_filter' module Capistrano class Configuration class Servers @@ -39,7 +40,7 @@ module Capistrano def fetch_roles(required, options) filter_roles = RoleFilter.for(required, available_roles) - select(servers_with_roles(filter_roles), options) + HostFilter.for(select(servers_with_roles(filter_roles), options)) end def servers_with_roles(roles) diff --git a/lib/capistrano/configuration/servers/host_filter.rb b/lib/capistrano/configuration/servers/host_filter.rb new file mode 100644 index 00000000..568cc6b7 --- /dev/null +++ b/lib/capistrano/configuration/servers/host_filter.rb @@ -0,0 +1,82 @@ +module Capistrano + class Configuration + class Servers + class HostFilter + + def initialize(available) + @available = available + end + + def self.for(available) + new(available).hosts + end + + def hosts + if host_filter.any? + @available.select { |server| host_filter.include? server.hostname } + else + @available + end + end + + private + + def filter + if host_filter.any? + host_filter + else + @available + end + end + + def host_filter + env_filter | configuration_filter + end + + def configuration_filter + ConfigurationFilter.new.hosts + end + + def env_filter + EnvFilter.new.hosts + end + + class ConfigurationFilter + + def hosts + if filter + Array(filter.fetch(:hosts, [])) + else + [] + end + end + + def config + Configuration.env + end + + def filter + config.fetch(:filter) || config.fetch(:select) + end + end + + + class EnvFilter + + def hosts + if filter + filter.split(',') + else + [] + end + end + + def filter + ENV['HOSTS'] + end + end + + end + end + end +end diff --git a/spec/lib/capistrano/configuration/servers/host_filter_spec.rb b/spec/lib/capistrano/configuration/servers/host_filter_spec.rb new file mode 100644 index 00000000..c9e3a82e --- /dev/null +++ b/spec/lib/capistrano/configuration/servers/host_filter_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +module Capistrano + class Configuration + class Servers + + describe HostFilter do + let(:host_filter) { HostFilter.new(available) } + let(:available) { [ Server.new('server1'), Server.new('server2'), Server.new('server3') ] } + + describe '#new' do + it 'takes one array of hostnames' do + expect(host_filter) + end + end + + describe '.for' do + + subject { HostFilter.for(available) } + + context 'without env vars' do + + it 'returns all available hosts' do + expect(subject).to eq available + end + + end + + context 'with ENV vars' do + before do + ENV.stubs(:[]).with('HOSTS').returns('server1,server2') + end + + it 'returns all required hosts defined in HOSTS' do + expect(subject).to eq [Server.new('server1'), Server.new('server2')] + end + end + + context 'with configuration filters' do + before do + Configuration.env.set(:filter, hosts: %w{server1 server2}) + end + + it 'returns all required hosts defined in the filter' do + expect(subject).to eq [Server.new('server1'), Server.new('server2')] + end + + after do + Configuration.env.delete(:filter) + end + end + + context 'with a single configuration filter' do + before do + Configuration.env.set(:filter, hosts: 'server3') + end + + it 'returns all required hosts defined in the filter' do + expect(subject).to eq [Server.new('server3')] + end + + after do + Configuration.env.delete(:filter) + end + end + + context 'with configuration filters and ENV vars' do + before do + Configuration.env.set(:filter, hosts: 'server1') + ENV.stubs(:[]).with('HOSTS').returns('server3') + end + + it 'returns all required hosts defined in the filter' do + expect(subject).to eq [Server.new('server1'), Server.new('server3')] + end + + after do + Configuration.env.delete(:filter) + end + end + end + end + + end + end +end diff --git a/spec/lib/capistrano/configuration/servers_spec.rb b/spec/lib/capistrano/configuration/servers_spec.rb index 6b659c51..c97eb25f 100644 --- a/spec/lib/capistrano/configuration/servers_spec.rb +++ b/spec/lib/capistrano/configuration/servers_spec.rb @@ -179,6 +179,7 @@ module Capistrano before do ENV.stubs(:[]).with('ROLES').returns('web,db') + ENV.stubs(:[]).with('HOSTS').returns(nil) servers.add_host('1', roles: :app, active: true) servers.add_host('2', roles: :app) servers.add_host('3', roles: :web)