morpheus-cli 4.2.21 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +30 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +16 -2
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +14 -0
  14. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  15. data/lib/morpheus/api/usage_interface.rb +18 -0
  16. data/lib/morpheus/cli.rb +7 -3
  17. data/lib/morpheus/cli/apps.rb +6 -27
  18. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  19. data/lib/morpheus/cli/backups_command.rb +3 -0
  20. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  21. data/lib/morpheus/cli/cli_command.rb +70 -21
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -24
  28. data/lib/morpheus/cli/cypher_command.rb +6 -2
  29. data/lib/morpheus/cli/deploy.rb +199 -90
  30. data/lib/morpheus/cli/deployments.rb +341 -28
  31. data/lib/morpheus/cli/deploys.rb +206 -41
  32. data/lib/morpheus/cli/error_handler.rb +7 -0
  33. data/lib/morpheus/cli/forgot_password.rb +133 -0
  34. data/lib/morpheus/cli/groups.rb +1 -1
  35. data/lib/morpheus/cli/health_command.rb +59 -2
  36. data/lib/morpheus/cli/hosts.rb +265 -34
  37. data/lib/morpheus/cli/instances.rb +186 -100
  38. data/lib/morpheus/cli/invoices_command.rb +33 -16
  39. data/lib/morpheus/cli/jobs_command.rb +28 -6
  40. data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
  41. data/lib/morpheus/cli/library_option_types_command.rb +5 -2
  42. data/lib/morpheus/cli/logs_command.rb +9 -6
  43. data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
  44. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  45. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  46. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  47. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
  49. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  50. data/lib/morpheus/cli/option_types.rb +271 -22
  51. data/lib/morpheus/cli/ping.rb +0 -1
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +453 -113
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  57. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  58. data/lib/morpheus/cli/setup.rb +1 -1
  59. data/lib/morpheus/cli/shell.rb +33 -11
  60. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  61. data/lib/morpheus/cli/tasks.rb +29 -32
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +429 -254
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +75 -7
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +14 -2
@@ -596,22 +596,28 @@ class Morpheus::Cli::NetworkPoolsCommand
596
596
  def add_ip(args)
597
597
  options = {}
598
598
  params = {}
599
+ next_free_ip = false
599
600
  optparse = Morpheus::Cli::OptionParser.new do |opts|
600
- opts.banner = subcommand_usage("[network-pool] [ip]")
601
+ opts.banner = subcommand_usage("[network-pool] [ip] [--next]")
601
602
  opts.on('--ip-address VALUE', String, "IP Address for this network pool IP") do |val|
602
603
  options[:options]['ipAddress'] = val
603
604
  end
605
+ opts.on('--next-free-ip', '--next-free-ip', "Use the next available ip address. This can be used instead of specifying an ip address") do
606
+ next_free_ip = true
607
+ end
604
608
  opts.on('--hostname VALUE', String, "Hostname for this network pool IP") do |val|
605
609
  options[:options]['hostname'] = val
606
610
  end
607
611
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
608
612
  opts.footer = "Create a new network pool IP." + "\n" +
609
613
  "[network-pool] is required. This is the name or id of a network pool.\n" +
610
- "[ip] is required and can be passed as --ip-address instead."
614
+ "[ip] is required or --next-free-ip to use the next available address instead."
611
615
  end
612
616
  optparse.parse!(args)
613
- if args.count < 1 || args.count > 2
614
- raise_command_error "wrong number of arguments, expected 1-2 and got (#{args.count}) #{args}\n#{optparse}"
617
+ if next_free_ip
618
+ verify_args!(args:args, count:1, optparse:optparse)
619
+ else
620
+ verify_args!(args:args, min:1, max:2, optparse:optparse)
615
621
  end
616
622
  connect(options)
617
623
  begin
@@ -639,8 +645,10 @@ class Morpheus::Cli::NetworkPoolsCommand
639
645
  payload['networkPoolIp'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
640
646
 
641
647
  # IP Address
642
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'fieldLabel' => 'IP Address', 'type' => 'text', 'required' => true, 'description' => 'IP Address for this network pool IP.'}], options[:options])
643
- payload['networkPoolIp']['ipAddress'] = v_prompt['ipAddress'] unless v_prompt['ipAddress'].to_s.empty?
648
+ unless next_free_ip
649
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'fieldLabel' => 'IP Address', 'type' => 'text', 'required' => true, 'description' => 'IP Address for this network pool IP.'}], options[:options])
650
+ payload['networkPoolIp']['ipAddress'] = v_prompt['ipAddress'] unless v_prompt['ipAddress'].to_s.empty?
651
+ end
644
652
 
645
653
  # Hostname
646
654
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname for this network pool IP.'}], options[:options])
@@ -5,6 +5,7 @@ module Morpheus
5
5
  module Cli
6
6
  module OptionTypes
7
7
  include Term::ANSIColor
8
+ # include Morpheus::Cli::PrintHelper
8
9
 
9
10
  def self.confirm(message,options={})
10
11
  if options[:yes] == true
@@ -72,9 +73,15 @@ module Morpheus
72
73
  field_name = namespaces.pop
73
74
 
74
75
  # respect optionType.dependsOnCode
75
- if option_type['dependsOnCode'] && option_type['dependsOnCode'] != ""
76
+ # i guess this switched to visibleOnCode, respect one or the other
77
+ visible_option_check_value = option_type['dependsOnCode']
78
+ if !option_type['visibleOnCode'].to_s.empty?
79
+ visible_option_check_value = option_type['visibleOnCode']
80
+ end
81
+ if !visible_option_check_value.to_s.empty?
76
82
  # support formats code=value or code:value OR code:(value|value2|value3)
77
- parts = option_type['dependsOnCode'].include?("=") ? option_type['dependsOnCode'].split("=") : option_type['dependsOnCode'].split(":")
83
+ # OR fieldContext.fieldName=value
84
+ parts = visible_option_check_value.include?("=") ? visible_option_check_value.split("=") : visible_option_check_value.split(":")
78
85
  depends_on_code = parts[0]
79
86
  depends_on_value = parts[1].to_s.strip
80
87
  depends_on_values = []
@@ -87,14 +94,29 @@ module Morpheus
87
94
  depends_on_values = depends_on_value.split("|").collect { |it| it.strip }
88
95
  end
89
96
  depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
90
- # could not find the dependent option type, proceed and prompt
91
- if !depends_on_option_type.nil?
97
+ if !depends_on_option_type
98
+ depends_on_option_type = option_types.find {|it|
99
+ (it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
100
+ }
101
+ end
102
+ if depends_on_option_type
92
103
  # dependent option type has a different value
93
104
  depends_on_field_key = depends_on_option_type['fieldContext'] ? "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldName']}"
94
105
  found_dep_value = get_object_value(results, depends_on_field_key) || get_object_value(options, depends_on_field_key)
95
- if depends_on_values.size > 0 && !depends_on_values.include?(found_dep_value)
96
- next
106
+ if depends_on_values.size > 0
107
+ # must be in the specified values
108
+ # todo: uhh this actually needs to change to parse regex
109
+ if !depends_on_values.include?(found_dep_value)
110
+ next
111
+ end
112
+ else
113
+ # no value found
114
+ if found_dep_value.to_s.empty?
115
+ next
116
+ end
97
117
  end
118
+ else
119
+ # could not find the dependent option type, proceed and prompt
98
120
  end
99
121
  end
100
122
 
@@ -115,22 +137,32 @@ module Morpheus
115
137
  # use the value passed in the options map
116
138
  if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
117
139
  value = cur_namespace[field_name]
118
- input_value = ['select', 'multiSelect'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
140
+ input_value = ['select', 'multiSelect','typeahead', 'multiTypeahead'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
119
141
  if option_type['type'] == 'number'
120
142
  value = value.to_s.include?('.') ? value.to_f : value.to_i
121
143
  # these select prompts should just fall down through below, with the extra params no_prompt, use_value
122
144
  elsif option_type['type'] == 'select'
123
- value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (api_params || {}).merge(results), true)
145
+ value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
124
146
  elsif option_type['type'] == 'multiSelect'
125
147
  # support value as csv like "thing1, thing2"
126
148
  value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
127
149
  input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
128
150
  select_value_list = []
129
151
  value_list.each_with_index do |v, i|
130
- select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (api_params || {}).merge(results), true)
152
+ 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)
153
+ end
154
+ value = select_value_list
155
+ elsif option_type['type'] == 'typeahead'
156
+ value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
157
+ elsif option_type['type'] == 'multiTypeahead'
158
+ # support value as csv like "thing1, thing2"
159
+ value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
160
+ input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
161
+ select_value_list = []
162
+ value_list.each_with_index do |v, i|
163
+ 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)
131
164
  end
132
165
  value = select_value_list
133
-
134
166
  end
135
167
  if options[:always_prompt] != true
136
168
  value_found = true
@@ -147,7 +179,7 @@ module Morpheus
147
179
  no_prompt = no_prompt || options[:no_prompt]
148
180
  if no_prompt
149
181
  if !value_found
150
- if option_type['defaultValue'] != nil && !['select', 'multiSelect'].include?(option_type['type'])
182
+ if option_type['defaultValue'] != nil && !['select', 'multiSelect','typeahead','multiTypeahead'].include?(option_type['type'])
151
183
  value = option_type['defaultValue']
152
184
  value_found = true
153
185
  end
@@ -155,7 +187,11 @@ module Morpheus
155
187
  # select type is special because it supports skipSingleOption
156
188
  # and prints the available options on error
157
189
  if ['select', 'multiSelect'].include?(option_type['type'])
158
- value = select_prompt(option_type, api_client, (api_params || {}).merge(results), true)
190
+ value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
191
+ value_found = !!value
192
+ end
193
+ if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
194
+ value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
159
195
  value_found = !!value
160
196
  end
161
197
  if !value_found
@@ -192,11 +228,23 @@ module Morpheus
192
228
  # I suppose the entered value should take precedence
193
229
  # api_params = api_params.merge(options) # this might be good enough
194
230
  # dup it
195
- value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
231
+ value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
196
232
  if value && option_type['type'] == 'multiSelect'
197
233
  value = [value]
198
234
  while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
199
- if addn_value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
235
+ if addn_value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
236
+ value << addn_value
237
+ else
238
+ break
239
+ end
240
+ end
241
+ end
242
+ elsif ['typeahead', 'multiTypeahead'].include?(option_type['type'])
243
+ value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
244
+ if value && option_type['type'] == 'multiTypeahead'
245
+ value = [value]
246
+ while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
247
+ if addn_value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
200
248
  value << addn_value
201
249
  else
202
250
  break
@@ -310,11 +358,12 @@ module Morpheus
310
358
  value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
311
359
  default_value = option_type['defaultValue']
312
360
  default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
361
+ api_params ||= {}
313
362
  # local array of options
314
363
  if option_type['selectOptions']
315
364
  # calculate from inline lambda
316
365
  if option_type['selectOptions'].is_a?(Proc)
317
- select_options = option_type['selectOptions'].call()
366
+ select_options = option_type['selectOptions'].call(api_client, grails_params(api_params || {}))
318
367
  else
319
368
  # todo: better type validation
320
369
  select_options = option_type['selectOptions']
@@ -325,7 +374,7 @@ module Morpheus
325
374
  select_options = option_type['optionSource'].call(api_client, grails_params(api_params || {}))
326
375
  elsif option_type['optionSource'] == 'list'
327
376
  # /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
328
- select_options = load_source_options(option_type['optionSource'], api_client, {'optionTypeId' => option_type['id']})
377
+ select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}).merge({'optionTypeId' => option_type['id']}))
329
378
  else
330
379
  # remote optionSource aka /api/options/$optionSource?
331
380
  select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}))
@@ -345,7 +394,7 @@ module Morpheus
345
394
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
346
395
  if select_options && select_options.size > 10
347
396
  display_select_options(option_type, select_options.first(10))
348
- puts " (#{select_options.size-1} more)"
397
+ puts " (#{select_options.size-10} more)"
349
398
  else
350
399
  display_select_options(option_type, select_options)
351
400
  end
@@ -388,7 +437,7 @@ module Morpheus
388
437
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
389
438
  if select_options && select_options.size > 10
390
439
  display_select_options(option_type, select_options.first(10))
391
- puts " (#{select_options.size-1} more)"
440
+ puts " (#{select_options.size-10} more)"
392
441
  else
393
442
  display_select_options(option_type, select_options)
394
443
  end
@@ -458,6 +507,154 @@ module Morpheus
458
507
  value
459
508
  end
460
509
 
510
+ # this works like select_prompt, but refreshes options with ?query=value between inputs
511
+ # paging_enabled is ignored right now
512
+ def self.typeahead_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
513
+ select_options = []
514
+ field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
515
+ help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
516
+ input = ""
517
+ value_found = false
518
+ value = nil
519
+ value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
520
+ default_value = option_type['defaultValue']
521
+ default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
522
+
523
+ while !value_found do
524
+ # ok get input, refresh options and see if it matches
525
+ # if matches one, cool otherwise print matches and reprompt or error
526
+ if use_value
527
+ input = use_value
528
+ elsif no_prompt
529
+ input = default_value
530
+ else
531
+ Readline.completion_append_character = ""
532
+ Readline.basic_word_break_characters = ''
533
+ Readline.completion_proc = proc {|s|
534
+ matches = []
535
+ available_options = (select_options || [])
536
+ available_options.each{|option|
537
+ if option['name'] && option['name'] =~ /^#{Regexp.escape(s)}/
538
+ matches << option['name']
539
+ # elsif option['id'] && option['id'].to_s =~ /^#{Regexp.escape(s)}/
540
+ elsif option[value_field] && option[value_field].to_s == s
541
+ matches << option['name']
542
+ end
543
+ }
544
+ matches
545
+ }
546
+ # prompt for typeahead input value
547
+ 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 options]: ", false).to_s
548
+ input = input.chomp.strip
549
+ end
550
+
551
+ # just hit enter, use [default] if set
552
+ if input.empty? && default_value
553
+ input = default_value.to_s
554
+ end
555
+
556
+ # not required and no value? ok proceed
557
+ if input.to_s == "" && option_type['required'] != true
558
+ value_found = true
559
+ value = nil # or "" # hmm
560
+ #next
561
+ break
562
+ end
563
+
564
+ # required and no value? you need help
565
+ # if input.to_s == "" && option_type['required'] == true
566
+ # help_prompt(option_type)
567
+ # display_select_options(option_type, select_options) unless select_options.empty?
568
+ # next
569
+ # end
570
+
571
+ # looking for help with this input
572
+ if input == '?'
573
+ help_prompt(option_type)
574
+ display_select_options(option_type, select_options) unless select_options.empty?
575
+ next
576
+ end
577
+
578
+ # just hit enter? scram
579
+ # looking for help with this input
580
+ # if input == ""
581
+ # help_prompt(option_type)
582
+ # display_select_options(option_type, select_options)
583
+ # next
584
+ # end
585
+
586
+ # this is how typeahead works, it keeps refreshing the options with a new ?query={value}
587
+ # query_value = (value || use_value || default_value || '')
588
+ query_value = (input || '')
589
+ api_params ||= {}
590
+ api_params['query'] = query_value
591
+ # skip refresh if you just hit enter
592
+ if !query_value.empty?
593
+ select_options = load_options(option_type, api_client, api_params, query_value)
594
+ end
595
+
596
+ # match input to option name or value
597
+ # actually that is redundant, it should already be filtered to matches
598
+ # and can just do this:
599
+ # select_option = select_options.size == 1 ? select_options[0] : nil
600
+ select_option = select_options.find{|b| (b[value_field] && (b[value_field].to_s == input.to_s)) || ((b[value_field].nil? || b[value_field].empty?) && (input == "")) }
601
+ if select_option.nil?
602
+ select_option = select_options.find{|b| b['name'] && b['name'] == input }
603
+ end
604
+
605
+ # found matching value, else did not find a value, show matching options and prompt again or error
606
+ if select_option
607
+ value = select_option[value_field]
608
+ set_last_select(select_option)
609
+ value_found = true
610
+ else
611
+ if use_value || no_prompt
612
+ # todo: make this nicer
613
+ # help_prompt(option_type)
614
+ print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
615
+ print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
616
+ if select_options && select_options.size > 10
617
+ display_select_options(option_type, select_options.first(10))
618
+ puts " (#{select_options.size-10} more)"
619
+ else
620
+ display_select_options(option_type, select_options)
621
+ end
622
+ print "\n"
623
+ if select_options.empty?
624
+ print "The value '#{input}' matched 0 options.\n"
625
+ # print "Please try again.\n"
626
+ else
627
+ print "The value '#{input}' matched #{select_options.size()} options.\n"
628
+ print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
629
+ # print "Please try again.\n"
630
+ end
631
+ print "\n"
632
+ exit 1
633
+ else
634
+ #help_prompt(option_type)
635
+ display_select_options(option_type, select_options)
636
+ print "\n"
637
+ if select_options.empty?
638
+ print "The value '#{input}' matched 0 options.\n"
639
+ print "Please try again.\n"
640
+ else
641
+ print "The value '#{input}' matched #{select_options.size()} options.\n"
642
+ print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
643
+ print "Please try again.\n"
644
+ end
645
+ print "\n"
646
+ # reprompting now...
647
+ end
648
+ end
649
+ end # end while !value_found
650
+
651
+ # wrap in object when using fieldInput
652
+ if value && !option_type['fieldInput'].nil?
653
+ value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
654
+ end
655
+ value
656
+ end
657
+
461
658
  # this is a funky one, the user is prompted for yes/no
462
659
  # but the return value is 'on','off',nil
463
660
  # todo: maybe make this easier to use, and have the api's be flexible too..
@@ -544,6 +741,9 @@ module Morpheus
544
741
  # value = input.empty? ? option_type['defaultValue'] : input
545
742
  if input == '?' && value.nil?
546
743
  help_prompt(option_type)
744
+ elsif input.chomp == '' && value.nil?
745
+ # just hit enter right away to skip this
746
+ value_found = true
547
747
  elsif input.chomp == 'EOF'
548
748
  value_found = true
549
749
  else
@@ -682,6 +882,42 @@ module Morpheus
682
882
  return file_params
683
883
  end
684
884
 
885
+ def self.load_options(option_type, api_client, api_params, query_value=nil)
886
+ select_options = []
887
+ # local array of options
888
+ if option_type['selectOptions']
889
+ # calculate from inline lambda
890
+ if option_type['selectOptions'].is_a?(Proc)
891
+ select_options = option_type['selectOptions'].call(api_client, grails_params(api_params || {}))
892
+ else
893
+ select_options = option_type['selectOptions']
894
+ end
895
+ # filter options ourselves
896
+ if query_value.to_s != ""
897
+ filtered_options = select_options.select { |it| it['value'].to_s == query_value.to_s }
898
+ if filtered_options.empty?
899
+ filtered_options = select_options.select { |it| it['name'].to_s == query_value.to_s }
900
+ end
901
+ select_options = filtered_options
902
+ end
903
+ elsif option_type['optionSource']
904
+ # calculate from inline lambda
905
+ if option_type['optionSource'].is_a?(Proc)
906
+ select_options = option_type['optionSource'].call(api_client, grails_params(api_params || {}))
907
+ elsif option_type['optionSource'] == 'list'
908
+ # /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
909
+ select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}).merge({'optionTypeId' => option_type['id']}))
910
+ else
911
+ # remote optionSource aka /api/options/$optionSource?
912
+ select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}))
913
+ end
914
+ else
915
+ raise "option '#{field_key}' is type: 'typeahead' and missing selectOptions or optionSource!"
916
+ end
917
+
918
+ return select_options
919
+ end
920
+
685
921
  def self.help_prompt(option_type)
686
922
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
687
923
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
@@ -691,6 +927,11 @@ module Morpheus
691
927
  else
692
928
  print Term::ANSIColor.green," * #{option_type['fieldLabel']} [-O #{help_field_key}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
693
929
  end
930
+ if option_type['type'].to_s == 'typeahead'
931
+ print "This is a typeahead input. Enter the name or value of an option.\n"
932
+ print "If the specified input matches more than one option, they will be printed and you will be prompted again.\n"
933
+ print "the matching options will be shown and you can try again.\n"
934
+ end
694
935
  end
695
936
 
696
937
 
@@ -698,7 +939,8 @@ module Morpheus
698
939
  api_client.options.options_for_source(source,params)['data']
699
940
  end
700
941
 
701
- def self.display_select_options(opt, select_options = [], paging = nil)
942
+ def self.format_select_options_help(opt, select_options = [], paging = nil)
943
+ out = ""
702
944
  header = opt['fieldLabel'] ? "#{opt['fieldLabel']} Options" : "Options"
703
945
  if paging
704
946
  offset = paging[:cur_page] * paging[:page_size]
@@ -706,11 +948,18 @@ module Morpheus
706
948
  header = "#{header} (#{offset+1}-#{limit+1} of #{paging[:total]})"
707
949
  select_options = select_options[(offset)..(limit)]
708
950
  end
709
- puts "\n#{header}"
710
- puts "==============="
951
+ out = ""
952
+ out << "\n"
953
+ out << "#{header}\n"
954
+ out << "===============\n"
711
955
  select_options.each do |option|
712
- puts " * #{option['name']} [#{option['value']}]"
956
+ out << " * #{option['name']} [#{option['value']}]\n"
713
957
  end
958
+ return out
959
+ end
960
+
961
+ def self.display_select_options(opt, select_options = [], paging = nil)
962
+ puts format_select_options_help(opt, select_options, paging)
714
963
  end
715
964
 
716
965
  def self.format_option_types_help(option_types, opts={})