choria-mcorpc-support 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. 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