mcollective-client 1.3.3

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.
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