morpheus-cli 8.0.11.1 → 8.0.12.1
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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/migrations_interface.rb +49 -0
- data/lib/morpheus/cli/commands/benchmark_command.rb +3 -4
- data/lib/morpheus/cli/commands/budgets_command.rb +0 -1
- data/lib/morpheus/cli/commands/clouds.rb +29 -22
- data/lib/morpheus/cli/commands/cluster_types.rb +179 -0
- data/lib/morpheus/cli/commands/clusters.rb +37 -22
- data/lib/morpheus/cli/commands/hosts.rb +37 -3
- data/lib/morpheus/cli/commands/instances.rb +25 -0
- data/lib/morpheus/cli/commands/migrations.rb +453 -0
- data/lib/morpheus/cli/commands/networks_command.rb +1 -1
- data/lib/morpheus/cli/commands/price_sets_command.rb +3 -8
- data/lib/morpheus/cli/commands/prices_command.rb +2 -12
- data/lib/morpheus/cli/mixins/processes_helper.rb +136 -0
- data/lib/morpheus/cli/mixins/rest_command.rb +2 -2
- data/lib/morpheus/cli/option_types.rb +5 -3
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/currency.rb +184 -0
- data/lib/morpheus/formatters.rb +6 -6
- data/lib/morpheus/routes.rb +2 -1
- data/morpheus-cli.gemspec +0 -1
- data/test/cli/migrations_test.rb +39 -0
- metadata +8 -16
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
|
2
|
+
|
|
3
|
+
class Morpheus::Cli::Migrations
|
|
4
|
+
include Morpheus::Cli::CliCommand
|
|
5
|
+
include Morpheus::Cli::RestCommand
|
|
6
|
+
include Morpheus::Cli::ProcessesHelper
|
|
7
|
+
#include Morpheus::Cli::MigrationsHelper
|
|
8
|
+
|
|
9
|
+
set_command_name :'migrations'
|
|
10
|
+
set_command_description "View and manage migrations."
|
|
11
|
+
register_subcommands :list, :get, :add, :update, :run, :remove, :history
|
|
12
|
+
|
|
13
|
+
# RestCommand settings
|
|
14
|
+
register_interfaces :migrations, :processes
|
|
15
|
+
# set_rest_has_type false
|
|
16
|
+
# set_rest_type :migration_types
|
|
17
|
+
|
|
18
|
+
def render_response_for_get(json_response, options)
|
|
19
|
+
render_response(json_response, options, rest_object_key) do
|
|
20
|
+
record = json_response[rest_object_key]
|
|
21
|
+
print_h1 rest_label, [], options
|
|
22
|
+
print cyan
|
|
23
|
+
print_description_list(rest_column_definitions(options), record, options)
|
|
24
|
+
# show Migration Configuration
|
|
25
|
+
# config = record['config']
|
|
26
|
+
# if config && !config.empty?
|
|
27
|
+
# print_h2 "Virtual Machines"
|
|
28
|
+
# print_description_list(config.keys, config)
|
|
29
|
+
# end
|
|
30
|
+
# Datastores
|
|
31
|
+
datastores = record['datastores']
|
|
32
|
+
print_h2 "Datastores", options
|
|
33
|
+
if datastores && datastores.size > 0
|
|
34
|
+
columns = [
|
|
35
|
+
{"Source" => lambda {|it| it['sourceDatastore'] ? "#{it['sourceDatastore']['name']} [#{it['sourceDatastore']['id']}]" : "" } },
|
|
36
|
+
{"Destination" => lambda {|it| it['destinationDatastore'] ? "#{it['destinationDatastore']['name']} [#{it['destinationDatastore']['id']}]" : "" } },
|
|
37
|
+
]
|
|
38
|
+
print as_pretty_table(datastores, columns, options)
|
|
39
|
+
else
|
|
40
|
+
print cyan,"No datatores in migration",reset,"\n"
|
|
41
|
+
end
|
|
42
|
+
# Networks
|
|
43
|
+
print_h2 "Networks", options
|
|
44
|
+
networks = record['networks']
|
|
45
|
+
if networks && networks.size > 0
|
|
46
|
+
columns = [
|
|
47
|
+
{"Source" => lambda {|it| it['sourceNetwork'] ? "#{it['sourceNetwork']['name']} [#{it['sourceNetwork']['id']}]" : "" } },
|
|
48
|
+
{"Destination" => lambda {|it| it['destinationNetwork'] ? "#{it['destinationNetwork']['name']} [#{it['destinationNetwork']['id']}]" : "" } },
|
|
49
|
+
]
|
|
50
|
+
print as_pretty_table(networks, columns, options)
|
|
51
|
+
else
|
|
52
|
+
print cyan,"No networks found in migration",reset,"\n"
|
|
53
|
+
end
|
|
54
|
+
# Virtual Machines
|
|
55
|
+
print_h2 "Virtual Machines", options
|
|
56
|
+
servers = record['servers']
|
|
57
|
+
if servers && servers.size > 0
|
|
58
|
+
columns = [
|
|
59
|
+
# {"ID" => lambda {|it| it['sourceServer'] ? it['sourceServer']['id'] : "" } },
|
|
60
|
+
# {"Name" => lambda {|it| it['sourceServer'] ? it['sourceServer']['name'] : "" } },
|
|
61
|
+
{"Source" => lambda {|it| it['sourceServer'] ? "#{it['sourceServer']['name']} [#{it['sourceServer']['id']}]" : "" } },
|
|
62
|
+
{"Destination" => lambda {|it| it['destinationServer'] ? "#{it['destinationServer']['name']} [#{it['destinationServer']['id']}]" : "" } },
|
|
63
|
+
{"Status" => lambda {|it| format_migration_server_status(it) } }
|
|
64
|
+
]
|
|
65
|
+
print as_pretty_table(servers, columns, options)
|
|
66
|
+
else
|
|
67
|
+
print cyan,"No virtual machines found in migration",reset,"\n"
|
|
68
|
+
end
|
|
69
|
+
print reset,"\n"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def history(args)
|
|
74
|
+
handle_history_command(args, "migration", "Migration", "migrationPlan") do |id|
|
|
75
|
+
record = rest_find_by_name_or_id(id)
|
|
76
|
+
if record.nil?
|
|
77
|
+
# raise_command_error "#{rest_name} not found for '#{id}'"
|
|
78
|
+
return 1, "#{rest_name} not found for '#{id}'"
|
|
79
|
+
end
|
|
80
|
+
record
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add(args)
|
|
85
|
+
options = {}
|
|
86
|
+
params = {}
|
|
87
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
88
|
+
opts.banner = subcommand_usage("[name] [options]")
|
|
89
|
+
build_option_type_options(opts, options, add_migration_option_types)
|
|
90
|
+
build_standard_add_options(opts, options)
|
|
91
|
+
opts.footer = <<-EOT
|
|
92
|
+
Create a new migration plan.
|
|
93
|
+
EOT
|
|
94
|
+
end
|
|
95
|
+
optparse.parse!(args)
|
|
96
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
|
97
|
+
options[:options]['name'] = args[0] if args[0]
|
|
98
|
+
connect(options)
|
|
99
|
+
payload = {}
|
|
100
|
+
if options[:payload]
|
|
101
|
+
payload = options[:payload]
|
|
102
|
+
payload.deep_merge!({rest_object_key => parse_passed_options(options)})
|
|
103
|
+
else
|
|
104
|
+
params.deep_merge!(parse_passed_options(options))
|
|
105
|
+
# prompt for option types
|
|
106
|
+
# skip config if using interactive prompt
|
|
107
|
+
add_option_types = add_migration_option_types
|
|
108
|
+
# handle some option types in a special way
|
|
109
|
+
servers_option_type = add_option_types.find {|it| it['fieldName'] == 'servers' } # || {'switch' => 'servers', 'fieldName' => 'servers', 'fieldLabel' => 'Virtual Machines', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'required' => true, 'description' => 'Virtual Machines to be migrated, comma separated list of server names or IDs.'}
|
|
110
|
+
add_option_types.reject! {|it| it['fieldName'] == 'servers' }
|
|
111
|
+
# prompt
|
|
112
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(add_option_types, options[:options], @api_client, options[:params])
|
|
113
|
+
params.deep_merge!(v_prompt)
|
|
114
|
+
# convert checkbox "on" and "off" to true and false
|
|
115
|
+
params.booleanize!
|
|
116
|
+
|
|
117
|
+
# prompt for servers
|
|
118
|
+
server_ids = nil
|
|
119
|
+
if params['sourceServerIds']
|
|
120
|
+
server_ids = parse_id_list(params.delete('sourceServerIds'))
|
|
121
|
+
elsif params['servers']
|
|
122
|
+
server_ids = parse_id_list(params.delete('servers'))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if server_ids
|
|
126
|
+
# lookup each value as an id or name and collect id
|
|
127
|
+
# server_ids = server_ids.collect {|it| find_server_by_name_or_id(it)}.compact.collect {|it| it['id']}
|
|
128
|
+
# available_servers = @api_client.options.options_for_source("searchServers", {'cloudId' => params['sourceCloudId'], 'max' => 1000})['data']
|
|
129
|
+
available_servers = @api_client.migrations.source_servers({'sourceCloudId' => params['sourceCloudId'], 'max' => 5000})['sourceServers']
|
|
130
|
+
bad_ids = []
|
|
131
|
+
server_ids = server_ids.collect {|server_id|
|
|
132
|
+
found_option = available_servers.find {|it| it['id'].to_s == server_id.to_s || it['name'] == server_id.to_s }
|
|
133
|
+
if found_option
|
|
134
|
+
found_option['value'] || found_option['id']
|
|
135
|
+
else
|
|
136
|
+
bad_ids << server_id
|
|
137
|
+
end
|
|
138
|
+
}
|
|
139
|
+
if bad_ids.size > 0
|
|
140
|
+
raise_command_error "No such server found for: #{bad_ids.join(', ')}"
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
# prompt for servers
|
|
144
|
+
# servers_option_type = {'fieldName' => 'servers', 'fieldLabel' => 'Virtual Machines', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'description' => 'Select virtual machine servers to be migrated.', 'required' => true}
|
|
145
|
+
api_params = {'cloudId' => params['sourceCloudId']}
|
|
146
|
+
# server_ids = Morpheus::Cli::OptionTypes.prompt([servers_option_type], options[:options], @api_client, {'cloudId' => params['sourceCloudId'], 'max' => 1000})['servers']
|
|
147
|
+
server_ids = Morpheus::Cli::OptionTypes.prompt([servers_option_type], options[:options], @api_client, {'sourceCloudId' => params['sourceCloudId'], 'max' => 5000})['servers']
|
|
148
|
+
# todo: Add prompt for Add more servers?
|
|
149
|
+
# while self.confirm("Add more #{servers_option_type['fieldLabel']}?", {:default => false}) do
|
|
150
|
+
# more_ids = Morpheus::Cli::OptionTypes.prompt([servers_option_type.merge({'required' => false})], {}, @api_client, api_params)['servers']
|
|
151
|
+
# server_ids += more_ids
|
|
152
|
+
# end
|
|
153
|
+
end
|
|
154
|
+
server_ids.uniq!
|
|
155
|
+
params['sourceServerIds'] = server_ids
|
|
156
|
+
|
|
157
|
+
# prompt for datastores
|
|
158
|
+
datastore_mappings = []
|
|
159
|
+
source_datastores = @api_client.migrations.source_storage({'sourceCloudId' => params['sourceCloudId'], 'sourceServerIds' => params['sourceServerIds'].join(",")})['sourceStorage']
|
|
160
|
+
source_datastores.each do |datastore|
|
|
161
|
+
target_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => "datastore.#{datastore['id']}", 'fieldLabel' => "Datastore #{datastore['name']}", 'type' => 'select', 'required' => true, 'defaultFirstOption' => true, 'description' => "Datastore destination for datastore #{datastore['name']} [#{datastore['id']}]", 'optionSource' => lambda {|api_client, api_params|
|
|
162
|
+
api_client.migrations.target_storage(api_params)['targetStorage'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
163
|
+
} }], options[:options], @api_client, {'targetCloudId' => params['targetCloudId'], 'targetPoolId' => params['targetPoolId']})["datastore"]["#{datastore['id']}"]
|
|
164
|
+
datastore_mappings << {'sourceDatastore' => {'id' => datastore['id']}, 'destinationDatastore' => {'id' => target_id}}
|
|
165
|
+
end
|
|
166
|
+
params['datastores'] = datastore_mappings
|
|
167
|
+
params.delete('datastore') # remove options passed in as -O datastore.id=
|
|
168
|
+
|
|
169
|
+
# prompt for networks
|
|
170
|
+
network_mappings = []
|
|
171
|
+
source_networks = @api_client.migrations.source_networks({'sourceCloudId' => params['sourceCloudId'], 'sourceServerIds' => params['sourceServerIds'].join(",")})['sourceNetworks']
|
|
172
|
+
source_networks.each do |network|
|
|
173
|
+
target_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => "network.#{network['id']}", 'fieldLabel' => "Network #{network['name']}", 'type' => 'select', 'required' => true, 'defaultFirstOption' => true, 'description' => "Network destination for network #{network['name']} [#{network['id']}]", 'optionSource' => lambda {|api_client, api_params|
|
|
174
|
+
api_client.migrations.target_networks(api_params)['targetNetworks'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
175
|
+
} }], options[:options], @api_client, {'targetCloudId' => params['targetCloudId'], 'targetPoolId' => params['targetPoolId']})["network"]["#{network['id']}"]
|
|
176
|
+
network_mappings << {'sourceNetwork' => {'id' => network['id']}, 'destinationNetwork' => {'id' => target_id}}
|
|
177
|
+
end
|
|
178
|
+
params['networks'] = network_mappings
|
|
179
|
+
params.delete('network') # remove options passed in as -O network.id=
|
|
180
|
+
|
|
181
|
+
payload.deep_merge!({rest_object_key => params})
|
|
182
|
+
end
|
|
183
|
+
@migrations_interface.setopts(options)
|
|
184
|
+
if options[:dry_run]
|
|
185
|
+
print_dry_run @migrations_interface.dry.create(payload)
|
|
186
|
+
return 0, nil
|
|
187
|
+
end
|
|
188
|
+
json_response = @migrations_interface.create(payload)
|
|
189
|
+
migration = json_response[rest_object_key]
|
|
190
|
+
render_response(json_response, options, rest_object_key) do
|
|
191
|
+
print_green_success "Added migration #{migration['name']}"
|
|
192
|
+
return _get(migration["id"], {}, options)
|
|
193
|
+
end
|
|
194
|
+
return 0, nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def update(args)
|
|
198
|
+
options = {}
|
|
199
|
+
params = {}
|
|
200
|
+
payload = {}
|
|
201
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
202
|
+
opts.banner = subcommand_usage("[migration] [options]")
|
|
203
|
+
build_option_type_options(opts, options, update_migration_option_types)
|
|
204
|
+
build_standard_update_options(opts, options)
|
|
205
|
+
opts.footer = <<-EOT
|
|
206
|
+
Update a migration plan.
|
|
207
|
+
[migration] is required. This is the name or id of a migration plan.
|
|
208
|
+
EOT
|
|
209
|
+
end
|
|
210
|
+
optparse.parse!(args)
|
|
211
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
212
|
+
connect(options)
|
|
213
|
+
migration = find_migration_by_name_or_id(args[0])
|
|
214
|
+
return 1 if migration.nil?
|
|
215
|
+
payload = {}
|
|
216
|
+
if options[:payload]
|
|
217
|
+
payload = options[:payload]
|
|
218
|
+
payload.deep_merge!({rest_object_key => parse_passed_options(options)})
|
|
219
|
+
else
|
|
220
|
+
params.deep_merge!(parse_passed_options(options))
|
|
221
|
+
# prompt for option types
|
|
222
|
+
# skip config if using interactive prompt
|
|
223
|
+
update_option_types = update_migration_option_types
|
|
224
|
+
# handle some option types in a special way
|
|
225
|
+
servers_option_type = update_option_types.find {|it| it['fieldName'] == 'servers' } # || {'switch' => 'servers', 'fieldName' => 'servers', 'fieldLabel' => 'Virtual Machines', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'required' => true, 'description' => 'Virtual Machines to be migrated, comma separated list of server names or IDs.'}
|
|
226
|
+
update_option_types.reject! {|it| it['fieldName'] == 'servers' }
|
|
227
|
+
# prompt (edit uses no_prompt)
|
|
228
|
+
# need these parameters for prompting..
|
|
229
|
+
default_api_params = {}
|
|
230
|
+
default_api_params['sourceCloudId'] = migration['sourceCloud']['id'] if migration['sourceCloud']
|
|
231
|
+
default_api_params['targetCloudId'] = migration['targetCloud']['id'] if migration['targetCloud']
|
|
232
|
+
default_api_params['targetGroupId'] = migration['targetGroup']['id'] if migration['targetGroup']
|
|
233
|
+
default_api_params['targetPoolId'] = "pool-" + migration['targetPool']['id'].to_s if migration['targetPool']
|
|
234
|
+
options[:params] = default_api_params.merge(options[:options])
|
|
235
|
+
v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_option_types, options[:options], @api_client, options[:params])
|
|
236
|
+
params.deep_merge!(v_prompt)
|
|
237
|
+
# convert checkbox "on" and "off" to true and false
|
|
238
|
+
params.booleanize!
|
|
239
|
+
|
|
240
|
+
# prompt for servers
|
|
241
|
+
server_ids = nil
|
|
242
|
+
if params['sourceServerIds']
|
|
243
|
+
server_ids = parse_id_list(params.delete('sourceServerIds'))
|
|
244
|
+
elsif params['servers']
|
|
245
|
+
server_ids = parse_id_list(params.delete('servers'))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if server_ids
|
|
249
|
+
# lookup each value as an id or name and collect id
|
|
250
|
+
# server_ids = server_ids.collect {|it| find_server_by_name_or_id(it)}.compact.collect {|it| it['id']}
|
|
251
|
+
# available_servers = @api_client.options.options_for_source("searchServers", {'cloudId' => params['sourceCloudId'], 'max' => 1000})['data']
|
|
252
|
+
available_servers = @api_client.migrations.source_servers({'sourceCloudId' => params['sourceCloudId'], 'max' => 5000})['sourceServers']
|
|
253
|
+
bad_ids = []
|
|
254
|
+
server_ids = server_ids.collect {|server_id|
|
|
255
|
+
found_option = available_servers.find {|it| it['id'].to_s == server_id.to_s || it['name'] == server_id.to_s }
|
|
256
|
+
if found_option
|
|
257
|
+
found_option['value'] || found_option['id']
|
|
258
|
+
else
|
|
259
|
+
bad_ids << server_id
|
|
260
|
+
end
|
|
261
|
+
}
|
|
262
|
+
if bad_ids.size > 0
|
|
263
|
+
raise_command_error "No such server found for: #{bad_ids.join(', ')}"
|
|
264
|
+
end
|
|
265
|
+
server_ids.uniq!
|
|
266
|
+
params['sourceServerIds'] = server_ids
|
|
267
|
+
else
|
|
268
|
+
# no prompt for update
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
source_server_ids = params['sourceServerIds'] || migration['servers'].collect {|it| it['sourceServer'] ? it['sourceServer']['id'] : nil }.compact
|
|
272
|
+
source_cloud_id = params['sourceCloudId'] || (migration['sourceCloud'] ? migration['sourceCloud']['id'] : nil)
|
|
273
|
+
target_cloud_id = params['targetCloudId'] || (migration['targetCloud'] ? migration['targetCloud']['id'] : nil)
|
|
274
|
+
target_pool_id = params['targetPoolId'] || (migration['targetPool'] ? migration['targetPool']['id'] : nil)
|
|
275
|
+
|
|
276
|
+
# prompt for datastores
|
|
277
|
+
if options[:options]['datastore'].is_a?(Hash)
|
|
278
|
+
datastore_mappings = []
|
|
279
|
+
source_datastores = @api_client.migrations.source_storage({'sourceCloudId' => source_cloud_id, 'sourceServerIds' => source_server_ids.join(",")})['sourceStorage']
|
|
280
|
+
source_datastores.each do |datastore|
|
|
281
|
+
found_mapping = migration['datastores'].find {|it| it['sourceDatastore'] && it['sourceDatastore']['id'] == datastore['id'] }
|
|
282
|
+
default_value = found_mapping && found_mapping['destinationDatastore'] ? found_mapping['destinationDatastore']['name'] : nil
|
|
283
|
+
target_id = Morpheus::Cli::OptionTypes.no_prompt([{'fieldName' => "datastore.#{datastore['id']}", 'fieldLabel' => "Datastore #{datastore['name']}", 'type' => 'select', 'description' => "Datastore destination for datastore #{datastore['name']} [#{datastore['id']}]", 'defaultValue' => default_value, 'optionSource' => lambda {|api_client, api_params|
|
|
284
|
+
api_client.migrations.target_storage(api_params)['targetStorage'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
285
|
+
} }], options[:options], @api_client, {'targetCloudId' => target_cloud_id, 'targetPoolId' => target_pool_id})["datastore"]["#{datastore['id']}"]
|
|
286
|
+
datastore_mappings << {'sourceDatastore' => {'id' => datastore['id']}, 'destinationDatastore' => {'id' => target_id}}
|
|
287
|
+
end
|
|
288
|
+
params['datastores'] = datastore_mappings
|
|
289
|
+
params.delete('datastore') # remove options passed in as -O datastore.id=
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# prompt for networks
|
|
293
|
+
if options[:options]['network'].is_a?(Hash)
|
|
294
|
+
network_mappings = []
|
|
295
|
+
source_networks = @api_client.migrations.source_networks({'sourceCloudId' => source_cloud_id, 'sourceServerIds' => source_server_ids.join(",")})['sourceNetworks']
|
|
296
|
+
source_networks.each do |network|
|
|
297
|
+
found_mapping = migration['networks'].find {|it| it['sourceNetwork'] && it['sourceNetwork']['id'] == network['id'] }
|
|
298
|
+
default_value = found_mapping && found_mapping['destinationNetwork'] ? found_mapping['destinationNetwork']['name'] : nil
|
|
299
|
+
target_id = Morpheus::Cli::OptionTypes.no_prompt([{'fieldName' => "network.#{network['id']}", 'fieldLabel' => "Network #{network['name']}", 'type' => 'select', 'description' => "Network destination for network #{network['name']} [#{network['id']}]", 'defaultValue' => default_value, 'optionSource' => lambda {|api_client, api_params|
|
|
300
|
+
api_client.migrations.target_networks(api_params)['targetNetworks'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
301
|
+
} }], options[:options], @api_client, {'targetCloudId' => target_cloud_id, 'targetPoolId' => target_pool_id})["network"]["#{network['id']}"]
|
|
302
|
+
network_mappings << {'sourceNetwork' => {'id' => network['id']}, 'destinationNetwork' => {'id' => target_id}}
|
|
303
|
+
end
|
|
304
|
+
params['networks'] = network_mappings
|
|
305
|
+
params.delete('network') # remove options passed in as -O network.id=
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
if params.empty? # || options[:no_prompt]
|
|
309
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
310
|
+
end
|
|
311
|
+
payload.deep_merge!({rest_object_key => params})
|
|
312
|
+
end
|
|
313
|
+
@migrations_interface.setopts(options)
|
|
314
|
+
if options[:dry_run]
|
|
315
|
+
print_dry_run @migrations_interface.dry.update(migration['id'], payload)
|
|
316
|
+
return
|
|
317
|
+
end
|
|
318
|
+
json_response = @migrations_interface.update(migration['id'], payload)
|
|
319
|
+
migration = json_response[rest_object_key]
|
|
320
|
+
render_response(json_response, options, rest_object_key) do
|
|
321
|
+
print_green_success "Updated migration #{migration['name']}"
|
|
322
|
+
return _get(migration["id"], {}, options)
|
|
323
|
+
end
|
|
324
|
+
return 0, nil
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def run(args)
|
|
328
|
+
options = {}
|
|
329
|
+
params = {}
|
|
330
|
+
payload = {}
|
|
331
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
332
|
+
opts.banner = subcommand_usage("[migration]")
|
|
333
|
+
build_standard_post_options(opts, options, [:auto_confirm])
|
|
334
|
+
opts.footer = <<-EOT
|
|
335
|
+
Runs a migration plan to transition it from pending to scheduled for execution.
|
|
336
|
+
[migration] is required. This is the name or id of a migration.
|
|
337
|
+
EOT
|
|
338
|
+
end
|
|
339
|
+
optparse.parse!(args)
|
|
340
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
341
|
+
connect(options)
|
|
342
|
+
migration = find_migration_by_name_or_id(args[0])
|
|
343
|
+
return 1 if migration.nil?
|
|
344
|
+
parse_payload(options) do |payload|
|
|
345
|
+
end
|
|
346
|
+
servers = migration['servers']
|
|
347
|
+
print cyan, "The following #{servers.size == 1 ? 'server' : servers.size.to_s + ' servers'} will be migrated:", "\n"
|
|
348
|
+
puts ""
|
|
349
|
+
print as_pretty_table(servers, {"Virtual Machine" => lambda {|it| it['sourceServer'] ? "#{it['sourceServer']['name']} [#{it['sourceServer']['id']}]" : "" } }, options)
|
|
350
|
+
puts ""
|
|
351
|
+
confirm!("Are you sure you want to execute the migration plan?", options)
|
|
352
|
+
execute_api(@migrations_interface, :run, [migration['id']], options, 'migration') do |json_response|
|
|
353
|
+
print_green_success "Running migration #{migration['name']}"
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
protected
|
|
358
|
+
|
|
359
|
+
def migration_list_column_definitions(options)
|
|
360
|
+
{
|
|
361
|
+
"ID" => 'id',
|
|
362
|
+
"Name" => 'name',
|
|
363
|
+
"VMs" => lambda {|it| it['servers'] ? it['servers'].size : 0 },
|
|
364
|
+
"Status" => lambda {|it| format_migration_status(it) },
|
|
365
|
+
}
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def migration_column_definitions(options)
|
|
369
|
+
{
|
|
370
|
+
"ID" => 'id',
|
|
371
|
+
"Name" => 'name',
|
|
372
|
+
"Source Cloud" => lambda {|it| it['sourceCloud'] ? it['sourceCloud']['name'] : '' },
|
|
373
|
+
"Destination Cloud" => lambda {|it| it['targetCloud'] ? it['targetCloud']['name'] : '' },
|
|
374
|
+
"Resource Pool" => lambda {|it| it['targetPool'] ? it['targetPool']['name'] : '' },
|
|
375
|
+
"Group" => lambda {|it| it['targetGroup'] ? it['targetGroup']['name'] : '' },
|
|
376
|
+
"Skip Prechecks" => lambda {|it| format_boolean(it['skipPrechecks']) },
|
|
377
|
+
"Install Guest Tools" => lambda {|it| format_boolean(it['installGuestTools']) },
|
|
378
|
+
# "ReInitialize Server" => lambda {|it| format_boolean(it['reInitializeServerOnMigration']) },
|
|
379
|
+
"VMs" => lambda {|it| it['servers'] ? it['servers'].size : 0 },
|
|
380
|
+
"Status" => lambda {|it| format_migration_status(it) },
|
|
381
|
+
}
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def add_migration_option_types()
|
|
385
|
+
[
|
|
386
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
|
387
|
+
# {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
|
|
388
|
+
{'switch' => 'source-cloud', 'fieldName' => 'sourceCloudId', 'fieldLabel' => 'Source Cloud', 'type' => 'select', 'required' => true, 'description' => 'Source Cloud', 'optionSource' => lambda {|api_client, api_params|
|
|
389
|
+
api_client.migrations.source_clouds(api_params)['sourceClouds'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
390
|
+
} },
|
|
391
|
+
{'switch' => 'cloud', 'fieldName' => 'targetCloudId', 'fieldLabel' => 'Destination Cloud', 'type' => 'select', 'required' => true, 'description' => 'Destination Cloud', 'optionSource' => lambda {|api_client, api_params|
|
|
392
|
+
api_client.migrations.target_clouds(api_params)['targetClouds'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
393
|
+
} },
|
|
394
|
+
{'switch' => 'group', 'fieldName' => 'targetGroupId', 'fieldLabel' => 'Group', 'type' => 'select', 'optionSource' => 'targetGroups', 'required' => true, 'defaultFirstOption' => true, 'description' => 'Destination Group'},
|
|
395
|
+
{'switch' => 'pool', 'fieldName' => 'targetPoolId', 'fieldLabel' => 'Resource Pool', 'type' => 'select', 'required' => true, 'defaultFirstOption' => true, 'optionSource' => lambda {|api_client, api_params|
|
|
396
|
+
api_params = api_params.merge({'provisionTypeCode' => 'kvm', 'zoneId' => api_params['targetCloudId'], 'groupId' => api_params['targetGroupId']})
|
|
397
|
+
api_client.options.options_for_source("zonePools", api_params)['data']
|
|
398
|
+
} },
|
|
399
|
+
{'switch' => 'servers', 'fieldName' => 'servers', 'fieldLabel' => 'Virtual Machines', 'type' => 'multiSelect', 'required' => true, 'description' => 'Virtual Machines to be migrated, comma separated list of server names or IDs.', 'optionSource' => lambda {|api_client, api_params|
|
|
400
|
+
api_client.migrations.source_servers(api_params)['sourceServers'].collect {|it| {'name' => it['name'], 'value' => it['id']} }
|
|
401
|
+
} },
|
|
402
|
+
{'fieldName' => 'skipPrechecks', 'fieldLabel' => 'Skip Prechecks', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false},
|
|
403
|
+
{'fieldName' => 'installGuestTools', 'fieldLabel' => 'Install Guest Tools', 'type' => 'checkbox', 'required' => false, 'defaultValue' => true},
|
|
404
|
+
]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def add_migration_advanced_option_types()
|
|
408
|
+
[
|
|
409
|
+
# {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'fieldGroup' => 'Advanced', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}], 'required' => false, 'description' => 'Visibility', 'category' => 'permissions'},
|
|
410
|
+
# {'fieldName' => 'tenants', 'fieldLabel' => 'Tenants', 'fieldGroup' => 'Advanced', 'type' => 'multiSelect', 'optionSource' => lambda { |api_client, api_params|
|
|
411
|
+
# api_client.options.options_for_source("allTenants", {})['data']
|
|
412
|
+
# }},
|
|
413
|
+
]
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def update_migration_option_types()
|
|
417
|
+
add_migration_option_types.collect {|it|
|
|
418
|
+
it.delete('required')
|
|
419
|
+
it.delete('defaultValue')
|
|
420
|
+
it.delete('defaultFirstOption')
|
|
421
|
+
it
|
|
422
|
+
}
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def update_migration_advanced_option_types()
|
|
426
|
+
add_migration_advanced_option_types()
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def format_migration_status(migration, return_color=cyan)
|
|
430
|
+
# migration statuses: pending, scheduled, precheck, running, failed, completed
|
|
431
|
+
out = ""
|
|
432
|
+
status_string = migration['status']
|
|
433
|
+
if status_string.nil? || status_string.empty? || status_string == "unknown"
|
|
434
|
+
out << "#{white}UNKNOWN#{return_color}"
|
|
435
|
+
elsif status_string == 'completed'
|
|
436
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
|
437
|
+
elsif status_string == 'pending' || status_string == 'scheduled' || status_string == 'precheck' || status_string == 'running'
|
|
438
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
|
439
|
+
else
|
|
440
|
+
out << "#{red}#{status_string ? status_string.upcase : 'N/A'}#{migration['statusMessage'] ? "#{return_color} - #{migration['statusMessage']}" : ''}#{return_color}"
|
|
441
|
+
end
|
|
442
|
+
out
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def format_migration_server_status(migration_server, return_color=cyan)
|
|
446
|
+
return format_migration_status(migration_server, return_color)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def find_migration_by_name_or_id(arg)
|
|
450
|
+
find_by_name_or_id(rest_key, arg)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
end
|
|
@@ -791,7 +791,7 @@ class Morpheus::Cli::NetworksCommand
|
|
|
791
791
|
end
|
|
792
792
|
|
|
793
793
|
# Allow IP Override
|
|
794
|
-
if
|
|
794
|
+
if payload['network']['allowStaticOverride'].nil?
|
|
795
795
|
if options['allowStaticOverride'] != nil
|
|
796
796
|
payload['network']['allowStaticOverride'] = options['allowStaticOverride']
|
|
797
797
|
else
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'morpheus/cli/cli_command'
|
|
2
|
-
require 'money'
|
|
3
2
|
|
|
4
3
|
class Morpheus::Cli::PriceSetsCommand
|
|
5
4
|
include Morpheus::Cli::CliCommand
|
|
@@ -153,7 +152,7 @@ class Morpheus::Cli::PriceSetsCommand
|
|
|
153
152
|
{
|
|
154
153
|
id: it['id'],
|
|
155
154
|
name: it['name'],
|
|
156
|
-
pricing: (it['priceType'] == 'platform' ? '+' : '') +
|
|
155
|
+
pricing: (it['priceType'] == 'platform' ? '+' : '') + currency_symbol(it['currency']) + (it['price'] || 0.0).to_s + (it['additionalPriceUnit'].nil? ? '' : '/' + it['additionalPriceUnit']) + '/' + (it['priceUnit'] || 'month').capitalize
|
|
157
156
|
}
|
|
158
157
|
end
|
|
159
158
|
print as_pretty_table(rows, [:id, :name, :pricing], options)
|
|
@@ -465,17 +464,13 @@ class Morpheus::Cli::PriceSetsCommand
|
|
|
465
464
|
|
|
466
465
|
private
|
|
467
466
|
|
|
468
|
-
def currency_sym(currency)
|
|
469
|
-
Money::Currency.new((currency.to_s != '' ? currency : 'usd').to_sym).symbol
|
|
470
|
-
end
|
|
471
|
-
|
|
472
467
|
def price_prefix(price)
|
|
473
|
-
(['platform', 'software'].include?(price['priceType']) ? '+' : '') +
|
|
468
|
+
(['platform', 'software'].include?(price['priceType']) ? '+' : '') + currency_symbol(price['currency'])
|
|
474
469
|
end
|
|
475
470
|
|
|
476
471
|
def price_markup(price)
|
|
477
472
|
if price['markupType'] == 'fixed'
|
|
478
|
-
|
|
473
|
+
currency_symbol(price['currency']) + format_amount(price['markup'] || 0)
|
|
479
474
|
elsif price['markupType'] == 'percent'
|
|
480
475
|
(price['markupPercent'] || 0).to_s + '%'
|
|
481
476
|
else
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'morpheus/cli/cli_command'
|
|
2
|
-
require 'money'
|
|
3
2
|
|
|
4
3
|
class Morpheus::Cli::PricesCommand
|
|
5
4
|
include Morpheus::Cli::CliCommand
|
|
@@ -564,22 +563,13 @@ class Morpheus::Cli::PricesCommand
|
|
|
564
563
|
(val.to_s =~ /\A\d{1,}\Z/) ? @prices_interface.get_volume_type(val.to_i)['volumeType'] : @prices_interface.list_volume_types({'name' => val})['volumeTypes'].first
|
|
565
564
|
end
|
|
566
565
|
|
|
567
|
-
def currency_sym(currency)
|
|
568
|
-
begin
|
|
569
|
-
Money::Currency.new((currency.to_s.empty? ? 'usd' : currency).to_sym).symbol
|
|
570
|
-
rescue
|
|
571
|
-
# sometimes '' is getting passed in here, so figure that out...
|
|
572
|
-
Money::Currency.new(:usd).symbol
|
|
573
|
-
end
|
|
574
|
-
end
|
|
575
|
-
|
|
576
566
|
def price_prefix(price)
|
|
577
|
-
(['platform', 'software'].include?(price['priceType']) ? '+' : '') +
|
|
567
|
+
(['platform', 'software'].include?(price['priceType']) ? '+' : '') + currency_symbol(price['currency'])
|
|
578
568
|
end
|
|
579
569
|
|
|
580
570
|
def price_markup(price)
|
|
581
571
|
if price['markupType'] == 'fixed'
|
|
582
|
-
|
|
572
|
+
currency_symbol(price['currency']) + format_amount(price['markup'] || 0)
|
|
583
573
|
elsif price['markupType'] == 'percent'
|
|
584
574
|
(price['markupPercent'] || 0).to_s + '%'
|
|
585
575
|
else
|