mcollective-client 1.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (103) hide show
  1. data/bin/mc-call-agent +54 -0
  2. data/bin/mco +27 -0
  3. data/lib/mcollective.rb +70 -0
  4. data/lib/mcollective/agents.rb +160 -0
  5. data/lib/mcollective/application.rb +354 -0
  6. data/lib/mcollective/applications.rb +145 -0
  7. data/lib/mcollective/client.rb +292 -0
  8. data/lib/mcollective/config.rb +202 -0
  9. data/lib/mcollective/connector.rb +18 -0
  10. data/lib/mcollective/connector/base.rb +24 -0
  11. data/lib/mcollective/facts.rb +39 -0
  12. data/lib/mcollective/facts/base.rb +86 -0
  13. data/lib/mcollective/log.rb +103 -0
  14. data/lib/mcollective/logger.rb +5 -0
  15. data/lib/mcollective/logger/base.rb +73 -0
  16. data/lib/mcollective/logger/console_logger.rb +61 -0
  17. data/lib/mcollective/logger/file_logger.rb +46 -0
  18. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  19. data/lib/mcollective/matcher.rb +16 -0
  20. data/lib/mcollective/matcher/parser.rb +93 -0
  21. data/lib/mcollective/matcher/scanner.rb +123 -0
  22. data/lib/mcollective/message.rb +201 -0
  23. data/lib/mcollective/monkey_patches.rb +104 -0
  24. data/lib/mcollective/optionparser.rb +164 -0
  25. data/lib/mcollective/pluginmanager.rb +180 -0
  26. data/lib/mcollective/pluginpackager.rb +26 -0
  27. data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
  28. data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
  29. data/lib/mcollective/registration.rb +16 -0
  30. data/lib/mcollective/registration/base.rb +75 -0
  31. data/lib/mcollective/rpc.rb +188 -0
  32. data/lib/mcollective/rpc/actionrunner.rb +142 -0
  33. data/lib/mcollective/rpc/agent.rb +441 -0
  34. data/lib/mcollective/rpc/audit.rb +38 -0
  35. data/lib/mcollective/rpc/client.rb +793 -0
  36. data/lib/mcollective/rpc/ddl.rb +258 -0
  37. data/lib/mcollective/rpc/helpers.rb +339 -0
  38. data/lib/mcollective/rpc/progress.rb +63 -0
  39. data/lib/mcollective/rpc/reply.rb +61 -0
  40. data/lib/mcollective/rpc/request.rb +51 -0
  41. data/lib/mcollective/rpc/result.rb +41 -0
  42. data/lib/mcollective/rpc/stats.rb +185 -0
  43. data/lib/mcollective/runnerstats.rb +90 -0
  44. data/lib/mcollective/security.rb +26 -0
  45. data/lib/mcollective/security/base.rb +237 -0
  46. data/lib/mcollective/shell.rb +87 -0
  47. data/lib/mcollective/ssl.rb +246 -0
  48. data/lib/mcollective/unix_daemon.rb +37 -0
  49. data/lib/mcollective/util.rb +274 -0
  50. data/lib/mcollective/vendor.rb +41 -0
  51. data/lib/mcollective/vendor/require_vendored.rb +2 -0
  52. data/lib/mcollective/windows_daemon.rb +25 -0
  53. data/spec/Rakefile +16 -0
  54. data/spec/fixtures/application/test.rb +7 -0
  55. data/spec/fixtures/test-cert.pem +15 -0
  56. data/spec/fixtures/test-private.pem +15 -0
  57. data/spec/fixtures/test-public.pem +6 -0
  58. data/spec/monkey_patches/instance_variable_defined.rb +7 -0
  59. data/spec/spec.opts +1 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/unit/agents_spec.rb +280 -0
  62. data/spec/unit/application_spec.rb +636 -0
  63. data/spec/unit/applications_spec.rb +155 -0
  64. data/spec/unit/array.rb +30 -0
  65. data/spec/unit/config_spec.rb +148 -0
  66. data/spec/unit/facts/base_spec.rb +118 -0
  67. data/spec/unit/facts_spec.rb +39 -0
  68. data/spec/unit/log_spec.rb +71 -0
  69. data/spec/unit/logger/base_spec.rb +110 -0
  70. data/spec/unit/logger/syslog_logger_spec.rb +86 -0
  71. data/spec/unit/matcher/parser_spec.rb +106 -0
  72. data/spec/unit/matcher/scanner_spec.rb +71 -0
  73. data/spec/unit/message_spec.rb +401 -0
  74. data/spec/unit/optionparser_spec.rb +113 -0
  75. data/spec/unit/pluginmanager_spec.rb +173 -0
  76. data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
  77. data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
  78. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
  79. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
  80. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
  81. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
  82. data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
  83. data/spec/unit/registration/base_spec.rb +77 -0
  84. data/spec/unit/rpc/actionrunner_spec.rb +213 -0
  85. data/spec/unit/rpc/agent_spec.rb +155 -0
  86. data/spec/unit/rpc/client_spec.rb +523 -0
  87. data/spec/unit/rpc/ddl_spec.rb +388 -0
  88. data/spec/unit/rpc/helpers_spec.rb +55 -0
  89. data/spec/unit/rpc/reply_spec.rb +143 -0
  90. data/spec/unit/rpc/request_spec.rb +115 -0
  91. data/spec/unit/rpc/result_spec.rb +66 -0
  92. data/spec/unit/rpc/stats_spec.rb +288 -0
  93. data/spec/unit/runnerstats_spec.rb +40 -0
  94. data/spec/unit/security/base_spec.rb +279 -0
  95. data/spec/unit/shell_spec.rb +144 -0
  96. data/spec/unit/ssl_spec.rb +244 -0
  97. data/spec/unit/symbol.rb +11 -0
  98. data/spec/unit/unix_daemon.rb +41 -0
  99. data/spec/unit/util_spec.rb +342 -0
  100. data/spec/unit/vendor_spec.rb +34 -0
  101. data/spec/unit/windows_daemon.rb +43 -0
  102. data/spec/windows_spec.opts +1 -0
  103. metadata +242 -0
data/bin/mc-call-agent ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mcollective'
4
+ require 'pp'
5
+
6
+ oparser = MCollective::Optionparser.new({:verbose => true}, "filter")
7
+
8
+ options = oparser.parse{|parser, options|
9
+ parser.define_head "Call an agent parsing an argument to it"
10
+ parser.banner = "Usage: mc-call-agent [options] --agent agent --argument arg"
11
+
12
+ parser.on('-a', '--agent AGENT', 'Agent to call') do |v|
13
+ options[:agent] = v
14
+ end
15
+
16
+ parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v|
17
+ options[:argument] = v
18
+ end
19
+ }
20
+
21
+ if options[:agent] == nil || options[:argument] == nil
22
+ puts("Please use either --agent or --argument")
23
+ exit 1
24
+ end
25
+
26
+ begin
27
+ options[:filter]["agent"] << options[:agent]
28
+
29
+ client = MCollective::Client.new(options[:config])
30
+ client.options = options
31
+
32
+ c = 0
33
+
34
+ stats = client.discovered_req(options[:argument], options[:agent]) do |resp|
35
+ next if resp == nil
36
+
37
+ c += 1
38
+
39
+ if options[:verbose]
40
+ puts("#{resp[:senderid]}>")
41
+ pp resp[:body]
42
+ else
43
+ puts if c % 4 == 1
44
+ printf("%-30s", resp[:senderid])
45
+ end
46
+ end
47
+
48
+ client.disconnect
49
+ rescue Exception => e
50
+ STDERR.puts "Could not call remote agent: #{e}"
51
+ exit 1
52
+ end
53
+
54
+ client.display_stats(stats)
data/bin/mco ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mcollective'
4
+
5
+ Version = MCollective.version
6
+ known_applications = MCollective::Applications.list
7
+
8
+ # links from mc-ping to mc will result in ping being run
9
+ if $0 =~ /mc\-([a-zA-Z\-_\.]+)$/
10
+ app_name = $1
11
+ else
12
+ app_name = ARGV.first
13
+ ARGV.delete_at(0)
14
+ end
15
+
16
+ if known_applications.include?(app_name)
17
+ # make sure the various options classes shows the right help etc
18
+ $0 = app_name
19
+
20
+ MCollective::Applications.run(app_name)
21
+ else
22
+ puts "The Marionette Collective version #{MCollective.version}"
23
+ puts
24
+ puts "#{$0}: command (options)"
25
+ puts
26
+ puts "Known commands: #{known_applications.join " "}"
27
+ end
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'stomp'
3
+ require 'timeout'
4
+ require 'digest/md5'
5
+ require 'optparse'
6
+ require 'singleton'
7
+ require 'socket'
8
+ require 'erb'
9
+ require 'shellwords'
10
+ require 'mcollective/monkey_patches'
11
+ require 'tempfile'
12
+ require 'rbconfig'
13
+ require 'tmpdir'
14
+
15
+ # == The Marionette Collective
16
+ #
17
+ # Framework to build and run Systems Administration agents running on a
18
+ # publish/subscribe middleware system. The system allows you to treat your
19
+ # network as the only true source of the state of your platform via discovery agents
20
+ # and allow you to run agents matching discovery criteria.
21
+ #
22
+ # For an overview of the idea behind this and what it enables please see:
23
+ # http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php
24
+ module MCollective
25
+ # Exceptions for the RPC system
26
+ class RPCError<StandardError;end
27
+ class RPCAborted<RPCError;end
28
+ class UnknownRPCAction<RPCError;end
29
+ class MissingRPCData<RPCError;end
30
+ class InvalidRPCData<RPCError;end
31
+ class UnknownRPCError<RPCError;end
32
+ class NotTargettedAtUs<RuntimeError;end
33
+ class SecurityValidationFailed<RuntimeError;end
34
+ class DDLValidationError<RuntimeError;end
35
+ class MsgTTLExpired<RuntimeError;end
36
+ class MsgDoesNotMatchRequestID < RuntimeError; end
37
+
38
+
39
+ autoload :Config, "mcollective/config"
40
+ autoload :Log, "mcollective/log"
41
+ autoload :Logger, "mcollective/logger"
42
+ autoload :Runner, "mcollective/runner"
43
+ autoload :RunnerStats, "mcollective/runnerstats"
44
+ autoload :Agents, "mcollective/agents"
45
+ autoload :Client, "mcollective/client"
46
+ autoload :Util, "mcollective/util"
47
+ autoload :Optionparser, "mcollective/optionparser"
48
+ autoload :Connector, "mcollective/connector"
49
+ autoload :Security, "mcollective/security"
50
+ autoload :Facts, "mcollective/facts"
51
+ autoload :Registration, "mcollective/registration"
52
+ autoload :PluginManager, "mcollective/pluginmanager"
53
+ autoload :RPC, "mcollective/rpc"
54
+ autoload :Matcher, "mcollective/matcher"
55
+ autoload :Message, "mcollective/message"
56
+ autoload :SSL, "mcollective/ssl"
57
+ autoload :Application, "mcollective/application"
58
+ autoload :Applications, "mcollective/applications"
59
+ autoload :Vendor, "mcollective/vendor"
60
+ autoload :Shell, "mcollective/shell"
61
+ autoload :PluginPackager, "mcollective/pluginpackager"
62
+
63
+ MCollective::Vendor.load_vendored
64
+
65
+ VERSION="@DEVELOPMENT_VERSION@"
66
+
67
+ def self.version
68
+ VERSION
69
+ end
70
+ end
@@ -0,0 +1,160 @@
1
+ module MCollective
2
+ # A collection of agents, loads them, reloads them and dispatches messages to them.
3
+ # It uses the PluginManager to store, load and manage instances of plugins.
4
+ class Agents
5
+ def initialize(agents = {})
6
+ @config = Config.instance
7
+ raise ("Configuration has not been loaded, can't load agents") unless @config.configured
8
+
9
+ @@agents = agents
10
+
11
+ loadagents
12
+ end
13
+
14
+ # Deletes all agents
15
+ def clear!
16
+ @@agents.each_key do |agent|
17
+ PluginManager.delete "#{agent}_agent"
18
+ Util.unsubscribe(Util.make_subscriptions(agent, :broadcast))
19
+ end
20
+
21
+ @@agents = {}
22
+ end
23
+
24
+ # Loads all agents from disk
25
+ def loadagents
26
+ Log.debug("Reloading all agents from disk")
27
+
28
+ clear!
29
+
30
+ @config.libdir.each do |libdir|
31
+ agentdir = "#{libdir}/mcollective/agent"
32
+ next unless File.directory?(agentdir)
33
+
34
+ Dir.new(agentdir).grep(/\.rb$/).each do |agent|
35
+ agentname = File.basename(agent, ".rb")
36
+ loadagent(agentname) unless PluginManager.include?("#{agentname}_agent")
37
+ end
38
+ end
39
+ end
40
+
41
+ # Loads a specified agent from disk if available
42
+ def loadagent(agentname)
43
+ agentfile = findagentfile(agentname)
44
+ return false unless agentfile
45
+ classname = class_for_agent(agentname)
46
+
47
+ PluginManager.delete("#{agentname}_agent")
48
+
49
+ begin
50
+ single_instance = ["registration", "discovery"].include?(agentname)
51
+
52
+ PluginManager.loadclass(classname)
53
+
54
+ if activate_agent?(agentname)
55
+ PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance}
56
+
57
+ Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname)
58
+
59
+ @@agents[agentname] = {:file => agentfile}
60
+ return true
61
+ else
62
+ Log.debug("Not activating agent #{agentname} due to agent policy in activate? method")
63
+ return false
64
+ end
65
+ rescue Exception => e
66
+ Log.error("Loading agent #{agentname} failed: #{e}")
67
+ PluginManager.delete("#{agentname}_agent")
68
+ return false
69
+ end
70
+ end
71
+
72
+ # Builds a class name string given a Agent name
73
+ def class_for_agent(agent)
74
+ "MCollective::Agent::#{agent.capitalize}"
75
+ end
76
+
77
+ # Checks if a plugin should be activated by
78
+ # calling #activate? on it if it responds to
79
+ # that method else always activate it
80
+ def activate_agent?(agent)
81
+ klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize)
82
+
83
+ if klass.respond_to?("activate?")
84
+ return klass.activate?
85
+ else
86
+ Log.debug("#{klass} does not have an activate? method, activating as default")
87
+ return true
88
+ end
89
+ rescue Exception => e
90
+ Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}")
91
+ return false
92
+ end
93
+
94
+ # searches the libdirs for agents
95
+ def findagentfile(agentname)
96
+ @config.libdir.each do |libdir|
97
+ agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"])
98
+ if File.exist?(agentfile)
99
+ Log.debug("Found #{agentname} at #{agentfile}")
100
+ return agentfile
101
+ end
102
+ end
103
+ return false
104
+ end
105
+
106
+ # Determines if we have an agent with a certain name
107
+ def include?(agentname)
108
+ PluginManager.include?("#{agentname}_agent")
109
+ end
110
+
111
+ # Returns the help for an agent after first trying to get
112
+ # rid of some indentation infront
113
+ def help(agentname)
114
+ raise("No such agent") unless include?(agentname)
115
+
116
+ body = PluginManager["#{agentname}_agent"].help.split("\n")
117
+
118
+ if body.first =~ /^(\s+)\S/
119
+ indent = $1
120
+
121
+ body = body.map {|b| b.gsub(/^#{indent}/, "")}
122
+ end
123
+
124
+ body.join("\n")
125
+ end
126
+
127
+ # Dispatches a message to an agent, accepts a block that will get run if there are
128
+ # any replies to process from the agent
129
+ def dispatch(request, connection)
130
+ Log.debug("Dispatching a message to agent #{request.agent}")
131
+
132
+ Thread.new do
133
+ begin
134
+ agent = PluginManager["#{request.agent}_agent"]
135
+
136
+ Timeout::timeout(agent.timeout) do
137
+ replies = agent.handlemsg(request.payload, connection)
138
+
139
+ # Agents can decide if they wish to reply or not,
140
+ # returning nil will mean nothing goes back to the
141
+ # requestor
142
+ unless replies == nil
143
+ yield(replies)
144
+ end
145
+ end
146
+ rescue Timeout::Error => e
147
+ Log.warn("Timeout while handling message for #{request.agent}")
148
+ rescue Exception => e
149
+ Log.error("Execution of #{request.agent} failed: #{e}")
150
+ Log.error(e.backtrace.join("\n\t\t"))
151
+ end
152
+ end
153
+ end
154
+
155
+ # Get a list of agents that we have
156
+ def self.agentlist
157
+ @@agents.keys
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,354 @@
1
+ module MCollective
2
+ class Application
3
+ include RPC
4
+
5
+ class << self
6
+ # Intialize a blank set of options if its the first time used
7
+ # else returns active options
8
+ def application_options
9
+ intialize_application_options unless @application_options
10
+ @application_options
11
+ end
12
+
13
+ # set an option in the options hash
14
+ def []=(option, value)
15
+ intialize_application_options unless @application_options
16
+ @application_options[option] = value
17
+ end
18
+
19
+ # retrieves a specific option
20
+ def [](option)
21
+ intialize_application_options unless @application_options
22
+ @application_options[option]
23
+ end
24
+
25
+ # Sets the application description, there can be only one
26
+ # description per application so multiple calls will just
27
+ # change the description
28
+ def description(descr)
29
+ self[:description] = descr
30
+ end
31
+
32
+ # Supplies usage information, calling multiple times will
33
+ # create multiple usage lines in --help output
34
+ def usage(usage)
35
+ self[:usage] << usage
36
+ end
37
+
38
+ def exclude_argument_sections(*sections)
39
+ sections = [sections].flatten
40
+
41
+ sections.each do |s|
42
+ raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s)
43
+ end
44
+
45
+ intialize_application_options unless @application_options
46
+ self[:exclude_arg_sections] = sections
47
+ end
48
+
49
+ # Wrapper to create command line options
50
+ #
51
+ # - name: varaible name that will be used to access the option value
52
+ # - description: textual info shown in --help
53
+ # - arguments: a list of possible arguments that can be used
54
+ # to activate this option
55
+ # - type: a data type that ObjectParser understand of :bool or :array
56
+ # - required: true or false if this option has to be supplied
57
+ # - validate: a proc that will be called with the value used to validate
58
+ # the supplied value
59
+ #
60
+ # option :foo,
61
+ # :description => "The foo option"
62
+ # :arguments => ["--foo ARG"]
63
+ #
64
+ # after this the value supplied will be in configuration[:foo]
65
+ def option(name, arguments)
66
+ opt = {:name => name,
67
+ :description => nil,
68
+ :arguments => [],
69
+ :type => String,
70
+ :required => false,
71
+ :validate => Proc.new { true }}
72
+
73
+ arguments.each_pair{|k,v| opt[k] = v}
74
+
75
+ self[:cli_arguments] << opt
76
+ end
77
+
78
+ # Creates an empty set of options
79
+ def intialize_application_options
80
+ @application_options = {:description => nil,
81
+ :usage => [],
82
+ :cli_arguments => [],
83
+ :exclude_arg_sections => []}
84
+ end
85
+ end
86
+
87
+ # The application configuration built from CLI arguments
88
+ def configuration
89
+ @application_configuration ||= {}
90
+ @application_configuration
91
+ end
92
+
93
+ # The active options hash used for MC::Client and other configuration
94
+ def options
95
+ @options
96
+ end
97
+
98
+ # Calls the supplied block in an option for validation, an error raised
99
+ # will log to STDERR and exit the application
100
+ def validate_option(blk, name, value)
101
+ validation_result = blk.call(value)
102
+
103
+ unless validation_result == true
104
+ STDERR.puts "Validation of #{name} failed: #{validation_result}"
105
+ exit 1
106
+ end
107
+ end
108
+
109
+ # Creates a standard options hash, pass in a block to add extra headings etc
110
+ # see Optionparser
111
+ def clioptions(help)
112
+ oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections])
113
+
114
+ options = oparser.parse do |parser, options|
115
+ if block_given?
116
+ yield(parser, options)
117
+ end
118
+
119
+ RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc")
120
+ end
121
+
122
+ return oparser.parser.help if help
123
+
124
+ validate_cli_options
125
+
126
+ post_option_parser(configuration) if respond_to?(:post_option_parser)
127
+
128
+ return options
129
+ rescue Exception => e
130
+ application_failure(e)
131
+ end
132
+
133
+ # Builds an ObjectParser config, parse the CLI options and validates based
134
+ # on the option config
135
+ def application_parse_options(help=false)
136
+ @options ||= {:verbose => false}
137
+
138
+ @options = clioptions(help) do |parser, options|
139
+ parser.define_head application_description if application_description
140
+ parser.banner = ""
141
+
142
+ if application_usage
143
+ parser.separator ""
144
+
145
+ application_usage.each do |u|
146
+ parser.separator "Usage: #{u}"
147
+ end
148
+
149
+ parser.separator ""
150
+ end
151
+
152
+ parser.define_tail ""
153
+ parser.define_tail "The Marionette Collective #{MCollective.version}"
154
+
155
+
156
+ application_cli_arguments.each do |carg|
157
+ opts_array = []
158
+
159
+ opts_array << :on
160
+
161
+ # if a default is set from the application set it up front
162
+ if carg.include?(:default)
163
+ configuration[carg[:name]] = carg[:default]
164
+ end
165
+
166
+ # :arguments are multiple possible ones
167
+ if carg[:arguments].is_a?(Array)
168
+ carg[:arguments].each {|a| opts_array << a}
169
+ else
170
+ opts_array << carg[:arguments]
171
+ end
172
+
173
+ # type was given and its not one of our special types, just pass it onto optparse
174
+ opts_array << carg[:type] if carg[:type] and ! [:bool, :array].include?(carg[:type])
175
+
176
+ opts_array << carg[:description]
177
+
178
+ # Handle our special types else just rely on the optparser to handle the types
179
+ if carg[:type] == :bool
180
+ parser.send(*opts_array) do |v|
181
+ validate_option(carg[:validate], carg[:name], v)
182
+
183
+ configuration[carg[:name]] = true
184
+ end
185
+
186
+ elsif carg[:type] == :array
187
+ parser.send(*opts_array) do |v|
188
+ validate_option(carg[:validate], carg[:name], v)
189
+
190
+ configuration[carg[:name]] = [] unless configuration.include?(carg[:name])
191
+ configuration[carg[:name]] << v
192
+ end
193
+
194
+ else
195
+ parser.send(*opts_array) do |v|
196
+ validate_option(carg[:validate], carg[:name], v)
197
+
198
+ configuration[carg[:name]] = v
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ def validate_cli_options
206
+ # Check all required parameters were set
207
+ validation_passed = true
208
+ application_cli_arguments.each do |carg|
209
+ # Check for required arguments
210
+ if carg[:required]
211
+ unless configuration[ carg[:name] ]
212
+ validation_passed = false
213
+ STDERR.puts "The #{carg[:name]} option is mandatory"
214
+ end
215
+ end
216
+ end
217
+
218
+ unless validation_passed
219
+ STDERR.puts "\nPlease run with --help for detailed help"
220
+ exit 1
221
+ end
222
+
223
+
224
+ end
225
+
226
+ # Retrieves the full hash of application options
227
+ def application_options
228
+ self.class.application_options
229
+ end
230
+
231
+ # Retrieve the current application description
232
+ def application_description
233
+ application_options[:description]
234
+ end
235
+
236
+ # Return the current usage text false if nothing is set
237
+ def application_usage
238
+ usage = application_options[:usage]
239
+
240
+ usage.empty? ? false : usage
241
+ end
242
+
243
+ # Returns an array of all the arguments built using
244
+ # calls to optin
245
+ def application_cli_arguments
246
+ application_options[:cli_arguments]
247
+ end
248
+
249
+ # Handles failure, if we're far enough in the initialization
250
+ # phase it will log backtraces if its in verbose mode only
251
+ def application_failure(e, err_dest=STDERR)
252
+ # peole can use exit() anywhere and not get nasty backtraces as a result
253
+ if e.is_a?(SystemExit)
254
+ disconnect
255
+ raise(e)
256
+ end
257
+
258
+ err_dest.puts "#{$0} failed to run: #{e} (#{e.class})"
259
+
260
+ if options.nil? || options[:verbose]
261
+ e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"}
262
+ end
263
+
264
+ disconnect
265
+
266
+ exit 1
267
+ end
268
+
269
+ def help
270
+ application_parse_options(true)
271
+ end
272
+
273
+ # The main logic loop, builds up the options, validate configuration and calls
274
+ # the main as supplied by the user. Disconnects when done and pass any exception
275
+ # onto the application_failure helper
276
+ def run
277
+ application_parse_options
278
+
279
+ validate_configuration(configuration) if respond_to?(:validate_configuration)
280
+
281
+ Util.setup_windows_sleeper if Util.windows?
282
+
283
+ main
284
+
285
+ disconnect
286
+
287
+ rescue Exception => e
288
+ application_failure(e)
289
+ end
290
+
291
+ def disconnect
292
+ MCollective::PluginManager["connector_plugin"].disconnect
293
+ rescue
294
+ end
295
+
296
+ # Fake abstract class that logs if the user tries to use an application without
297
+ # supplying a main override method.
298
+ def main
299
+ STDERR.puts "Applications need to supply a 'main' method"
300
+ exit 1
301
+ end
302
+
303
+ # A helper that creates a consistent exit code for applications by looking at an
304
+ # instance of MCollective::RPC::Stats
305
+ #
306
+ # Exit with 0 if nodes were discovered and all passed
307
+ # Exit with 0 if no discovery were done and > 0 responses were received
308
+ # Exit with 1 if no nodes were discovered
309
+ # Exit with 2 if nodes were discovered but some RPC requests failed
310
+ # Exit with 3 if nodes were discovered, but not responses receivedif
311
+ # Exit with 4 if no discovery were done and no responses were received
312
+ def halt(stats)
313
+ request_stats = {:discoverytime => 0,
314
+ :discovered => 0,
315
+ :failcount => 0}.merge(stats.to_hash)
316
+
317
+ # was discovery done?
318
+ if request_stats[:discoverytime] != 0
319
+ # was any nodes discovered
320
+ if request_stats[:discovered] == 0
321
+ exit 1
322
+
323
+ # nodes were discovered, did we get responses
324
+ elsif request_stats[:responses] == 0
325
+ exit 3
326
+
327
+ else
328
+ # we got responses and discovery was done, no failures
329
+ if request_stats[:failcount] == 0
330
+ exit 0
331
+ else
332
+ exit 2
333
+ end
334
+ end
335
+ else
336
+ # discovery wasnt done and we got no responses
337
+ if request_stats[:responses] == 0
338
+ exit 4
339
+ else
340
+ exit 0
341
+ end
342
+ end
343
+ end
344
+
345
+ # Wrapper around MC::RPC#rpcclient that forcably supplies our options hash
346
+ # if someone forgets to pass in options in an application the filters and other
347
+ # cli options wouldnt take effect which could have a disasterous outcome
348
+ def rpcclient(agent, flags = {})
349
+ flags[:options] = options unless flags.include?(:options)
350
+
351
+ super
352
+ end
353
+ end
354
+ end