cloudflock 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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