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.
Files changed (52) hide show
  1. checksums.yaml +15 -0
  2. data/bin/cloudflock +7 -1
  3. data/bin/cloudflock-files +2 -14
  4. data/bin/cloudflock-profile +3 -15
  5. data/bin/cloudflock-servers +3 -22
  6. data/bin/cloudflock.default +3 -22
  7. data/lib/cloudflock/app/common/cleanup/unix.rb +23 -0
  8. data/lib/cloudflock/app/common/cleanup.rb +107 -0
  9. data/lib/cloudflock/app/common/exclusions/unix/centos.rb +18 -0
  10. data/lib/cloudflock/app/common/exclusions/unix/redhat.rb +18 -0
  11. data/lib/cloudflock/app/common/exclusions/unix.rb +58 -0
  12. data/lib/cloudflock/app/common/exclusions.rb +57 -0
  13. data/lib/cloudflock/app/common/platform_action.rb +59 -0
  14. data/lib/cloudflock/app/common/rackspace.rb +63 -0
  15. data/lib/cloudflock/app/common/servers.rb +673 -0
  16. data/lib/cloudflock/app/files-migrate.rb +246 -0
  17. data/lib/cloudflock/app/server-migrate.rb +327 -0
  18. data/lib/cloudflock/app/server-profile.rb +130 -0
  19. data/lib/cloudflock/app.rb +87 -0
  20. data/lib/cloudflock/error.rb +6 -19
  21. data/lib/cloudflock/errstr.rb +31 -0
  22. data/lib/cloudflock/remote/files.rb +82 -22
  23. data/lib/cloudflock/remote/ssh.rb +234 -278
  24. data/lib/cloudflock/target/servers/platform.rb +92 -115
  25. data/lib/cloudflock/target/servers/profile.rb +331 -340
  26. data/lib/cloudflock/task/server-profile.rb +651 -0
  27. data/lib/cloudflock.rb +6 -8
  28. metadata +49 -68
  29. data/lib/cloudflock/interface/cli/app/common/servers.rb +0 -128
  30. data/lib/cloudflock/interface/cli/app/files.rb +0 -179
  31. data/lib/cloudflock/interface/cli/app/servers/migrate.rb +0 -491
  32. data/lib/cloudflock/interface/cli/app/servers/profile.rb +0 -88
  33. data/lib/cloudflock/interface/cli/app/servers.rb +0 -2
  34. data/lib/cloudflock/interface/cli/console.rb +0 -213
  35. data/lib/cloudflock/interface/cli/opts/servers.rb +0 -20
  36. data/lib/cloudflock/interface/cli/opts.rb +0 -87
  37. data/lib/cloudflock/interface/cli.rb +0 -15
  38. data/lib/cloudflock/target/servers/data/exceptions/base.txt +0 -44
  39. data/lib/cloudflock/target/servers/data/exceptions/platform/amazon.txt +0 -10
  40. data/lib/cloudflock/target/servers/data/exceptions/platform/centos.txt +0 -7
  41. data/lib/cloudflock/target/servers/data/exceptions/platform/debian.txt +0 -0
  42. data/lib/cloudflock/target/servers/data/exceptions/platform/redhat.txt +0 -7
  43. data/lib/cloudflock/target/servers/data/exceptions/platform/suse.txt +0 -1
  44. data/lib/cloudflock/target/servers/data/post-migration/chroot/base.txt +0 -1
  45. data/lib/cloudflock/target/servers/data/post-migration/chroot/platform/amazon.txt +0 -19
  46. data/lib/cloudflock/target/servers/data/post-migration/pre/base.txt +0 -3
  47. data/lib/cloudflock/target/servers/data/post-migration/pre/platform/amazon.txt +0 -4
  48. data/lib/cloudflock/target/servers/migrate.rb +0 -466
  49. data/lib/cloudflock/target/servers/platform/v1.rb +0 -97
  50. data/lib/cloudflock/target/servers/platform/v2.rb +0 -93
  51. data/lib/cloudflock/target/servers.rb +0 -5
  52. data/lib/cloudflock/version.rb +0 -3
@@ -1,394 +1,385 @@
1
1
  require 'cloudflock/remote/ssh'
2
+ require 'socket'
2
3
  require 'cpe'
3
4
 
4
- # Public: Provides methods to create a profile for a given host, mapping it to
5
- # available public cloud offerings, and reference data gathered during this
6
- # process.
7
- #
8
- # Examples
9
- #
10
- # # Generate a profile from a shell already logged in to a remote host
11
- # profile = Profile.new(ssh_object)
12
- # profile.build
13
- #
14
- # # Only determine the memory and I/O statistics for a host
15
- # profile = Profile.new(ssh_object)
16
- # profile.determine_memory
17
- # profile.determine_io
18
- class CloudFlock::Target::Servers::Profile
19
- # Public: Array containing warnings generated by the info gathering process.
20
- attr_reader :warnings
21
-
22
- # Public: Initialize the Profile object.
23
- #
24
- # shell - An SSH object open to the host to be profiled.
25
- #
26
- # Raises ArgumentError if passed anything but an SSH object.
27
- def initialize(shell)
28
- raise ArgumentError unless shell.kind_of? CloudFlock::Remote::SSH
29
-
30
- @shell = shell
31
-
32
- @warnings = []
33
- @info = {}
34
- end
35
-
36
- # Public: Run all available determinations against the SSH object.
37
- #
38
- # Returns nothing.
39
- def build
40
- determine_version
41
- determine_arch
42
- determine_hostname
43
- determine_memory
44
- determine_cpu
45
- determine_disk
46
- determine_ips
47
- determine_io
48
- determine_web
49
- determine_db
50
- determine_lib
51
- determine_rsync
52
- determine_processes
53
- end
54
-
55
- # Public: Determine vendor and version of the OS running on the target host,
56
- # and create an appropriate CPE object for it, assigning it to @info[:cpe].
57
- #
58
- # Returns nothing.
59
- def determine_version
60
- release = @shell.query("CPE", "cat /etc/system-release-cpe")
5
+ module CloudFlock; module Target; module Servers
6
+ class Profile
7
+ # Public: List of linux distributions supported by CloudFlock
8
+ SUPPORTED_DISTROS = %w{Arch CentOS Debian SUSE Ubuntu RedHat Gentoo}
9
+
10
+ # Public: Initialize the Profile object.
11
+ #
12
+ # shell - An SSH object which is open to the host which will be profiled.
13
+ #
14
+ # Raises TypeError if shell is not of type SSH.
15
+ def initialize(shell)
16
+ unless shell.is_a?(CloudFlock::Remote::SSH)
17
+ raise(TypeError, Errstr::NOT_SSH)
18
+ end
61
19
 
62
- begin
63
- cpe = CPE.parse(release)
64
- cpe.version.gsub!(/[^0-9.]/, '')
65
- @info[:cpe] = cpe
66
- return
67
- rescue ArgumentError
68
- cpe = CPE.new(part: CPE::OS, product: "linux")
20
+ @shell = shell
21
+ @warnings = []
22
+ @info = {}
69
23
  end
70
24
 
71
- issue = @shell.query("ISSUE", "cat /etc/issue")
72
-
73
- case issue
74
- when /Arch/
75
- cpe.vendor = "Arch"
76
- when /CentOS/
77
- cpe.vendor = "CentOS"
78
- issue.gsub!(/\.\d.*$/, '')
79
- when /Debian/
80
- cpe.vendor = "Debian"
81
- issue.gsub!(/\.\d.*$/, '')
82
- when /This is/
83
- cpe.vendor = "Gentoo"
84
- when /SUSE/
85
- cpe.vendor = "openSUSE"
86
- when /Ubuntu/
87
- cpe.vendor = "Ubuntu"
88
- when /Red/
89
- cpe.vendor = "Redhat"
90
- issue.gsub!(/\.\d.*$/, '')
91
- else
92
- cpe.vendor = "Unknown"
25
+ # Public: Build the profile by calling all methods which begin with
26
+ # 'determine_'.
27
+ #
28
+ # Returns nothing.
29
+ def build
30
+ methods.select { |x| x =~ /^determine_/ }.each do |method|
31
+ self.send(method)
32
+ end
33
+ methods.select { |x| x =~ /^warning_/ }.each do |method|
34
+ self.send(method)
35
+ end
93
36
  end
94
37
 
95
- cpe.version = version_number(issue)
96
-
97
- @info[:cpe] = cpe
98
- end
38
+ # Public: Allow access to the list of keys in @info.
39
+ #
40
+ # Returns an Array of keys in @info.
41
+ def keys
42
+ @info.keys
43
+ end
99
44
 
100
- # Public: Determine the architecture of the target host and assign it to
101
- # @info[:arch].
102
- #
103
- # Returns nothing.
104
- def determine_arch
105
- uname = @shell.query("UNAME", "uname -m")
106
- case uname
107
- when /x86_64/
108
- @info[:arch] = 'x86_64'
109
- when /i\d86/
110
- @info[:arch] = 'i386'
111
- else
112
- @info[:arch] = "Unknown"
45
+ # Public: Simplify access to @info.
46
+ #
47
+ # key - Object to be used as the key in the @info Hash.
48
+ #
49
+ # Returns a value stored in @info.
50
+ def [](key)
51
+ @info[key]
113
52
  end
114
- end
115
53
 
116
- # Public: Determine the hostname of the target host and assign it to
117
- # @info[:hostname]
118
- #
119
- # Returns nothing.
120
- def determine_hostname
121
- @info[:hostname] = @shell.query("HOST", "hostname")
122
- end
54
+ # Public: Return server information and warnings as a Hash.
55
+ #
56
+ # Returns a Hash.
57
+ def to_hash
58
+ @info.merge({warnings: @warnings})
59
+ end
123
60
 
124
- # Public: Determine the available and total memory on the target host as
125
- # well as historical usage via sar(1), if possible. Assign these to
126
- # @info[:memory] and @info[:memory_hist], respectively.
127
- #
128
- # Returns nothing.
129
- def determine_memory
130
- result = {}
131
-
132
- free = %w{free -m |awk '$1 ~ /Mem/ {print $2, $2-$6-$7}; $1 ~ /Swap/
133
- {print $3}'|xargs}.join(' ')
134
- mem = @shell.query("MEMORY", free)
135
- total, used, swap = mem.split(/\s+/)
136
-
137
- result[:total] = total.to_i
138
- result[:mem_used] = used.to_i
139
- result[:swap_used] = swap.to_i
140
- result[:swapping?] = swap.to_i > 0
141
- @info[:memory] = result
142
-
143
- # Determine average mem and swap usage
144
- result = {}
145
- sar_location = @shell.query("SAR", "which sar 2>/dev/null")
146
- sar_command = %w{for l in $(find /var/log/ -name 'sa??'); do sar -r -f $l |
147
- grep Average; done | awk '{I+=1; TOT=$2+$3; CACHE+=$5+$6;
148
- FREE+=$2; SWAP+=$9;} END {CACHE=CACHE/I; FREE=FREE/I;
149
- SWAP=SWAP/I; print (TOT-(CACHE+FREE))/TOT*100,
150
- SWAP;}'}.join(' ')
151
-
152
- if sar_location =~ /bin\//
153
- sar_usage = @shell.query("HIST_MEM", sar_command)
154
-
155
- if sar_usage =~ /\d \d/
156
- hist_mem, hist_swap = sar_usage.split(/ /)
157
- result[:mem_used] = hist_mem.to_i
158
- result[:swap_used] = hist_swap.to_i
61
+ private
62
+
63
+ # Internal: Determine important statistics relating to the CPU (available
64
+ # core count, speed).
65
+ #
66
+ # Returns nothing.
67
+ def determine_cpu
68
+ cpu = @info[:cpu] = {}
69
+
70
+ lscpu = @shell.query('LSCPU', 'lscpu')
71
+ if lscpu.empty?
72
+ cpuinfo = @shell.query('cat /proc/cpuinfo')
73
+ count = cpuinfo.lines.select { |l| l =~ /^processor\s*: [0-9]/}
74
+ speed = cpuinfo.lines.select { |l| l =~ /MHz/ }
75
+ cpu[:count] = count.size
76
+ cpu[:speed] = speed[0].to_s.gsub(/.* /, '')
77
+ else
78
+ cpu[:count] = lscpu.select { |l| l =~ /CPU\(s\)/ }.gsub(/.* /, '')
79
+ cpu[:speed] = lscpu.select { |l| l =~ /MHz/ }.gsub(/.* /, '')
159
80
  end
160
81
  end
161
82
 
162
- @info[:memory_hist] = result
163
- end
164
-
165
- # Public: Determine the number of CPUs present on the target host and the
166
- # speed of the processors. Assign these to @info[:cpu][:count] and
167
- # @info[:cpu][:speed] respectively.
168
- #
169
- # Returns nothing.
170
- def determine_cpu
171
- count_command = 'cat /proc/cpuinfo|grep "^processor\\s*: [0-9]"|wc -l'
172
- speed_command = 'cat /proc/cpuinfo|grep "MHz"|head -1'
173
- cpus = {}
174
- cpus[:count] = @shell.query("CPU", count_command).to_i
175
- cpus[:speed] = @shell.query("MHZ", speed_command).gsub(/.*: /, '').to_i
176
- @info[:cpu] = cpus
177
- end
178
-
179
- # Public: Determine the amount of disk space in use on the target host and
180
- # set that to @info[:disk].
181
- #
182
- # Returns nothing.
183
- def determine_disk
184
- # Use a less accurate (tends to inflate) method if du takes too long
185
- df_command = "df 2>/dev/null |awk '$1 ~ /\\// {I = I + $3} END {print I}'"
186
- disk = @shell.query("DISK_USED_DF", df_command)
187
-
188
- # Result is returned as KiB used. We need GB used.
189
- @info[:disk] = disk.to_f / 1000 ** 2
190
- end
191
-
192
- # Public: Determine the number of public and private IP addressess in use on
193
- # the target host and assign that to @info[:ip].
194
- #
195
- # Returns nothing.
196
- def determine_ips
197
- ips = {:private => [], :public => []}
83
+ # Internal: Determine the number and size of MySQL databases resident on
84
+ # the target host.
85
+ #
86
+ # Returns nothing.
87
+ def determine_databases
88
+ db = @info[:db] = {}
89
+ mysql_count_cmd = 'find /var/lib/mysql* -maxdepth 0 -type d ' \
90
+ '2>/dev/null|wc -l'
91
+ db[:count] = @shell.query('DB_MYSQL_COUNT', mysql_count_cmd)
92
+ db[:count] = db[:count].to_i
93
+
94
+ mysql_size_cmd = "du -s /var/lib/mysql 2>/dev/null|awk '{print $1}'"
95
+ db[:size] = @shell.query('DB_MYSQL_SIZE', mysql_size_cmd)
96
+ db[:size] = db[:size].to_i
97
+ end
198
98
 
199
- ip_command = %w{/sbin/ifconfig | grep 'inet addr' | egrep -v ':127' | sed
200
- -e 's/.*addr:\([0-9.]*\) .*/\1/'}.join(' ')
201
- ifconfig = @shell.query("IP_CONFIG", ip_command)
99
+ # Internal: Determine the amount of disk space in use on the target host.
100
+ #
101
+ # Returns nothing.
102
+ def determine_disk
103
+ df_cmd = "df 2>/dev/null|awk '$1 ~ /\\// {I=I+$3} END {print I}'"
104
+ disk = @shell.query('DISK_DF', df_cmd)
202
105
 
203
- ifconfig.each_line do |ip|
204
- ip.strip!
205
- ips[rfc1918?(ip)] << ip
106
+ # Result is expected to be in KiB. Convert to GB.
107
+ @info[:disk] = disk.to_f / 1000 ** 2
206
108
  end
207
109
 
208
- @info[:ip] = ips
209
- end
110
+ # Internal: Attempt to determine which linux distribution the target host
111
+ # is running.
112
+ #
113
+ # Returns nothing.
114
+ def determine_distribution
115
+ # Some distros ship with a file containing the CPE for their platform;
116
+ # this should be used if at all possible.
117
+ release = @shell.query('CPE', 'cat /etc/system-release-cpe')
118
+ begin
119
+ cpe = CPE.parse(release)
120
+ cpe.version.gsub!(/[^0-9.]/, '')
121
+ @info[:cpe] = cpe
122
+ return
123
+ rescue ArgumentError
124
+ cpe = CPE.new(part: CPE::OS, product: 'linux')
125
+ end
210
126
 
211
- # Public: Determine amount of historical I/O usage via sysstat and set it to
212
- # @info[:io].
213
- #
214
- # Returns nothing.
215
- def determine_io
216
- io = {}
127
+ # Fall back to depending on /etc/issue if available
128
+ issue = @shell.query('ISSUE', 'cat /etc/issue')
129
+ cpe.vendor = distro_name(issue)
217
130
 
218
- iostat = @shell.query("IOSTAT", "iostat -c | sed -n 4p | awk '{print $4}'")
219
- io[:wait] = iostat.to_f
131
+ # If all else fails, resort to looking in release files
132
+ if cpe.vendor.empty?
133
+ release_cmd = "grep -h '^ID=' /etc/[A-Za-z]*[_-][rv]e[lr]*|head -1"
134
+ release = @shell.query("RELEASE", release_cmd)
135
+ cpe.vendor = distro_name(release)
136
+ end
220
137
 
221
- up = @shell.query("UPTIME", "uptime | sed -e 's/.*up\\([^,]*\\),.*/\\1/'")
222
- io[:uptime] = up.chomp
138
+ # Fall back to "Unknown"
139
+ cpe.vendor = "" if cpe.vendor.empty?
223
140
 
224
- @info[:io] = io
225
- end
141
+ # Version number will be determined from /etc/issue
142
+ cpe.version = version_number(issue)
143
+ @info[:cpe] = cpe
144
+ end
226
145
 
227
- # Public: Determine the web server used on the host, and attempt to enumerate
228
- # domain names configured on the server for both non-SSL and SSL; set this
229
- # information to @info[:web]. Presently only Apache is supported.
230
- #
231
- # Returns nothing.
232
- def determine_web
233
- web = {}
234
-
235
- netstat_command = %w{netstat -ntlp | awk '$4 ~ /:80$/ || $4 ~ /:443$/
236
- {sub(/^[^\/]*\//, ""); print $NF}' | head -1}.join(' ')
237
- web[:binary] = @shell.query("WEB_NETSTAT", netstat_command)
238
-
239
- unless web[:binary].empty?
240
- version_command = "`which #{web[:binary]}` -v | grep version"
241
- web[:version] = @shell.query("WEB_VERSION", version_command)
242
- web[:version].gsub!(/.*version: /i, '')
243
- binary = web[:binary] == "httpd" ? "apachectl" : "apache2ctl"
244
- binary << " -S 2>&1"
245
-
246
- web_command = "#{binary} | grep ')' | grep -vi 'default' | wc -l"
247
- hosts = @shell.query("WEB_HTTP", web_command)
248
- web[:hosts_http] = hosts.to_i
249
-
250
- ssl_command = "#{binary} | grep ':443'|grep -vi 'default' | wc -l"
251
- ssl_hosts = @shell.query("WEB_HTTPS", ssl_command)
252
- web[:hosts_https] = ssl_hosts.to_i
146
+ # Internal: Determine the hostname of the target host.
147
+ #
148
+ # Returns nothing.
149
+ def determine_hostname
150
+ @info[:hostname] = @shell.query('HOST', 'hostname')
253
151
  end
254
152
 
255
- @info[:web] = web
256
- end
153
+ # Internal: Determine the amount of historical IO activity on the target
154
+ # host using sysstat if available.
155
+ #
156
+ # Returns nothing.
157
+ def determine_io
158
+ io = @info[:io] = {}
257
159
 
258
- # Public: Determine the amount of disk usage attributable to databases, as
259
- # well as database count, and set this to @info[:database]. Currently
260
- # supports MySQL.
261
- #
262
- # Returns nothing.
263
- def determine_db
264
- db = {}
265
- mysql_count = %w{find /var/lib/mysql/* -maxdepth 0 -type d 2>/dev/null |
266
- wc -l}.join(' ')
267
- db[:count] = @shell.query("DB_MYSQL_COUNT", mysql_count)
160
+ iostat = @shell.query('IOSTAT', "iostat -c|sed -n 4p|awk '#{print $4}'")
161
+ io[:wait] = iostat.to_f
268
162
 
269
- mysql_size = "du -s /var/lib/mysql 2>/dev/null | awk '{print $1}'"
270
- db[:size] = @shell.query("DB_MYSQL_SIZE", mysql_size)
163
+ up = @shell.query('UPTIME', "uptime|sed -e 's/.*up\\([^,]*\\),.*/\\1/'")
164
+ io[:uptime] = up.chomp
165
+ end
271
166
 
272
- db[:count] = db[:count].to_i
273
- db[:size] = db[:size].to_i
167
+ # Internal: Determine IPv4 addresses in use by the target host, splitting
168
+ # them into public and private groups.
169
+ #
170
+ # Returns nothing.
171
+ def determine_ips
172
+ ips = @info[:ip] = {private: [], public: []}
274
173
 
275
- @info[:db] = db
276
- end
174
+ ifc_cmd = "/sbin/ifconfig|grep 'inet addr'|grep -v ':127'|sed -e " \
175
+ "'s/.*addr:\([0-9.]*\) .*/\\1/'"
176
+ ifconfig = @shell.query('IFCONFIG', ifc_cmd)
277
177
 
278
- # Public: Gather information about the currently installed libc, ruby, perl,
279
- # python and php versions on the host, then set these values to @info[:lib].
280
- #
281
- # Returns nothing.
282
- def determine_lib
283
- lib = {}
178
+ ifconfig.each_line do |ip|
179
+ ip.strip!
180
+ ips[rfc1918?(ip)] << ip
181
+ end
182
+ end
284
183
 
285
- libc_command = %w{ls -al `find /lib /usr/lib -name 'libc.so*' | head -1` |
286
- sed 's/.*-> //'}.join(' ')
287
- lib[:libc] = @shell.query("LIBC", libc_command)
288
- lib[:libc].gsub!(/^.*-|\.so$/, '')
184
+ # Internal: Determine common libraries installed on the system.
185
+ #
186
+ # Returns nothing.
187
+ def determine_libraries
188
+ lib = @info[:lib] = {}
289
189
 
290
- lib[:perl] = @shell.query("PERL", "perl -e 'print $^V;'")
291
- lib[:perl] = lib[:perl].gsub(/^v([0-9.]*).*/, '\1')
190
+ libc_cmd = "ls -la `find /lib /usr/lib -name 'libc.so*'|head -1`|" \
191
+ "sed 's/.*-> //'"
192
+ lib[:libc] = @shell.query('LIBC', libc_cmd)
193
+ lib[:libc].gsub!(/^.*-|\.so$/, '')
292
194
 
293
- python_command = "python -c 'import sys; print sys.version' 2>/dev/null"
294
- lib[:python] = @shell.query("PYTHON", python_command)
295
- lib[:python] = lib[:python].gsub(/^([0-9.]*).*/m, '\1')
195
+ lib[:perl] = @shell.query('PERL', 'perl -e "print $^V;"')
196
+ lib[:perl].gsub!(/^v([0-9.]*).*/, '\1')
296
197
 
297
- ruby_command = "ruby -e 'print RUBY_VERSION' 2>/dev/null"
298
- lib[:ruby] = @shell.query("RUBY", ruby_command)
198
+ python_cmd = 'python -c "import sys; print sys.version" 2>/dev/null'
199
+ lib[:python] = @shell.query('PYTHON', python_cmd)
200
+ lib[:python].gsub!(/([0-9.]*).*/m, '\1')
299
201
 
300
- lib[:php] = @shell.query("PHP_VER", "php -v 2>/dev/null | head -1")
301
- lib[:php] = lib[:php].gsub(/^PHP ([0-9.]*).*/, '\1')
202
+ ruby_cmd = 'ruby -e "print RUBY_VERSION" 2>/dev/null'
203
+ lib[:ruby] = @shell.query('RUBY', ruby_cmd)
302
204
 
303
- @info[:lib] = lib
304
- end
205
+ lib[:php] = @shell.query('PHP', 'php -v 2>/dev/null|head -1')
206
+ lib[:php].gsub!(/^PHP ([0-9.]*).*/, '\1')
207
+ end
305
208
 
306
- # Public: Check for the existence of rsync(1) on the host. Set @info[:rsync]
307
- # accordingly.
308
- #
309
- # Returns nothing.
310
- def determine_rsync
311
- rsync_command = %w{which rsync 2>/dev/null || ([ -f
312
- /root/.rackspace/rsync ] && printf
313
- '/root/.rackspace/rsync') || printf
314
- 'NONE'}.join(' ')
209
+ # Internal: Determine the total amount of memory on the target host, the
210
+ # amount of memory in use, and the amount of swap space being used.
211
+ #
212
+ # Returns nothing.
213
+ def determine_memory
214
+ result = @info[:memory] = {}
215
+
216
+ free_cmd = "free -m|awk '$1 ~ /Mem/ {print $2, $2-$6-$7}; $1 ~ /Swap/ " \
217
+ "{print $3}'|xargs"
218
+ mem = @shell.query('MEMORY', free_cmd)
219
+ total, used, swap = mem.split(/\s+/)
220
+
221
+ result[:total] = total.to_i
222
+ result[:mem_used] = used.to_i
223
+ result[:swap_used] = swap.to_i
224
+ result[:swapping?] = swqp.to_i > 0
225
+ end
315
226
 
316
- rsync = @shell.query("RSYNC", rsync_command)
227
+ # Internal: If the sysstat suite is installed on the target host, determine
228
+ # the average amount of memory and swap used over whatever historical
229
+ # period sar is able to represent.
230
+ #
231
+ # Returns nothing.
232
+ def determine_memory_history
233
+ result = @info[:memory_hist] = {}
234
+ sar_cmd = "for l in $(find /var/log -name 'sa??');do sar -r -f $l|" \
235
+ "grep Average;done|awk '{I+=1;TOT=$2+$3;CACHE+=$5+$6;" \
236
+ "FREE+=$2;SWAP+=$9;} END {CACHE=CACHE/I;FREE=FREE/I;" \
237
+ "SWAP=SWAP/I;print (TOT-(CACHE+FREE))/TOT*100,SWAP;}'"
238
+
239
+ sar_location = @shell.query('SAR_LOCATION', 'which sar 2>/dev/null')
240
+ if sar_location =~ /bin\//
241
+ sar_usage = @shell.query('SAR', sar_cmd)
242
+
243
+ if sar_usage =~/\d \d/
244
+ mem, swap = sar_usage.split(/ /)
245
+ result[:mem_used] = mem
246
+ result[:swap_used] = swap
247
+ end
248
+ end
249
+ end
317
250
 
318
- @info[:rsync] = (rsync =~ /NONE/).nil? ? rsync : false
319
- end
251
+ # Internal: Gather a list of all listening ports on the target host.
252
+ #
253
+ # Returns nothing.
254
+ def determine_ports
255
+ ports = @info[:ports] = {}
256
+
257
+ netstat = @shell.query('NETSTAT', "netstat -ntlp|awk '{print $4, $NF}'")
258
+ netstat.lines.each do |line|
259
+ net, process = line.split(/\s+/, 2)
260
+ process = process.split(/\//, 2)[1]
261
+ net = net.gsub(/([0-9.:]+):([0-9]+)/, '\1 \2')
262
+ net, port = net.split(/ /, 2)
263
+
264
+ ports[net] ||= {}
265
+ ports[net][port] = process
266
+ end
267
+ end
320
268
 
321
- # Public: Check the process listing and store it in
322
- #
323
- # Returns nothing.
324
- def determine_processes
325
- ps_command = 'ps aux'
269
+ # Internal: Gather a list of running processes on the target host.
270
+ #
271
+ # Returns nothing.
272
+ def determine_processes
273
+ procs = @shell.query('PROCESSES', 'ps aux')
274
+ @info[:processes] = procs.gsub(/\r/, '').split(/\n/)
275
+ end
326
276
 
327
- processes = @shell.query("PS_LIST", ps_command)
328
- @info[:processes] = processes.gsub(/\r/, '').split(/\n/)
277
+ # Internal: Locate rsync on the target host.
278
+ #
279
+ # Returns nothing.
280
+ def determine_rsync
281
+ rsync = @shell.query('RSYNC', 'which rsync 2>/dev/null')
282
+
283
+ if rsync.empty?
284
+ rsync_cmd = '[ -f /root/.cloudflock/rsync ] && printf ' \
285
+ '"/root/.rackspace/rsync"'
286
+ rsync = @shell.query('LOCAL_RSYNC', rsync_cmd)
287
+ rsync = nil if rsync.empty?
288
+ end
329
289
 
330
- unless @info[:processes].grep(/psa/i).empty?
331
- @warnings << "Server likely to be running Plesk"
290
+ @info[:rsync] = rsync
332
291
  end
333
- unless @info[:processes].grep(/cpanellogd/i).empty?
334
- @warnings << "Server likely to be running cPanel"
292
+
293
+ # Internal: Determine the architecture of the target host.
294
+ #
295
+ # Returns nothing.
296
+ def determine_system_architecture
297
+ @info[:arch] = @shell.query('UNAME', 'uname -m')
298
+ @info[:arch].gsub!(/i\d86/, 'i386')
335
299
  end
336
- end
337
300
 
338
- # Public: Simplify access to @info.
339
- #
340
- # key - Key to check in @info.
341
- #
342
- # Return value contained in @info by key.
343
- def [](key)
344
- @info[key]
345
- end
301
+ # Internal: Determine which web server, if any, is running on the target
302
+ # host. If the web server is supported, discover how many HTTP/HTTPS
303
+ # domains are configured on the server.
304
+ #
305
+ # Returns nothing.
306
+ def determine_web
307
+ web = @info[:web] = {}
308
+ netstat_cmd = 'netstat -ntlp|awk \'$4 ~ /:80$/ || $4 ~ /:443$/ ' \
309
+ '{sub (/^[^\/]*\//, ""); print $NF}\'|head -1'
310
+ web[:binary] = @shell.query('WEB_NETSTAT', netstat_command)
311
+
312
+ unless web[:binary].empty?
313
+ if web[:binary] == 'httpd' || web[:binary] == 'apache2'
314
+ version_cmd = "`which #{web[:binary]}` -v|grep version"
315
+ web[:version] = @shell.query('WEB_VERSION', version_cmd)
316
+ web[:version].gsub!(/.*version: /i, '')
317
+
318
+ ctl_cmd = web[:binary] == 'httpd' ? 'apachectl' : 'apache2ctl'
319
+ ctl_cmd << ' -S 2>&1'
320
+
321
+ web_cmd = "#{ctl_cmd}|grep -vi 'default'|wc -l"
322
+ hosts = @shell.query('WEB_HOSTS', web_cmd)
323
+
324
+ ssl = hosts.lines.select { |line| line =~ /:443([^\d]|$)/ }
325
+ http = hosts.lines - ssl
326
+ web[:hosts_http] = http.length
327
+ web[:hosts_https] = https.length
328
+ end
329
+ end
330
+ end
346
331
 
347
- # Public: Allow access to the list of keys extant in @info.
348
- #
349
- # Return an Array of keys present in @info.
350
- def keys
351
- @info.keys
352
- end
332
+ # Internal: Check for signs of running Plesk.
333
+ #
334
+ # Returns nothing.
335
+ def warning_plesk
336
+ unless @info[:processes].to_a.grep(/psa/i).empty?
337
+ @warnings << "Server likely to be running Plesk"
338
+ end
339
+ end
353
340
 
354
- # Public: Return server information and warnings as a Hash. Useful for
355
- # calling Hash#merge.
356
- #
357
- # Return info Hash.
358
- def to_hash
359
- @info.merge({warnings: @warnings})
360
- end
341
+ # Internal: Check for signs of running cPanel.
342
+ #
343
+ # Returns nothing.
344
+ def warning_webmin
345
+ unless @info[:processes].to_a.grep(/cpanel/i).empty?
346
+ @warnings << "Server likely to be running cPanel"
347
+ end
348
+ end
361
349
 
362
- # Internal: Deconstruct the version String provided in order to strip out
363
- # extraneous data before and after the version number. Version number
364
- # strings are defined as beginning with a digit, ending with a digit, and
365
- # containing nothing but digits and at most one decimal point.
366
- #
367
- # version - A String containing version number of the OS on the target host.
368
- #
369
- # Returns a String containing the parsed version string.
370
- def version_number(version)
371
- if version =~ /\d/
372
- version.gsub(/^[^\d]*/, '').gsub(/[^\d]*$/, '').gsub(/(\d*\.\d*).*/, '\1')
373
- else
374
- "-"
350
+ # Internal: Search for names of supported Linux distributions in a string
351
+ # which may contain the name of the distribution currently installed on
352
+ # the target host.
353
+ #
354
+ # Returns a String.
355
+ def distro_name(str)
356
+ SUPPORTED_DISTROS.select do |distro|
357
+ Regexp.new(distro, Regexp::IGNORECASE).match(str)
358
+ end[0].to_s
375
359
  end
376
- end
377
360
 
378
- # Internal: Determine whether a given IP resides in a block designated as
379
- # private by RFC1918.
380
- #
381
- # ip - A String containing an IP address.
382
- #
383
- # Returns :private or :public Symbol based on whether the IP is private.
384
- def rfc1918?(ip)
385
- octets = ip.split /\./
386
- if octets[0] == "10" || (octets[0] == "192" && octets[1] == "168")
387
- return :private
388
- elsif octets[0] == "172" && octets[1].to_i >=16 && octets[1].to_i <= 31
389
- return :private
361
+ # Internal: Inspect a String which may contain information regarding the
362
+ # version of the Linux distribution running on the target host.
363
+ #
364
+ # Returns nothing.
365
+ def version_number(str)
366
+ if str =~ /\d/
367
+ str.gsub(/^[^\d]*/, '').gsub(/[^\d]*$/, '').gsub(/(\d*\.\d*).*/, '\1')
368
+ else
369
+ '-'
370
+ end
390
371
  end
391
372
 
392
- :public
373
+ # Internal: Determine if a v4 IP address belongs to a private (RFC 1918)
374
+ # network.
375
+ #
376
+ # ip - String containing an IP.
377
+ #
378
+ # Returns either the symbol :public or :private.
379
+ def rfc1918?(ip)
380
+ return :private if Addrinfo.ip(ip).ipv4_private?
381
+
382
+ :public
383
+ end
393
384
  end
394
- end
385
+ end; end; end