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.
- data/bin/mc-call-agent +54 -0
- data/bin/mco +27 -0
- data/lib/mcollective.rb +70 -0
- data/lib/mcollective/agents.rb +160 -0
- data/lib/mcollective/application.rb +354 -0
- data/lib/mcollective/applications.rb +145 -0
- data/lib/mcollective/client.rb +292 -0
- data/lib/mcollective/config.rb +202 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +24 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +86 -0
- data/lib/mcollective/log.rb +103 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +73 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +46 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +16 -0
- data/lib/mcollective/matcher/parser.rb +93 -0
- data/lib/mcollective/matcher/scanner.rb +123 -0
- data/lib/mcollective/message.rb +201 -0
- data/lib/mcollective/monkey_patches.rb +104 -0
- data/lib/mcollective/optionparser.rb +164 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +26 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
- data/lib/mcollective/registration.rb +16 -0
- data/lib/mcollective/registration/base.rb +75 -0
- data/lib/mcollective/rpc.rb +188 -0
- data/lib/mcollective/rpc/actionrunner.rb +142 -0
- data/lib/mcollective/rpc/agent.rb +441 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +793 -0
- data/lib/mcollective/rpc/ddl.rb +258 -0
- data/lib/mcollective/rpc/helpers.rb +339 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +61 -0
- data/lib/mcollective/rpc/request.rb +51 -0
- data/lib/mcollective/rpc/result.rb +41 -0
- data/lib/mcollective/rpc/stats.rb +185 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +237 -0
- data/lib/mcollective/shell.rb +87 -0
- data/lib/mcollective/ssl.rb +246 -0
- data/lib/mcollective/unix_daemon.rb +37 -0
- data/lib/mcollective/util.rb +274 -0
- data/lib/mcollective/vendor.rb +41 -0
- data/lib/mcollective/vendor/require_vendored.rb +2 -0
- data/lib/mcollective/windows_daemon.rb +25 -0
- data/spec/Rakefile +16 -0
- data/spec/fixtures/application/test.rb +7 -0
- data/spec/fixtures/test-cert.pem +15 -0
- data/spec/fixtures/test-private.pem +15 -0
- data/spec/fixtures/test-public.pem +6 -0
- data/spec/monkey_patches/instance_variable_defined.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/agents_spec.rb +280 -0
- data/spec/unit/application_spec.rb +636 -0
- data/spec/unit/applications_spec.rb +155 -0
- data/spec/unit/array.rb +30 -0
- data/spec/unit/config_spec.rb +148 -0
- data/spec/unit/facts/base_spec.rb +118 -0
- data/spec/unit/facts_spec.rb +39 -0
- data/spec/unit/log_spec.rb +71 -0
- data/spec/unit/logger/base_spec.rb +110 -0
- data/spec/unit/logger/syslog_logger_spec.rb +86 -0
- data/spec/unit/matcher/parser_spec.rb +106 -0
- data/spec/unit/matcher/scanner_spec.rb +71 -0
- data/spec/unit/message_spec.rb +401 -0
- data/spec/unit/optionparser_spec.rb +113 -0
- data/spec/unit/pluginmanager_spec.rb +173 -0
- data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
- data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
- data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
- data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
- data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
- data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
- data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
- data/spec/unit/registration/base_spec.rb +77 -0
- data/spec/unit/rpc/actionrunner_spec.rb +213 -0
- data/spec/unit/rpc/agent_spec.rb +155 -0
- data/spec/unit/rpc/client_spec.rb +523 -0
- data/spec/unit/rpc/ddl_spec.rb +388 -0
- data/spec/unit/rpc/helpers_spec.rb +55 -0
- data/spec/unit/rpc/reply_spec.rb +143 -0
- data/spec/unit/rpc/request_spec.rb +115 -0
- data/spec/unit/rpc/result_spec.rb +66 -0
- data/spec/unit/rpc/stats_spec.rb +288 -0
- data/spec/unit/runnerstats_spec.rb +40 -0
- data/spec/unit/security/base_spec.rb +279 -0
- data/spec/unit/shell_spec.rb +144 -0
- data/spec/unit/ssl_spec.rb +244 -0
- data/spec/unit/symbol.rb +11 -0
- data/spec/unit/unix_daemon.rb +41 -0
- data/spec/unit/util_spec.rb +342 -0
- data/spec/unit/vendor_spec.rb +34 -0
- data/spec/unit/windows_daemon.rb +43 -0
- data/spec/windows_spec.opts +1 -0
- 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
|
data/lib/mcollective.rb
ADDED
@@ -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
|