cloudflock 0.4.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.
- data/bin/flock +32 -0
- data/bin/flock-profile +17 -0
- data/bin/flock-servers +24 -0
- data/bin/flock.default +24 -0
- data/lib/cloudflock/error.rb +26 -0
- data/lib/cloudflock/interface/cli/app/common/servers.rb +128 -0
- data/lib/cloudflock/interface/cli/app/servers/migrate.rb +357 -0
- data/lib/cloudflock/interface/cli/app/servers/profile.rb +88 -0
- data/lib/cloudflock/interface/cli/app/servers.rb +2 -0
- data/lib/cloudflock/interface/cli/console.rb +213 -0
- data/lib/cloudflock/interface/cli/opts/servers.rb +20 -0
- data/lib/cloudflock/interface/cli/opts.rb +87 -0
- data/lib/cloudflock/interface/cli.rb +15 -0
- data/lib/cloudflock/patch/fog.rb +113 -0
- data/lib/cloudflock/remote/ssh.rb +311 -0
- data/lib/cloudflock/target/servers/data/exceptions/base.txt +44 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/amazon.txt +10 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/debian.txt +0 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/redhat.txt +5 -0
- data/lib/cloudflock/target/servers/data/exceptions/platform/suse.txt +1 -0
- data/lib/cloudflock/target/servers/data/post-migration/chroot/base.txt +1 -0
- data/lib/cloudflock/target/servers/data/post-migration/chroot/platform/amazon.txt +19 -0
- data/lib/cloudflock/target/servers/data/post-migration/pre/base.txt +3 -0
- data/lib/cloudflock/target/servers/data/post-migration/pre/platform/amazon.txt +4 -0
- data/lib/cloudflock/target/servers/migrate.rb +466 -0
- data/lib/cloudflock/target/servers/platform/v1.rb +97 -0
- data/lib/cloudflock/target/servers/platform/v2.rb +93 -0
- data/lib/cloudflock/target/servers/platform.rb +133 -0
- data/lib/cloudflock/target/servers/profile.rb +394 -0
- data/lib/cloudflock/target/servers.rb +5 -0
- data/lib/cloudflock/version.rb +3 -0
- data/lib/cloudflock.rb +10 -0
- metadata +128 -0
data/bin/flock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
current_path = File.expand_path('../', __FILE__)
|
4
|
+
function = ARGV.shift.to_s
|
5
|
+
bin = File.basename($0, '.dev')
|
6
|
+
|
7
|
+
if function.match(/^[^-]/)
|
8
|
+
target = "#{current_path}/#{bin}-#{function}"
|
9
|
+
else
|
10
|
+
ARGV.unshift(function)
|
11
|
+
target = "#{current_path}/#{bin}.default"
|
12
|
+
end
|
13
|
+
|
14
|
+
if File.exists?(target)
|
15
|
+
load target
|
16
|
+
else
|
17
|
+
print "Function \"#{function}\" not found. " unless target =~ /\.default$/
|
18
|
+
puts "Available functions:"
|
19
|
+
|
20
|
+
functions = []
|
21
|
+
Dir.foreach(current_path) do |file|
|
22
|
+
next unless file.match(/^#{bin}-[^.]*$/)
|
23
|
+
functions << file.gsub(/^#{bin}-/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
until functions.empty?
|
27
|
+
a, b, c = functions.shift(3)
|
28
|
+
puts "%-15s %-15s %-15s" % [a, b, c].map { |i| i.to_s }
|
29
|
+
end
|
30
|
+
|
31
|
+
exit 1
|
32
|
+
end
|
data/bin/flock-profile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/profile'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse
|
14
|
+
options[:function] = :profile
|
15
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
16
|
+
|
17
|
+
CloudFlock::Interface::CLI::App::Servers::Profile.new(options)
|
data/bin/flock-servers
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/migrate'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse(:servers)
|
14
|
+
type_prompt = "Type of migration (servers, opencloud)?"
|
15
|
+
|
16
|
+
if options[:function].nil?
|
17
|
+
options[:function] = CLI.prompt(type_prompt, valid_answers:
|
18
|
+
["servers", "opencloud"],
|
19
|
+
default_answer: "opencloud")
|
20
|
+
end
|
21
|
+
options[:function] = options[:function].to_sym
|
22
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
23
|
+
|
24
|
+
CloudFlock::Interface::CLI::App::Servers::Migrate.new(options)
|
data/bin/flock.default
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cloudflock/interface/cli/app/servers/migrate'
|
4
|
+
CLI = CloudFlock::Interface::CLI::Console
|
5
|
+
Opts = CloudFlock::Interface::CLI::Opts
|
6
|
+
|
7
|
+
# Trap C-c to kill the application in a friendly manner
|
8
|
+
trap 'INT' do
|
9
|
+
puts "\nCaught SIGINT, exiting..."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = Opts.parse(:servers)
|
14
|
+
type_prompt = "Type of migration (servers, opencloud)?"
|
15
|
+
|
16
|
+
if options[:function].nil?
|
17
|
+
options[:function] = CLI.prompt(type_prompt, valid_answers:
|
18
|
+
["servers", "opencloud"],
|
19
|
+
default_answer: "opencloud")
|
20
|
+
end
|
21
|
+
options[:function] = options[:function].to_sym
|
22
|
+
options[:config] = Opts.parse_config_file(options[:config_file])
|
23
|
+
|
24
|
+
CloudFlock::Interface::CLI::App::Servers::Migrate.new(options)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CloudFlock
|
2
|
+
module Remote
|
3
|
+
class SSH
|
4
|
+
class InvalidHostname < StandardError; end
|
5
|
+
class ConnectionFailed < StandardError; end
|
6
|
+
class LoginFailed < StandardError; end
|
7
|
+
class RootFailed < StandardError; end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Target
|
12
|
+
module Servers
|
13
|
+
module Migrate
|
14
|
+
class LongRunFailed < StandardError; end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Platform
|
18
|
+
class ValueError < StandardError; end
|
19
|
+
class MapUndefined < NameError; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Profile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'cloudflock'
|
2
|
+
require 'cloudflock/interface/cli'
|
3
|
+
require 'cloudflock/remote/ssh'
|
4
|
+
|
5
|
+
# Public: The ServersCommon module provides common methods for CLI interaction
|
6
|
+
# pertaining to interaction with remote (Unix) servers.
|
7
|
+
module CloudFlock::Interface::CLI::App::Common::Servers
|
8
|
+
include CloudFlock::Target::Servers
|
9
|
+
|
10
|
+
SSH = CloudFlock::Remote::SSH
|
11
|
+
CLI = CloudFlock::Interface::CLI::Console
|
12
|
+
|
13
|
+
# Internal: Collect information about the source server to be migrated.
|
14
|
+
#
|
15
|
+
# opts - Hash containing any applicable options mappings for the server in
|
16
|
+
# question.
|
17
|
+
#
|
18
|
+
# Returns a Hash containing information pertinent to logging in to a host.
|
19
|
+
def define_source(opts)
|
20
|
+
host = {}
|
21
|
+
|
22
|
+
host[:host] = opts[:source_host] || CLI.prompt("Source host")
|
23
|
+
host[:port] = opts[:source_port] || CLI.prompt("Source SSH port",
|
24
|
+
default_answer: "22")
|
25
|
+
host[:username] = opts[:source_user] || CLI.prompt("Source username",
|
26
|
+
default_answer: "root")
|
27
|
+
host[:password] = opts[:source_pass] || CLI.prompt("Source password",
|
28
|
+
default_answer: "")
|
29
|
+
|
30
|
+
until host[:public_key].kind_of?(String)
|
31
|
+
key = opts[:public_key] || CLI.prompt("SSH Key", default_answer: "")
|
32
|
+
if File.file?(File.expand_path(key)) || key.empty?
|
33
|
+
host[:public_key] = key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Only need to use sudo if the user isn't root
|
38
|
+
if host[:username] == "root"
|
39
|
+
host[:sudo] = false
|
40
|
+
elsif !opts[:source_sudo].nil?
|
41
|
+
host[:sudo] = opts[:source_sudo]
|
42
|
+
else
|
43
|
+
host[:sudo] = CLI.prompt_yn("Use sudo? (Y/N)", default_answer: "Y")
|
44
|
+
end
|
45
|
+
|
46
|
+
# We need the root pass if non-root and no sudo
|
47
|
+
if host[:username] == "root" || host[:sudo]
|
48
|
+
host[:root_pass] = host[:password]
|
49
|
+
else
|
50
|
+
host[:root_pass] = CLI.prompt("Password for root")
|
51
|
+
end
|
52
|
+
|
53
|
+
host
|
54
|
+
end
|
55
|
+
|
56
|
+
# Internal: Collect information about the destination server to target in a
|
57
|
+
# migration -- only used for resume functions.
|
58
|
+
#
|
59
|
+
# Returns a Hash containing information pertinent to logging in to a host.
|
60
|
+
def define_destination
|
61
|
+
host = Hash.new
|
62
|
+
|
63
|
+
host[:host] = CLI.prompt("Destination host")
|
64
|
+
host[:password] = CLI.prompt("Destination root password")
|
65
|
+
host[:pre_steps] = CLI.prompt_yn("Perform pre-migration steps? (Y/N)")
|
66
|
+
host[:username] = "root"
|
67
|
+
|
68
|
+
host
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: Initiate a connection to a given host and obtain root privileges.
|
72
|
+
#
|
73
|
+
# host - Hash containing information for connecting to the host:
|
74
|
+
# :host - String containing the location to which to connect.
|
75
|
+
# :port - String or Fixnum containing the port on which ssh
|
76
|
+
# listens. (default: "22")
|
77
|
+
# :username - String containing the username to use when logging in.
|
78
|
+
# :password - String containing the password for the above user.
|
79
|
+
# :sudo - Boolean value defining whether to use sudo to obtain
|
80
|
+
# root. (default: false)
|
81
|
+
# :root_pass - String containing the password to use to obtain root,
|
82
|
+
# if the user isn't root and sudo isn't used.
|
83
|
+
# :verbose - Boolean value defining whether to flush output to
|
84
|
+
# STDOUT. (default: false)
|
85
|
+
#
|
86
|
+
# Returns an SSH object.
|
87
|
+
# Raises ArgumentError unless at least host and user are defined.
|
88
|
+
def host_login(host)
|
89
|
+
if host[:host].nil? || host[:username].nil?
|
90
|
+
raise ArgumentError, "Need at least host and username defined"
|
91
|
+
end
|
92
|
+
|
93
|
+
host[:flush_buffer] = host[:verbose] || false
|
94
|
+
|
95
|
+
ssh = SSH.new(host)
|
96
|
+
ssh.get_root(host[:root_pass], host[:sudo])
|
97
|
+
|
98
|
+
ssh
|
99
|
+
end
|
100
|
+
|
101
|
+
# Internal: Initiate a connection to a destination host.
|
102
|
+
#
|
103
|
+
# host - Hash containing information for connecting to the host:
|
104
|
+
# :host - String containing the location to which to connect.
|
105
|
+
# :password - String containing the password for the above user.
|
106
|
+
# :verbose - Boolean value defining whether to flush output to
|
107
|
+
# STDOUT. (default: false)
|
108
|
+
#
|
109
|
+
# Returns an SSH object.
|
110
|
+
# Raises ArgumentError unless at least host and user are defined.
|
111
|
+
def destination_login(host)
|
112
|
+
host[:username] = "root"
|
113
|
+
message = "Connecting to destination host (password: #{host[:password]})"
|
114
|
+
r = 0
|
115
|
+
|
116
|
+
destination_host = CLI.spinner(message) do
|
117
|
+
begin
|
118
|
+
host_login(host)
|
119
|
+
rescue Timeout::Error
|
120
|
+
if (r += 1) < 5
|
121
|
+
sleep 300
|
122
|
+
retry
|
123
|
+
end
|
124
|
+
raise
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'cloudflock/target/servers'
|
3
|
+
require 'cloudflock/interface/cli/app/common/servers'
|
4
|
+
|
5
|
+
# Public: The Servers::Migrate app provides the interface to Servers migrations
|
6
|
+
# (primarily targeting Managed/Unmanaged Rackspace First-gen and Open Cloud,
|
7
|
+
# but other migrations are possible) on the CLI.
|
8
|
+
class CloudFlock::Interface::CLI::App::Servers::Migrate
|
9
|
+
include CloudFlock::Interface::CLI::App::Common::Servers
|
10
|
+
CLI = CloudFlock::Interface::CLI::Console
|
11
|
+
|
12
|
+
# Public: Begin Servers migration on the command line
|
13
|
+
#
|
14
|
+
# opts - Hash containing options mappings.
|
15
|
+
def initialize(opts)
|
16
|
+
opencloud = (opts[:function] == :opencloud)
|
17
|
+
|
18
|
+
resume = opts[:resume]
|
19
|
+
source_host_def = define_source(opts[:config])
|
20
|
+
source_host_ssh = CLI.spinner("Logging in to #{source_host_def[:host]}") do
|
21
|
+
host_login(source_host_def)
|
22
|
+
end
|
23
|
+
|
24
|
+
source_profile = CLI.spinner("Checking source host") do
|
25
|
+
profile = Profile.new(source_host_ssh)
|
26
|
+
profile.build
|
27
|
+
profile
|
28
|
+
end
|
29
|
+
|
30
|
+
if opencloud
|
31
|
+
target_platform = Platform::V2
|
32
|
+
else
|
33
|
+
target_platform = Platform::V1
|
34
|
+
end
|
35
|
+
platform = target_platform.new(source_profile[:cpe])
|
36
|
+
build_target = platform.build_recommendation(source_profile)
|
37
|
+
flavor_list = target_platform::FLAVOR_LIST
|
38
|
+
default_target = flavor_list[build_target[:flavor]]
|
39
|
+
|
40
|
+
# Generate and display a brief summary of the server Platform/Profile
|
41
|
+
os_tag = source_profile[:cpe].vendor == "Unknown" ? CLI.red : CLI.blue
|
42
|
+
ram_qty = default_target[:mem]
|
43
|
+
hdd_qty = default_target[:hdd]
|
44
|
+
decision_reason = "#{CLI.bold}#{build_target[:flavor_reason]}#{CLI.reset}"
|
45
|
+
|
46
|
+
puts "OS: #{CLI.bold}#{os_tag}#{platform}#{CLI.reset}"
|
47
|
+
puts "---"
|
48
|
+
puts "Recommended server:"
|
49
|
+
puts "RAM: #{CLI.bold} % 6d MiB#{CLI.reset}" % ram_qty
|
50
|
+
puts "HDD: #{CLI.bold} % 7d GB#{CLI.reset}" % hdd_qty
|
51
|
+
puts "The reason for this decision is: #{decision_reason}"
|
52
|
+
puts "---"
|
53
|
+
unless source_profile.warnings.empty?
|
54
|
+
print CLI.red + CLI.bold
|
55
|
+
source_profile.warnings.each { |warning| puts warning }
|
56
|
+
print CLI.reset
|
57
|
+
puts "---"
|
58
|
+
end
|
59
|
+
|
60
|
+
if resume
|
61
|
+
destination_host_def = define_destination
|
62
|
+
migration_exclusions = determine_exclusions(source_profile[:cpe])
|
63
|
+
platform.managed = CLI.prompt_yn("Managed service level? (Y/N)",
|
64
|
+
default_answer: "Y")
|
65
|
+
platform.rack_connect = CLI.prompt_yn("Rack Connected? (Y/N)",
|
66
|
+
default_answer: "N")
|
67
|
+
else
|
68
|
+
api = {}
|
69
|
+
api[:version] = opencloud ? :v2 : :v1
|
70
|
+
|
71
|
+
proceed = CLI.prompt_yn("Spin up this server? (Y/N)", default_answer: "Y")
|
72
|
+
if proceed
|
73
|
+
api[:flavor] = default_target[:id]
|
74
|
+
else
|
75
|
+
puts CLI.build_grid(flavor_list,
|
76
|
+
{id: "ID", mem: "RAM (MiB)", hdd: "HDD (GB)" })
|
77
|
+
api[:flavor] = CLI.prompt("Flavor ID",
|
78
|
+
default_answer: default_target[:id])
|
79
|
+
api[:flavor] = api[:flavor].to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
migration_exclusions = determine_exclusions(source_profile[:cpe])
|
83
|
+
platform.managed = CLI.prompt_yn("Managed service level? (Y/N)",
|
84
|
+
default_answer: "Y")
|
85
|
+
platform.rack_connect = CLI.prompt_yn("Rack Connected? (Y/N)",
|
86
|
+
default_answer: "N")
|
87
|
+
|
88
|
+
# Warn against Rack Connect
|
89
|
+
if platform.rack_connect
|
90
|
+
puts "#{CLI.bold}#{CLI.red}*** Rack Connect servers might not " +
|
91
|
+
"provision properly when spun up from the API! Resume " +
|
92
|
+
"recommended!#{CLI.reset}"
|
93
|
+
sleep 5
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Check to make sure we have a valid flavor ID
|
98
|
+
exit 0 if api[:flavor] == 0 or flavor_list[api[:flavor]-1].nil?
|
99
|
+
|
100
|
+
# Build our API call
|
101
|
+
api[:hostname] = CLI.prompt("New Server Name",
|
102
|
+
default_answer: source_profile[:hostname])
|
103
|
+
|
104
|
+
# OpenCloud only supports US migrations presently
|
105
|
+
if opts[:function] == :opencloud
|
106
|
+
api[:region] = CLI.prompt("Region (dfw, ord)", default_answer: "dfw",
|
107
|
+
valid_answers: ["ord", "dfw"])
|
108
|
+
else
|
109
|
+
api[:region] = :dfw
|
110
|
+
end
|
111
|
+
|
112
|
+
api[:username] = CLI.prompt("RS Cloud Username")
|
113
|
+
api[:api_key] = CLI.prompt("RS Cloud API key")
|
114
|
+
|
115
|
+
# Name cannot have any special characters in it
|
116
|
+
api[:hostname].gsub!(/[^A-Za-z0-9.]/, '-')
|
117
|
+
|
118
|
+
rack_api = Fog::Compute.new(provider: 'rackspace',
|
119
|
+
rackspace_username: api[:username],
|
120
|
+
rackspace_api_key: api[:api_key],
|
121
|
+
rackspace_region: api[:region],
|
122
|
+
version: api[:version])
|
123
|
+
|
124
|
+
# Rescue patch has to be loaded after the connection is created
|
125
|
+
require 'cloudflock/patch/fog'
|
126
|
+
|
127
|
+
# Send API call
|
128
|
+
new_server = CLI.spinner("Spinning up new server: #{api[:hostname]}") do
|
129
|
+
rack_api.servers::create(name: api[:hostname],
|
130
|
+
image_id: platform.image,
|
131
|
+
flavor_id: api[:flavor])
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# Set the destination host address
|
136
|
+
destination_host_def = {}
|
137
|
+
CLI.spinner("Obtaining information for new instance") do
|
138
|
+
# Obtain the administrative pass for the new host.
|
139
|
+
destination_host_def[:password] = new_server.password
|
140
|
+
server_id = new_server.id
|
141
|
+
|
142
|
+
until new_server.state == 'ACTIVE'
|
143
|
+
sleep 30
|
144
|
+
begin
|
145
|
+
new_server.update
|
146
|
+
rescue NoMethodError
|
147
|
+
new_server.reload
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
if opencloud
|
152
|
+
dest_host = new_server.addresses["public"].select do |e|
|
153
|
+
e["version"] == 4
|
154
|
+
end
|
155
|
+
destination_host_def[:host] = dest_host[0]["addr"]
|
156
|
+
else
|
157
|
+
destination_host_def[:host] = new_server.addresses["public"][0]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# If we're working within the Managed service level, ensure that Chef
|
162
|
+
# has finished successfully
|
163
|
+
if platform.managed
|
164
|
+
r = 0
|
165
|
+
destination_host_ssh = destination_login(destination_host_def)
|
166
|
+
|
167
|
+
begin
|
168
|
+
message =
|
169
|
+
finished = CLI.spinner("Waiting for Chef to finish") do
|
170
|
+
# Sleep 180 seconds before trying
|
171
|
+
sleep 180
|
172
|
+
Migrate.setup_managed(destination_host_ssh)
|
173
|
+
end
|
174
|
+
unless finished
|
175
|
+
panic = "#{CLI.bold}#{CLI.red}*** MGC Cloud Scripts appear to " +
|
176
|
+
"have failed to run in a reasonable amount of time." +
|
177
|
+
"#{CLI.reset}"
|
178
|
+
puts panic
|
179
|
+
exit unless CLI.prompt_yn("Continue? (Y/N)", default_answer: "Y")
|
180
|
+
end
|
181
|
+
destination_host_ssh.logout!
|
182
|
+
rescue
|
183
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
184
|
+
"destination host. Bailing out.#{CLI.reset}"
|
185
|
+
puts panic
|
186
|
+
raise
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
if opts[:function] == :opencloud
|
191
|
+
host = destination_host_def[:host]
|
192
|
+
CLI.spinner("Putting #{host} into rescue mode") do
|
193
|
+
new_server.rescue
|
194
|
+
destination_host_def[:password] = new_server.password
|
195
|
+
new_server.update
|
196
|
+
end
|
197
|
+
else
|
198
|
+
pass_prompt = "Please put #{api[:hostname]} into rescue mode and " +
|
199
|
+
"give password"
|
200
|
+
destination_host_def[:password] = CLI.prompt(pass_prompt)
|
201
|
+
end
|
202
|
+
|
203
|
+
CLI.spinner "Letting rescue mode come up..." do
|
204
|
+
until new_server.state == 'RESCUE'
|
205
|
+
sleep 30
|
206
|
+
begin
|
207
|
+
new_server.update
|
208
|
+
rescue NoMethodError
|
209
|
+
sleep 60
|
210
|
+
new_server.reload
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
Thread.new do
|
216
|
+
continue = false
|
217
|
+
until continue
|
218
|
+
r = 0
|
219
|
+
message = "Checking for SSH on #{destination_host_def[:host]}"
|
220
|
+
ssh_command = "ssh #{SSH::SSH_ARGUMENTS} " +
|
221
|
+
"root@#{destination_host_def[:host]}"
|
222
|
+
continue = CLI.spinner(message) do
|
223
|
+
begin
|
224
|
+
sleep 20
|
225
|
+
ssh_expect = Expectr.new(ssh_command, flush_buffer: false)
|
226
|
+
ssh_expect.expect("password")
|
227
|
+
rescue
|
228
|
+
retry if (r+=1) < 10
|
229
|
+
raise
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end.join
|
234
|
+
end
|
235
|
+
|
236
|
+
destination_host_ssh = destination_login(destination_host_def)
|
237
|
+
|
238
|
+
unless destination_host_def[:pre_steps] == false
|
239
|
+
# Attempt to set up the source host 5 times. If there is a failure,
|
240
|
+
# sleep for 60 seconds before retrying.
|
241
|
+
r = 0
|
242
|
+
begin
|
243
|
+
message = "Setting up source host (attempt #{r + 1}/5)"
|
244
|
+
pubkey = CLI.spinner(message) do
|
245
|
+
begin
|
246
|
+
message.gsub!(/\d\/5/, "#{r + 1}/5")
|
247
|
+
sleep 60 unless r == 0
|
248
|
+
Migrate.setup_source(source_host_ssh, migration_exclusions)
|
249
|
+
rescue
|
250
|
+
retry if (r += 1) < 5
|
251
|
+
raise
|
252
|
+
end
|
253
|
+
end
|
254
|
+
rescue
|
255
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
256
|
+
"source host. Bailing out.#{CLI.reset}"
|
257
|
+
puts panic
|
258
|
+
raise
|
259
|
+
end
|
260
|
+
|
261
|
+
# Attempt to set up the destination host 5 times. If there is a
|
262
|
+
# failure, sleep for 60 seconds before retrying.
|
263
|
+
r = 0
|
264
|
+
begin
|
265
|
+
message = "Setting up destination host (attempt #{r + 1}/5)"
|
266
|
+
CLI.spinner(message) do
|
267
|
+
begin
|
268
|
+
message.gsub!(/\d\/5/, "#{r + 1}/5")
|
269
|
+
sleep 60 unless r == 0
|
270
|
+
Migrate.setup_destination(destination_host_ssh, pubkey)
|
271
|
+
rescue
|
272
|
+
retry if (r += 1) < 5
|
273
|
+
raise
|
274
|
+
end
|
275
|
+
end
|
276
|
+
rescue
|
277
|
+
panic = "#{CLI.bold}#{CLI.red}*** Unable to communicate with the " +
|
278
|
+
"destination host. Bailing out.#{CLI.reset}"
|
279
|
+
puts panic
|
280
|
+
raise
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Determine if Service Net can be used
|
285
|
+
begin
|
286
|
+
CLI.spinner "Checking for ServiceNet" do
|
287
|
+
target_addr = Migrate.check_servicenet(source_host_ssh,
|
288
|
+
destination_host_ssh)
|
289
|
+
raise if target_addr.nil?
|
290
|
+
destination_host_def[:target_addr] = target_addr
|
291
|
+
end
|
292
|
+
rescue
|
293
|
+
destination_host_def[:target_addr] = destination_host_def[:host]
|
294
|
+
end
|
295
|
+
|
296
|
+
# Define no timeout for the migration rsync
|
297
|
+
destination_host_def[:timeout] = -1
|
298
|
+
|
299
|
+
# Kick off the migration proper
|
300
|
+
if opts[:verbose]
|
301
|
+
Migrate.migrate_server(source_host_ssh, destination_host_def)
|
302
|
+
else
|
303
|
+
CLI.spinner "Performing migration" do
|
304
|
+
Migrate.migrate_server(source_host_ssh, destination_host_def)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
CLI.spinner "Cleaning up destination host" do
|
309
|
+
Migrate.clean_destination(destination_host_ssh, source_profile[:cpe])
|
310
|
+
end
|
311
|
+
|
312
|
+
[destination_host_ssh, source_host_ssh].each do |host|
|
313
|
+
host.logout!
|
314
|
+
end
|
315
|
+
|
316
|
+
puts
|
317
|
+
puts "#{CLI.bold}#{CLI.blue}*** Migration complete#{CLI.reset}\a"
|
318
|
+
end
|
319
|
+
|
320
|
+
# Internal: Ask whether or not to edit the default exclusion list for a given
|
321
|
+
# platform, and facilitate the edit if so.
|
322
|
+
#
|
323
|
+
# cpe - CPE object for the host in question.
|
324
|
+
#
|
325
|
+
# Returns a String containing the exclusions.
|
326
|
+
# Raises ArgumentError if cpe isn't a CPE object.
|
327
|
+
def determine_exclusions(cpe)
|
328
|
+
raise ArgumentError unless cpe.kind_of?(CPE)
|
329
|
+
|
330
|
+
exclusion_string = Migrate.build_default_exclusions(cpe)
|
331
|
+
alter = CLI.prompt_yn("Edit default exclusions list? (Y/N)",
|
332
|
+
default_answer: "N")
|
333
|
+
|
334
|
+
if alter
|
335
|
+
require 'tempfile'
|
336
|
+
|
337
|
+
tmp_file = Tempfile.new("exclude")
|
338
|
+
tmp_file.write(exclusion_string)
|
339
|
+
tmp_file.close
|
340
|
+
|
341
|
+
# Allow for "other" editors
|
342
|
+
if File.exists?("/usr/bin/editor")
|
343
|
+
editor = "/usr/bin/editor"
|
344
|
+
else
|
345
|
+
editor = "vim"
|
346
|
+
end
|
347
|
+
|
348
|
+
system("#{editor} #{tmp_file.path}")
|
349
|
+
tmp_file.open
|
350
|
+
exclusion_string = tmp_file.read
|
351
|
+
tmp_file.close
|
352
|
+
tmp_file.unlink
|
353
|
+
end
|
354
|
+
|
355
|
+
exclusion_string
|
356
|
+
end
|
357
|
+
end
|