cloudflock 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/bin/cloudflock +7 -1
- data/bin/cloudflock-files +2 -14
- data/bin/cloudflock-profile +3 -15
- data/bin/cloudflock-servers +3 -22
- data/bin/cloudflock.default +3 -22
- data/lib/cloudflock/app/common/cleanup/unix.rb +23 -0
- data/lib/cloudflock/app/common/cleanup.rb +107 -0
- data/lib/cloudflock/app/common/exclusions/unix/centos.rb +18 -0
- data/lib/cloudflock/app/common/exclusions/unix/redhat.rb +18 -0
- data/lib/cloudflock/app/common/exclusions/unix.rb +58 -0
- data/lib/cloudflock/app/common/exclusions.rb +57 -0
- data/lib/cloudflock/app/common/platform_action.rb +59 -0
- data/lib/cloudflock/app/common/rackspace.rb +63 -0
- data/lib/cloudflock/app/common/servers.rb +673 -0
- data/lib/cloudflock/app/files-migrate.rb +246 -0
- data/lib/cloudflock/app/server-migrate.rb +327 -0
- data/lib/cloudflock/app/server-profile.rb +130 -0
- data/lib/cloudflock/app.rb +87 -0
- data/lib/cloudflock/error.rb +6 -19
- data/lib/cloudflock/errstr.rb +31 -0
- data/lib/cloudflock/remote/files.rb +82 -22
- data/lib/cloudflock/remote/ssh.rb +234 -278
- data/lib/cloudflock/target/servers/platform.rb +92 -115
- data/lib/cloudflock/target/servers/profile.rb +331 -340
- data/lib/cloudflock/task/server-profile.rb +651 -0
- data/lib/cloudflock.rb +6 -8
- metadata +49 -68
- data/lib/cloudflock/interface/cli/app/common/servers.rb +0 -128
- data/lib/cloudflock/interface/cli/app/files.rb +0 -179
- data/lib/cloudflock/interface/cli/app/servers/migrate.rb +0 -491
- data/lib/cloudflock/interface/cli/app/servers/profile.rb +0 -88
- data/lib/cloudflock/interface/cli/app/servers.rb +0 -2
- data/lib/cloudflock/interface/cli/console.rb +0 -213
- data/lib/cloudflock/interface/cli/opts/servers.rb +0 -20
- data/lib/cloudflock/interface/cli/opts.rb +0 -87
- data/lib/cloudflock/interface/cli.rb +0 -15
- data/lib/cloudflock/target/servers/data/exceptions/base.txt +0 -44
- data/lib/cloudflock/target/servers/data/exceptions/platform/amazon.txt +0 -10
- data/lib/cloudflock/target/servers/data/exceptions/platform/centos.txt +0 -7
- data/lib/cloudflock/target/servers/data/exceptions/platform/debian.txt +0 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/redhat.txt +0 -7
- data/lib/cloudflock/target/servers/data/exceptions/platform/suse.txt +0 -1
- data/lib/cloudflock/target/servers/data/post-migration/chroot/base.txt +0 -1
- data/lib/cloudflock/target/servers/data/post-migration/chroot/platform/amazon.txt +0 -19
- data/lib/cloudflock/target/servers/data/post-migration/pre/base.txt +0 -3
- data/lib/cloudflock/target/servers/data/post-migration/pre/platform/amazon.txt +0 -4
- data/lib/cloudflock/target/servers/migrate.rb +0 -466
- data/lib/cloudflock/target/servers/platform/v1.rb +0 -97
- data/lib/cloudflock/target/servers/platform/v2.rb +0 -93
- data/lib/cloudflock/target/servers.rb +0 -5
- data/lib/cloudflock/version.rb +0 -3
@@ -0,0 +1,651 @@
|
|
1
|
+
require 'cloudflock/remote/ssh'
|
2
|
+
require 'cpe'
|
3
|
+
|
4
|
+
module CloudFlock; module Task
|
5
|
+
class ServerProfile
|
6
|
+
# Public: List of linux distributions supported by CloudFlock
|
7
|
+
DISTRO_NAMES = %w{Arch CentOS Debian Gentoo Scientific SUSE Ubuntu RedHat}
|
8
|
+
|
9
|
+
# Internal: Sections of the profile.
|
10
|
+
Section = Struct.new(:title, :entries)
|
11
|
+
|
12
|
+
# Internal: Individual entries for profiled data.
|
13
|
+
Entry = Struct.new(:name, :values)
|
14
|
+
|
15
|
+
attr_reader :cpe
|
16
|
+
attr_reader :warnings
|
17
|
+
attr_reader :process_list
|
18
|
+
|
19
|
+
# Public: Initialize the Profile object.
|
20
|
+
#
|
21
|
+
# shell - An SSH object which is open to the host which will be profiled.
|
22
|
+
def initialize(shell)
|
23
|
+
@shell = shell
|
24
|
+
@cpe = nil
|
25
|
+
@warnings = []
|
26
|
+
@info = []
|
27
|
+
|
28
|
+
build
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Return server information and warnings as a Hash.
|
32
|
+
#
|
33
|
+
# Returns a Hash.
|
34
|
+
def to_hash
|
35
|
+
{ info: @info, warnings: @warnings }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Select from the info Array, passing Section titles to the block
|
39
|
+
# provided and returning a list of entries contained within matching
|
40
|
+
# sections.
|
41
|
+
#
|
42
|
+
# Examples
|
43
|
+
#
|
44
|
+
# profile.select { |title| title == 'Memory Statistics' }
|
45
|
+
# # => [...]
|
46
|
+
#
|
47
|
+
# profile.select { |title| /Memory/.match title }
|
48
|
+
# # => [...]
|
49
|
+
#
|
50
|
+
# Yields titles of Section structs (Strings).
|
51
|
+
#
|
52
|
+
# Returns an Array of Entry structs.
|
53
|
+
def select(&block)
|
54
|
+
sections = @info.select { |section| block.call(section.title) }
|
55
|
+
sections.map! { |section| section.entries }
|
56
|
+
sections.flatten
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Select values from within entries, specifying both section and
|
60
|
+
# entry names.
|
61
|
+
#
|
62
|
+
# section - String or Regexp specifying the section name.
|
63
|
+
# name - String or Regexp specifying the desired entry's name.
|
64
|
+
#
|
65
|
+
# Examples
|
66
|
+
#
|
67
|
+
# profile.select_entries('Memory Statistics', 'Used RAM')
|
68
|
+
# # => [...]
|
69
|
+
#
|
70
|
+
# profile.select_entries(/Memory/, 'Used RAM')
|
71
|
+
# # => [...]
|
72
|
+
#
|
73
|
+
# Returns an Array of Strings.
|
74
|
+
def select_entries(section, name)
|
75
|
+
entries = select { |header| header.match section }
|
76
|
+
filtered = entries.select { |entry| name.match entry.name }
|
77
|
+
filtered.map(&:values)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Internal: Build the profile by calling all methods which begin with
|
83
|
+
# 'build_' and 'warning_'.
|
84
|
+
#
|
85
|
+
# Returns nothing.
|
86
|
+
def build
|
87
|
+
private_methods.select { |x| x =~ /^build_/ }.each do |method|
|
88
|
+
@info.push self.send(method)
|
89
|
+
end
|
90
|
+
private_methods.select { |x| x =~ /^warning_/ }.each do |method|
|
91
|
+
self.send(method)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Internal: Filter methods by name, creating a new Section struct in which
|
96
|
+
# to hold results.
|
97
|
+
#
|
98
|
+
# name - Name to give to the new Section struct.
|
99
|
+
# method_filter - Regexp against which to match method names.
|
100
|
+
#
|
101
|
+
# Examples
|
102
|
+
#
|
103
|
+
# filter_tasks('System Information', /^determine_system_/)
|
104
|
+
#
|
105
|
+
# Returns a Section struct.
|
106
|
+
def filter_tasks(name, method_filter)
|
107
|
+
section = Section.new(name)
|
108
|
+
tasks = private_methods.select { |task| method_filter.match(task) }
|
109
|
+
section.entries = tasks.map do |method|
|
110
|
+
self.send(method)
|
111
|
+
end
|
112
|
+
|
113
|
+
section
|
114
|
+
end
|
115
|
+
|
116
|
+
# Internal: Build the "System Information" Entries.
|
117
|
+
#
|
118
|
+
# Returns nothing.
|
119
|
+
def build_system
|
120
|
+
filter_tasks('System Information', /^determine_system_/)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Build the "CPU Statistics" Entries.
|
124
|
+
#
|
125
|
+
# Returns nothing.
|
126
|
+
def build_cpu
|
127
|
+
filter_tasks('CPU Statistics', /^determine_cpu_/)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Internal: Build the "Memory Statistics" Entries.
|
131
|
+
#
|
132
|
+
# Returns nothing.
|
133
|
+
def build_memory
|
134
|
+
filter_tasks('Memory Statistics', /^determine_memory_/)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Internal: Build the "System Usage" Entries.
|
138
|
+
#
|
139
|
+
# Returns nothing.
|
140
|
+
def build_load
|
141
|
+
filter_tasks('System Usage', /^determine_load_/)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Internal: Build the "Storage Statistics" Entries.
|
145
|
+
#
|
146
|
+
# Returns nothing.
|
147
|
+
def build_storage
|
148
|
+
filter_tasks('Storage Statistics', /^determine_storage_/)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Internal: Build the "IP Usage" Entries.
|
152
|
+
#
|
153
|
+
# Returns nothing.
|
154
|
+
def build_network
|
155
|
+
filter_tasks('IP Usage', /^determine_network_/)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Internal: Build the "Installed Libraries" Entries.
|
159
|
+
#
|
160
|
+
# Returns nothing.
|
161
|
+
def build_library
|
162
|
+
filter_tasks('Installed Libraries', /^determine_library_/)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Internal: Build the "System Services" Entries.
|
166
|
+
#
|
167
|
+
# Returns nothing.
|
168
|
+
def build_services
|
169
|
+
filter_tasks('System Services', /^determine_services_/)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Internal: Attempt to determine which linux distribution the target host
|
173
|
+
# is running.
|
174
|
+
#
|
175
|
+
# Returns an Entry struct.
|
176
|
+
def determine_system_distribution
|
177
|
+
set_system_cpe
|
178
|
+
vendor = cpe.vendor
|
179
|
+
product = cpe.product.gsub(/_/, ' ').capitalize
|
180
|
+
version = cpe.version
|
181
|
+
platform = [vendor, product, version].join(' ')
|
182
|
+
|
183
|
+
warn("Unable to determine the target host's platform") if vendor.empty?
|
184
|
+
|
185
|
+
Entry.new('OS', platform + " (#{@cpe.to_s})")
|
186
|
+
end
|
187
|
+
|
188
|
+
# Internal: Determine the architecture of the target host.
|
189
|
+
#
|
190
|
+
# Returns an Entry struct.
|
191
|
+
def determine_system_architecture
|
192
|
+
arch = query('uname -m').gsub(/i\d86/, 'x86')
|
193
|
+
|
194
|
+
warn("Unable to determine target architecture") if arch.empty?
|
195
|
+
|
196
|
+
Entry.new('Arch', arch)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Internal: Determine the hostname of the target host.
|
200
|
+
#
|
201
|
+
# Returns an Entry struct.
|
202
|
+
def determine_system_hostname
|
203
|
+
hostname = query('hostname')
|
204
|
+
Entry.new('Hostname', hostname)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Internal: Gather a list of running processes on the target host.
|
208
|
+
#
|
209
|
+
# Returns an Entry struct.
|
210
|
+
def determine_system_process_list
|
211
|
+
procs = query('ps aux')
|
212
|
+
@process_list = procs.lines
|
213
|
+
|
214
|
+
Entry.new('Process Count', procs.lines.to_a.length)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Internal: Determine the number of CPU cores on the target host.
|
218
|
+
#
|
219
|
+
# Returns an Entry struct.
|
220
|
+
def determine_cpu_count
|
221
|
+
lscpu = query('lscpu')
|
222
|
+
|
223
|
+
# If lscpu is available, it gives the best information. Otherwise, fall
|
224
|
+
# back to sysctl which should handle the vast majority of Unix systems.
|
225
|
+
if /CPUs?/.match(lscpu)
|
226
|
+
count = lscpu.lines.select { |l| l =~ /CPU\(s\)/ }[0].gsub(/.* /, '')
|
227
|
+
count = count
|
228
|
+
else
|
229
|
+
# hw.ncpu covers BSD hosts (the primary case when lscpu(1) is not
|
230
|
+
# present on a system). kernel.sched_domain covers linux hosts which
|
231
|
+
# do not have have lscpu installed.
|
232
|
+
#
|
233
|
+
# Example expected outputs on a 2-core smp system:
|
234
|
+
# $ sysctl hw.ncpu
|
235
|
+
# hw.ncpu: 2
|
236
|
+
#
|
237
|
+
# $ sysctl kernel.sched_domain
|
238
|
+
# kernel.sched_domain.cpu0.domain0.busy_factor = 64
|
239
|
+
# ...
|
240
|
+
# kernel.sched_domain.cpu1.domain1.wake_idx = 0
|
241
|
+
sysctl = query('sysctl hw.ncpu || sysctl kernel.sched_domain')
|
242
|
+
if /hw\.ncpu: /.match sysctl
|
243
|
+
count = sysctl.gsub(/.*(\d)/, '\\1').to_i
|
244
|
+
else
|
245
|
+
sysctl = sysctl.lines.select { |line| /cpu\.?\d+/.match(line) }
|
246
|
+
sysctl.map! { |line| line.gsub(/.*(cpu).*?(\d*).*/m, '\\1\\2') }
|
247
|
+
count = sysctl.uniq.length
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
warn("Unable to determine target CPU count") if count.to_i < 1
|
252
|
+
|
253
|
+
Entry.new('CPU Count', count.to_i)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Internal: Determine the CPU model on the target host.
|
257
|
+
#
|
258
|
+
# Returns an Entry struct.
|
259
|
+
def determine_cpu_model
|
260
|
+
lscpu = query('lscpu').lines.select { |l| l =~ /^model name/i }
|
261
|
+
if lscpu.empty?
|
262
|
+
cpuinfo = query('cat /proc/cpuinfo')
|
263
|
+
model = cpuinfo.lines.select { |l| l =~ /model name/i }
|
264
|
+
model = model[0].to_s.strip.gsub(/.*: */, '')
|
265
|
+
else
|
266
|
+
model = lscpu[0].strip.gsub(/.* /, '')
|
267
|
+
end
|
268
|
+
|
269
|
+
warn("Unable to determine target CPU model") if model.empty?
|
270
|
+
|
271
|
+
Entry.new('Processor Model', model)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Internal: Determine the total amount of memory on the target host.
|
275
|
+
#
|
276
|
+
# Returns an Entry struct.
|
277
|
+
def determine_memory_total
|
278
|
+
mem = query('free -m')
|
279
|
+
if /^Mem/.match(mem)
|
280
|
+
total = mem.lines.select { |l| l =~ /^Mem/ }.first.split(/\s+/)[1]
|
281
|
+
else
|
282
|
+
total = 0
|
283
|
+
warn('Unable to determine target Memory')
|
284
|
+
end
|
285
|
+
Entry.new('Total RAM', "#{total} MiB")
|
286
|
+
end
|
287
|
+
|
288
|
+
# Internal: Determine the total amount of wired memory on the target host.
|
289
|
+
#
|
290
|
+
# Returns an Entry struct.
|
291
|
+
def determine_memory_wired
|
292
|
+
mem = query('free -m')
|
293
|
+
if /^Mem/.match(mem)
|
294
|
+
mem = mem.lines.select { |l| l =~ /^Mem/ }.first
|
295
|
+
mem = mem.split(/\s+/).map(&:to_i)
|
296
|
+
total = mem[1]
|
297
|
+
used = total - mem[3..-1].reduce(&:+)
|
298
|
+
used = sprintf("%#{total.to_s.length}d", used)
|
299
|
+
percent = ((used.to_f / total) * 100).to_i
|
300
|
+
else
|
301
|
+
used = 0
|
302
|
+
percent = 0
|
303
|
+
end
|
304
|
+
|
305
|
+
Entry.new('Used RAM', "#{used} MiB (#{percent}%)")
|
306
|
+
end
|
307
|
+
|
308
|
+
# Internal: Determine the total amount of swap used on the target host.
|
309
|
+
#
|
310
|
+
# Returns an Entry struct.
|
311
|
+
def determine_memory_swap
|
312
|
+
mem = query('free -m')
|
313
|
+
if /^Swap/.match(mem)
|
314
|
+
mem = mem.lines.select { |l| l =~ /^Swap/ }.first
|
315
|
+
total, used = mem.split(/\s+/).map(&:to_i)[1..2]
|
316
|
+
used = sprintf("%#{total.to_s.length}d", used)
|
317
|
+
percent = ((used.to_f / total) * 100).to_i
|
318
|
+
|
319
|
+
warn('Host is swapping') if percent > 0
|
320
|
+
else
|
321
|
+
used = percent = 0
|
322
|
+
warn('Unable to enumerate swap')
|
323
|
+
end
|
324
|
+
|
325
|
+
Entry.new('Used Swap', "#{used} MiB (#{percent}%)")
|
326
|
+
rescue ZeroDivisionError, FloatDomainError
|
327
|
+
Entry.new('Used Swap', 'No swap configured')
|
328
|
+
end
|
329
|
+
|
330
|
+
# Internal: If the sysstat suite is installed on the target host, determine
|
331
|
+
# the average amount of memory used over whatever historical period sar is
|
332
|
+
# able to represent.
|
333
|
+
#
|
334
|
+
# Returns an Entry struct.
|
335
|
+
def determine_memory_usage_historical_average
|
336
|
+
sar_location = query('which sar')
|
337
|
+
usage = ''
|
338
|
+
|
339
|
+
if sar_location =~ /bin\//
|
340
|
+
sar_cmd = "for l in $(find /var/log -name 'sa??');do sar -r -f $l|" \
|
341
|
+
"grep Average;done|awk '{I+=1;TOT=$2+$3;CACHE+=$5+$6;" \
|
342
|
+
"FREE+=$2;} END {CACHE=CACHE/I;FREE=FREE/I;" \
|
343
|
+
"print (TOT-(CACHE+FREE))/TOT*100;}'"
|
344
|
+
|
345
|
+
usage = query(sar_cmd)
|
346
|
+
end
|
347
|
+
usage = '' unless usage =~ /\d/
|
348
|
+
|
349
|
+
warn('No historical usage information available') if usage.empty?
|
350
|
+
|
351
|
+
Entry.new('Average Used', usage)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Internal: If the sysstat suite is installed on the target host, determine
|
355
|
+
# the average amount of swap used over whatever historical period sar is
|
356
|
+
# able to represent.
|
357
|
+
#
|
358
|
+
# Returns an Entry struct.
|
359
|
+
def determine_memory_swap_historical_average
|
360
|
+
sar_location = query('which sar 2>/dev/null')
|
361
|
+
usage = ''
|
362
|
+
|
363
|
+
if sar_location =~ /bin\//
|
364
|
+
sar_cmd = "for l in $(find /var/log -name 'sa??');do sar -r -f $l|" \
|
365
|
+
"grep Average;done|awk '{I+=1;;SWAP+=$9;} END " \
|
366
|
+
"{SWAP=SWAP/I;print SWAP;}'"
|
367
|
+
|
368
|
+
usage = query(sar_cmd)
|
369
|
+
end
|
370
|
+
usage = '' unless usage =~ /\d/
|
371
|
+
|
372
|
+
Entry.new('Average Swap', usage)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Internal: Determine the amount of time the target host has been running.
|
376
|
+
#
|
377
|
+
# Returns an Entry struct.
|
378
|
+
def determine_load_uptime
|
379
|
+
up = query('uptime')
|
380
|
+
up.gsub!(/.* up([^,]*),.*/, '\\1')
|
381
|
+
|
382
|
+
Entry.new('Uptime', up.strip)
|
383
|
+
end
|
384
|
+
|
385
|
+
# Internal: Determine the load averages on the target host.
|
386
|
+
#
|
387
|
+
# Returns an Entry struct.
|
388
|
+
def determine_load_average
|
389
|
+
avg = query('uptime')
|
390
|
+
avg.gsub!(/^.* load averages?: |,.*$/i, '')
|
391
|
+
|
392
|
+
warn('System is under heavy load') if avg.to_i > 10
|
393
|
+
|
394
|
+
Entry.new('Load Average', avg)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Internal: If the sysstat suite is installed on the target host, determine
|
398
|
+
# the amount of historical IO activity on the target host.
|
399
|
+
#
|
400
|
+
# Returns an Entry struct.
|
401
|
+
def determine_load_iowait
|
402
|
+
iostat = query('iostat').lines.to_a[3].to_s.strip.split(/\s+/)[3]
|
403
|
+
wait = iostat.to_f
|
404
|
+
|
405
|
+
warn('Cannot determine IO Wait') if iostat.to_s.empty?
|
406
|
+
warn('IO Wait is high') if wait > 10
|
407
|
+
|
408
|
+
Entry.new('IO Wait', wait)
|
409
|
+
end
|
410
|
+
|
411
|
+
# Internal: Determine the amount of disk space in use on the target host.
|
412
|
+
#
|
413
|
+
# Returns an Entry struct.
|
414
|
+
def determine_storage_disk_usage
|
415
|
+
mounts = query('df').lines.select do |line|
|
416
|
+
fs, blocks, _ = line.split(/\s+/, 3)
|
417
|
+
/^\/dev/.match(fs) || blocks.to_i > 10000000
|
418
|
+
end
|
419
|
+
|
420
|
+
usage = mounts.reduce(0) do |collector, mount|
|
421
|
+
collector + mount.split(/\s+/, 4)[2].to_i
|
422
|
+
end
|
423
|
+
|
424
|
+
usage = sprintf('%.1f', usage.to_f / 1000**2)
|
425
|
+
|
426
|
+
warn('Unable to find meaningful mounts') if mounts.empty?
|
427
|
+
warn('Unable to determine disk usage') if usage.to_f < 1
|
428
|
+
|
429
|
+
Entry.new('Disk Usage', "#{usage} GB")
|
430
|
+
end
|
431
|
+
|
432
|
+
# Internal: Determine public IPv4 addresses in use by the target host.
|
433
|
+
#
|
434
|
+
# Returns an Entry struct.
|
435
|
+
def determine_network_public_v4_ips
|
436
|
+
addresses = list_v4_ips
|
437
|
+
addresses.reject! { |ip| rfc1918?(ip) }
|
438
|
+
|
439
|
+
Entry.new('Public IPs', addresses.sort.join(', '))
|
440
|
+
end
|
441
|
+
|
442
|
+
# Internal: Determine private IPv4 addresses in use by the target host.
|
443
|
+
#
|
444
|
+
# Returns an Entry struct.
|
445
|
+
def determine_network_private_v4_ips
|
446
|
+
addresses = list_v4_ips
|
447
|
+
addresses.select! { |ip| rfc1918?(ip) }
|
448
|
+
|
449
|
+
Entry.new('Private IPs', addresses.sort.join(', '))
|
450
|
+
end
|
451
|
+
|
452
|
+
# Internal: Determine which perl version (if any) is installed on the
|
453
|
+
# target host.
|
454
|
+
#
|
455
|
+
# Returns an Entry struct.
|
456
|
+
def determine_library_perl
|
457
|
+
perl = query("perl -e 'print $^V;'")
|
458
|
+
perl.gsub!(/^v([0-9.]*).*/, '\1')
|
459
|
+
perl = '' unless /[0-9]/.match(perl)
|
460
|
+
|
461
|
+
Entry.new('Perl', perl)
|
462
|
+
end
|
463
|
+
|
464
|
+
# Internal: Determine which python version (if any) is installed on the
|
465
|
+
# target host.
|
466
|
+
#
|
467
|
+
# Returns an Entry struct.
|
468
|
+
def determine_library_python
|
469
|
+
python = query('python -c "import sys; print sys.version"')
|
470
|
+
python.gsub!(/([0-9.]*).*/m, '\1')
|
471
|
+
python = '' unless /\d/.match(python)
|
472
|
+
|
473
|
+
Entry.new('Python', python)
|
474
|
+
end
|
475
|
+
|
476
|
+
# Internal: Determine which ruby version (if any) is installed on the
|
477
|
+
# target host.
|
478
|
+
#
|
479
|
+
# Returns an Entry struct.
|
480
|
+
def determine_library_ruby
|
481
|
+
ruby = query('ruby -e "print RUBY_VERSION"')
|
482
|
+
ruby = '' unless /\d/.match(ruby)
|
483
|
+
|
484
|
+
Entry.new('Ruby', ruby)
|
485
|
+
end
|
486
|
+
|
487
|
+
# Internal: Determine which php version (if any) is installed on the target
|
488
|
+
# host.
|
489
|
+
#
|
490
|
+
# Returns an Entry struct.
|
491
|
+
def determine_library_php
|
492
|
+
php = query('php -v').lines.to_a[0].to_s
|
493
|
+
php.gsub!(/^PHP ([0-9.]*).*/, '\1')
|
494
|
+
php = '' unless /\d/.match(php)
|
495
|
+
|
496
|
+
Entry.new('PHP', php)
|
497
|
+
end
|
498
|
+
|
499
|
+
# Internal: Gather a list of all listening ports on the target host.
|
500
|
+
#
|
501
|
+
# Returns nothing.
|
502
|
+
def determine_services_ports
|
503
|
+
netstat = as_root('netstat -untlp')
|
504
|
+
netstat = netstat.lines.select { |line| /^[tu][cd]p/.match(line) }
|
505
|
+
netstat.map! { |line| line.split(/\s+/) }
|
506
|
+
|
507
|
+
addresses = netstat.map { |row| row[3].gsub(/:[^:]*$/, '') }.uniq.sort
|
508
|
+
netstat.map! do |row|
|
509
|
+
port = row[3].gsub(/.*:/, '')
|
510
|
+
pid = row[-1].gsub(/.*\//, '')
|
511
|
+
" " * 16 + "% 6d %s" % [port, pid]
|
512
|
+
end
|
513
|
+
netstat.uniq!
|
514
|
+
netstat.sort! { |x,y| x.to_i <=> y.to_i }
|
515
|
+
|
516
|
+
warn('Cannot enumerate listening ports') if netstat.empty?
|
517
|
+
|
518
|
+
netstat[0] = netstat[0].to_s[16..-1]
|
519
|
+
Entry.new('Listening Ports', "#{netstat.join("\n")}")
|
520
|
+
end
|
521
|
+
|
522
|
+
# Internal: Check for signs of running control panels.
|
523
|
+
#
|
524
|
+
# Returns nothing.
|
525
|
+
def warning_control_panels
|
526
|
+
if @process_list.grep(/psa/i).any?
|
527
|
+
warn('Server likely to be running Plesk')
|
528
|
+
end
|
529
|
+
if @process_list.grep(/cpanel/i).any?
|
530
|
+
warn('Server likely to be running cPanel')
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
# Internal: Enumerate v4 IPs on the host.
|
535
|
+
#
|
536
|
+
# Returns an Array of IPv4 addresses outside of 127/8
|
537
|
+
def list_v4_ips
|
538
|
+
addresses = query('/sbin/ifconfig').lines.select do |line|
|
539
|
+
/inet[^6]/.match(line)
|
540
|
+
end
|
541
|
+
addresses.map! { |ip| ip.split(/\s+/).grep(/(?:\d+\.){3}\d+/)[0] }
|
542
|
+
addresses.map! { |ip| ip.gsub(/[^\d]*((?:\d+\.){3}\d+)[^\d]*/, '\\1')}
|
543
|
+
addresses.reject! { |ip| /127(\.\d+){3}/.match(ip) }
|
544
|
+
end
|
545
|
+
|
546
|
+
# Internal: Wrap SSH#query
|
547
|
+
#
|
548
|
+
# args - Globbed args to pass to SSH object
|
549
|
+
#
|
550
|
+
# Returns a String
|
551
|
+
def query(*args)
|
552
|
+
@shell.query(*args)
|
553
|
+
end
|
554
|
+
|
555
|
+
# Internal: Wrap SSH#as_root
|
556
|
+
#
|
557
|
+
# args - Globbed args to pass to SSH object
|
558
|
+
#
|
559
|
+
# Returns a String
|
560
|
+
def as_root(*args)
|
561
|
+
@shell.as_root(*args)
|
562
|
+
end
|
563
|
+
|
564
|
+
# Internal: Add a warning to the list of warnings encountered.
|
565
|
+
#
|
566
|
+
# warning - String containing warning text.
|
567
|
+
#
|
568
|
+
# Returns nothing.
|
569
|
+
def warn(warning)
|
570
|
+
@warnings.push warning
|
571
|
+
end
|
572
|
+
|
573
|
+
# Internal: Determine and set CPE representative of the running system.
|
574
|
+
# Resort to a best guess if this cannot be reliably accomplished.
|
575
|
+
#
|
576
|
+
# Sets @cpe.
|
577
|
+
#
|
578
|
+
# Returns nothing.
|
579
|
+
def set_system_cpe
|
580
|
+
# Some distros ship with a file containing the CPE for their platform;
|
581
|
+
# this should be used if at all possible.
|
582
|
+
release = query('cat /etc/system-release-cpe')
|
583
|
+
begin
|
584
|
+
cpe = CPE.parse(release)
|
585
|
+
cpe.version.gsub!(/[^0-9.]/, '')
|
586
|
+
@cpe = cpe
|
587
|
+
return
|
588
|
+
rescue ArgumentError
|
589
|
+
end
|
590
|
+
|
591
|
+
cpe = CPE.new(part: CPE::OS)
|
592
|
+
|
593
|
+
# Depend on the reported kernel name for product name
|
594
|
+
cpe.product = query('uname -s')
|
595
|
+
|
596
|
+
# Depend on /etc/issue if it's available
|
597
|
+
issue = query('cat /etc/issue')
|
598
|
+
cpe.vendor = distro_name(issue)
|
599
|
+
|
600
|
+
# If /etc/issue fails, resort to looking any release/version file
|
601
|
+
if cpe.vendor.empty?
|
602
|
+
release = query("grep -h '^ID=' /etc/*[_-][rv]e[lr]*").lines.first
|
603
|
+
cpe.vendor = distro_name(release)
|
604
|
+
end
|
605
|
+
|
606
|
+
# Fall back to depending on the OS reported by uname if all else fails
|
607
|
+
cpe.vendor = query('uname -o') if cpe.vendor.empty?
|
608
|
+
|
609
|
+
# Version number will be determined from /etc/issue
|
610
|
+
cpe.version = version_number(issue)
|
611
|
+
|
612
|
+
# If Version is not represented, fall back to kernel reported version
|
613
|
+
cpe.version = version_number(query('uname -r')) if cpe.version.empty?
|
614
|
+
@cpe = cpe
|
615
|
+
end
|
616
|
+
|
617
|
+
# Internal: Search for nicely formatted names of Linux distributions in a
|
618
|
+
# string which may contain the name of the distribution currently running
|
619
|
+
# on the target host. If multiple matches exist, resort to the first one.
|
620
|
+
#
|
621
|
+
# Returns a String.
|
622
|
+
def distro_name(str)
|
623
|
+
matches = DISTRO_NAMES.select do |distro|
|
624
|
+
Regexp.new(distro, Regexp::IGNORECASE).match(str)
|
625
|
+
end
|
626
|
+
matches.first.to_s
|
627
|
+
end
|
628
|
+
|
629
|
+
# Internal: Inspect a String which may contain a version number. Sanitize
|
630
|
+
# the version number, removing any extraneous information.
|
631
|
+
#
|
632
|
+
# Returns a String.
|
633
|
+
def version_number(str)
|
634
|
+
if str =~ /\d/
|
635
|
+
str.gsub(/^[^\d]*/, '').gsub(/[^\d]*$/, '').gsub(/(\d*\.\d*).*/, '\1')
|
636
|
+
else
|
637
|
+
''
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
# Internal: Determine if a v4 IP address belongs to a private (RFC 1918)
|
642
|
+
# network.
|
643
|
+
#
|
644
|
+
# ip - String containing an IP.
|
645
|
+
#
|
646
|
+
# Returns true if the IP falls within the private range, false otherwise.
|
647
|
+
def rfc1918?(ip)
|
648
|
+
Addrinfo.ip(ip).ipv4_private?
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end; end
|
data/lib/cloudflock.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
+
require 'cloudflock/error'
|
2
|
+
require 'cloudflock/errstr'
|
3
|
+
|
4
|
+
# Public: Encapsulate all functionality related to the CloudFlock API and any
|
5
|
+
# apps built with such.
|
1
6
|
module CloudFlock
|
2
|
-
|
3
|
-
module Interface; end
|
4
|
-
module Target;
|
5
|
-
module Servers; end
|
6
|
-
end
|
7
|
+
VERSION = '0.7.0'
|
7
8
|
end
|
8
|
-
|
9
|
-
require 'cloudflock/version'
|
10
|
-
require 'cloudflock/error'
|