morpheus-cli 5.4.0 → 5.4.3.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/account_users_interface.rb +68 -0
  4. data/lib/morpheus/api/api_client.rb +55 -10
  5. data/lib/morpheus/api/audit_interface.rb +9 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +20 -0
  7. data/lib/morpheus/api/instances_interface.rb +49 -0
  8. data/lib/morpheus/api/load_balancer_monitors_interface.rb +9 -0
  9. data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
  10. data/lib/morpheus/api/load_balancer_profiles_interface.rb +4 -5
  11. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +13 -4
  12. data/lib/morpheus/api/load_balancers_interface.rb +5 -0
  13. data/lib/morpheus/api/network_routers_interface.rb +9 -0
  14. data/lib/morpheus/api/network_static_routes_interface.rb +36 -0
  15. data/lib/morpheus/api/ping_interface.rb +2 -0
  16. data/lib/morpheus/api/read_interface.rb +4 -3
  17. data/lib/morpheus/api/rest_interface.rb +3 -3
  18. data/lib/morpheus/api/secondary_read_interface.rb +1 -1
  19. data/lib/morpheus/api/secondary_rest_interface.rb +19 -19
  20. data/lib/morpheus/api/setup_interface.rb +4 -0
  21. data/lib/morpheus/api/snapshots_interface.rb +19 -0
  22. data/lib/morpheus/api/storage_server_types_interface.rb +14 -0
  23. data/lib/morpheus/api/storage_servers_interface.rb +9 -0
  24. data/lib/morpheus/api/storage_volume_types_interface.rb +9 -0
  25. data/lib/morpheus/api/storage_volumes_interface.rb +9 -0
  26. data/lib/morpheus/api/users_interface.rb +16 -63
  27. data/lib/morpheus/cli/cli_command.rb +253 -5
  28. data/lib/morpheus/cli/cli_registry.rb +1 -1
  29. data/lib/morpheus/cli/commands/alias_command.rb +1 -1
  30. data/lib/morpheus/cli/commands/apps.rb +14 -78
  31. data/lib/morpheus/cli/commands/audit.rb +188 -0
  32. data/lib/morpheus/cli/commands/blueprints_command.rb +1 -1
  33. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +88 -0
  34. data/lib/morpheus/cli/commands/change_password_command.rb +4 -4
  35. data/lib/morpheus/cli/commands/clusters.rb +96 -58
  36. data/lib/morpheus/cli/commands/hosts.rb +27 -15
  37. data/lib/morpheus/cli/commands/image_builder_command.rb +4 -8
  38. data/lib/morpheus/cli/commands/instances.rb +359 -3
  39. data/lib/morpheus/cli/commands/integrations_command.rb +1 -12
  40. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -0
  41. data/lib/morpheus/cli/commands/load_balancer_monitors.rb +70 -0
  42. data/lib/morpheus/cli/commands/load_balancer_pools.rb +29 -50
  43. data/lib/morpheus/cli/commands/load_balancer_profiles.rb +64 -0
  44. data/lib/morpheus/cli/commands/load_balancer_types.rb +9 -4
  45. data/lib/morpheus/cli/commands/load_balancer_virtual_servers.rb +69 -58
  46. data/lib/morpheus/cli/commands/load_balancers.rb +109 -6
  47. data/lib/morpheus/cli/commands/network_firewalls_command.rb +22 -5
  48. data/lib/morpheus/cli/commands/network_routers_command.rb +96 -45
  49. data/lib/morpheus/cli/commands/network_static_routes_command.rb +451 -0
  50. data/lib/morpheus/cli/commands/network_transport_zones_command.rb +4 -4
  51. data/lib/morpheus/cli/commands/networks_command.rb +2 -2
  52. data/lib/morpheus/cli/commands/open_command.rb +30 -0
  53. data/lib/morpheus/cli/commands/options.rb +98 -0
  54. data/lib/morpheus/cli/commands/ping.rb +3 -5
  55. data/lib/morpheus/cli/commands/policies_command.rb +2 -2
  56. data/lib/morpheus/cli/commands/prices_command.rb +7 -7
  57. data/lib/morpheus/cli/commands/provisioning_settings_command.rb +1 -0
  58. data/lib/morpheus/cli/commands/remote.rb +20 -12
  59. data/lib/morpheus/cli/commands/roles.rb +1 -1
  60. data/lib/morpheus/cli/commands/security_groups.rb +2 -2
  61. data/lib/morpheus/cli/commands/service_plans_command.rb +1 -1
  62. data/lib/morpheus/cli/commands/setup.rb +1 -1
  63. data/lib/morpheus/cli/commands/shell.rb +2 -2
  64. data/lib/morpheus/cli/commands/snapshots.rb +139 -0
  65. data/lib/morpheus/cli/commands/storage_server_types.rb +50 -0
  66. data/lib/morpheus/cli/commands/storage_servers.rb +122 -0
  67. data/lib/morpheus/cli/commands/storage_volume_types.rb +50 -0
  68. data/lib/morpheus/cli/commands/storage_volumes.rb +103 -0
  69. data/lib/morpheus/cli/commands/tasks.rb +5 -5
  70. data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
  71. data/lib/morpheus/cli/commands/user_groups_command.rb +1 -1
  72. data/lib/morpheus/cli/commands/user_settings_command.rb +3 -2
  73. data/lib/morpheus/cli/commands/user_sources_command.rb +1 -1
  74. data/lib/morpheus/cli/commands/users.rb +28 -28
  75. data/lib/morpheus/cli/commands/view.rb +102 -0
  76. data/lib/morpheus/cli/commands/virtual_images.rb +4 -1
  77. data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -5
  78. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +24 -4
  79. data/lib/morpheus/cli/mixins/print_helper.rb +50 -18
  80. data/lib/morpheus/cli/mixins/processes_helper.rb +1 -2
  81. data/lib/morpheus/cli/mixins/provisioning_helper.rb +96 -6
  82. data/lib/morpheus/cli/mixins/rest_command.rb +148 -74
  83. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +174 -82
  84. data/lib/morpheus/cli/mixins/storage_servers_helper.rb +156 -0
  85. data/lib/morpheus/cli/mixins/storage_volumes_helper.rb +119 -0
  86. data/lib/morpheus/cli/option_types.rb +95 -28
  87. data/lib/morpheus/cli/version.rb +1 -1
  88. data/lib/morpheus/cli.rb +1 -0
  89. data/lib/morpheus/ext/string.rb +29 -6
  90. data/lib/morpheus/routes.rb +238 -0
  91. data/lib/morpheus/util.rb +6 -1
  92. metadata +26 -2
@@ -0,0 +1,119 @@
1
+ require 'morpheus/cli/mixins/print_helper'
2
+ require 'morpheus/cli/option_types'
3
+ require 'morpheus/rest_client'
4
+ # Mixin for Morpheus::Cli command classes
5
+ # Provides common methods for storage volume management
6
+ # including storage volumes and storage volume types
7
+ module Morpheus::Cli::StorageVolumesHelper
8
+
9
+ def self.included(klass)
10
+ klass.send :include, Morpheus::Cli::PrintHelper
11
+ end
12
+
13
+ def storage_volumes_interface
14
+ # @api_client.storage_volumes
15
+ raise "#{self.class} has not defined @storage_volumes_interface" if @storage_volumes_interface.nil?
16
+ @storage_volumes_interface
17
+ end
18
+
19
+ def storage_volume_types_interface
20
+ # @api_client.storage_volume_types
21
+ raise "#{self.class} has not defined @storage_volume_types_interface" if @storage_volume_types_interface.nil?
22
+ @storage_volume_types_interface
23
+ end
24
+
25
+ def storage_volume_object_key
26
+ 'storageVolume'
27
+ end
28
+
29
+ def storage_volume_list_key
30
+ 'storageVolumes'
31
+ end
32
+
33
+ def storage_volume_label
34
+ 'Storage Volume'
35
+ end
36
+
37
+ def storage_volume_label_plural
38
+ 'Storage Volume'
39
+ end
40
+
41
+ def storage_volume_type_object_key
42
+ 'storageVolumeType'
43
+ end
44
+
45
+ def storage_volume_type_list_key
46
+ 'storageVolumeTypes'
47
+ end
48
+
49
+ def storage_volume_type_label
50
+ 'Storage Volume Type'
51
+ end
52
+
53
+ def storage_volume_type_label_plural
54
+ 'Storage Volume Types'
55
+ end
56
+
57
+ def get_available_storage_volume_types(refresh=false)
58
+ if !@available_storage_volume_types || refresh
59
+ @available_storage_volume_types = storage_volume_types_interface.list({max:1000})[storage_volume_type_list_key]
60
+ end
61
+ return @available_storage_volume_types
62
+ end
63
+
64
+ def storage_volume_type_for_name_or_id(val)
65
+ if val.to_s =~ /\A\d{1,}\Z/
66
+ return storage_volume_type_for_id(val)
67
+ else
68
+ return storage_volume_type_for_name(val)
69
+ end
70
+ end
71
+
72
+ def storage_volume_type_for_id(val)
73
+ record = get_available_storage_volume_types().find { |z| z['id'].to_i == val.to_i}
74
+ label = "Storage Volume Type"
75
+ if record.nil?
76
+ print_red_alert "#{label} not found by id #{val}"
77
+ return nil
78
+ end
79
+ return record
80
+ end
81
+
82
+ def storage_volume_type_for_name(val)
83
+ records = get_available_storage_volume_types().select { |z| z['name'].downcase == val.downcase || z['code'].downcase == val.downcase}
84
+ label = "Storage Volume Type"
85
+ if records.empty?
86
+ print_red_alert "#{label} not found by name '#{val}'"
87
+ return nil
88
+ elsif records.size > 1
89
+ print_red_alert "More than one #{label.downcase} found by name '#{val}'"
90
+ print_error "\n"
91
+ puts_error as_pretty_table(records, [:id, :name], {color:red})
92
+ print_red_alert "Try using ID instead"
93
+ print_error reset,"\n"
94
+ return nil
95
+ else
96
+ return records[0]
97
+ end
98
+ end
99
+
100
+ def format_storage_volume_status(record, return_color=cyan)
101
+ out = ""
102
+ status_string = record['status']
103
+ if status_string.nil? || status_string.empty? || status_string == "unknown"
104
+ out << "#{white}UNKNOWN#{return_color}"
105
+ elsif status_string == 'provisioned' || status_string == 'unattached'
106
+ out << "#{cyan}#{status_string.capitalize}#{return_color}"
107
+ elsif status_string == 'syncing'
108
+ out << "#{yellow}#{status_string.capitalize}#{return_color}"
109
+ else
110
+ out << "#{red}#{status_string ? status_string.capitalize : 'N/A'}#{record['statusMessage'] ? "#{return_color} - #{record['statusMessage']}" : ''}#{return_color}"
111
+ end
112
+ out
113
+ end
114
+
115
+ def format_storage_volume_source(storage_volume)
116
+ storage_volume['source']
117
+ end
118
+
119
+ end
@@ -42,7 +42,7 @@ module Morpheus
42
42
  end
43
43
  end
44
44
 
45
- def self.prompt(option_types, options={}, api_client=nil, api_params={}, no_prompt=false, paging_enabled=false)
45
+ def self.prompt(option_types, options={}, api_client=nil, api_params={}, no_prompt=false, paging_enabled=false, ignore_empty=false)
46
46
  paging_enabled = false if Morpheus::Cli.windows?
47
47
  no_prompt = no_prompt || options[:no_prompt]
48
48
  results = {}
@@ -57,6 +57,10 @@ module Morpheus
57
57
  if option_type['fieldGroup'].to_s.downcase == 'options'
58
58
  option_type['fieldGroup'] = 'default'
59
59
  end
60
+ # apply custom templates
61
+ if option_type['fieldName'] == 'sshHosts'
62
+ option_type['type'] = 'multiText'
63
+ end
60
64
  end
61
65
  # puts "Options Prompt #{options}"
62
66
  # Sort options by default, group, advanced
@@ -128,7 +132,11 @@ module Morpheus
128
132
  get_object_value(options, depends_on_field_key) ||
129
133
  get_object_value(api_params, depends_on_field_key)
130
134
 
131
- if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.match?(depends_on_value))
135
+ if field_value.nil? && !options['_object_key'].nil?
136
+ field_value = get_object_value({options['_object_key'] => results}, depends_on_field_key)
137
+ end
138
+
139
+ if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.to_s.match?(depends_on_value))
132
140
  found_dep_value = true if match_type != 'all'
133
141
  else
134
142
  found_dep_value = false if match_type == 'all'
@@ -151,6 +159,10 @@ module Morpheus
151
159
  context_map = context_map[ns.to_s]
152
160
  end
153
161
 
162
+ # build parameters for option source api request
163
+ option_params = (option_type['noParams'] ? {} : (api_params || {}).deep_merge(results))
164
+ option_params.merge!(option_type['optionParams']) if option_type['optionParams']
165
+
154
166
  # use the value passed in the options map
155
167
  if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
156
168
  value = cur_namespace[field_name]
@@ -161,25 +173,25 @@ module Morpheus
161
173
  end
162
174
  # these select prompts should just fall down through below, with the extra params no_prompt, use_value
163
175
  elsif option_type['type'] == 'select'
164
- value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
176
+ value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty)
165
177
  elsif option_type['type'] == 'multiSelect'
166
178
  # support value as csv like "thing1, thing2"
167
179
  value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
168
180
  input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
169
181
  select_value_list = []
170
182
  value_list.each_with_index do |v, i|
171
- select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
183
+ select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty)
172
184
  end
173
185
  value = select_value_list
174
186
  elsif option_type['type'] == 'typeahead'
175
- value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
187
+ value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
176
188
  elsif option_type['type'] == 'multiTypeahead'
177
189
  # support value as csv like "thing1, thing2"
178
190
  value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
179
191
  input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
180
192
  select_value_list = []
181
193
  value_list.each_with_index do |v, i|
182
- select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
194
+ select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
183
195
  end
184
196
  value = select_value_list
185
197
  end
@@ -205,14 +217,14 @@ module Morpheus
205
217
  # select type is special because it supports skipSingleOption
206
218
  # and prints the available options on error
207
219
  if ['select', 'multiSelect'].include?(option_type['type'])
208
- value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
220
+ value = select_prompt(option_type, api_client, option_params, true, nil, false, ignore_empty)
209
221
  value_found = !!value
210
222
  end
211
223
  if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
212
- value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
224
+ value = typeahead_prompt(option_type, api_client, option_params, true)
213
225
  value_found = !!value
214
226
  end
215
- if !value_found
227
+ if !value_found && !ignore_empty
216
228
  if option_type['required']
217
229
  print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
218
230
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
@@ -246,11 +258,11 @@ module Morpheus
246
258
  # I suppose the entered value should take precedence
247
259
  # api_params = api_params.merge(options) # this might be good enough
248
260
  # dup it
249
- value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).deep_merge(results)), options[:no_prompt], nil, paging_enabled)
261
+ value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty)
250
262
  if value && option_type['type'] == 'multiSelect'
251
263
  value = [value]
252
264
  while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
253
- if addn_value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
265
+ if addn_value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty)
254
266
  value << addn_value
255
267
  else
256
268
  break
@@ -258,11 +270,11 @@ module Morpheus
258
270
  end
259
271
  end
260
272
  elsif ['typeahead', 'multiTypeahead'].include?(option_type['type'])
261
- value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
273
+ value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
262
274
  if value && option_type['type'] == 'multiTypeahead'
263
275
  value = [value]
264
276
  while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
265
- if addn_value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
277
+ if addn_value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
266
278
  value << addn_value
267
279
  else
268
280
  break
@@ -279,6 +291,8 @@ module Morpheus
279
291
  value = file_prompt(option_type)
280
292
  elsif option_type['type'] == 'file-content'
281
293
  value = file_content_prompt(option_type, options, api_client, {})
294
+ elsif option_type['type'] == 'multiText'
295
+ value = multitext_prompt(option_type)
282
296
  else
283
297
  value = generic_prompt(option_type)
284
298
  end
@@ -286,7 +300,11 @@ module Morpheus
286
300
 
287
301
  if option_type['type'] == 'multiSelect'
288
302
  value = [value] if !value.nil? && !value.is_a?(Array)
289
- # parent_context_map[parent_ns] = value
303
+ elsif option_type['type'] == 'multiText'
304
+ # multiText expects csv value
305
+ if value && value.is_a?(String)
306
+ value = value.split(",").collect {|it| it.strip }
307
+ end
290
308
  end
291
309
  context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
292
310
  parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
@@ -305,7 +323,7 @@ module Morpheus
305
323
  end
306
324
  optionString = options.collect{ |b| b[:checked] ? "(#{b[:key]})" : b[:key]}.join(', ')
307
325
  while !value_found do
308
- print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
326
+ print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
309
327
  input = $stdin.gets.chomp!
310
328
  if input == '?'
311
329
  help_prompt(option_type)
@@ -332,7 +350,7 @@ module Morpheus
332
350
  value_found = false
333
351
  value = nil
334
352
  while !value_found do
335
- print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
353
+ print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
336
354
  input = $stdin.gets.chomp!
337
355
  value = input.empty? ? option_type['defaultValue'] : input
338
356
  if !value.to_s.empty?
@@ -356,7 +374,7 @@ module Morpheus
356
374
  Thread.current[:_last_select]
357
375
  end
358
376
 
359
- def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
377
+ def self.select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false, ignore_empty=false)
360
378
  paging_enabled = false if Morpheus::Cli.windows?
361
379
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
362
380
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
@@ -365,7 +383,12 @@ module Morpheus
365
383
  value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
366
384
  default_value = option_type['defaultValue']
367
385
  default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
368
- api_params ||= {}
386
+
387
+ if !option_type['params'].nil?
388
+ api_params = (api_params || {}).select {|k,v| option_type['params'].key?(k) || option_type['params'].key?(k.to_s)}
389
+ option_type['params'].select {|k,v| !v.empty?}.each {|k,v| api_params[k] = v}
390
+ end
391
+
369
392
  # local array of options
370
393
  if option_type['selectOptions']
371
394
  # calculate from inline lambda
@@ -387,9 +410,11 @@ module Morpheus
387
410
  select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, api_params || {})
388
411
  end
389
412
  else
390
- raise "option '#{field_key}' is type: 'select' and missing selectOptions or optionSource!"
413
+ raise "option '#{help_field_key}' is type: 'select' and missing selectOptions or optionSource!"
391
414
  end
392
415
 
416
+ return nil if (select_options.nil? || select_options.count == 0) && ignore_empty
417
+
393
418
  # ensure the preselected value (passed as an option) is in the dropdown
394
419
  if !use_value.nil?
395
420
  matched_option = select_options.find {|opt| opt[value_field].to_s == use_value.to_s || opt['name'].to_s == use_value.to_s }
@@ -424,7 +449,8 @@ module Morpheus
424
449
  default_value = found_default_option['name'] # name is prettier than value
425
450
  end
426
451
  else
427
- found_default_option = select_options.find {|opt| opt[value_field].to_s == default_value.to_s}
452
+ found_default_option = select_options.find {|opt| opt[value_field].to_s == default_value.to_s || opt['name'] == default_value.to_s}
453
+ found_default_option = select_options.find {|opt| opt[value_field].to_s.start_with?(default_value.to_s) || opt['name'].to_s.start_with?(default_value.to_s)} if !found_default_option
428
454
  if found_default_option
429
455
  default_value = found_default_option['name'] # name is prettier than value
430
456
  end
@@ -483,7 +509,7 @@ module Morpheus
483
509
  }
484
510
 
485
511
  has_more_pages = paging && (paging[:cur_page] * paging[:page_size]) < paging[:total]
486
- input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages && paging[:cur_page] > 0 ? ' more ' : ' '}options]: ", false).to_s
512
+ input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages && paging[:cur_page] > 0 ? ' more ' : ' '}options]: ", false).to_s
487
513
  input = input.chomp.strip
488
514
  if input.empty? && default_value
489
515
  input = default_value.to_s
@@ -511,6 +537,10 @@ module Morpheus
511
537
  if value && !option_type['fieldInput'].nil?
512
538
  value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
513
539
  end
540
+
541
+ if value && !option_type['resultValueField'].nil?
542
+ value = {option_type['resultValueField'] => value}
543
+ end
514
544
  value
515
545
  end
516
546
 
@@ -554,7 +584,7 @@ module Morpheus
554
584
  }
555
585
  # prompt for typeahead input value
556
586
  has_more_pages = paging && ((paging[:cur_page] + 1) * paging[:page_size]) < paging[:total]
557
- input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages ? ' more ' : ' '}options]: ", false).to_s
587
+ input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages ? ' more ' : ' '}options]: ", false).to_s
558
588
  input = input.chomp.strip
559
589
  end
560
590
 
@@ -648,6 +678,10 @@ module Morpheus
648
678
  if select_options.empty?
649
679
  print "The value '#{input}' matched 0 options.\n"
650
680
  # print "Please try again.\n"
681
+ elsif select_options.size() == 1
682
+ print "The value '#{input}' matched 1 option.\n"
683
+ print "Perhaps you meant '#{select_options[0]['name']}' instead?"
684
+ # print "Please try again.\n"
651
685
  else
652
686
  print "The value '#{input}' matched #{select_options.size()} options.\n"
653
687
  print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
@@ -662,6 +696,9 @@ module Morpheus
662
696
  if select_options.empty?
663
697
  print "The value '#{input}' matched 0 options.\n"
664
698
  print "Please try again.\n"
699
+ elsif select_options.size() == 1
700
+ print "The value '#{input}' matched 1 option.\n"
701
+ print "Perhaps you meant '#{select_options[0]['name']}' instead?"
665
702
  else
666
703
  print "The value '#{input}' matched #{select_options.size()} options.\n"
667
704
  print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
@@ -743,7 +780,7 @@ module Morpheus
743
780
  value_found = false
744
781
  value = nil
745
782
  while !value_found do
746
- print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
783
+ print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
747
784
  input = $stdin.gets.chomp!
748
785
  value = input.empty? ? option_type['defaultValue'] : input
749
786
  if input == '?'
@@ -760,7 +797,7 @@ module Morpheus
760
797
  value = nil
761
798
  while !value_found do
762
799
  if value.nil?
763
- print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)} [Type 'EOF' to stop input]: \n"
800
+ print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)} [Type 'EOF' to stop input]: \n"
764
801
  end
765
802
  input = $stdin.gets.chomp!
766
803
  # value = input.empty? ? option_type['defaultValue'] : input
@@ -784,7 +821,7 @@ module Morpheus
784
821
  def self.password_prompt(option_type)
785
822
  value_found = false
786
823
  while !value_found do
787
- print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
824
+ print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
788
825
  input = $stdin.noecho(&:gets).chomp!
789
826
  value = input
790
827
  print "\n"
@@ -804,11 +841,11 @@ module Morpheus
804
841
  value_found = false
805
842
  value = nil
806
843
  while !value_found do
807
- #print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
844
+ #print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
808
845
  Readline.completion_append_character = ""
809
846
  Readline.basic_word_break_characters = ''
810
847
  Readline.completion_proc = proc {|s| Readline::FILENAME_COMPLETION_PROC.call(s) }
811
- input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: ", false).to_s
848
+ input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: ", false).to_s
812
849
  input = input.chomp.strip
813
850
  #input = $stdin.gets.chomp!
814
851
  value = input.empty? ? option_type['defaultValue'] : input.to_s
@@ -907,7 +944,35 @@ module Morpheus
907
944
  return file_params
908
945
  end
909
946
 
947
+ def self.multitext_prompt(option_type)
948
+ rtn = nil
949
+
950
+ # supports multi-part fields via config.fields
951
+ # {"fields": [{"name":"tag", "required":true, "label": "Tag"}, {"name":"value", "required":false, "label": "Scope"}]}
952
+ if option_type['config']['fields']
953
+ while (option_type['required'] && rtn.empty?) || self.confirm("Add#{rtn.empty? ? '': ' more'} #{option_type['fieldLabel']}?", {:default => false})
954
+ rtn ||= []
955
+ value = {}
956
+ option_type['config']['fields'].each do |field|
957
+ field_label = field['label'] || field['name'].capitalize
958
+ value[field['name']] = generic_prompt(option_type.merge({'fieldLabel' => field_label, 'required' => field['required'], 'description' => "#{option_type['fieldLabel']} #{field_label}"}))
959
+ end
960
+ rtn << value
961
+ end
962
+ else
963
+ if rtn = generic_prompt(option_type)
964
+ rtn = [rtn]
965
+ while self.confirm("Add more #{option_type['fieldLabel']}?", {:default => false}) do
966
+ rtn << generic_prompt(option_type)
967
+ end
968
+ end
969
+ end
970
+ rtn
971
+ end
972
+
910
973
  def self.load_options(option_type, api_client, api_params, query_value=nil)
974
+ field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
975
+ help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
911
976
  select_options = []
912
977
  # local array of options
913
978
  if option_type['selectOptions']
@@ -926,6 +991,8 @@ module Morpheus
926
991
  select_options = filtered_options
927
992
  end
928
993
  elsif option_type['optionSource']
994
+ api_params = api_params.select {|k,v| option_type['params'].include(k)} if !option_type['params'].nil? && api_params
995
+
929
996
  # calculate from inline lambda
930
997
  if option_type['optionSource'].is_a?(Proc)
931
998
  select_options = option_type['optionSource'].call(api_client, api_params || {})
@@ -937,7 +1004,7 @@ module Morpheus
937
1004
  select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, api_params || {})
938
1005
  end
939
1006
  else
940
- raise "option '#{field_key}' is type: 'typeahead' and missing selectOptions or optionSource!"
1007
+ raise "option '#{help_field_key}' is type: 'typeahead' and missing selectOptions or optionSource!"
941
1008
  end
942
1009
 
943
1010
  return select_options
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "5.4.0"
4
+ VERSION = "5.4.3.1"
5
5
  end
6
6
  end
data/lib/morpheus/cli.rb CHANGED
@@ -4,6 +4,7 @@ require 'morpheus/rest_client'
4
4
  require 'morpheus/formatters'
5
5
  require 'morpheus/logging'
6
6
  require 'morpheus/util'
7
+ require 'morpheus/routes'
7
8
  require 'term/ansicolor'
8
9
 
9
10
  Dir[File.dirname(__FILE__) + "/ext/*.rb"].each {|file| require file }
@@ -1,7 +1,7 @@
1
1
  class String
2
2
 
3
3
  def pluralize
4
- value = self
4
+ value = self.dup
5
5
  if value == ""
6
6
  value
7
7
  elsif value[-1].chr == "y"
@@ -18,7 +18,7 @@ class String
18
18
  end
19
19
 
20
20
  def singularize
21
- value = self
21
+ value = self.dup
22
22
  if value == ""
23
23
  value
24
24
  elsif value.size > 3 && value[-3..-1] == "ies"
@@ -27,15 +27,38 @@ class String
27
27
  value[0..-3]
28
28
  elsif value[-1] == "s"
29
29
  value[0..-2]
30
+ else
31
+ value
30
32
  end
31
33
  end
32
34
 
33
- def dasherize
34
- self.gsub(" ", "-").gsub("_", "-")
35
+ def underscore
36
+ value = self.dup
37
+ value.gsub!(/::/, '/')
38
+ value.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
39
+ value.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
40
+ value.tr!("-", "_")
41
+ value.tr!(" ", "_")
42
+ value.downcase!
43
+ value
44
+ end
45
+
46
+ def camelcase
47
+ value = self.underscore.gsub(/\_([a-z])/) do $1.upcase end
48
+ value = value[0, 1].downcase + value[1..-1]
49
+ value
35
50
  end
36
51
 
37
- def underscoreize
38
- self.gsub(" ", "_").gsub("-", "_")
52
+ def upcamelcase
53
+ self.camelcase.capitalize
54
+ end
55
+
56
+ def titleize
57
+ self.underscore.split("_").map(&:capitalize).join(" ")
58
+ end
59
+
60
+ def dasherize
61
+ self.underscore.gsub("_", "-")
39
62
  end
40
63
 
41
64
  end