mirror of
https://github.com/capistrano/capistrano
synced 2023-03-27 23:21:18 -04:00
Dynamic roles (closes #11107, thanks for the awesome patch!)
git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@8905 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
76957eeeef
commit
3cbc3496c8
4 changed files with 209 additions and 5 deletions
|
@ -1,5 +1,7 @@
|
|||
*SVN*
|
||||
|
||||
* Dynamic roles (e.g. role(:app) { "host.name" }) [dmasover]
|
||||
|
||||
* Implement Bzr#next_revision so that pending changes can be reported correctly [casret]
|
||||
|
||||
* Use a proper export command for bzr SCM [drudru]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'capistrano/server_definition'
|
||||
require 'capistrano/role'
|
||||
|
||||
module Capistrano
|
||||
class Configuration
|
||||
|
@ -15,7 +16,7 @@ module Capistrano
|
|||
|
||||
def initialize_with_roles(*args) #:nodoc:
|
||||
initialize_without_roles(*args)
|
||||
@roles = Hash.new { |h,k| h[k] = [] }
|
||||
@roles = Hash.new { |h,k| h[k] = Role.new }
|
||||
end
|
||||
|
||||
# Define a new role and its associated servers. You must specify at least
|
||||
|
@ -41,9 +42,10 @@ module Capistrano
|
|||
# that call to "role":
|
||||
#
|
||||
# role :web, "web2", "web3", :user => "www", :port => 2345
|
||||
def role(which, *args)
|
||||
def role(which, *args, &block)
|
||||
options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
which = which.to_sym
|
||||
roles[which].push(block, options) if block_given?
|
||||
args.each { |host| roles[which] << ServerDefinition.new(host, options) }
|
||||
end
|
||||
end
|
||||
|
|
112
lib/capistrano/role.rb
Normal file
112
lib/capistrano/role.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
|
||||
module Capistrano
|
||||
class Role
|
||||
include Enumerable
|
||||
|
||||
def initialize(*list)
|
||||
@static_servers = []
|
||||
@dynamic_servers = []
|
||||
push(*list)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
servers.each &block
|
||||
end
|
||||
|
||||
def push(*list)
|
||||
options = list.last.is_a?(Hash) ? list.pop : {}
|
||||
list.each do |item|
|
||||
if item.respond_to?(:call)
|
||||
@dynamic_servers << DynamicServerList.new(item, options)
|
||||
else
|
||||
@static_servers << self.class.wrap_server(item, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
alias_method :<<, :push
|
||||
|
||||
def servers
|
||||
@static_servers + dynamic_servers
|
||||
end
|
||||
alias_method :to_ary, :servers
|
||||
|
||||
def empty?
|
||||
servers.empty?
|
||||
end
|
||||
|
||||
# Resets the cache, so that proc values may be recalculated.
|
||||
# There should be a command in Configuration::Roles to do this,
|
||||
# but I haven't needed it yet, and I'm not sure yet
|
||||
# what to call that command. Suggestions?
|
||||
def reset!
|
||||
@dynamic_servers.each { |item| item.reset! }
|
||||
end
|
||||
|
||||
# Clears everything. I still thing this should be 'clear!', but that's not
|
||||
# the way Array does it.
|
||||
def clear
|
||||
@dynamic_servers.clear
|
||||
@static_servers.clear
|
||||
end
|
||||
|
||||
# Mostly for documentation purposes. Doesn't seem to do anything.
|
||||
protected
|
||||
|
||||
# This is the combination of a block, a hash of options, and a cached value.
|
||||
# It is protected because it is an implementation detail -- the original
|
||||
# implementation was two lists (blocks and cached results of calling them).
|
||||
class DynamicServerList
|
||||
def initialize (block, options)
|
||||
@block = block
|
||||
@options = options
|
||||
@cached = []
|
||||
@is_cached = false
|
||||
end
|
||||
|
||||
# Convert to a list of ServerDefinitions
|
||||
def to_ary
|
||||
unless @is_cached
|
||||
@cached = Role::wrap_list(@block.call(@options), @options)
|
||||
@is_cached = true
|
||||
end
|
||||
@cached
|
||||
end
|
||||
|
||||
# Clear the cached value
|
||||
def reset!
|
||||
@cached.clear
|
||||
@is_cached = false
|
||||
end
|
||||
end
|
||||
|
||||
# Attribute reader for the cached results of executing the blocks in turn
|
||||
def dynamic_servers
|
||||
@dynamic_servers.inject([]) { |list, item| list.concat item }
|
||||
end
|
||||
|
||||
# Wraps a string in a ServerDefinition, if it isn't already.
|
||||
# This and wrap_list should probably go in ServerDefinition in some form.
|
||||
def self.wrap_server (item, options)
|
||||
item.is_a?(ServerDefinition) ? item : ServerDefinition.new(item, options)
|
||||
end
|
||||
|
||||
# Turns a list, or something resembling a list, into a properly-formatted
|
||||
# ServerDefinition list. Keep an eye on this one -- it's entirely too
|
||||
# magical for its own good. In particular, if ServerDefinition ever inherits
|
||||
# from Array, this will break.
|
||||
def self.wrap_list (*list)
|
||||
options = list.last.is_a?(Hash) ? list.pop : {}
|
||||
if list.length == 1
|
||||
if list.first.nil?
|
||||
return []
|
||||
elsif list.first.is_a?(Array)
|
||||
list = list.first
|
||||
end
|
||||
end
|
||||
options.merge! list.pop if list.last.is_a?(Hash)
|
||||
list.map do |item|
|
||||
self.wrap_server item, options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
require "#{File.dirname(__FILE__)}/../utils"
|
||||
require 'capistrano/configuration/roles'
|
||||
require 'capistrano/server_definition'
|
||||
|
||||
class ConfigurationRolesTest < Test::Unit::TestCase
|
||||
class MockConfig
|
||||
|
@ -16,6 +17,10 @@ class ConfigurationRolesTest < Test::Unit::TestCase
|
|||
@config = MockConfig.new
|
||||
end
|
||||
|
||||
def assert_role_equals(list)
|
||||
assert_equal list, @config.roles[:app].map { |s| s.host }
|
||||
end
|
||||
|
||||
def test_initialize_should_initialize_roles_collection
|
||||
assert @config.original_initialize_called
|
||||
assert @config.roles.empty?
|
||||
|
@ -29,13 +34,34 @@ class ConfigurationRolesTest < Test::Unit::TestCase
|
|||
def test_role_with_one_argument_should_add_to_roles_collection
|
||||
@config.role :app, "app1.capistrano.test"
|
||||
assert_equal [:app], @config.roles.keys
|
||||
assert_equal %w(app1.capistrano.test), @config.roles[:app].map { |s| s.host }
|
||||
assert_role_equals %w(app1.capistrano.test)
|
||||
end
|
||||
|
||||
def test_role_block_returning_single_string_is_added_to_roles_collection
|
||||
@config.role :app do
|
||||
'app1.capistrano.test'
|
||||
end
|
||||
assert_role_equals %w(app1.capistrano.test)
|
||||
end
|
||||
|
||||
def test_role_with_multiple_arguments_should_add_each_to_roles_collection
|
||||
@config.role :app, "app1.capistrano.test", "app2.capistrano.test"
|
||||
assert_equal [:app], @config.roles.keys
|
||||
assert_equal %w(app1.capistrano.test app2.capistrano.test), @config.roles[:app].map { |s| s.host }
|
||||
assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
|
||||
end
|
||||
|
||||
def test_role_with_block_and_strings_should_add_both_to_roles_collection
|
||||
@config.role :app, 'app1.capistrano.test' do
|
||||
'app2.capistrano.test'
|
||||
end
|
||||
assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
|
||||
end
|
||||
|
||||
def test_role_block_returning_array_should_add_each_to_roles_collection
|
||||
@config.role :app do
|
||||
['app1.capistrano.test', 'app2.capistrano.test']
|
||||
end
|
||||
assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
|
||||
end
|
||||
|
||||
def test_role_with_options_should_apply_options_to_each_argument
|
||||
|
@ -44,4 +70,66 @@ class ConfigurationRolesTest < Test::Unit::TestCase
|
|||
assert_equal({:extra => :value}, server.options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_role_with_options_should_apply_options_to_block_results
|
||||
@config.role :app, :extra => :value do
|
||||
['app1.capistrano.test', 'app2.capistrano.test']
|
||||
end
|
||||
@config.roles[:app].each do |server|
|
||||
assert_equal({:extra => :value}, server.options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_options_should_apply_only_to_this_argument_set
|
||||
@config.role :app, 'app1.capistrano.test', 'app2.capistrano.test' do
|
||||
['app3.capistrano.test', 'app4.capistrano.test']
|
||||
end
|
||||
@config.role :app, 'app5.capistrano.test', 'app6.capistrano.test', :extra => :value do
|
||||
['app7.capistrano.test', 'app8.capistrano.test']
|
||||
end
|
||||
@config.role :app, 'app9.capistrano.test'
|
||||
|
||||
option_hosts = ['app5.capistrano.test', 'app6.capistrano.test', 'app7.capistrano.test', 'app8.capistrano.test']
|
||||
@config.roles[:app].each do |server|
|
||||
if (option_hosts.include? server.host)
|
||||
assert_equal({:extra => :value}, server.options)
|
||||
else
|
||||
assert_not_equal({:extra => :value}, server.options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Here, the source should be more readable than the method name
|
||||
def test_role_block_returns_options_hash_is_merged_with_role_options_argument
|
||||
@config.role :app, :first => :one, :second => :two do
|
||||
['app1.capistrano.test', 'app2.capistrano.test', {:second => :please, :third => :three}]
|
||||
end
|
||||
@config.roles[:app].each do |server|
|
||||
assert_equal({:first => :one, :second => :please, :third => :three}, server.options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_role_block_can_override_role_options_argument
|
||||
@config.role :app, :value => :wrong do
|
||||
Capistrano::ServerDefinition.new('app.capistrano.test')
|
||||
end
|
||||
@config.roles[:app].servers
|
||||
@config.roles[:app].servers.each do |server|
|
||||
assert_not_equal({:value => :wrong}, server.options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_role_block_can_return_nil
|
||||
@config.role :app do
|
||||
nil
|
||||
end
|
||||
assert_role_equals ([])
|
||||
end
|
||||
|
||||
def test_role_block_can_return_empty_array
|
||||
@config.role :app do
|
||||
[]
|
||||
end
|
||||
assert_role_equals ([])
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue