morpheus-cli 9.0.1 → 9.1.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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/affinity_groups_interface.rb +46 -0
- data/lib/morpheus/api/api_client.rb +17 -0
- data/lib/morpheus/api/clouds_interface.rb +42 -0
- data/lib/morpheus/api/clusters_interface.rb +55 -1
- data/lib/morpheus/api/distributed_workers_interface.rb +41 -0
- data/lib/morpheus/api/host_vm_groups_interface.rb +37 -0
- data/lib/morpheus/api/servers_interface.rb +8 -0
- data/lib/morpheus/api/virtual_switches_interface.rb +9 -0
- data/lib/morpheus/cli/commands/affinity_groups_command.rb +554 -0
- data/lib/morpheus/cli/commands/backups_command.rb +11 -0
- data/lib/morpheus/cli/commands/clouds.rb +421 -29
- data/lib/morpheus/cli/commands/clusters.rb +507 -20
- data/lib/morpheus/cli/commands/distributed_workers_command.rb +282 -0
- data/lib/morpheus/cli/commands/host_vm_groups_command.rb +423 -0
- data/lib/morpheus/cli/commands/hosts.rb +72 -1
- data/lib/morpheus/cli/commands/instances.rb +12 -1
- data/lib/morpheus/cli/commands/networks_command.rb +35 -3
- data/lib/morpheus/cli/commands/virtual_switches_command.rb +336 -0
- data/lib/morpheus/cli/mixins/affinity_helper.rb +118 -0
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +143 -4
- data/lib/morpheus/cli/option_types.rb +36 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/affinity_groups_interface_test.rb +43 -0
- data/test/api/clouds_interface_test.rb +67 -0
- data/test/api/host_vm_groups_interface_test.rb +35 -0
- data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
- data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
- data/test/cli/affinity_groups_test.rb +57 -0
- data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
- data/test/cli/affinity_helper_test.rb +367 -0
- data/test/cli/backups_test.rb +212 -0
- data/test/cli/clusters_uuid_test.rb +131 -0
- data/test/cli/distributed_workers_test.rb +68 -0
- data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
- data/test/cli/host_vm_groups_test.rb +63 -0
- data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
- metadata +39 -2
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
|
2
|
+
|
|
3
|
+
class Morpheus::Cli::HostVmGroupsCommand
|
|
4
|
+
include Morpheus::Cli::CliCommand
|
|
5
|
+
include Morpheus::Cli::ProvisioningHelper
|
|
6
|
+
include Morpheus::Cli::WhoamiHelper
|
|
7
|
+
include Morpheus::Cli::InfrastructureHelper
|
|
8
|
+
include Morpheus::Cli::AffinityHelper
|
|
9
|
+
|
|
10
|
+
set_command_name :'host-vm-groups'
|
|
11
|
+
|
|
12
|
+
register_subcommands :list, :get, :add, :update, :remove
|
|
13
|
+
|
|
14
|
+
def connect(opts)
|
|
15
|
+
@api_client = establish_remote_appliance_connection(opts)
|
|
16
|
+
@host_vm_groups_interface = @api_client.host_vm_groups
|
|
17
|
+
@clouds_interface = @api_client.clouds
|
|
18
|
+
@clusters_interface = @api_client.clusters
|
|
19
|
+
@cloud_resource_pools_interface = @api_client.cloud_resource_pools
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle(args)
|
|
23
|
+
handle_subcommand(args)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def list(args)
|
|
27
|
+
options = {}
|
|
28
|
+
params = {}
|
|
29
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
30
|
+
opts.banner = subcommand_usage()
|
|
31
|
+
opts.on('--cloud CLOUD', String, "Filter by cloud name or id") do |val|
|
|
32
|
+
options[:cloud] = val
|
|
33
|
+
end
|
|
34
|
+
opts.on('--cluster CLUSTER', String, "Filter by cluster name or id") do |val|
|
|
35
|
+
options[:cluster] = val
|
|
36
|
+
end
|
|
37
|
+
build_standard_list_options(opts, options)
|
|
38
|
+
opts.footer = "List host/VM groups.\n" \
|
|
39
|
+
"Use --cloud or --cluster to scope results. Without a scope all groups visible to the user are listed."
|
|
40
|
+
end
|
|
41
|
+
optparse.parse!(args)
|
|
42
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
|
43
|
+
connect(options)
|
|
44
|
+
|
|
45
|
+
if options[:cloud] && options[:cluster]
|
|
46
|
+
cloud = find_cloud_by_name_or_id(options[:cloud])
|
|
47
|
+
return 1 if cloud.nil?
|
|
48
|
+
pool = find_resource_pool_cluster_by_name_or_id(cloud, options[:cluster])
|
|
49
|
+
return 1 if pool.nil?
|
|
50
|
+
params.merge!(parse_list_options(options))
|
|
51
|
+
params['poolId'] = pool['id']
|
|
52
|
+
@clouds_interface.setopts(options)
|
|
53
|
+
if options[:dry_run]
|
|
54
|
+
print_dry_run @clouds_interface.dry.list_host_vm_groups(cloud['id'], params)
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
json_response = @clouds_interface.list_host_vm_groups(cloud['id'], params)
|
|
58
|
+
render_response(json_response, options, 'hostVmGroups') do
|
|
59
|
+
rows = json_response['hostVmGroups']
|
|
60
|
+
print_h1 "Host/VM Groups: #{cloud['name']} / #{pool['name']}", parse_list_subtitles(options), options
|
|
61
|
+
if rows.empty?
|
|
62
|
+
print cyan, "No host/VM groups found.", reset, "\n"
|
|
63
|
+
else
|
|
64
|
+
print as_pretty_table(rows, host_vm_group_list_columns, options)
|
|
65
|
+
print_results_pagination(json_response)
|
|
66
|
+
end
|
|
67
|
+
print reset, "\n"
|
|
68
|
+
end
|
|
69
|
+
elsif options[:cloud]
|
|
70
|
+
cloud = find_cloud_by_name_or_id(options[:cloud])
|
|
71
|
+
return 1 if cloud.nil?
|
|
72
|
+
params.merge!(parse_list_options(options))
|
|
73
|
+
@clouds_interface.setopts(options)
|
|
74
|
+
if options[:dry_run]
|
|
75
|
+
print_dry_run @clouds_interface.dry.list_host_vm_groups(cloud['id'], params)
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
json_response = @clouds_interface.list_host_vm_groups(cloud['id'], params)
|
|
79
|
+
render_response(json_response, options, 'hostVmGroups') do
|
|
80
|
+
rows = json_response['hostVmGroups']
|
|
81
|
+
print_h1 "Host/VM Groups: #{cloud['name']}", parse_list_subtitles(options), options
|
|
82
|
+
if rows.empty?
|
|
83
|
+
print cyan, "No host/VM groups found.", reset, "\n"
|
|
84
|
+
else
|
|
85
|
+
print as_pretty_table(rows, host_vm_group_list_columns, options)
|
|
86
|
+
print_results_pagination(json_response)
|
|
87
|
+
end
|
|
88
|
+
print reset, "\n"
|
|
89
|
+
end
|
|
90
|
+
elsif options[:cluster]
|
|
91
|
+
cluster_scope = find_cluster_list_scope_by_name_or_id(options[:cluster])
|
|
92
|
+
return 1 if cluster_scope.nil?
|
|
93
|
+
params.merge!(parse_list_options(options))
|
|
94
|
+
if cluster_scope[:type] == :managed_cluster
|
|
95
|
+
cluster = cluster_scope[:cluster]
|
|
96
|
+
@clusters_interface.setopts(options)
|
|
97
|
+
if options[:dry_run]
|
|
98
|
+
print_dry_run @clusters_interface.dry.list_host_vm_groups(cluster['id'], params)
|
|
99
|
+
return
|
|
100
|
+
end
|
|
101
|
+
json_response = @clusters_interface.list_host_vm_groups(cluster['id'], params)
|
|
102
|
+
scope_name = cluster['name']
|
|
103
|
+
else
|
|
104
|
+
cloud = cluster_scope[:cloud]
|
|
105
|
+
pool = cluster_scope[:pool]
|
|
106
|
+
params['poolId'] = pool['id']
|
|
107
|
+
@clouds_interface.setopts(options)
|
|
108
|
+
if options[:dry_run]
|
|
109
|
+
print_dry_run @clouds_interface.dry.list_host_vm_groups(cloud['id'], params)
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
json_response = @clouds_interface.list_host_vm_groups(cloud['id'], params)
|
|
113
|
+
scope_name = "#{cloud['name']} / #{pool['name']}"
|
|
114
|
+
end
|
|
115
|
+
render_response(json_response, options, 'hostVmGroups') do
|
|
116
|
+
rows = json_response['hostVmGroups']
|
|
117
|
+
print_h1 "Host/VM Groups: #{scope_name}", parse_list_subtitles(options), options
|
|
118
|
+
if rows.empty?
|
|
119
|
+
print cyan, "No host/VM groups found.", reset, "\n"
|
|
120
|
+
else
|
|
121
|
+
print as_pretty_table(rows, host_vm_group_list_columns, options)
|
|
122
|
+
print_results_pagination(json_response)
|
|
123
|
+
end
|
|
124
|
+
print reset, "\n"
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
# No top-level list route; /api/host-vm-groups exposes only /{id}.
|
|
128
|
+
print_error Morpheus::Terminal.angry_prompt
|
|
129
|
+
puts_error "#{command_name} list requires a scope: pass --cloud CLOUD or --cluster CLUSTER\n#{optparse}"
|
|
130
|
+
return 1, "scope required"
|
|
131
|
+
end
|
|
132
|
+
return 0, nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def get(args)
|
|
136
|
+
options = {}
|
|
137
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
138
|
+
opts.banner = subcommand_usage("[id]")
|
|
139
|
+
build_standard_get_options(opts, options)
|
|
140
|
+
opts.footer = "Get details about a host/VM group.\n" \
|
|
141
|
+
"[id] is required. This is the numeric id of the host/VM group."
|
|
142
|
+
end
|
|
143
|
+
optparse.parse!(args)
|
|
144
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
145
|
+
connect(options)
|
|
146
|
+
|
|
147
|
+
id = args[0]
|
|
148
|
+
params = {}
|
|
149
|
+
params.merge!(parse_query_options(options))
|
|
150
|
+
@host_vm_groups_interface.setopts(options)
|
|
151
|
+
if options[:dry_run]
|
|
152
|
+
print_dry_run @host_vm_groups_interface.dry.get(id, params)
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
json_response = @host_vm_groups_interface.get(id, params)
|
|
156
|
+
render_response(json_response, options, 'hostVmGroup') do
|
|
157
|
+
row = json_response['hostVmGroup']
|
|
158
|
+
print_h1 "Host/VM Group Details", [], options
|
|
159
|
+
print_description_list(host_vm_group_detail_columns, row)
|
|
160
|
+
if row['servers'] && row['servers'].size > 0
|
|
161
|
+
print_h2 "Servers", options
|
|
162
|
+
print as_pretty_table(row['servers'], [:id, :name], options)
|
|
163
|
+
end
|
|
164
|
+
print reset, "\n"
|
|
165
|
+
end
|
|
166
|
+
return 0, nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def add(args)
|
|
170
|
+
options = {}
|
|
171
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
172
|
+
opts.banner = subcommand_usage("[options]")
|
|
173
|
+
opts.on('--cloud CLOUD', String, "Cloud name or id") do |val|
|
|
174
|
+
options[:options] ||= {}
|
|
175
|
+
options[:options]['cloudId'] = val
|
|
176
|
+
end
|
|
177
|
+
opts.on('--cluster CLUSTER', String, "Cluster name or id") do |val|
|
|
178
|
+
options[:options] ||= {}
|
|
179
|
+
options[:options]['clusterId'] = val
|
|
180
|
+
end
|
|
181
|
+
opts.on('--servers ID1,ID2', Array, "Server ids to add to the group") do |val|
|
|
182
|
+
options[:options] ||= {}
|
|
183
|
+
options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
|
|
184
|
+
end
|
|
185
|
+
add_perms_options(opts, options, ['plans'])
|
|
186
|
+
build_standard_add_options(opts, options)
|
|
187
|
+
opts.footer = "Create a new host/VM group.\n" \
|
|
188
|
+
"Specify --cloud or --cluster to scope the group."
|
|
189
|
+
end
|
|
190
|
+
optparse.parse!(args)
|
|
191
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
|
192
|
+
connect(options)
|
|
193
|
+
|
|
194
|
+
begin
|
|
195
|
+
if options[:payload]
|
|
196
|
+
payload = options[:payload]
|
|
197
|
+
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
|
198
|
+
else
|
|
199
|
+
options[:options] ||= {}
|
|
200
|
+
host_vm_group = Morpheus::Cli::OptionTypes.prompt(add_host_vm_group_option_types, options[:options], @api_client, {})
|
|
201
|
+
|
|
202
|
+
unless options[:options].key?('servers')
|
|
203
|
+
server_prompt = Morpheus::Cli::OptionTypes.prompt([{
|
|
204
|
+
'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead',
|
|
205
|
+
'optionSource' => 'searchServers', 'searchParameter' => 'phrase',
|
|
206
|
+
'description' => 'Select servers to add to the group.'
|
|
207
|
+
}], options[:options], @api_client, {})
|
|
208
|
+
host_vm_group['servers'] = server_prompt['servers'] if server_prompt['servers']
|
|
209
|
+
else
|
|
210
|
+
host_vm_group['servers'] = options[:options]['servers']
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
|
|
214
|
+
host_vm_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
|
|
215
|
+
host_vm_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil? || perms['tenantPermissions']['accounts'].empty?
|
|
216
|
+
host_vm_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
|
217
|
+
|
|
218
|
+
payload = {'hostVmGroup' => host_vm_group}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Create is scoped -- POST /api/clusters/{id}/host-vm-groups or
|
|
222
|
+
# /api/zones/{id}/host-vm-groups. There is no top-level create route.
|
|
223
|
+
scope_cloud = options[:options] && options[:options]['cloudId']
|
|
224
|
+
scope_cluster = options[:options] && options[:options]['clusterId']
|
|
225
|
+
if scope_cloud && scope_cluster
|
|
226
|
+
cloud = find_cloud_by_name_or_id(scope_cloud)
|
|
227
|
+
return 1 if cloud.nil?
|
|
228
|
+
pool = find_resource_pool_cluster_by_name_or_id(cloud, scope_cluster)
|
|
229
|
+
return 1 if pool.nil?
|
|
230
|
+
if payload['hostVmGroup'].is_a?(Hash)
|
|
231
|
+
payload['hostVmGroup'].delete('cloudId')
|
|
232
|
+
payload['hostVmGroup'].delete('clusterId')
|
|
233
|
+
payload['hostVmGroup']['pool'] = {'id' => pool['id']}
|
|
234
|
+
end
|
|
235
|
+
@clouds_interface.setopts(options)
|
|
236
|
+
if options[:dry_run]
|
|
237
|
+
print_dry_run @clouds_interface.dry.create_host_vm_group(cloud['id'], payload)
|
|
238
|
+
return
|
|
239
|
+
end
|
|
240
|
+
json_response = @clouds_interface.create_host_vm_group(cloud['id'], payload)
|
|
241
|
+
elsif scope_cloud
|
|
242
|
+
cloud = find_cloud_by_name_or_id(scope_cloud)
|
|
243
|
+
return 1 if cloud.nil?
|
|
244
|
+
payload['hostVmGroup'].delete('cloudId') if payload['hostVmGroup'].is_a?(Hash)
|
|
245
|
+
@clouds_interface.setopts(options)
|
|
246
|
+
if options[:dry_run]
|
|
247
|
+
print_dry_run @clouds_interface.dry.create_host_vm_group(cloud['id'], payload)
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
json_response = @clouds_interface.create_host_vm_group(cloud['id'], payload)
|
|
251
|
+
elsif scope_cluster
|
|
252
|
+
cluster_scope = find_cluster_list_scope_by_name_or_id(scope_cluster)
|
|
253
|
+
return 1 if cluster_scope.nil?
|
|
254
|
+
if cluster_scope[:type] == :managed_cluster
|
|
255
|
+
cluster = cluster_scope[:cluster]
|
|
256
|
+
payload['hostVmGroup'].delete('clusterId') if payload['hostVmGroup'].is_a?(Hash)
|
|
257
|
+
@clusters_interface.setopts(options)
|
|
258
|
+
if options[:dry_run]
|
|
259
|
+
print_dry_run @clusters_interface.dry.create_host_vm_group(cluster['id'], payload)
|
|
260
|
+
return
|
|
261
|
+
end
|
|
262
|
+
json_response = @clusters_interface.create_host_vm_group(cluster['id'], payload)
|
|
263
|
+
else
|
|
264
|
+
cloud = cluster_scope[:cloud]
|
|
265
|
+
pool = cluster_scope[:pool]
|
|
266
|
+
if payload['hostVmGroup'].is_a?(Hash)
|
|
267
|
+
payload['hostVmGroup'].delete('clusterId')
|
|
268
|
+
payload['hostVmGroup']['pool'] = {'id' => pool['id']}
|
|
269
|
+
end
|
|
270
|
+
@clouds_interface.setopts(options)
|
|
271
|
+
if options[:dry_run]
|
|
272
|
+
print_dry_run @clouds_interface.dry.create_host_vm_group(cloud['id'], payload)
|
|
273
|
+
return
|
|
274
|
+
end
|
|
275
|
+
json_response = @clouds_interface.create_host_vm_group(cloud['id'], payload)
|
|
276
|
+
end
|
|
277
|
+
else
|
|
278
|
+
print_error Morpheus::Terminal.angry_prompt
|
|
279
|
+
puts_error "#{command_name} add requires a scope: pass --cloud CLOUD or --cluster CLUSTER\n#{optparse}"
|
|
280
|
+
return 1, "scope required"
|
|
281
|
+
end
|
|
282
|
+
render_response(json_response, options, 'hostVmGroup') do
|
|
283
|
+
row = json_response['hostVmGroup']
|
|
284
|
+
print_green_success "Created host/VM group #{row ? row['name'] : ''}"
|
|
285
|
+
end
|
|
286
|
+
return 0, nil
|
|
287
|
+
rescue RestClient::Exception => e
|
|
288
|
+
handle_affinity_rest_exception(e, options)
|
|
289
|
+
exit 1
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def update(args)
|
|
294
|
+
options = {}
|
|
295
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
296
|
+
opts.banner = subcommand_usage("[id] [options]")
|
|
297
|
+
build_option_type_options(opts, options, update_host_vm_group_option_types)
|
|
298
|
+
opts.on('--servers ID1,ID2', Array, "Server ids") do |val|
|
|
299
|
+
options[:options] ||= {}
|
|
300
|
+
options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
|
|
301
|
+
end
|
|
302
|
+
add_perms_options(opts, options, ['plans'])
|
|
303
|
+
build_standard_update_options(opts, options)
|
|
304
|
+
opts.footer = "Update a host/VM group.\n" \
|
|
305
|
+
"[id] is required. This is the numeric id of the host/VM group."
|
|
306
|
+
end
|
|
307
|
+
optparse.parse!(args)
|
|
308
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
309
|
+
connect(options)
|
|
310
|
+
|
|
311
|
+
begin
|
|
312
|
+
id = args[0]
|
|
313
|
+
if options[:payload]
|
|
314
|
+
payload = options[:payload]
|
|
315
|
+
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
|
316
|
+
else
|
|
317
|
+
payload = {'hostVmGroup' => {}}
|
|
318
|
+
v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_host_vm_group_option_types, options[:options], @api_client, {})
|
|
319
|
+
payload.deep_merge!({'hostVmGroup' => v_prompt})
|
|
320
|
+
|
|
321
|
+
perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
|
|
322
|
+
payload['hostVmGroup']['permissions'] = perms['resourcePermissions'] if perms['resourcePermissions']
|
|
323
|
+
payload['hostVmGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
|
324
|
+
|
|
325
|
+
if options[:options]
|
|
326
|
+
payload.deep_merge!({'hostVmGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) || payload['hostVmGroup'].key?(k) }})
|
|
327
|
+
end
|
|
328
|
+
if payload['hostVmGroup'].empty?
|
|
329
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
@host_vm_groups_interface.setopts(options)
|
|
334
|
+
if options[:dry_run]
|
|
335
|
+
print_dry_run @host_vm_groups_interface.dry.update(id, payload)
|
|
336
|
+
return
|
|
337
|
+
end
|
|
338
|
+
json_response = @host_vm_groups_interface.update(id, payload)
|
|
339
|
+
render_response(json_response, options, 'hostVmGroup') do
|
|
340
|
+
updated = json_response['hostVmGroup']
|
|
341
|
+
print_green_success "Updated host/VM group #{updated ? updated['name'] : id}"
|
|
342
|
+
end
|
|
343
|
+
return 0, nil
|
|
344
|
+
rescue RestClient::Exception => e
|
|
345
|
+
handle_affinity_rest_exception(e, options)
|
|
346
|
+
exit 1
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def remove(args)
|
|
351
|
+
options = {}
|
|
352
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
353
|
+
opts.banner = subcommand_usage("[id]")
|
|
354
|
+
build_standard_remove_options(opts, options)
|
|
355
|
+
opts.footer = "Delete a host/VM group.\n" \
|
|
356
|
+
"[id] is required. This is the numeric id of the host/VM group."
|
|
357
|
+
end
|
|
358
|
+
optparse.parse!(args)
|
|
359
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
360
|
+
connect(options)
|
|
361
|
+
|
|
362
|
+
id = args[0]
|
|
363
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete host/VM group '#{id}'?", options)
|
|
364
|
+
return 9, "aborted command"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
@host_vm_groups_interface.setopts(options)
|
|
368
|
+
if options[:dry_run]
|
|
369
|
+
print_dry_run @host_vm_groups_interface.dry.destroy(id)
|
|
370
|
+
return
|
|
371
|
+
end
|
|
372
|
+
begin
|
|
373
|
+
json_response = @host_vm_groups_interface.destroy(id)
|
|
374
|
+
render_response(json_response, options) do
|
|
375
|
+
print_green_success "Removed host/VM group #{id}"
|
|
376
|
+
end
|
|
377
|
+
return 0, nil
|
|
378
|
+
rescue RestClient::Exception => e
|
|
379
|
+
handle_affinity_rest_exception(e, options)
|
|
380
|
+
exit 1
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
private
|
|
385
|
+
|
|
386
|
+
def host_vm_group_list_columns
|
|
387
|
+
{
|
|
388
|
+
"ID" => 'id',
|
|
389
|
+
"Name" => 'name',
|
|
390
|
+
"Type" => lambda {|it| format_host_vm_group_type(it['type']) },
|
|
391
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
|
392
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
393
|
+
}.upcase_keys!
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def host_vm_group_detail_columns
|
|
397
|
+
{
|
|
398
|
+
"ID" => 'id',
|
|
399
|
+
"Name" => 'name',
|
|
400
|
+
"Type" => lambda {|it| format_host_vm_group_type(it['type']) },
|
|
401
|
+
"Ref Type" => 'refType',
|
|
402
|
+
"Ref ID" => 'refId',
|
|
403
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
|
404
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
|
405
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
406
|
+
"Source" => 'source',
|
|
407
|
+
}
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def add_host_vm_group_option_types
|
|
411
|
+
[
|
|
412
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
|
413
|
+
host_vm_group_type_option_type(required: true, default: 'HOST_GROUP'),
|
|
414
|
+
]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def update_host_vm_group_option_types
|
|
418
|
+
[
|
|
419
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text'},
|
|
420
|
+
]
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
end
|
|
@@ -15,7 +15,7 @@ class Morpheus::Cli::Hosts
|
|
|
15
15
|
:wiki, :update_wiki,
|
|
16
16
|
:maintenance, :leave_maintenance, :placement,
|
|
17
17
|
:list_devices, :assign_device, :detach_device, :attach_device,
|
|
18
|
-
:snapshot
|
|
18
|
+
:snapshot, :create_linked_clone
|
|
19
19
|
alias_subcommand :details, :get
|
|
20
20
|
set_default_subcommand :list
|
|
21
21
|
|
|
@@ -796,6 +796,10 @@ class Morpheus::Cli::Hosts
|
|
|
796
796
|
opts.on("--security-groups LIST", Integer, "Security Groups, comma separated list of security group IDs") do |val|
|
|
797
797
|
options[:security_groups] = val.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
|
|
798
798
|
end
|
|
799
|
+
opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
|
|
800
|
+
options[:options] ||= {}
|
|
801
|
+
options[:options]['storageController'] = JSON.parse(val)
|
|
802
|
+
end
|
|
799
803
|
opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
|
800
804
|
options[:metadata] = val
|
|
801
805
|
end
|
|
@@ -956,6 +960,10 @@ class Morpheus::Cli::Hosts
|
|
|
956
960
|
payload['volumes'] = volumes
|
|
957
961
|
end
|
|
958
962
|
|
|
963
|
+
if options[:options] && options[:options]['storageController']
|
|
964
|
+
payload['storageController'] = options[:options]['storageController']
|
|
965
|
+
end
|
|
966
|
+
|
|
959
967
|
# plan customizations
|
|
960
968
|
plan_opts = prompt_service_plan_options(service_plan, options, @api_client, {})
|
|
961
969
|
if plan_opts && !plan_opts.empty?
|
|
@@ -1347,6 +1355,10 @@ class Morpheus::Cli::Hosts
|
|
|
1347
1355
|
options = {}
|
|
1348
1356
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
1349
1357
|
opts.banner = subcommand_usage("[name]")
|
|
1358
|
+
opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
|
|
1359
|
+
options[:options] ||= {}
|
|
1360
|
+
options[:options]['storageController'] = JSON.parse(val)
|
|
1361
|
+
end
|
|
1350
1362
|
build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
|
|
1351
1363
|
end
|
|
1352
1364
|
optparse.parse!(args)
|
|
@@ -1435,6 +1447,9 @@ class Morpheus::Cli::Hosts
|
|
|
1435
1447
|
# only amazon supports this option
|
|
1436
1448
|
# for now, always do this
|
|
1437
1449
|
payload[:deleteOriginalVolumes] = true
|
|
1450
|
+
if options[:options] && options[:options]['storageController']
|
|
1451
|
+
payload[:storageController] = options[:options]['storageController']
|
|
1452
|
+
end
|
|
1438
1453
|
@servers_interface.setopts(options)
|
|
1439
1454
|
if options[:dry_run]
|
|
1440
1455
|
print_dry_run @servers_interface.dry.resize(server['id'], payload)
|
|
@@ -2716,6 +2731,62 @@ EOT
|
|
|
2716
2731
|
return 0, nil
|
|
2717
2732
|
end
|
|
2718
2733
|
|
|
2734
|
+
def create_linked_clone(args)
|
|
2735
|
+
options = {}
|
|
2736
|
+
server = nil
|
|
2737
|
+
snapshot_id = nil
|
|
2738
|
+
|
|
2739
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
2740
|
+
opts.banner = subcommand_usage("[host]")
|
|
2741
|
+
opts.on("--snapshot ID", String, "Snapshot ID") do |val|
|
|
2742
|
+
snapshot_id = val
|
|
2743
|
+
end
|
|
2744
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
|
2745
|
+
opts.footer = "Create a linked clone using the selected snapshot of a Host." + "\n" +
|
|
2746
|
+
"[snapshotId] is required. This is the id of the snapshot which the clone will refer to."
|
|
2747
|
+
end
|
|
2748
|
+
|
|
2749
|
+
optparse.parse!(args)
|
|
2750
|
+
if args.count != 1
|
|
2751
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
|
2752
|
+
end
|
|
2753
|
+
connect(options)
|
|
2754
|
+
begin
|
|
2755
|
+
server = find_host_by_name_or_id(args[0])
|
|
2756
|
+
options[:options]['serverId'] = server['id']
|
|
2757
|
+
begin
|
|
2758
|
+
snapshot_options = @servers_interface.snapshots(server['id'], {})['snapshots'].collect {|it| {'name' => "#{it['name']} (#{it['id']})", 'value' => it['id']} }
|
|
2759
|
+
snapshot_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'snapshotId', 'type' => 'select', 'fieldLabel' => 'Snapshot', 'selectOptions' => snapshot_options, 'required' => true, 'description' => 'Select Snapshot.'}], {}, @api_client, options[:options])
|
|
2760
|
+
|
|
2761
|
+
if !snapshot_prompt['snapshotId'].to_s.empty?
|
|
2762
|
+
snapshot_id = snapshot_prompt['snapshotId']
|
|
2763
|
+
end
|
|
2764
|
+
rescue RestClient::Exception => e
|
|
2765
|
+
puts "Failed to load host snapshots"
|
|
2766
|
+
end
|
|
2767
|
+
|
|
2768
|
+
@servers_interface.setopts(options)
|
|
2769
|
+
|
|
2770
|
+
payload = {}
|
|
2771
|
+
if options[:dry_run]
|
|
2772
|
+
print_dry_run @servers_interface.dry.create_linked_clone(server['id'], snapshot_id, payload)
|
|
2773
|
+
return
|
|
2774
|
+
end
|
|
2775
|
+
|
|
2776
|
+
json_response = @servers_interface.create_linked_clone(server['id'], snapshot_id, payload)
|
|
2777
|
+
if options[:json]
|
|
2778
|
+
puts as_json(json_response, options)
|
|
2779
|
+
else
|
|
2780
|
+
print_green_success "Linked Clone creation initiated."
|
|
2781
|
+
end
|
|
2782
|
+
return 0
|
|
2783
|
+
|
|
2784
|
+
rescue RestClient::Exception => e
|
|
2785
|
+
print_rest_exception(e, options)
|
|
2786
|
+
exit 1
|
|
2787
|
+
end
|
|
2788
|
+
end
|
|
2789
|
+
|
|
2719
2790
|
## Server Devices
|
|
2720
2791
|
|
|
2721
2792
|
def list_devices(args)
|
|
@@ -465,6 +465,10 @@ class Morpheus::Cli::Instances
|
|
|
465
465
|
opts.on("--layout-size NUMBER", Integer, "Apply a multiply factor of containers/vms within the instance") do |val|
|
|
466
466
|
options[:layout_size] = val.to_i
|
|
467
467
|
end
|
|
468
|
+
opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable. Example: '[{\"busNumber\":\"1\",\"typeName\":\"SCSI\"}]'") do |val|
|
|
469
|
+
options[:options] ||= {}
|
|
470
|
+
options[:options]['storageController'] = JSON.parse(val)
|
|
471
|
+
end
|
|
468
472
|
opts.on( '-l', '--layout LAYOUT', "Layout ID" ) do |val|
|
|
469
473
|
options[:layout] = val
|
|
470
474
|
end
|
|
@@ -595,6 +599,9 @@ class Morpheus::Cli::Instances
|
|
|
595
599
|
end
|
|
596
600
|
|
|
597
601
|
payload['instance'] ||= {}
|
|
602
|
+
if options[:options] && options[:options]['storageController']
|
|
603
|
+
payload['storageController'] = options[:options]['storageController']
|
|
604
|
+
end
|
|
598
605
|
if options[:instance_name]
|
|
599
606
|
payload['instance']['name'] = options[:instance_name]
|
|
600
607
|
end
|
|
@@ -2910,6 +2917,10 @@ class Morpheus::Cli::Instances
|
|
|
2910
2917
|
opts.on('--include-network-interfaces','--include-network-interfaces', "Populate payload networkInterfaces with current interfaces") do
|
|
2911
2918
|
options[:include_nics] = true
|
|
2912
2919
|
end
|
|
2920
|
+
opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
|
|
2921
|
+
options[:options] ||= {}
|
|
2922
|
+
options[:options]['storageController'] = JSON.parse(val)
|
|
2923
|
+
end
|
|
2913
2924
|
build_standard_update_options(opts, options)
|
|
2914
2925
|
end
|
|
2915
2926
|
optparse.parse!(args)
|
|
@@ -3958,7 +3969,7 @@ List snapshots for an instance.
|
|
|
3958
3969
|
|
|
3959
3970
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3960
3971
|
opts.banner = subcommand_usage("[instance]")
|
|
3961
|
-
opts.on("--snapshot ID", String, "
|
|
3972
|
+
opts.on("--snapshot ID", String, "Snapshot ID") do |val|
|
|
3962
3973
|
snapshot_id = val
|
|
3963
3974
|
end
|
|
3964
3975
|
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|