cloudflock 0.5.1 → 0.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.
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'fog'
|
2
|
+
require 'socket'
|
3
|
+
require 'tempfile'
|
2
4
|
require 'cloudflock/target/servers'
|
3
5
|
require 'cloudflock/interface/cli/app/common/servers'
|
4
6
|
|
@@ -303,12 +305,19 @@ class CloudFlock::Interface::CLI::App::Servers::Migrate
|
|
303
305
|
end
|
304
306
|
end
|
305
307
|
|
308
|
+
CLI.spinner "Logging out of source host" do
|
309
|
+
source_host_ssh.logout!
|
310
|
+
end
|
311
|
+
|
306
312
|
CLI.spinner "Cleaning up destination host" do
|
307
313
|
Migrate.clean_destination(destination_host_ssh, source_profile[:cpe])
|
308
314
|
end
|
309
315
|
|
310
|
-
[
|
311
|
-
|
316
|
+
ip_list = determine_ips(source_profile[:ip][:public])
|
317
|
+
ip_list.each { |ip| remediate_ip_config(destination_host_ssh, ip, destination_host_def[:host]) }
|
318
|
+
|
319
|
+
CLI.spinner "Logging out of destination host" do
|
320
|
+
source_host_ssh.logout!
|
312
321
|
end
|
313
322
|
|
314
323
|
puts
|
@@ -330,8 +339,6 @@ class CloudFlock::Interface::CLI::App::Servers::Migrate
|
|
330
339
|
default_answer: "N")
|
331
340
|
|
332
341
|
if alter
|
333
|
-
require 'tempfile'
|
334
|
-
|
335
342
|
tmp_file = Tempfile.new("exclude")
|
336
343
|
tmp_file.write(exclusion_string)
|
337
344
|
tmp_file.close
|
@@ -352,4 +359,133 @@ class CloudFlock::Interface::CLI::App::Servers::Migrate
|
|
352
359
|
|
353
360
|
exclusion_string
|
354
361
|
end
|
362
|
+
|
363
|
+
# Internal: Show the list of IPs detected and ascertain whether to perform
|
364
|
+
# remediation of configuration files.
|
365
|
+
#
|
366
|
+
# ip_list - Array containing Strings containing IP addresses detected from
|
367
|
+
# the source host.
|
368
|
+
#
|
369
|
+
# Returns an Array of Strings containing IPs to consider for remediation.
|
370
|
+
# Raises an ArgumentError if ip_list is not an Array.
|
371
|
+
def determine_ips(ip_list)
|
372
|
+
unless ip_list.kind_of?(Array)
|
373
|
+
raise ArgumentError, "Expected an Array, got a #{ip_list.class.name}"
|
374
|
+
end
|
375
|
+
return([]) if ip_list.empty?
|
376
|
+
|
377
|
+
puts "IP Addresses detected for remediation:"
|
378
|
+
puts ip_list
|
379
|
+
|
380
|
+
if CLI.prompt_yn("Edit IP list? (Y/N)", default_answer: "N")
|
381
|
+
tmp_file = Tempfile.new("ips")
|
382
|
+
tmp_file.write(ip_list.join("\n"))
|
383
|
+
tmp_file.close
|
384
|
+
|
385
|
+
launch_editor(tmp_file.path)
|
386
|
+
tmp_file.open
|
387
|
+
|
388
|
+
ip_list = tmp_file.lines.select do |ip|
|
389
|
+
begin
|
390
|
+
Socket.getaddrinfo(ip, nil, nil, Socket::SOCK_STREAM)[0][3]
|
391
|
+
rescue SocketError
|
392
|
+
false
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
tmp_file.close
|
397
|
+
tmp_file.unlink
|
398
|
+
end
|
399
|
+
|
400
|
+
ip_list
|
401
|
+
end
|
402
|
+
|
403
|
+
# Internal: Find instances of a given IP in configuration files and
|
404
|
+
# selectively replace them with an IP available on the target system.
|
405
|
+
#
|
406
|
+
# destination_shell - CloudFlock::Remote::SSH object connected to the
|
407
|
+
# destination host.
|
408
|
+
# source_ip - String containing an IP.
|
409
|
+
# destination_ip - String containing primary IP detected for the host.
|
410
|
+
#
|
411
|
+
# Returns nothing.
|
412
|
+
# Raises ArgumentError if source_ip is not a String.
|
413
|
+
def remediate_ip_config(destination_shell, source_ip, destination_ip)
|
414
|
+
unless destination_shell.kind_of?(CloudFlock::Remote::SSH)
|
415
|
+
raise ArgumentError, "Expected an SSH session, got a " +
|
416
|
+
"#{destination_shell.class.name}"
|
417
|
+
end
|
418
|
+
unless source_ip.kind_of?(String)
|
419
|
+
raise ArgumentError, "Expected a String, got a #{source_ip.class.name}"
|
420
|
+
end
|
421
|
+
source_ip = Socket.getaddrinfo(source_ip, nil, nil,
|
422
|
+
Socket::SOCK_STREAM)[0][3]
|
423
|
+
|
424
|
+
grep_command = "grep -rl #{source_ip} /mnt/migration_target/etc " +
|
425
|
+
"2>/dev/null | grep -v log"
|
426
|
+
files = destination_shell.query("IP_CHECK", grep_command)
|
427
|
+
unless files.strip.empty?
|
428
|
+
tmp_file = Tempfile.new("filelist")
|
429
|
+
tmp_file.write("Configuration files found for #{source_ip}:\n\n#{files}")
|
430
|
+
tmp_file.close
|
431
|
+
system("less -C #{tmp_file.path}")
|
432
|
+
tmp_file.unlink
|
433
|
+
|
434
|
+
proceed = CLI.prompt_yn("Replace #{source_ip} in these files? (Y/N)",
|
435
|
+
default_answer: "Y")
|
436
|
+
if proceed
|
437
|
+
edit = false
|
438
|
+
else
|
439
|
+
edit = CLI.prompt_yn("Edit the list? (Y/N)", default: "Y")
|
440
|
+
end
|
441
|
+
|
442
|
+
if edit
|
443
|
+
tmp_file = Tempfile.new("filelist")
|
444
|
+
tmp_file.write("#{files}")
|
445
|
+
tmp_file.close
|
446
|
+
launch_editor(tmp_file.path)
|
447
|
+
tmp_file.open
|
448
|
+
files = tmp_file.read
|
449
|
+
tmp_file.unlink
|
450
|
+
proceed = true
|
451
|
+
end
|
452
|
+
|
453
|
+
if proceed
|
454
|
+
replacement_ip = CLI.prompt("IP to replace #{source_ip}",
|
455
|
+
default_answer: destination_ip)
|
456
|
+
CLI.spinner("Remediating configuration files for #{source_ip}") do
|
457
|
+
files.each_line do |file|
|
458
|
+
file.strip!
|
459
|
+
next if file.empty?
|
460
|
+
file.gsub!(/'/, "\\'")
|
461
|
+
|
462
|
+
sed_command = "sed -i 's/#{source_ip}/#{replacement_ip}/' '#{file}'"
|
463
|
+
destination_shell.puts(sed_command)
|
464
|
+
destination_shell.prompt
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Internal: Launch the user's editor to edit a file.
|
472
|
+
#
|
473
|
+
# path - String containing the path to the file to edit
|
474
|
+
#
|
475
|
+
# Returns nothing.
|
476
|
+
# Raises Errno::ENOENT if the path does not point to an existing file.
|
477
|
+
def launch_editor(path)
|
478
|
+
unless File.exists?(path)
|
479
|
+
raise Errno::ENOENT, "Unable to open #{path}"
|
480
|
+
end
|
481
|
+
|
482
|
+
# Allow for "other" editors
|
483
|
+
if File.exists?("/usr/bin/editor")
|
484
|
+
editor = "/usr/bin/editor"
|
485
|
+
else
|
486
|
+
editor = "vim"
|
487
|
+
end
|
488
|
+
|
489
|
+
system("#{editor} #{path}")
|
490
|
+
end
|
355
491
|
end
|
@@ -211,55 +211,6 @@ module CloudFlock::Target::Servers::Migrate extend self
|
|
211
211
|
end
|
212
212
|
end
|
213
213
|
|
214
|
-
# Internal: Execute rsync and return true if everything appears to have completed successfully
|
215
|
-
#
|
216
|
-
# source_host - SSH object logged in to the source host.
|
217
|
-
# args - Hash containing additional parameters for operation.
|
218
|
-
# Expected parameters are:
|
219
|
-
# :target_addr - String containing the address to use when
|
220
|
-
# communicating with the destination host.
|
221
|
-
# :rsync - String containing path to rsync binary on
|
222
|
-
# the source machine. If this is nil, copy
|
223
|
-
# rsync from the destination machine to
|
224
|
-
# /root/.rackspace/ for the purposes of
|
225
|
-
# carrying out the migration.
|
226
|
-
# (default: nil)
|
227
|
-
# :timeout - Fixnum containing the number of seconds
|
228
|
-
# to wait before reporting failure/hung
|
229
|
-
# rsync process. If this is set to -1, a
|
230
|
-
# failure will never be reported--use
|
231
|
-
# Watchdogs in this case to prevent
|
232
|
-
# indefinite migrations. (default: 14400)
|
233
|
-
#
|
234
|
-
# Returns true if rsync finishes.
|
235
|
-
# Returns false if rsync does not complete within timeout.
|
236
|
-
def migration_watcher(source_host, args)
|
237
|
-
rsync_command = "#{args[:rsync]} -azP -e 'ssh " +
|
238
|
-
"#{CloudFlock::Remote::SSH::SSH_ARGUMENTS} -i " +
|
239
|
-
"/tmp/RACKSPACE_MIGRATION/migration_id_rsa' " +
|
240
|
-
"--exclude-from='/root/.rackspace/" +
|
241
|
-
"migration_exceptions.txt' / " +
|
242
|
-
"root@#{args[:target_addr]}:/mnt/migration_target"
|
243
|
-
source_host.puts(rsync_command)
|
244
|
-
|
245
|
-
source_host.set_timeout(60)
|
246
|
-
if(args[:timeout] >= 0)
|
247
|
-
i = args[:timeout]/60 + 1
|
248
|
-
else
|
249
|
-
i = -1
|
250
|
-
end
|
251
|
-
|
252
|
-
begin
|
253
|
-
source_host.prompt
|
254
|
-
rescue Timeout::Error
|
255
|
-
i -= 1
|
256
|
-
retry unless i == 0
|
257
|
-
return false
|
258
|
-
end
|
259
|
-
|
260
|
-
true
|
261
|
-
end
|
262
|
-
|
263
214
|
# Public: Build exclusions list from generic and targeted exclusions
|
264
215
|
# definitions per CPE.
|
265
216
|
#
|
@@ -319,35 +270,6 @@ module CloudFlock::Target::Servers::Migrate extend self
|
|
319
270
|
true
|
320
271
|
end
|
321
272
|
|
322
|
-
# Internal: Create user and restore entries from backup passwd and shadow
|
323
|
-
# files.
|
324
|
-
#
|
325
|
-
# destination_host - SSH object logged in to the host on which to restore a
|
326
|
-
# user.
|
327
|
-
# username - String containing the user to restore.
|
328
|
-
#
|
329
|
-
# Returns true if success, false otherwise.
|
330
|
-
def restore_user(destination_host, username)
|
331
|
-
username.strip!
|
332
|
-
sanity_check = "(grep '^#{username}:' /etc/migration.passwd && grep " +
|
333
|
-
"'^#{username}:' /etc/migration.shadow) >/dev/null " +
|
334
|
-
"2>/dev/null && printf 'PRESENT'"
|
335
|
-
|
336
|
-
sane = destination_host.query("USER_CHECK", sanity_check)
|
337
|
-
return false if sane.empty?
|
338
|
-
|
339
|
-
steps = ["useradd #{username}",
|
340
|
-
"chown -R #{username}.#{username} /home/#{username}",
|
341
|
-
"sed -i '/^#{username}:.*$/d' /etc/shadow",
|
342
|
-
"grep '^#{username}:' /etc/migration.shadow >> /etc/shadow"]
|
343
|
-
steps.each do |step|
|
344
|
-
destination_host.puts(step)
|
345
|
-
destination_host.prompt
|
346
|
-
end
|
347
|
-
|
348
|
-
true
|
349
|
-
end
|
350
|
-
|
351
273
|
# Public: Perform post-migration clean up of a destination host. Base
|
352
274
|
# clean up off of cleanup scripts located at data/cleanup/.
|
353
275
|
#
|
@@ -417,6 +339,84 @@ module CloudFlock::Target::Servers::Migrate extend self
|
|
417
339
|
long_run(destination_host, "/bin/bash /root/migration_clean_post.sh")
|
418
340
|
end
|
419
341
|
|
342
|
+
# Internal: Execute rsync and return true if everything appears to have completed successfully
|
343
|
+
#
|
344
|
+
# source_host - SSH object logged in to the source host.
|
345
|
+
# args - Hash containing additional parameters for operation.
|
346
|
+
# Expected parameters are:
|
347
|
+
# :target_addr - String containing the address to use when
|
348
|
+
# communicating with the destination host.
|
349
|
+
# :rsync - String containing path to rsync binary on
|
350
|
+
# the source machine. If this is nil, copy
|
351
|
+
# rsync from the destination machine to
|
352
|
+
# /root/.rackspace/ for the purposes of
|
353
|
+
# carrying out the migration.
|
354
|
+
# (default: nil)
|
355
|
+
# :timeout - Fixnum containing the number of seconds
|
356
|
+
# to wait before reporting failure/hung
|
357
|
+
# rsync process. If this is set to -1, a
|
358
|
+
# failure will never be reported--use
|
359
|
+
# Watchdogs in this case to prevent
|
360
|
+
# indefinite migrations. (default: 14400)
|
361
|
+
#
|
362
|
+
# Returns true if rsync finishes.
|
363
|
+
# Returns false if rsync does not complete within timeout.
|
364
|
+
def migration_watcher(source_host, args)
|
365
|
+
rsync_command = "#{args[:rsync]} -azP -e 'ssh " +
|
366
|
+
"#{CloudFlock::Remote::SSH::SSH_ARGUMENTS} -i " +
|
367
|
+
"/tmp/RACKSPACE_MIGRATION/migration_id_rsa' " +
|
368
|
+
"--exclude-from='/root/.rackspace/" +
|
369
|
+
"migration_exceptions.txt' / " +
|
370
|
+
"root@#{args[:target_addr]}:/mnt/migration_target"
|
371
|
+
source_host.puts(rsync_command)
|
372
|
+
|
373
|
+
source_host.set_timeout(60)
|
374
|
+
if(args[:timeout] >= 0)
|
375
|
+
i = args[:timeout]/60 + 1
|
376
|
+
else
|
377
|
+
i = -1
|
378
|
+
end
|
379
|
+
|
380
|
+
begin
|
381
|
+
source_host.prompt
|
382
|
+
rescue Timeout::Error
|
383
|
+
i -= 1
|
384
|
+
retry unless i == 0
|
385
|
+
return false
|
386
|
+
end
|
387
|
+
|
388
|
+
true
|
389
|
+
end
|
390
|
+
|
391
|
+
# Internal: Create user and restore entries from backup passwd and shadow
|
392
|
+
# files.
|
393
|
+
#
|
394
|
+
# destination_host - SSH object logged in to the host on which to restore a
|
395
|
+
# user.
|
396
|
+
# username - String containing the user to restore.
|
397
|
+
#
|
398
|
+
# Returns true if success, false otherwise.
|
399
|
+
def restore_user(destination_host, username)
|
400
|
+
username.strip!
|
401
|
+
sanity_check = "(grep '^#{username}:' /etc/migration.passwd && grep " +
|
402
|
+
"'^#{username}:' /etc/migration.shadow) >/dev/null " +
|
403
|
+
"2>/dev/null && printf 'PRESENT'"
|
404
|
+
|
405
|
+
sane = destination_host.query("USER_CHECK", sanity_check)
|
406
|
+
return false if sane.empty?
|
407
|
+
|
408
|
+
steps = ["useradd #{username}",
|
409
|
+
"chown -R #{username}.#{username} /home/#{username}",
|
410
|
+
"sed -i '/^#{username}:.*$/d' /etc/shadow",
|
411
|
+
"grep '^#{username}:' /etc/migration.shadow >> /etc/shadow"]
|
412
|
+
steps.each do |step|
|
413
|
+
destination_host.puts(step)
|
414
|
+
destination_host.prompt
|
415
|
+
end
|
416
|
+
|
417
|
+
true
|
418
|
+
end
|
419
|
+
|
420
420
|
# Internal: Insure that new output is being produced by a running process
|
421
421
|
# which is expected to run over an indeterminate amount of time to catch
|
422
422
|
# hanging processes, but not punish properly running ones.
|
data/lib/cloudflock/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudflock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05
|
12
|
+
date: 2013-06-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 1.11.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: expectr
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|