cloudflock 0.6.1 → 0.7.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 +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'
|