morpheus-cli 5.3.2.1 → 5.3.4

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/clouds_interface.rb +4 -11
  5. data/lib/morpheus/api/instances_interface.rb +18 -5
  6. data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
  7. data/lib/morpheus/api/load_balancer_profiles_interface.rb +10 -0
  8. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +4 -4
  9. data/lib/morpheus/api/network_routers_interface.rb +21 -0
  10. data/lib/morpheus/api/network_servers_interface.rb +42 -0
  11. data/lib/morpheus/api/rest_interface.rb +2 -1
  12. data/lib/morpheus/api/virtual_images_interface.rb +23 -2
  13. data/lib/morpheus/api/virtual_servers_interface.rb +9 -0
  14. data/lib/morpheus/cli/apps.rb +3 -2
  15. data/lib/morpheus/cli/cli_command.rb +14 -6
  16. data/lib/morpheus/cli/cli_registry.rb +55 -2
  17. data/lib/morpheus/cli/cloud_resource_pools_command.rb +170 -134
  18. data/lib/morpheus/cli/clouds.rb +22 -40
  19. data/lib/morpheus/cli/clusters.rb +51 -33
  20. data/lib/morpheus/cli/hosts.rb +0 -1
  21. data/lib/morpheus/cli/instances.rb +372 -150
  22. data/lib/morpheus/cli/invoices_command.rb +117 -133
  23. data/lib/morpheus/cli/library_cluster_layouts_command.rb +20 -0
  24. data/lib/morpheus/cli/library_option_lists_command.rb +3 -3
  25. data/lib/morpheus/cli/load_balancer_pools.rb +111 -0
  26. data/lib/morpheus/cli/load_balancer_virtual_servers.rb +136 -0
  27. data/lib/morpheus/cli/load_balancers.rb +0 -155
  28. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +2 -2
  29. data/lib/morpheus/cli/mixins/provisioning_helper.rb +155 -112
  30. data/lib/morpheus/cli/mixins/rest_command.rb +53 -37
  31. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +488 -0
  32. data/lib/morpheus/cli/monitoring_checks_command.rb +2 -0
  33. data/lib/morpheus/cli/network_routers_command.rb +291 -7
  34. data/lib/morpheus/cli/network_scopes_command.rb +442 -0
  35. data/lib/morpheus/cli/networks_command.rb +3 -3
  36. data/lib/morpheus/cli/option_parser.rb +25 -17
  37. data/lib/morpheus/cli/option_types.rb +42 -15
  38. data/lib/morpheus/cli/subnets_command.rb +7 -2
  39. data/lib/morpheus/cli/tasks.rb +25 -2
  40. data/lib/morpheus/cli/vdi_pools_command.rb +4 -1
  41. data/lib/morpheus/cli/version.rb +1 -1
  42. data/lib/morpheus/cli/virtual_images.rb +251 -29
  43. data/lib/morpheus/cli.rb +9 -1
  44. data/morpheus-cli.gemspec +1 -1
  45. metadata +11 -4
@@ -0,0 +1,442 @@
1
+ require 'rest_client'
2
+ require 'optparse'
3
+ require 'filesize'
4
+ require 'morpheus/cli/cli_command'
5
+ require 'morpheus/cli/mixins/infrastructure_helper'
6
+
7
+ class Morpheus::Cli::NetworkScopesCommand
8
+ include Morpheus::Cli::CliCommand
9
+ include Morpheus::Cli::ProvisioningHelper
10
+ include Morpheus::Cli::WhoamiHelper
11
+
12
+ set_command_name :'network-scopes'
13
+ register_subcommands :list, :get, :add, :update, :remove
14
+
15
+ def connect(opts)
16
+ @api_client = establish_remote_appliance_connection(opts)
17
+ @account_interface = @api_client.accounts
18
+ @network_servers_interface = @api_client.network_servers
19
+ @options_interface = @api_client.options
20
+ end
21
+
22
+ def handle(args)
23
+ handle_subcommand(args)
24
+ end
25
+
26
+ def list(args)
27
+ options = {}
28
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
29
+ opts.banner = subcommand_usage("[server]")
30
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
31
+ opts.footer = "List network scopes." + "\n" +
32
+ "[server] is required. This is the name or id of a network server."
33
+ end
34
+
35
+ optparse.parse!(args)
36
+ connect(options)
37
+
38
+ if args.count < 1
39
+ puts optparse
40
+ return 1
41
+ end
42
+
43
+ server = find_network_server(args[0])
44
+ if server.nil?
45
+ return 1
46
+ end
47
+
48
+ _list(server, options)
49
+ end
50
+
51
+ def _list(server, options)
52
+ @network_servers_interface.setopts(options)
53
+
54
+ if options[:dry_run]
55
+ print_dry_run @network_servers_interface.dry.list_scopes(server['id'])
56
+ return
57
+ end
58
+
59
+ if server['type']['hasScopes']
60
+ json_response = @network_servers_interface.list_scopes(server['id'])
61
+ render_response(json_response, options, 'networkScopes') do
62
+ print_h1 "Network Scopes For: #{server['name']}"
63
+ print cyan
64
+ print_scopes(server, json_response['networkScopes'])
65
+ end
66
+ else
67
+ print_red_alert "Scopes not supported for #{server['type']['name']}"
68
+ end
69
+ print reset
70
+ end
71
+
72
+ def get(args)
73
+ options = {}
74
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
75
+ opts.banner = subcommand_usage("[server] [scope]")
76
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
77
+ opts.footer = "Display details on a network scope." + "\n" +
78
+ "[server] is required. This is the name or id of a network server.\n" +
79
+ "[scope] is required. This is the id of a network scope.\n"
80
+ end
81
+
82
+ optparse.parse!(args)
83
+ connect(options)
84
+
85
+ if args.count < 2
86
+ puts optparse
87
+ return 1
88
+ end
89
+
90
+ server = find_network_server(args[0])
91
+ if server.nil?
92
+ return 1
93
+ end
94
+
95
+ _get(server, args[1], options)
96
+ end
97
+
98
+ def _get(server, scope_id, options)
99
+ @network_servers_interface.setopts(options)
100
+
101
+ if options[:dry_run]
102
+ if scope_id.to_s =~ /\A\d{1,}\Z/
103
+ print_dry_run @network_servers_interface.dry.get_scope(server['id'], scope_id.to_i)
104
+ else
105
+ print_dry_run @network_servers_interface.dry.list_scopes(server['id'], {name: scope_id})
106
+ end
107
+ return
108
+ end
109
+
110
+ if server['type']['hasScopes']
111
+ scope = find_scope(server['id'], scope_id)
112
+
113
+ return 1 if scope.nil?
114
+
115
+ render_response({networkScope: scope}, options, 'networkScope') do
116
+ print_h1 "Network Scope Details"
117
+ print cyan
118
+
119
+ description_cols = {
120
+ "ID" => lambda {|it| it['id']},
121
+ "Name" => lambda {|it| it['name']},
122
+ "Description" => lambda {|it| it['description']},
123
+ "Status" => lambda {|it| it['status']}
124
+ }
125
+
126
+ if is_master_account
127
+ description_cols["Visibility"] = lambda {|it| it['visibility']}
128
+ description_cols["Tenants"] = lambda {|it| it['tenants'].collect {|tenant| tenant['name']}.join(', ')}
129
+ end
130
+
131
+ server['type']['scopeOptionTypes'].sort_by {|it| it['displayOrder']}.each do |option_type|
132
+ description_cols[option_type['fieldLabel']] = lambda {|it| Morpheus::Cli::OptionTypes.get_option_value(it, option_type, true)}
133
+ end
134
+ print_description_list(description_cols, scope)
135
+ end
136
+ else
137
+ print_red_alert "Scopes not supported for #{server['type']['name']}"
138
+ end
139
+ println reset
140
+ end
141
+
142
+ def add(args)
143
+ options = {:options=>{}}
144
+ params = {}
145
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
146
+ opts.banner = subcommand_usage("[server]")
147
+ opts.on( '--name NAME', "Name" ) do |val|
148
+ options[:name] = val.to_s
149
+ end
150
+ opts.on("--description [TEXT]", String, "Description") do |val|
151
+ options[:description] = val.to_s
152
+ end
153
+ add_perms_options(opts, options, ['plans', 'groups'])
154
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
155
+ opts.footer = "Create a network scope." + "\n" +
156
+ "[server] is required. This is the name or id of a network server.\n";
157
+ end
158
+ optparse.parse!(args)
159
+ connect(options)
160
+ if args.count < 1
161
+ print_error Morpheus::Terminal.angry_prompt
162
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
163
+ return 1
164
+ end
165
+
166
+ server = find_network_server(args[0])
167
+ if server.nil?
168
+ return 1
169
+ end
170
+
171
+ if !server['type']['hasScopes']
172
+ print_red_alert "Scopes not supported for #{server['type']['name']}"
173
+ return 1
174
+ end
175
+
176
+ if options[:payload]
177
+ payload = options[:payload]
178
+ else
179
+ params['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true, 'description' => 'Name.'}],options[:options],@api_client,{})['name']
180
+ params['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false, 'description' => 'Description.'}],options[:options],@api_client,{})['description']
181
+
182
+ option_types = server['type']['scopeOptionTypes'].sort_by {|it| it['displayOrder']}
183
+
184
+ # prompt options
185
+ option_result = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options].deep_merge({:context_map => {'scope' => ''}}), @api_client, {'networkServerId' => server['id']}, nil, true)
186
+
187
+ # prompt permissions
188
+ perms = prompt_permissions(options, ['plans', 'groups'])
189
+ perms = {'visibility' => perms['resourcePool']['visibility'], 'tenants' => perms['tenantPermissions']['accounts'].collect {|it| {'id' => it}}}
190
+ payload = {'networkScope' => params.deep_merge(option_result).deep_merge(perms)}
191
+ end
192
+
193
+ @network_servers_interface.setopts(options)
194
+
195
+ if options[:dry_run]
196
+ print_dry_run @network_servers_interface.dry.create_scope(server['id'], payload)
197
+ return
198
+ end
199
+
200
+ json_response = @network_servers_interface.create_scope(server['id'], payload)
201
+ render_response(json_response, options, 'networkScope') do
202
+ print_green_success "\nAdded Network Scope #{json_response['id']}\n"
203
+ _get(server, json_response['id'], options)
204
+ end
205
+ end
206
+
207
+ def update(args)
208
+ options = {:options=>{}}
209
+ params = {}
210
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
211
+ opts.banner = subcommand_usage("[server] [scope]")
212
+ opts.on( '--name NAME', "Name" ) do |val|
213
+ params['name'] = val.to_s
214
+ end
215
+ opts.on("--description [TEXT]", String, "Description") do |val|
216
+ params['description'] = val.to_s
217
+ end
218
+ add_perms_options(opts, options, ['plans', 'groups'])
219
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
220
+ opts.footer = "Update a network scope.\n" +
221
+ "[server] is required. This is the name or id of an existing network server.\n" +
222
+ "[scope] is required. This is the name or id of an existing network scope."
223
+ end
224
+ optparse.parse!(args)
225
+ if args.count != 2
226
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
227
+ end
228
+ connect(options)
229
+
230
+ server = find_network_server(args[0])
231
+ if server.nil?
232
+ return 1
233
+ end
234
+
235
+ if !server['type']['hasScopes']
236
+ print_red_alert "Scopes not supported for #{server['type']['name']}"
237
+ return 1
238
+ end
239
+
240
+ scope = find_scope(server['id'], args[1])
241
+ return 1 if scope.nil?
242
+
243
+ payload = parse_payload(options) || {'networkScope' => params}
244
+ payload['networkScope'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options] && !payload['networkScope'].nil?
245
+
246
+ if !options[:visibility].nil?
247
+ payload['networkScope']['visibility'] = options[:visibility]
248
+ end
249
+ if !options[:tenants].nil?
250
+ payload['networkScope']['tenants'] = options[:tenants].collect {|id| {id: id.to_i}}
251
+ end
252
+
253
+ if payload['networkScope'].empty?
254
+ option_types = server['type']['scopeOptionTypes'].sort_by {|it| it['displayOrder']}
255
+ print_green_success "Nothing to update"
256
+ println cyan
257
+ print Morpheus::Cli::OptionTypes.display_option_types_help(
258
+ option_types,
259
+ {:include_context => true, :context_map => {'scope' => ''}, :color => cyan, :title => "Available Scope Options"}
260
+ )
261
+ exit 1
262
+ end
263
+
264
+ #payload = {'networkScope' => scope.deep_merge(payload['networkScope'])}
265
+
266
+ @network_servers_interface.setopts(options)
267
+
268
+ if options[:dry_run]
269
+ print_dry_run @network_servers_interface.dry.update_scope(server['id'], scope['id'], payload)
270
+ return
271
+ end
272
+
273
+ json_response = @network_servers_interface.update_scope(server['id'], scope['id'], payload)
274
+ render_response(json_response, options, 'networkScope') do
275
+ print_green_success "\nUpdated Network Scope #{scope['id']}\n"
276
+ _get(server, scope['id'], options)
277
+ end
278
+ end
279
+
280
+ def remove(args)
281
+ options = {}
282
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
283
+ opts.banner = subcommand_usage("[server] [scope]")
284
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
285
+ opts.footer = "Delete a network scope.\n" +
286
+ "[server] is required. This is the name or id of an existing network server.\n" +
287
+ "[scope] is required. This is the name or id of an existing network scope."
288
+ end
289
+ optparse.parse!(args)
290
+ if args.count != 2
291
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
292
+ end
293
+ connect(options)
294
+
295
+ server = find_network_server(args[0])
296
+ if server.nil?
297
+ return 1
298
+ end
299
+
300
+ if !server['type']['hasScopes']
301
+ print_red_alert "Scopes not supported for #{server['type']['name']}"
302
+ return 1
303
+ end
304
+
305
+ scope = find_scope(server['id'], args[1])
306
+ return 1 if scope.nil?
307
+
308
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the network scope '#{scope['name']}' from server '#{server['name']}'?", options)
309
+ return 9, "aborted command"
310
+ end
311
+
312
+ @network_servers_interface.setopts(options)
313
+
314
+ if options[:dry_run]
315
+ print_dry_run @network_servers_interface.dry.destroy_scope(server['id'], scope['id'])
316
+ return
317
+ end
318
+ json_response = @network_servers_interface.destroy_scope(server['id'], scope['id'])
319
+ render_response(json_response, options, 'networkScope') do
320
+ print_green_success "\nDeleted Network Scope #{scope['name']}\n"
321
+ _list(server, options)
322
+ end
323
+ end
324
+
325
+ private
326
+
327
+ def print_scopes(server, scopes)
328
+ if scopes.count > 0
329
+ cols = [:id, :name, :description, :status]
330
+ server['type']['scopeOptionTypes'].sort_by {|it| it['displayOrder']}.each do |option_type|
331
+ cols << option_type['fieldLabel']
332
+ end
333
+ cols += [:visibility, :tenants] if is_master_account
334
+ rows = scopes.collect do |it|
335
+ row = {
336
+ id: it['id'],
337
+ name: it['name'],
338
+ description: it['description'],
339
+ status: it['statusMessage'],
340
+ }
341
+ server['type']['scopeOptionTypes'].sort_by {|it| it['displayOrder']}.each do |option_type|
342
+ row[option_type['fieldLabel']] = Morpheus::Cli::OptionTypes.get_option_value(it, option_type, true)
343
+ end
344
+ if is_master_account
345
+ row = row.merge({
346
+ visibility: it['visibility'],
347
+ tenants: it['tenants'].collect {|it| it['name']}.join(', ')
348
+ })
349
+ end
350
+ row
351
+ end
352
+ puts as_pretty_table(rows, cols)
353
+ else
354
+ println "No Scopes\n"
355
+ end
356
+ end
357
+
358
+ def find_network_server(val)
359
+ if val.to_s =~ /\A\d{1,}\Z/
360
+ return find_network_server_by_id(val)
361
+ else
362
+ if server = find_network_server_by_name(val)
363
+ return find_network_server_by_id(server['id'])
364
+ end
365
+ end
366
+ end
367
+
368
+ def find_network_server_by_id(id)
369
+ begin
370
+ json_response = @network_servers_interface.get(id.to_i)
371
+ return json_response['networkServer']
372
+ rescue RestClient::Exception => e
373
+ if e.response && e.response.code == 404
374
+ print_red_alert "Network Server not found by id #{id}"
375
+ return nil
376
+ else
377
+ raise e
378
+ end
379
+ end
380
+ end
381
+
382
+ def find_network_server_by_name(name)
383
+ json_response = @network_servers_interface.list({phrase: name.to_s})
384
+ servers = json_response['networkServers']
385
+ if servers.empty?
386
+ print_red_alert "Network Server not found by name #{name}"
387
+ return nil
388
+ elsif servers.size > 1
389
+ print_red_alert "#{servers.size} network servers found by name #{name}"
390
+ rows = servers.collect do |it|
391
+ {id: it['id'], name: it['name']}
392
+ end
393
+ puts as_pretty_table(rows, [:id, :name], {color:red})
394
+ return nil
395
+ else
396
+ return servers[0]
397
+ end
398
+ end
399
+
400
+ def find_scope(server_id, val)
401
+ if val.to_s =~ /\A\d{1,}\Z/
402
+ return find_scope_by_id(server_id, val)
403
+ else
404
+ if scope = find_scope_by_name(server_id, val)
405
+ return find_scope_by_id(server_id, scope['id'])
406
+ end
407
+ end
408
+ end
409
+
410
+ def find_scope_by_id(server_id, scope_id)
411
+ begin
412
+ json_response = @network_servers_interface.get_scope(server_id, scope_id.to_i)
413
+ return json_response['networkScope']
414
+ rescue RestClient::Exception => e
415
+ if e.response && e.response.code == 404
416
+ print_red_alert "Network Scope not found by id #{id}"
417
+ return nil
418
+ else
419
+ raise e
420
+ end
421
+ end
422
+ end
423
+
424
+ def find_scope_by_name(server_id, name)
425
+ json_response = @network_servers_interface.list_scope(server_id, {phrase: name.to_s})
426
+ scopes = json_response['networkScopes']
427
+ if scopes.empty?
428
+ print_red_alert "Network Scope not found by name #{name}"
429
+ return nil
430
+ elsif scopes.size > 1
431
+ print_red_alert "#{scopes.size} network scopes found by name #{name}"
432
+ rows = scopes.collect do |it|
433
+ {id: it['id'], name: it['name']}
434
+ end
435
+ puts as_pretty_table(rows, [:id, :name], {color:red})
436
+ return nil
437
+ else
438
+ return scopes[0]
439
+ end
440
+ end
441
+
442
+ end
@@ -617,9 +617,10 @@ class Morpheus::Cli::NetworksCommand
617
617
  if network_type_option_types && network_type_option_types.size > 0
618
618
  api_params = {}
619
619
  api_params['network.site.id'] = group ? group['id'] : 'shared'
620
+ api_params['network.zone.id'] = cloud['id'] if !cloud.nil?
620
621
  api_params['network.type.id'] = network_type['id']
621
622
  api_params['network.networkServer.id'] = network_server_id if !network_server_id.nil?
622
- network_type_params = Morpheus::Cli::OptionTypes.prompt(network_type_option_types,options[:options],@api_client, api_params)
623
+ network_type_params = Morpheus::Cli::OptionTypes.prompt(network_type_option_types,(options[:options] || {}).merge(payload),@api_client, api_params)
623
624
  # network context options belong at network level and not network.network
624
625
  network_context_params = network_type_params.delete('network')
625
626
  payload['network'].deep_merge!(network_context_params) if network_context_params
@@ -660,8 +661,7 @@ class Morpheus::Cli::NetworksCommand
660
661
  payload['network']['pool'] = options['pool'].to_i
661
662
  else
662
663
  # todo: select dropdown
663
- # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'select', 'optionSource' => 'networkPools', 'required' => false, 'description' => ''}], options, @api_client, {zoneId: cloud['id']})
664
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'text', 'required' => false, 'description' => ''}], options)
664
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'select', 'optionSource' => 'networkPools', 'required' => false, 'description' => ''}], options, @api_client, api_params)
665
665
  payload['network']['pool'] = v_prompt['pool'].to_i if v_prompt['pool']
666
666
  end
667
667
  end
@@ -32,27 +32,35 @@ module Morpheus
32
32
  #out << "Options:\n"
33
33
  # the default way..
34
34
  # out << summarize().join("")
35
-
36
35
  # super hacky, should be examining the option, not the fully formatted description
37
36
  my_summaries = summarize()
38
- summarize().each do |opt_description|
39
- is_hidden = (@hidden_options || []).find { |hidden_switch|
40
- # opt_description.include?("--#{hidden_switch}")
41
- if hidden_switch.start_with?("-")
42
- opt_description.to_s.strip.start_with?("#{hidden_switch} ")
43
- else
44
- opt_description.to_s.strip.start_with?("--#{hidden_switch} ")
45
- end
46
- }
47
- if is_hidden
48
- if opts[:show_hidden_options]
49
- # out << opt_description + " (hidden)"
50
- out << opt_description
37
+ if opts[:show_hidden_options]
38
+ my_summaries.each do |full_line|
39
+ out << full_line
40
+ end
41
+ else
42
+ on_hidden_option = false
43
+ my_summaries.each do |full_line|
44
+ opt_description = full_line.to_s.strip
45
+ if opt_description.start_with?("-")
46
+ is_hidden = (@hidden_options || []).find { |hidden_switch|
47
+ if hidden_switch.start_with?("-")
48
+ opt_description.start_with?("#{hidden_switch} ")
49
+ else
50
+ opt_description.start_with?("--#{hidden_switch} ")
51
+ end
52
+ }
53
+ if is_hidden
54
+ on_hidden_option = true
55
+ else
56
+ on_hidden_option = false
57
+ out << full_line
58
+ end
51
59
  else
52
- # hidden
60
+ if on_hidden_option == false
61
+ out << full_line
62
+ end
53
63
  end
54
- else
55
- out << opt_description
56
64
  end
57
65
  end
58
66
  end
@@ -61,11 +61,11 @@ module Morpheus
61
61
  ).each do |option_type|
62
62
  context_map = results
63
63
  value = nil
64
- value_found=false
64
+ value_found = false
65
+ field_group = (option_type['fieldGroup'] || 'default').to_s.sub(/options\Z/i, "").strip # avoid "ADVANCED OPTION OPTIONS"
65
66
 
66
- if cur_field_group != (option_type['fieldGroup'] || 'default')
67
- cur_field_group = option_type['fieldGroup']
68
- cur_field_group = cur_field_group.to_s.sub(/options\Z/i, "").strip # avoid "ADVANCED OPTION OPTIONS"
67
+ if cur_field_group != field_group
68
+ cur_field_group = field_group
69
69
  print "\n#{cur_field_group.upcase} OPTIONS\n#{"=" * ("#{cur_field_group} OPTIONS".length)}\n\n"
70
70
  end
71
71
 
@@ -102,10 +102,10 @@ module Morpheus
102
102
  end
103
103
 
104
104
  found_dep_value = match_type == 'all' ? true : false
105
- visible_option_check_value.split(',').each do |value|
105
+ visible_option_check_value.sub(',', ' ').split(' ').each do |value|
106
106
  parts = value.split(':')
107
107
  depends_on_code = parts[0]
108
- depends_on_value = parts.count > 1 ? parts[1].to_s.strip : '.'
108
+ depends_on_value = parts.count > 1 ? parts[1].to_s.strip : nil
109
109
  depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
110
110
  if !depends_on_option_type
111
111
  depends_on_option_type = option_types.find {|it|
@@ -115,13 +115,18 @@ module Morpheus
115
115
 
116
116
  if depends_on_option_type
117
117
  depends_on_field_key = depends_on_option_type['fieldContext'].nil? || depends_on_option_type['fieldContext'].empty? ? "#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}"
118
- field_value = get_object_value(results, depends_on_field_key) || get_object_value(options, depends_on_field_key)
118
+ else
119
+ depends_on_field_key = depends_on_code
120
+ end
119
121
 
120
- if !field_value.nil? && field_value.match?(depends_on_value)
121
- found_dep_value = true if match_type != 'all'
122
- else
123
- found_dep_value = false if match_type == 'all'
124
- end
122
+ field_value = get_object_value(results, depends_on_field_key) ||
123
+ get_object_value(options, depends_on_field_key) ||
124
+ get_object_value(api_params, depends_on_field_key)
125
+
126
+ if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.match?(depends_on_value))
127
+ found_dep_value = true if match_type != 'all'
128
+ else
129
+ found_dep_value = false if match_type == 'all'
125
130
  end
126
131
  end
127
132
  next if !found_dep_value
@@ -237,7 +242,7 @@ module Morpheus
237
242
  # I suppose the entered value should take precedence
238
243
  # api_params = api_params.merge(options) # this might be good enough
239
244
  # dup it
240
- value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
245
+ value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).deep_merge(results)), options[:no_prompt], nil, paging_enabled)
241
246
  if value && option_type['type'] == 'multiSelect'
242
247
  value = [value]
243
248
  while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
@@ -276,7 +281,8 @@ module Morpheus
276
281
  value = [value] if !value.nil? && !value.is_a?(Array)
277
282
  # parent_context_map[parent_ns] = value
278
283
  end
279
- context_map[field_name] = value
284
+ context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
285
+ parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
280
286
  end
281
287
  results
282
288
  end
@@ -947,8 +953,9 @@ module Morpheus
947
953
  out << "\n"
948
954
  out << "#{header}\n"
949
955
  out << "#{'=' * header.length}\n"
956
+
950
957
  select_options.each do |option|
951
- out << " * #{option['name']} [#{option['value']}]\n"
958
+ out << (option['isGroup'] ? "- #{option['name']}\n" : " * #{option['name']} [#{option['value']}]\n")
952
959
  end
953
960
  return out
954
961
  end
@@ -985,6 +992,26 @@ module Morpheus
985
992
  return option_type['required'] ? '' : ' (optional)'
986
993
  end
987
994
  end
995
+
996
+ def self.get_option_value(obj, option_type, format=false)
997
+ context = option_type['fieldContext'] == 'config' ? obj['config'] : obj
998
+ name = option_type['fieldName']
999
+ tokens = name.split('.')
1000
+
1001
+ if tokens.length > 1
1002
+ tokens.slice(0, tokens.length - 1).each do |token|
1003
+ context = context[name = token]
1004
+ end
1005
+ end
1006
+
1007
+ rtn = context[name]
1008
+
1009
+ if format
1010
+ rtn = (rtn ? 'On' : 'Off') if option_type['type'] == 'checkbox'
1011
+ rtn = rtn.join(', ') if rtn.is_a?(Array)
1012
+ end
1013
+ rtn
1014
+ end
988
1015
  end
989
1016
  end
990
1017
  end
@@ -350,8 +350,13 @@ class Morpheus::Cli::SubnetsCommand
350
350
  if subnet_type_option_types && subnet_type_option_types.size > 0
351
351
  # prompt for option types
352
352
  subnet_type_params = Morpheus::Cli::OptionTypes.prompt(subnet_type_option_types,options[:options],@api_client, {networkId: network['id'], zoneId: network['zone']['id']})
353
- payload['subnet'].deep_merge!(subnet_type_params['subnet'])
354
-
353
+ if subnet_type_params['subnet']
354
+ payload['subnet'].deep_merge!(subnet_type_params['subnet'])
355
+ end
356
+ if subnet_type_params['config']
357
+ payload['subnet']['config'] ||= {}
358
+ payload['subnet']['config'].deep_merge!(subnet_type_params['config'])
359
+ end
355
360
  else
356
361
  # DEFAULT INPUTS
357
362