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
- [destination_host_ssh, source_host_ssh].each do |host|
311
- host.logout!
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.
@@ -1,3 +1,3 @@
1
1
  module CloudFlock
2
- VERSION = '0.5.1'
2
+ VERSION = '0.6.0'
3
3
  end
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.5.1
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-23 00:00:00.000000000 Z
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