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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/affinity_groups_interface.rb +46 -0
  4. data/lib/morpheus/api/api_client.rb +17 -0
  5. data/lib/morpheus/api/clouds_interface.rb +42 -0
  6. data/lib/morpheus/api/clusters_interface.rb +55 -1
  7. data/lib/morpheus/api/distributed_workers_interface.rb +41 -0
  8. data/lib/morpheus/api/host_vm_groups_interface.rb +37 -0
  9. data/lib/morpheus/api/servers_interface.rb +8 -0
  10. data/lib/morpheus/api/virtual_switches_interface.rb +9 -0
  11. data/lib/morpheus/cli/commands/affinity_groups_command.rb +554 -0
  12. data/lib/morpheus/cli/commands/backups_command.rb +11 -0
  13. data/lib/morpheus/cli/commands/clouds.rb +421 -29
  14. data/lib/morpheus/cli/commands/clusters.rb +507 -20
  15. data/lib/morpheus/cli/commands/distributed_workers_command.rb +282 -0
  16. data/lib/morpheus/cli/commands/host_vm_groups_command.rb +423 -0
  17. data/lib/morpheus/cli/commands/hosts.rb +72 -1
  18. data/lib/morpheus/cli/commands/instances.rb +12 -1
  19. data/lib/morpheus/cli/commands/networks_command.rb +35 -3
  20. data/lib/morpheus/cli/commands/virtual_switches_command.rb +336 -0
  21. data/lib/morpheus/cli/mixins/affinity_helper.rb +118 -0
  22. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +143 -4
  23. data/lib/morpheus/cli/option_types.rb +36 -1
  24. data/lib/morpheus/cli/version.rb +1 -1
  25. data/test/api/affinity_groups_interface_test.rb +43 -0
  26. data/test/api/clouds_interface_test.rb +67 -0
  27. data/test/api/host_vm_groups_interface_test.rb +35 -0
  28. data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
  29. data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
  30. data/test/cli/affinity_groups_test.rb +57 -0
  31. data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
  32. data/test/cli/affinity_helper_test.rb +367 -0
  33. data/test/cli/backups_test.rb +212 -0
  34. data/test/cli/clusters_uuid_test.rb +131 -0
  35. data/test/cli/distributed_workers_test.rb +68 -0
  36. data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
  37. data/test/cli/host_vm_groups_test.rb +63 -0
  38. data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
  39. 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, "Optional snapshot") do |val|
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])