screwcap 0.3.5 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Manifest.txt CHANGED
@@ -1,5 +1,3 @@
1
- Gemfile
2
- Gemfile.lock
3
1
  History.txt
4
2
  Manifest.txt
5
3
  README.rdoc
@@ -8,23 +6,25 @@ TODO
8
6
  bin/screwcap
9
7
  lib/screwcap.rb
10
8
  lib/screwcap/base.rb
11
- lib/screwcap/deployer.rb
12
9
  lib/screwcap/message_logger.rb
13
10
  lib/screwcap/runner.rb
14
11
  lib/screwcap/sequence.rb
15
12
  lib/screwcap/server.rb
16
13
  lib/screwcap/task.rb
14
+ lib/screwcap/task_manager.rb
17
15
  lib/trollop.rb
18
16
  recipes/setup_rails.rb
17
+ runrdoc
19
18
  screwcap.gemspec
20
- spec/command_set_spec.rb
21
- spec/deployer_spec.rb
19
+ spec/runner_spec.rb
22
20
  spec/sequence_spec.rb
23
21
  spec/server_spec.rb
24
22
  spec/spec.opts
25
23
  spec/spec_helper.rb
24
+ spec/task_manager_spec.rb
26
25
  spec/task_spec.rb
27
26
  tasks/rspec.rake
27
+ test/config/bad_use.rb
28
28
  test/config/command_sets.rb
29
29
  test/config/expect.rb
30
30
  test/config/gateway.rb
@@ -32,9 +32,11 @@ test/config/local_task.rb
32
32
  test/config/no_server.rb
33
33
  test/config/rails_tasks.rb
34
34
  test/config/simple_recipe.rb
35
+ test/config/super_simple_recipe.rb
35
36
  test/config/undefined_command_set.rb
36
37
  test/config/undefined_item.rb
37
38
  test/config/undefined_server.rb
38
39
  test/config/unknown_use.rb
39
40
  test/config/upload.rb
40
41
  test/config/use.rb
42
+ test/config/use2.rb
data/README.rdoc CHANGED
@@ -20,6 +20,19 @@ For more info, see the main screwcap page at http://gammons.github.com/screwcap
20
20
 
21
21
  * gem install screwcap
22
22
 
23
+ == TODO:
24
+ * Explore removing the 'use' keyword, favoring require or load
25
+ * Add ability to parse user input
26
+ * Add ability to parse user input
27
+ * DONE add --dry-run to screwcap
28
+
29
+ == RECIPES:
30
+
31
+ === I need help writing rails recipes! Please help contribute some nice rock-solid rails recipes to the following repo:
32
+
33
+ http://github.com/gammons/screwcap_recipes
34
+
35
+
23
36
  == LICENSE:
24
37
 
25
38
  (The MIT License)
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ Hoe.plugin :newgem
11
11
  # Generate all the Rake tasks
12
12
  # Run 'rake -T' to see list of generated tasks (from gem root directory)
13
13
  $hoe = Hoe.spec 'screwcap' do
14
- self.version = '0.3.5'
14
+ self.version = '0.5'
15
15
  self.developer 'Grant Ammons', 'grant@pipelinedeals.com'
16
16
  self.rubyforge_name = self.name # TODO this is default value
17
17
  self.extra_deps = [['net-ssh','>= 2.0.23'],['net-ssh-gateway','>=1.0.1'], ['net-scp','>=1.0.4']]
data/bin/screwcap CHANGED
@@ -4,14 +4,14 @@ require 'rubygems'
4
4
  require File.expand_path(File.dirname(__FILE__) + '/../lib/screwcap')
5
5
  require File.expand_path(File.dirname(__FILE__) + '/../lib/trollop')
6
6
 
7
-
8
7
  p = Trollop::Parser.new do
9
- opt :silent, "Be silent"
8
+ opt :silent, "Be silent", :short => "s"
10
9
  opt :nocolor, "Do not color output"
11
- opt :debug, "Turn on debugger. Will print full stacktrace if an exeception was raised"
12
- opt :help, "Show this message"
13
- opt :tasks, "Display available tasks in recipe file"
14
- opt :setup_rails, "Setup a rails app to use screwcap"
10
+ opt :debug, "Turn on debugger. Will print full stacktrace if an exeception was raised", :short => "d"
11
+ opt :help, "Show this message", :short => "h"
12
+ opt :tasks, "Display available tasks in recipe file", :short => "t"
13
+ opt :setup_rails, "Setup a rails app to use screwcap", :short => "r"
14
+ opt :dry_run, "Setup a rails app to use screwcap", :short => "n"
15
15
  version <<-EOF
16
16
  Screwcap #{Screwcap::VERSION} by Grant Ammons (grant@pipelinedeals.com)
17
17
  More info at http://gammons.github.com/screwcap
@@ -31,7 +31,7 @@ opts = Trollop::with_standard_exception_handling p do
31
31
 
32
32
  if opts[:tasks] == true
33
33
  recipe_file = ARGV.shift
34
- deployer = Deployer.new(opts.merge(:recipe_file => recipe_file))
34
+ deployer = TaskManager.new(opts.merge(:recipe_file => recipe_file))
35
35
  $stdout << "Tasks Available:\n" if deployer.__tasks.size > 0
36
36
  deployer.__tasks.map {|t| t.__name }.each {|name| $stdout << " #{name}\n" }
37
37
  $stdout << "Sequences Available:\n" if deployer.__sequences.size > 0
@@ -39,8 +39,23 @@ opts = Trollop::with_standard_exception_handling p do
39
39
  exit
40
40
  end
41
41
 
42
+ if opts[:dry_run] == true
43
+ recipe_file = ARGV.shift
44
+ deployer = TaskManager.new(opts.merge(:recipe_file => recipe_file))
45
+ ARGV.map {|a| a.to_sym }.each do |tname|
46
+ task = deployer.__tasks.find {|t| t.__name == tname }
47
+ raise(Screwcap::TaskNotFound, "Could not find task named '#{tname}' in the recipe file.") if task.nil?
48
+ $stdout << "\n*** BEGIN dry-run task #{task.__name}\n"
49
+ task.__commands.each do |cmd|
50
+ $stdout << " I: (dry-run): #{cmd[:command]}\n"
51
+ end
52
+ $stdout << "*** END dry-run task #{task.__name}\n"
53
+ end
54
+ exit
55
+ end
56
+
42
57
  if opts[:setup_rails] == true
43
- Deployer.new(:recipe_file => File.expand_path(File.dirname(__FILE__) + "/../recipes/setup_rails.rb")).run! :setup_rails
58
+ TaskManager.new(:recipe_file => File.expand_path(File.dirname(__FILE__) + "/../recipes/setup_rails.rb")).run! :setup_rails
44
59
  $stdout << "\nYour rails app now has a sample recipe, ready for the editing, in config/screwcap/recipe.rb\n"
45
60
  $stdout << "Your recipes will be automatically available in rake. Screwcap uses the :remote namespace.\n"
46
61
  $stdout << "To see what recipes you can run, type 'rake -T remote'.\n"
@@ -51,7 +66,7 @@ opts = Trollop::with_standard_exception_handling p do
51
66
  raise Trollop::HelpNeeded if ARGV.size < 2
52
67
  recipe_file = ARGV.shift
53
68
  begin
54
- Deployer.new(opts.merge(:recipe_file => recipe_file)).run! ARGV.map {|a| a.to_sym }
69
+ TaskManager.new(opts.merge(:recipe_file => recipe_file)).run! ARGV.map {|a| a.to_sym }
55
70
  rescue Exception => e
56
71
  raise e if opts[:debug] == true
57
72
  $stderr << e
data/lib/screwcap.rb CHANGED
@@ -13,10 +13,10 @@ require 'screwcap/task'
13
13
  require 'screwcap/server'
14
14
  require 'screwcap/runner'
15
15
  require 'screwcap/sequence'
16
- require 'screwcap/deployer'
16
+ require 'screwcap/task_manager'
17
17
 
18
18
  module Screwcap
19
- VERSION='0.3.5'
19
+ VERSION='0.5'
20
20
 
21
21
  class TaskNotFound < RuntimeError; end
22
22
  class NoServersDefined < Exception; end
data/lib/screwcap/base.rb CHANGED
@@ -3,5 +3,9 @@ module Screwcap
3
3
  def set(var, *args)
4
4
  method_missing((var.to_s + "=").to_sym, args.first)
5
5
  end
6
+
7
+ def clone_from(object)
8
+ object.table.each {|k,v| self.set(k,v) unless k.to_s[0..1] == "__" }
9
+ end
6
10
  end
7
11
  end
@@ -16,7 +16,7 @@ module MessageLogger
16
16
 
17
17
  def logmsg(msg, output, options)
18
18
  return if @options and @options[:silent] == true
19
- if @options and not @options[:nocolor] == true
19
+ unless (@options and @options[:nocolor] == true)
20
20
  case options[:color]
21
21
  when :blue
22
22
  output << "\033[0;36m#{msg}#{"\033[0m" if options[:clear]}"
@@ -1,63 +1,76 @@
1
1
  class Runner
2
2
  include MessageLogger
3
+ @@silent = false
3
4
 
4
- def self.execute! task, options
5
- @task = task; @options = options
6
- threads = []
7
- if @task.__options[:local] == true
8
- log "\n*** BEGIN executing local task #{@task.__name}\n", :color => :blue
9
- @task.__commands.each do |command|
10
- ret = `#{command[:command]}`
11
- if $?.to_i == 0
12
- log " I: (local): #{command[:command]}\n", :color => :blue
13
- log(" O: (local): #{ret}\n", :color => :blue) unless ret.nil? or ret == ""
14
- else
15
- log " I: (local): #{command[:command]}\n", :color => :blue
16
- errorlog(" O: (local): #{ret}\n", :color => :red) unless ret.nil? or ret == ""
17
- errorlog(" E: (local): #{command[:command]} return exit code: #{$?}\n", :color => :red) if $? != 0
5
+ def self.execute! options
6
+ @@silent = options[:silent]
7
+ begin
8
+ _log "\n*** BEGIN executing task #{options[:name]} on #{options[:server].name} with address #{options[:address]}\n", :color => :blue unless options[:silent] == true
9
+ options[:server].__with_connection_for(options[:address]) do |ssh|
10
+ options[:commands].each do |command|
11
+ ret = run_command(command, ssh, options)
12
+ break if ret != 0 and command[:abort_on_fail] == true
18
13
  end
19
14
  end
20
- log "\n*** END executing local task #{@task.__name}\n", :color => :blue
21
- else
22
- @task.__servers.each do |_server|
23
- _server.__addresses.each do |_address|
24
- if task.__options[:parallel] == false
25
- execute_on(_server, _address)
26
- else
27
- threads << Thread.new(_server, _address) { |server, address| execute_on(server, address) }
28
- end
29
- end
15
+ rescue Net::SSH::AuthenticationFailed => e
16
+ raise Net::SSH::AuthenticationFailed, "Authentication failed for server named #{server.name}. Please check your authentication credentials."
17
+ #rescue Exception => e
18
+ # _errorlog " F: (#{options[:address]}): #{e}", :color => :red
19
+ ensure
20
+ _log "*** END executing task #{options[:name]} on #{options[:server].name} with address #{options[:address]}\n\n", :color => :blue
21
+ end
22
+ options[:commands] # for tests
23
+ end
24
+
25
+ def self.execute_locally! options
26
+ @@silent = options[:silent]
27
+ _log "\n*** BEGIN executing local task #{options[:name]}\n", :color => :blue
28
+ options[:commands].each do |command|
29
+ command[:stdout] = ret = `#{command[:command]}`
30
+
31
+ if $?.to_i == 0
32
+ _log " I: (local): #{command[:command]}\n", :color => :blue
33
+ _log(" O: (local): #{ret}\n", :color => :blue) unless ret.nil? or ret == ""
34
+ else
35
+ _log " I: (local): #{command[:command]}\n", :color => :blue
36
+ _errorlog(" O: (local): #{ret}\n", :color => :red) unless ret.nil? or ret == ""
37
+ _errorlog(" E: (local): #{command[:command]} return exit code: #{$?}\n", :color => :red) if $? != 0
30
38
  end
31
39
  end
32
- threads.each {|t| t.join }
40
+ _log "\n*** END executing local task #{options[:name]}\n", :color => :blue
41
+ options[:commands]
33
42
  end
34
43
 
35
44
  private
36
45
 
37
- def self.execute_on(server, address)
38
- begin
39
- log "\n*** BEGIN executing task #{@task.__name} on #{server.name} with address #{address}\n", :color => :blue
40
-
41
- server.__with_connection_for(address) do |ssh|
42
- failed_command = nil
43
- @task.__commands.each do |command|
44
- if run_command(ssh, address, server, command) != 0 and command[:onfailure]
45
- failed_command = command
46
- break
47
- end
48
- end
49
- if failed_command
50
- @task.__commands = []
51
- @task.send(failed_command[:onfailure])
52
- @task.__commands.each { |command| run_command(ssh, address, server, command) }
53
- end
46
+ def self.run_command(command, ssh, options)
47
+ if command[:type] == :remote
48
+ _log " I: (#{options[:address]}): #{command[:command]}\n", :color => :green
49
+ stdout, stderr, exit_code, exit_signal = ssh_exec! ssh, command[:command]
50
+ command[:stdout] = stdout
51
+ command[:stderr] = stderr
52
+ _log(" O: (#{options[:address]}): #{stdout}", :color => :green) unless stdout.nil? or stdout == ""
53
+ _errorlog(" O: (#{options[:address]}): #{stderr}", :color => :red) unless stderr.nil? or stderr == ""
54
+ _errorlog(" E: (#{options[:address]}): #{command[:command]} return exit code: #{exit_code}\n", :color => :red) if exit_code != 0
55
+ return exit_code
56
+ elsif command[:type] == :local
57
+ ret = `#{command[:command]}`
58
+ command[:stdout] = ret
59
+ if $?.to_i == 0
60
+ _log " I: (local): #{command[:command]}\n", :color => :blue
61
+ _log " O: (local): #{ret}\n", :color => :blue
62
+ else
63
+ _log " I: (local): #{command[:command]}\n", :color => :blue
64
+ _errorlog " O: (local): #{ret}\n", :color => :red
65
+ _errorlog(" E: (local): #{command[:command]} return exit code: #{$?}\n", :color => :red) if $? != 0
54
66
  end
55
- rescue Net::SSH::AuthenticationFailed => e
56
- raise Net::SSH::AuthenticationFailed, "Authentication failed for server named #{server.name}. Please check your authentication credentials."
57
- rescue Exception => e
58
- errorlog " F: (#{address}): #{e}", :color => :red
59
- ensure
60
- log "*** END executing task #{@task.__name} on #{server.name} with address #{address}\n\n", :color => :blue
67
+ return $?
68
+ elsif command[:type] == :scp
69
+ _log " I: (#{options[:address]}): SCP #{command[:local]} to #{options[:server].__user}@#{options[:address]}:#{command[:remote]}\n", :color => :green
70
+ options[:server].__upload_to!(options[:address], command[:local], command[:remote])
71
+
72
+ # this will need to be improved to allow for :onfailure
73
+ return 0
61
74
  end
62
75
  end
63
76
 
@@ -91,33 +104,13 @@ class Runner
91
104
  [stdout_data, stderr_data, exit_code, exit_signal]
92
105
  end
93
106
 
107
+ def self._log(message, options)
108
+ return if @@silent == true
109
+ log(message, options)
110
+ end
94
111
 
95
- def self.run_command(ssh, address, server, command)
96
- if command[:type] == :remote
97
- log " I: (#{address}): #{command[:command]}\n", :color => :green
98
- stdout, stderr, exit_code, exit_signal = ssh_exec! ssh, command[:command]
99
- log(" O: (#{address}): #{stdout}", :color => :green) unless stdout.nil? or stdout == ""
100
- errorlog(" O: (#{address}): #{stderr}", :color => :red) unless stderr.nil? or stderr == ""
101
- errorlog(" E: (#{address}): #{command[:command]} return exit code: #{exit_code}\n", :color => :red) if exit_code != 0
102
- return exit_code
103
- elsif command[:type] == :local
104
- ret = `#{command[:command]}`
105
- if $?.to_i == 0
106
- log " I: (local): #{command[:command]}\n", :color => :blue
107
- log " O: (local): #{ret}\n", :color => :blue
108
- else
109
- log " I: (local): #{command[:command]}\n", :color => :blue
110
- errorlog " O: (local): #{ret}\n", :color => :red
111
- errorlog(" E: (local): #{command[:command]} return exit code: #{$?}\n", :color => :red) if $? != 0
112
- end
113
- return $?
114
- elsif command[:type] == :scp
115
- log " I: (#{address}): SCP #{command[:local]} to #{server.__user}@#{address}:#{command[:remote]}\n", :color => :green
116
- server.__upload_to!(address, command[:local], command[:remote])
117
-
118
- # this will need to be improved to allow for :onfailure
119
- return 0
120
- end
121
-
112
+ def self._errorlog(message, options)
113
+ return if @@silent == true
114
+ errorlog(message, options)
122
115
  end
123
116
  end
@@ -23,16 +23,6 @@ class Sequence < Screwcap::Base
23
23
  super
24
24
  self.__options = opts
25
25
  self.__name = opts[:name]
26
- self.__deployment_task_names = opts[:deployment_task_names]
27
26
  self.__task_names = opts[:tasks]
28
- validate
29
- end
30
-
31
- private
32
-
33
- def validate
34
- self.__task_names.each do |tn|
35
- raise(Screwcap::ConfigurationError, "Could not find task #{tn} in the deployment recipe.") unless self.__deployment_task_names.include?(tn)
36
- end
37
27
  end
38
28
  end
@@ -13,11 +13,14 @@ class Server < Screwcap::Base
13
13
  # * *:password* specify the password to connect with. Not recommended. Use keys.
14
14
  def initialize(opts = {})
15
15
  super
16
+ self.__name = opts.delete(:name)
17
+ self.__user = opts.delete(:user)
18
+ self.__options[:keys] = [opts.delete(:key)] if opts[:key]
19
+
20
+ servers = opts.delete(:servers)
21
+ self.__gateway = servers.select {|s| s.__options[:is_gateway] == true }.find {|s| s.__name == opts[:gateway] } if servers
22
+
16
23
  self.__options = opts
17
- self.__name = opts[:name]
18
- self.__servers = opts[:servers]
19
- self.__user = opts[:user]
20
- self.__options[:keys] = [self.__options.delete(:key)] if self.__options[:key]
21
24
 
22
25
  if self.__options[:address] and self.__options[:addresses].nil?
23
26
  self.__addresses = [self.__options.delete(:address)]
@@ -31,9 +34,8 @@ class Server < Screwcap::Base
31
34
  end
32
35
 
33
36
  def __with_connection_for(address, &block)
34
- if self.__options[:gateway]
35
- gateway = self.__servers.select {|s| s.__options[:is_gateway] == true }.find {|s| s.__name == self.__options[:gateway] }
36
- gateway.__get_gateway_connection.ssh(address, self.__user, options_for_net_ssh) do |ssh|
37
+ if self.__gateway
38
+ __gateway.__get_gateway_connection.ssh(address, self.__user, options_for_net_ssh) do |ssh|
37
39
  yield ssh
38
40
  end
39
41
  else
data/lib/screwcap/task.rb CHANGED
@@ -3,21 +3,11 @@ class Task < Screwcap::Base
3
3
 
4
4
  def initialize(opts = {}, &block)
5
5
  super
6
- self.__name = opts[:name]
6
+ self.__name = opts.delete(:name)
7
7
  self.__options = opts
8
8
  self.__commands = []
9
- self.__command_sets = opts[:command_sets] || []
10
- self.__server_names = []
11
- self.__block = block if opts[:command_set] == true
12
-
13
-
14
- if opts[:server] and opts[:servers].nil?
15
- self.__server_names << opts[:server]
16
- else
17
- self.__server_names = opts[:servers]
18
- end
19
-
20
- validate(opts[:deployment_servers]) unless opts[:validate] == false or opts[:local] == true
9
+ self.__servers = opts.delete(:servers)
10
+ self.__block = block
21
11
  end
22
12
 
23
13
  # Run a command. This can either be a string, or a symbol that is the name of a command set to run.
@@ -45,8 +35,8 @@ class Task < Screwcap::Base
45
35
  # task_for :item, :servers => :server do
46
36
  # run "ls -l", :onfailure => :rollback
47
37
  # end
38
+
48
39
  def run arg, options = {}
49
-
50
40
  if arg.class == Symbol
51
41
  self.__commands << options.merge({:command => self.send(arg), :type => :remote, :from => self.__name})
52
42
  else
@@ -58,6 +48,7 @@ class Task < Screwcap::Base
58
48
  # task_for :item, :servers => :server do
59
49
  # scp :local => "/tmp/pirate_booty", :remote => "/mnt/app/config/booty.yml"
60
50
  # end
51
+
61
52
  def scp options = {}
62
53
  self.__commands << options.merge({:type => :scp})
63
54
  end
@@ -78,54 +69,68 @@ class Task < Screwcap::Base
78
69
  # task_for :item, :servers => :server do
79
70
  # local "herd_cats", :onfailure => :rollback
80
71
  # end
72
+
81
73
  def local arg, options = {}
82
74
  if arg.class == Symbol
83
75
  self.__commands << options.merge({:command => self.send(arg), :type => :local, :from => self.__name})
84
76
  else
85
77
  self.__commands << options.merge({:command => arg, :type => :local, :from => self.__name})
86
78
  end
87
- if failure_cmd = self.__commands.last[:onfailure]
88
- unless self.__command_sets.find {|cs| cs.__name == failure_cmd }
89
- raise ScrewCap::ConfigurationError, "Could not find failure command set named '#{failure_cmd}' for task '#{self.__name}'"
90
- end
91
- end
92
79
  end
93
80
 
94
- protected
81
+ def __build_commands(command_sets = [])
82
+ commands = []
95
83
 
96
- def method_missing(m, *args) # :nodoc
97
- if m.to_s[0..1] == "__" or [:run].include?(m) or m.to_s.reverse[0..0] == "="
98
- super(m, args.first)
99
- else
100
- if cs = self.__command_sets.find {|cs| cs.__name == m }
101
- # eval what is in the block
102
- clone_table_for(cs)
103
- cs.__commands = []
104
- cs.instance_eval(&cs.__block)
105
- self.__commands += cs.__commands
84
+ self.instance_eval(&self.__block)
85
+
86
+ # :before for before_ callback
87
+ if before = command_sets.find {|cs| cs.__name.to_s == "before_#{self.__name}" or cs.__name == self.__options[:before] } and before != self
88
+ before.clone_from(self)
89
+ commands << before.__build_commands(command_sets)
90
+ end
91
+
92
+ self.__commands.each do |command|
93
+ if command[:type] == :unknown
94
+ if cs = command_sets.find {|cs| cs.__name == command[:command] }
95
+ cs.clone_from(self)
96
+ commands << cs.__build_commands(command_sets)
97
+ else
98
+ raise(NoMethodError, "Cannot find task, command set, or other method named '#{command[:command]}'")
99
+ end
106
100
  else
107
- raise NoMethodError, "Undefined method '#{m.to_s}' for task :#{self.name.to_s}"
101
+ commands << command
108
102
  end
109
103
  end
110
- end
111
-
112
- private
113
104
 
114
- def clone_table_for(object)
115
- self.table.each do |k,v|
116
- object.set(k, v) unless [:__block, :__tasks, :__name, :__command_sets, :__commands, :__options].include?(k)
105
+ # :after for after_ callback
106
+ if after = command_sets.find {|cs| cs.__name.to_s == "after_#{self.__name}" or cs.__name == self.__options[:after] } and after != self
107
+ after.clone_from(self)
108
+ commands << after.__build_commands(command_sets)
117
109
  end
110
+
111
+ commands.flatten
118
112
  end
119
113
 
120
114
  def validate(servers)
121
- raise Screwcap::ConfigurationError, "Could not find a server to run this task on. Please specify :server => :servername or :servers => [:server1, :server2] in the task_for directive." if self.__server_names.nil? or self.__server_names == []
115
+ raise Screwcap::ConfigurationError, "Could not find a server to run this task on. Please specify :server => :servername or :servers => [:server1, :server2] in the task_for directive." if servers == [] or servers.nil?
122
116
 
123
- self.__server_names.each do |server_name|
124
- raise Screwcap::ConfigurationError, "Could not find a server to run this task on. Please specify :server => :servername or :servers => [:server1, :server2] in the task_for directive." unless servers.map(&:name).include?(server_name)
125
- end
117
+ # marshal :server into :servers
118
+ self.__servers = [self.__options.delete(:server)] if self.__options[:server]
119
+ self.__servers = [self.__servers] if self.__servers.class != Array
126
120
 
127
- # finally map the actual server objects via name
128
- self.__servers = self.__server_names.map {|name| servers.find {|s| s.name == name } }
121
+ server_names = servers.map {|s| s.__name }
122
+ self.__servers.each do |server_name|
123
+ raise Screwcap::ConfigurationError, "Could not find a server to run this task on. Please specify :server => :servername or :servers => [:server1, :server2] in the task_for directive." unless server_names.include?(server_name)
124
+ end
129
125
  end
130
126
 
127
+ private
128
+
129
+ def method_missing(m, *args) # :nodoc
130
+ if m.to_s[0..1] == "__" or [:run].include?(m) or m.to_s.reverse[0..0] == "="
131
+ super(m, args.first)
132
+ else
133
+ self.__commands << {:command => m, :type => :unknown, :from => self.__name}
134
+ end
135
+ end
131
136
  end