choria-mcorpc-support 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/mco +64 -0
- data/lib/mcollective.rb +63 -0
- data/lib/mcollective/agent.rb +5 -0
- data/lib/mcollective/agents.rb +149 -0
- data/lib/mcollective/aggregate.rb +85 -0
- data/lib/mcollective/aggregate/average.ddl +33 -0
- data/lib/mcollective/aggregate/average.rb +29 -0
- data/lib/mcollective/aggregate/base.rb +40 -0
- data/lib/mcollective/aggregate/result.rb +9 -0
- data/lib/mcollective/aggregate/result/base.rb +25 -0
- data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
- data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
- data/lib/mcollective/aggregate/sum.ddl +33 -0
- data/lib/mcollective/aggregate/sum.rb +18 -0
- data/lib/mcollective/aggregate/summary.ddl +33 -0
- data/lib/mcollective/aggregate/summary.rb +53 -0
- data/lib/mcollective/application.rb +365 -0
- data/lib/mcollective/application/completion.rb +104 -0
- data/lib/mcollective/application/describe_filter.rb +87 -0
- data/lib/mcollective/application/facts.rb +62 -0
- data/lib/mcollective/application/find.rb +23 -0
- data/lib/mcollective/application/help.rb +28 -0
- data/lib/mcollective/application/inventory.rb +344 -0
- data/lib/mcollective/application/ping.rb +82 -0
- data/lib/mcollective/application/plugin.rb +369 -0
- data/lib/mcollective/application/rpc.rb +111 -0
- data/lib/mcollective/applications.rb +134 -0
- data/lib/mcollective/cache.rb +145 -0
- data/lib/mcollective/client.rb +353 -0
- data/lib/mcollective/config.rb +245 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +26 -0
- data/lib/mcollective/data.rb +91 -0
- data/lib/mcollective/data/agent_data.ddl +22 -0
- data/lib/mcollective/data/agent_data.rb +17 -0
- data/lib/mcollective/data/base.rb +67 -0
- data/lib/mcollective/data/collective_data.ddl +20 -0
- data/lib/mcollective/data/collective_data.rb +9 -0
- data/lib/mcollective/data/fact_data.ddl +28 -0
- data/lib/mcollective/data/fact_data.rb +55 -0
- data/lib/mcollective/data/fstat_data.ddl +89 -0
- data/lib/mcollective/data/fstat_data.rb +56 -0
- data/lib/mcollective/data/result.rb +45 -0
- data/lib/mcollective/ddl.rb +113 -0
- data/lib/mcollective/ddl/agentddl.rb +253 -0
- data/lib/mcollective/ddl/base.rb +217 -0
- data/lib/mcollective/ddl/dataddl.rb +56 -0
- data/lib/mcollective/ddl/discoveryddl.rb +52 -0
- data/lib/mcollective/ddl/validatorddl.rb +6 -0
- data/lib/mcollective/discovery.rb +143 -0
- data/lib/mcollective/discovery/flatfile.ddl +11 -0
- data/lib/mcollective/discovery/flatfile.rb +48 -0
- data/lib/mcollective/discovery/mc.ddl +11 -0
- data/lib/mcollective/discovery/mc.rb +30 -0
- data/lib/mcollective/discovery/stdin.ddl +11 -0
- data/lib/mcollective/discovery/stdin.rb +68 -0
- data/lib/mcollective/exceptions.rb +28 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +100 -0
- data/lib/mcollective/facts/yaml_facts.rb +65 -0
- data/lib/mcollective/generators.rb +7 -0
- data/lib/mcollective/generators/agent_generator.rb +51 -0
- data/lib/mcollective/generators/base.rb +46 -0
- data/lib/mcollective/generators/data_generator.rb +51 -0
- data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
- data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
- data/lib/mcollective/generators/templates/ddl.erb +8 -0
- data/lib/mcollective/generators/templates/plugin.erb +7 -0
- data/lib/mcollective/log.rb +118 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +77 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +53 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +224 -0
- data/lib/mcollective/matcher/parser.rb +128 -0
- data/lib/mcollective/matcher/scanner.rb +241 -0
- data/lib/mcollective/message.rb +248 -0
- data/lib/mcollective/monkey_patches.rb +152 -0
- data/lib/mcollective/optionparser.rb +197 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +98 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
- data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
- data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
- data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
- data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
- data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
- data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
- data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
- data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
- data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
- data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
- data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
- data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
- data/lib/mcollective/registration/base.rb +91 -0
- data/lib/mcollective/rpc.rb +182 -0
- data/lib/mcollective/rpc/actionrunner.rb +158 -0
- data/lib/mcollective/rpc/agent.rb +374 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +1066 -0
- data/lib/mcollective/rpc/helpers.rb +321 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +87 -0
- data/lib/mcollective/rpc/request.rb +86 -0
- data/lib/mcollective/rpc/result.rb +90 -0
- data/lib/mcollective/rpc/stats.rb +294 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +244 -0
- data/lib/mcollective/shell.rb +126 -0
- data/lib/mcollective/ssl.rb +285 -0
- data/lib/mcollective/util.rb +579 -0
- data/lib/mcollective/validator.rb +85 -0
- data/lib/mcollective/validator/array_validator.ddl +7 -0
- data/lib/mcollective/validator/array_validator.rb +9 -0
- data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
- data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
- data/lib/mcollective/validator/length_validator.ddl +7 -0
- data/lib/mcollective/validator/length_validator.rb +11 -0
- data/lib/mcollective/validator/regex_validator.ddl +7 -0
- data/lib/mcollective/validator/regex_validator.rb +9 -0
- data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
- data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
- data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
- data/lib/mcollective/validator/typecheck_validator.rb +28 -0
- metadata +215 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
# Class to store stats about the mcollectived, it should live in the PluginManager
|
|
3
|
+
# so that agents etc can get hold of it and return the stats to callers
|
|
4
|
+
class RunnerStats
|
|
5
|
+
def initialize
|
|
6
|
+
@starttime = Time.now.to_i
|
|
7
|
+
@validated = 0
|
|
8
|
+
@unvalidated = 0
|
|
9
|
+
@filtered = 0
|
|
10
|
+
@passed = 0
|
|
11
|
+
@total = 0
|
|
12
|
+
@replies = 0
|
|
13
|
+
@ttlexpired = 0
|
|
14
|
+
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Records a message that failed TTL checks
|
|
19
|
+
def ttlexpired
|
|
20
|
+
Log.debug("Incrementing ttl expired stat")
|
|
21
|
+
@ttlexpired += 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Records a message that passed the filters
|
|
25
|
+
def passed
|
|
26
|
+
Log.debug("Incrementing passed stat")
|
|
27
|
+
@passed += 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Records a message that didnt pass the filters
|
|
31
|
+
def filtered
|
|
32
|
+
Log.debug("Incrementing filtered stat")
|
|
33
|
+
@filtered += 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Records a message that validated ok
|
|
37
|
+
def validated
|
|
38
|
+
Log.debug("Incrementing validated stat")
|
|
39
|
+
@validated += 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unvalidated
|
|
43
|
+
Log.debug("Incrementing unvalidated stat")
|
|
44
|
+
@unvalidated += 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Records receipt of a message
|
|
48
|
+
def received
|
|
49
|
+
Log.debug("Incrementing total stat")
|
|
50
|
+
@total += 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Records sending a message
|
|
54
|
+
def sent
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
Log.debug("Incrementing replies stat")
|
|
57
|
+
@replies += 1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns a hash with all stats
|
|
62
|
+
def to_hash
|
|
63
|
+
stats = {:validated => @validated,
|
|
64
|
+
:unvalidated => @unvalidated,
|
|
65
|
+
:passed => @passed,
|
|
66
|
+
:filtered => @filtered,
|
|
67
|
+
:starttime => @starttime,
|
|
68
|
+
:total => @total,
|
|
69
|
+
:ttlexpired => @ttlexpired,
|
|
70
|
+
:replies => @replies}
|
|
71
|
+
|
|
72
|
+
reply = {:stats => stats,
|
|
73
|
+
:threads => [],
|
|
74
|
+
:pid => Process.pid,
|
|
75
|
+
:times => {} }
|
|
76
|
+
|
|
77
|
+
::Process.times.each_pair{|k,v|
|
|
78
|
+
k = k.to_sym
|
|
79
|
+
reply[:times][k] = v
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Thread.list.each do |t|
|
|
83
|
+
reply[:threads] << "#{t.inspect}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
reply[:agents] = Agents.agentlist
|
|
87
|
+
reply
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
# Security is implimented using a module structure and installations
|
|
3
|
+
# can configure which module they want to use.
|
|
4
|
+
#
|
|
5
|
+
# Security modules deal with various aspects of authentication and authorization:
|
|
6
|
+
#
|
|
7
|
+
# - Determines if a filter excludes this host from dealing with a request
|
|
8
|
+
# - Serialization and Deserialization of messages
|
|
9
|
+
# - Validation of messages against keys, certificates or whatever the class choose to impliment
|
|
10
|
+
# - Encoding and Decoding of messages
|
|
11
|
+
#
|
|
12
|
+
# To impliment a new security class using SSL for example you would inherit from the base
|
|
13
|
+
# class and only impliment:
|
|
14
|
+
#
|
|
15
|
+
# - decodemsg
|
|
16
|
+
# - encodereply
|
|
17
|
+
# - encoderequest
|
|
18
|
+
# - validrequest?
|
|
19
|
+
#
|
|
20
|
+
# Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this
|
|
21
|
+
#
|
|
22
|
+
# Filtering can be extended by providing a new validate_filter? method.
|
|
23
|
+
module Security
|
|
24
|
+
require "mcollective/security/base"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
module Security
|
|
3
|
+
# This is a base class the other security modules should inherit from
|
|
4
|
+
# it handles statistics and validation of messages that should in most
|
|
5
|
+
# cases apply to all security models.
|
|
6
|
+
#
|
|
7
|
+
# To create your own security plugin you should provide a plugin that inherits
|
|
8
|
+
# from this and provides the following methods:
|
|
9
|
+
#
|
|
10
|
+
# decodemsg - Decodes a message that was received from the middleware
|
|
11
|
+
# encodereply - Encodes a reply message to a previous request message
|
|
12
|
+
# encoderequest - Encodes a new request message
|
|
13
|
+
# validrequest? - Validates a request received from the middleware
|
|
14
|
+
#
|
|
15
|
+
# Optionally if you are identifying users by some other means like certificate name
|
|
16
|
+
# you can provide your own callerid method that can provide the rest of the system
|
|
17
|
+
# with an id, and you would see this id being usable in SimpleRPC authorization methods
|
|
18
|
+
#
|
|
19
|
+
# The @initiated_by variable will be set to either :client or :node depending on
|
|
20
|
+
# who is using this plugin. This is to help security providers that operate in an
|
|
21
|
+
# asymetric mode like public/private key based systems.
|
|
22
|
+
#
|
|
23
|
+
# Specifics of each of these are a bit fluid and the interfaces for this is not
|
|
24
|
+
# set in stone yet, specifically the encode methods will be provided with a helper
|
|
25
|
+
# that takes care of encoding the core requirements. The best place to see how security
|
|
26
|
+
# works is by looking at the provided MCollective::Security::PSK plugin.
|
|
27
|
+
class Base
|
|
28
|
+
attr_reader :stats
|
|
29
|
+
attr_accessor :initiated_by
|
|
30
|
+
|
|
31
|
+
# Register plugins that inherits base
|
|
32
|
+
def self.inherited(klass)
|
|
33
|
+
PluginManager << {:type => "security_plugin", :class => klass.to_s}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Initializes configuration and logging as well as prepare a zero'd hash of stats
|
|
37
|
+
# various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample
|
|
38
|
+
def initialize
|
|
39
|
+
@config = Config.instance
|
|
40
|
+
@log = Log
|
|
41
|
+
@stats = PluginManager["global_stats"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Takes a Hash with a filter in it and validates it against host information.
|
|
45
|
+
#
|
|
46
|
+
# At present this supports filter matches against the following criteria:
|
|
47
|
+
#
|
|
48
|
+
# - puppet_class|cf_class - Presence of a configuration management class in
|
|
49
|
+
# the file configured with classesfile
|
|
50
|
+
# - agent - Presence of a MCollective agent with a supplied name
|
|
51
|
+
# - fact - The value of a fact avout this system
|
|
52
|
+
# - identity - the configured identity of the system
|
|
53
|
+
#
|
|
54
|
+
# TODO: Support REGEX and/or multiple filter keys to be AND'd
|
|
55
|
+
def validate_filter?(filter)
|
|
56
|
+
failed = 0
|
|
57
|
+
passed = 0
|
|
58
|
+
|
|
59
|
+
passed = 1 if Util.empty_filter?(filter)
|
|
60
|
+
|
|
61
|
+
filter.keys.each do |key|
|
|
62
|
+
case key
|
|
63
|
+
when /puppet_class|cf_class/
|
|
64
|
+
filter[key].each do |f|
|
|
65
|
+
Log.debug("Checking for class #{f}")
|
|
66
|
+
if Util.has_cf_class?(f) then
|
|
67
|
+
Log.debug("Passing based on configuration management class #{f}")
|
|
68
|
+
passed += 1
|
|
69
|
+
else
|
|
70
|
+
Log.debug("Failing based on configuration management class #{f}")
|
|
71
|
+
failed += 1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
when "compound"
|
|
76
|
+
filter[key].each do |compound|
|
|
77
|
+
result = false
|
|
78
|
+
truth_values = []
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
compound.each do |expression|
|
|
82
|
+
case expression.keys.first
|
|
83
|
+
when "statement"
|
|
84
|
+
truth_values << Matcher.eval_compound_statement(expression).to_s
|
|
85
|
+
when "fstatement"
|
|
86
|
+
truth_values << Matcher.eval_compound_fstatement(expression.values.first)
|
|
87
|
+
when "and"
|
|
88
|
+
truth_values << "&&"
|
|
89
|
+
when "or"
|
|
90
|
+
truth_values << "||"
|
|
91
|
+
when "("
|
|
92
|
+
truth_values << "("
|
|
93
|
+
when ")"
|
|
94
|
+
truth_values << ")"
|
|
95
|
+
when "not"
|
|
96
|
+
truth_values << "!"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
result = eval(truth_values.join(" "))
|
|
101
|
+
rescue DDLValidationError
|
|
102
|
+
result = false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if result
|
|
106
|
+
Log.debug("Passing based on class and fact composition")
|
|
107
|
+
passed +=1
|
|
108
|
+
else
|
|
109
|
+
Log.debug("Failing based on class and fact composition")
|
|
110
|
+
failed +=1
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
when "agent"
|
|
115
|
+
filter[key].each do |f|
|
|
116
|
+
if Util.has_agent?(f) || f == "mcollective"
|
|
117
|
+
Log.debug("Passing based on agent #{f}")
|
|
118
|
+
passed += 1
|
|
119
|
+
else
|
|
120
|
+
Log.debug("Failing based on agent #{f}")
|
|
121
|
+
failed += 1
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
when "fact"
|
|
126
|
+
filter[key].each do |f|
|
|
127
|
+
if Util.has_fact?(f[:fact], f[:value], f[:operator])
|
|
128
|
+
Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
|
|
129
|
+
passed += 1
|
|
130
|
+
else
|
|
131
|
+
Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
|
|
132
|
+
failed += 1
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
when "identity"
|
|
137
|
+
unless filter[key].empty?
|
|
138
|
+
# Identity filters should not be 'and' but 'or' as each node can only have one identity
|
|
139
|
+
matched = filter[key].select{|f| Util.has_identity?(f)}.size
|
|
140
|
+
|
|
141
|
+
if matched == 1
|
|
142
|
+
Log.debug("Passing based on identity")
|
|
143
|
+
passed += 1
|
|
144
|
+
else
|
|
145
|
+
Log.debug("Failed based on identity")
|
|
146
|
+
failed += 1
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if failed == 0 && passed > 0
|
|
153
|
+
Log.debug("Message passed the filter checks")
|
|
154
|
+
|
|
155
|
+
@stats.passed
|
|
156
|
+
|
|
157
|
+
return true
|
|
158
|
+
else
|
|
159
|
+
Log.debug("Message failed the filter checks")
|
|
160
|
+
|
|
161
|
+
@stats.filtered
|
|
162
|
+
|
|
163
|
+
return false
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def create_reply(reqid, agent, body)
|
|
168
|
+
Log.debug("Encoded a message for request #{reqid}")
|
|
169
|
+
|
|
170
|
+
{:senderid => @config.identity,
|
|
171
|
+
:requestid => reqid,
|
|
172
|
+
:senderagent => agent,
|
|
173
|
+
:msgtime => Time.now.utc.to_i,
|
|
174
|
+
:body => body}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
|
|
178
|
+
Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")
|
|
179
|
+
|
|
180
|
+
{:body => msg,
|
|
181
|
+
:senderid => @config.identity,
|
|
182
|
+
:requestid => reqid,
|
|
183
|
+
:filter => filter,
|
|
184
|
+
:collective => target_collective,
|
|
185
|
+
:agent => target_agent,
|
|
186
|
+
:callerid => callerid,
|
|
187
|
+
:ttl => ttl,
|
|
188
|
+
:msgtime => Time.now.utc.to_i}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Give a MC::Message instance and a message id this will figure out if you the incoming
|
|
192
|
+
# message id matches the one the Message object is expecting and raise if its not
|
|
193
|
+
#
|
|
194
|
+
# Mostly used by security plugins to figure out if they should do the hard work of decrypting
|
|
195
|
+
# etc messages that would only later on be ignored
|
|
196
|
+
def should_process_msg?(msg, msgid)
|
|
197
|
+
if msg.expected_msgid
|
|
198
|
+
unless msg.expected_msgid == msgid
|
|
199
|
+
msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
|
|
200
|
+
Log.debug msgtext
|
|
201
|
+
raise MsgDoesNotMatchRequestID, msgtext
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
true
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Validates a callerid. We do not want to allow things like \ and / in
|
|
209
|
+
# callerids since other plugins make assumptions that these are safe strings.
|
|
210
|
+
#
|
|
211
|
+
# callerids are generally in the form uid=123 or cert=foo etc so we do that
|
|
212
|
+
# here but security plugins could override this for some complex uses
|
|
213
|
+
def valid_callerid?(id)
|
|
214
|
+
!!id.match(/^[\w]+=[\w\.\-]+$/)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Returns a unique id for the caller, by default we just use the unix
|
|
218
|
+
# user id, security plugins can provide their own means of doing ids.
|
|
219
|
+
def callerid
|
|
220
|
+
"uid=#{Process.uid}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
|
224
|
+
def validrequest?(req)
|
|
225
|
+
Log.error("validrequest? is not implemented in #{self.class}")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
|
229
|
+
def encoderequest(sender, msg, filter={})
|
|
230
|
+
Log.error("encoderequest is not implemented in #{self.class}")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
|
234
|
+
def encodereply(sender, msg, requestcallerid=nil)
|
|
235
|
+
Log.error("encodereply is not implemented in #{self.class}")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
|
239
|
+
def decodemsg(msg)
|
|
240
|
+
Log.error("decodemsg is not implemented in #{self.class}")
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
# Wrapper around systemu that handles executing of system commands
|
|
3
|
+
# in a way that makes stdout, stderr and status available. Supports
|
|
4
|
+
# timeouts and sets a default sane environment.
|
|
5
|
+
#
|
|
6
|
+
# s = Shell.new("date", opts)
|
|
7
|
+
# s.runcommand
|
|
8
|
+
# puts s.stdout
|
|
9
|
+
# puts s.stderr
|
|
10
|
+
# puts s.status.exitstatus
|
|
11
|
+
#
|
|
12
|
+
# Options hash can have:
|
|
13
|
+
#
|
|
14
|
+
# cwd - the working directory the command will be run from
|
|
15
|
+
# stdin - a string that will be sent to stdin of the program
|
|
16
|
+
# stdout - a variable that will receive stdout, must support <<
|
|
17
|
+
# stderr - a variable that will receive stdin, must support <<
|
|
18
|
+
# environment - the shell environment, defaults to include LC_ALL=C
|
|
19
|
+
# set to nil to clear the environment even of LC_ALL
|
|
20
|
+
# timeout - a timeout in seconds after which the subprocess is killed,
|
|
21
|
+
# the special value :on_thread_exit kills the subprocess
|
|
22
|
+
# when the invoking thread (typically the agent) has ended
|
|
23
|
+
#
|
|
24
|
+
class Shell
|
|
25
|
+
attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd, :timeout
|
|
26
|
+
|
|
27
|
+
def initialize(command, options={})
|
|
28
|
+
@environment = {"LC_ALL" => "C"}
|
|
29
|
+
@command = command
|
|
30
|
+
@status = nil
|
|
31
|
+
@stdout = ""
|
|
32
|
+
@stderr = ""
|
|
33
|
+
@stdin = nil
|
|
34
|
+
@cwd = Dir.tmpdir
|
|
35
|
+
@timeout = nil
|
|
36
|
+
|
|
37
|
+
options.each do |opt, val|
|
|
38
|
+
case opt.to_s
|
|
39
|
+
when "stdout"
|
|
40
|
+
raise "stdout should support <<" unless val.respond_to?("<<")
|
|
41
|
+
@stdout = val
|
|
42
|
+
|
|
43
|
+
when "stderr"
|
|
44
|
+
raise "stderr should support <<" unless val.respond_to?("<<")
|
|
45
|
+
@stderr = val
|
|
46
|
+
|
|
47
|
+
when "stdin"
|
|
48
|
+
raise "stdin should be a String" unless val.is_a?(String)
|
|
49
|
+
@stdin = val
|
|
50
|
+
|
|
51
|
+
when "cwd"
|
|
52
|
+
raise "Directory #{val} does not exist" unless File.directory?(val)
|
|
53
|
+
@cwd = val
|
|
54
|
+
|
|
55
|
+
when "environment"
|
|
56
|
+
if val.nil?
|
|
57
|
+
@environment = {}
|
|
58
|
+
else
|
|
59
|
+
@environment.merge!(val.dup)
|
|
60
|
+
@environment = @environment.delete_if { |k,v| v.nil? }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
when "timeout"
|
|
64
|
+
raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Integer) && val>0 )
|
|
65
|
+
@timeout = val
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Actually does the systemu call passing in the correct environment, stdout and stderr
|
|
71
|
+
def runcommand
|
|
72
|
+
opts = {"env" => @environment,
|
|
73
|
+
"stdout" => @stdout,
|
|
74
|
+
"stderr" => @stderr,
|
|
75
|
+
"cwd" => @cwd}
|
|
76
|
+
|
|
77
|
+
opts["stdin"] = @stdin if @stdin
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
thread = Thread.current
|
|
81
|
+
# Start a double fork and exec with systemu which implies a guard thread.
|
|
82
|
+
# If a valid timeout is configured the guard thread will terminate the
|
|
83
|
+
# executing process and reap the pid.
|
|
84
|
+
# If no timeout is specified the process will run to completion with the
|
|
85
|
+
# guard thread reaping the pid on completion.
|
|
86
|
+
@status = systemu(@command, opts) do |cid|
|
|
87
|
+
begin
|
|
88
|
+
if timeout.is_a?(Integer)
|
|
89
|
+
# wait for the specified timeout
|
|
90
|
+
sleep timeout
|
|
91
|
+
else
|
|
92
|
+
# sleep while the agent thread is still alive
|
|
93
|
+
while(thread.alive?)
|
|
94
|
+
sleep 0.1
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# if the process is still running
|
|
99
|
+
if (Process.kill(0, cid))
|
|
100
|
+
# and a timeout was specified
|
|
101
|
+
if timeout
|
|
102
|
+
if Util.windows?
|
|
103
|
+
Process.kill('KILL', cid)
|
|
104
|
+
else
|
|
105
|
+
# Kill the process
|
|
106
|
+
Process.kill('TERM', cid)
|
|
107
|
+
sleep 2
|
|
108
|
+
Process.kill('KILL', cid) if (Process.kill(0, cid))
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
# only wait if the parent thread is dead
|
|
112
|
+
Process.waitpid(cid) unless thread.alive?
|
|
113
|
+
end
|
|
114
|
+
rescue SystemExit
|
|
115
|
+
rescue Errno::ESRCH
|
|
116
|
+
rescue Errno::ECHILD
|
|
117
|
+
Log.warn("Could not reap process '#{cid}'.")
|
|
118
|
+
rescue Exception => e
|
|
119
|
+
Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
@status.thread.kill
|
|
123
|
+
@status
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|