morpheus-cli 6.2.3 → 6.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,136 @@
1
+ require 'morpheus/cli/option_types'
2
+
1
3
  # Mixin for Morpheus::Cli command classes
2
- # Provides common methods for prompting for input
4
+ # Provides common methods for prompting for option type inputs and forms.
5
+ # Prompting is delegated to the {Morpheus::Cli::OptionTypes} module
6
+ # while the provided {#prompt} simplifies the required parameters
7
+ # The command class must establish the +@api_client+ on its own.
8
+ #
3
9
  module Morpheus::Cli::PromptHelper
4
10
 
5
- # prompt for a single option type and and return the input value
6
- # @param option_type [Hash] The OptionType input record to prompt for , contians fieldName, fieldLabel, etc.
7
- # @param options [Hash] The context being constructed, checks this for the value before prompting the user for input.
8
- # @param no_prompt [Boolean] The context being constructed, checks this for the value before prompting the user for input.
9
- # @param api_params [Hash] Optional map of parameters to include in API request for select option types
10
- # @return input value for the option type, usually a string or number if the value is an ID or of type: number
11
- def prompt_value(option_type, options, no_prompt=false, api_params={})
12
- # this does not work with fieldContext, so get rid of it
13
- return Morpheus::Cli::OptionTypes.prompt([option_type.merge({'fieldContext' => nil})], options, @api_client, api_params, no_prompt)[option_type['fieldName']]
14
- end
11
+ # Prompt for a list of inputs (OptionType) and return a Hash containing
12
+ # all of the provides values. The user is prompted to provide a value
13
+ # for each input, unless the value is already set in the options.
14
+ # @param [Array, Hash] option_types the list of OptionType inputs with fieldName, fieldLabel, etc.
15
+ # A single option type Hash can be passed instead.
16
+ # @param [Hash] options the standard command options
17
+ # This map is a mixture of keys that are Symbols that provide some common
18
+ # functionality like :no_prompt, :context_map, etc.
19
+ # Any keys that are Strings get used to lookup input values, along with
20
+ # +options[:options]+ and +options[:params]+.
21
+ # The precedence for providing option values is as follows:
22
+ # 1. options['foo'] 2. options[:params]['foo'] 3. options[:options]['foo'] 4. User is prompted.
23
+ # @option options [Hash] :options map of values provided by the user via the generic -O OPTIONS switch, gets merged into the context used for providing input values
24
+ # @option options [Hash] :params map of additional values provided by the user via explicite options, gets merged into the context used for providing input values
25
+ # @option options [Hash] :no_prompt supresses prompting, use default values and error if a required input is not provided. Default is of course +false+.
26
+ # @option options [Hash] :context_map Can to change the fieldContext of the option_types, eg. :context_map => {'networkDhcpRelay' => ''}
27
+ # @option options [APIClient] :api_client The {APIClient} to use for option types that request api calls. Default is the @api_client established by the class
28
+ # @option options [Hash] :api_params Optional map of parameters to include in API request for select option types
29
+ # @option options [Boolean] :paging_enabled Enable paging if there are a lot of available options to display. Default is +false+.
30
+ # @option options [Boolean] :ignore_empty Ignore inputs that have no options, this can be used to allow prompting to continue without error if a select input has no options.
31
+ # @option options [Boolean] :skip_sort Do not sort the inputs by displayOrder, assume they are already sorted. Default is +true+.
32
+ # @return [Hash] containing the values provided for each option type, the key is the input fieldName
33
+ #
34
+ # @example Prompt for name
35
+ #
36
+ # results = prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true}], options)
37
+ # puts "Name: #{results['name']}"
38
+ #
39
+ def prompt(option_types, options={})
40
+ # option types can be passed as a single input instead of an array
41
+ option_types = option_types.is_a?(Hash) ? [option_types] : option_types #Array(option_types)
42
+ # construct options parameter for Morpheus::Cli::OptionTypes.prompt
43
+ options = construct_prompt_options(options)
44
+ # by default the @api_client established by the command is used
45
+ api_client = options.key?(:api_client) ? options[:api_client] : @api_client
46
+ api_params = options.key?(:api_params) ? options[:api_params] : {}
47
+ no_prompt = options.key?(:no_prompt) ? options[:no_prompt] : false
48
+ paging_enabled = options.key?(:paging_enabled) ? options[:paging_enabled] : true
49
+ ignore_empty = options.key?(:ignore_empty) ? options[:ignore_empty] : false
50
+ # Defaulting skip_sort to true which is the opposite of OptionTypes.prompt()
51
+ # The API handles sorting most of the time now, calling function can sort before prompting if needed
52
+ # maybe switch this later if needed, removing skip_sort would be nice though...
53
+ skip_sort = options.key?(:skip_sort) ? options[:skip_sort] : true
54
+ results = Morpheus::Cli::OptionTypes.prompt(option_types, options, api_client, api_params, no_prompt, paging_enabled, ignore_empty, skip_sort)
55
+ # trying to get rid of the need to do these compact and booleanize calls..
56
+ # for now you can use options.merge({compact: true,booleanize: true}) or modify the results yourself
57
+ results.deep_compact! if options[:compact]
58
+ results.booleanize! if options[:booleanize] # 'on' => true
59
+ return results
60
+ end
61
+
62
+ # Process 1-N inputs (OptionType) in a special 'edit mode' that supresses user interaction.
63
+ # This is used by +update+ commands where we want to process option types
64
+ # without prompting the user so that the results only contains values that are passed in explicitely as options and default values are not used.
65
+ # @see {#prompt} method for details on the supported options
66
+ #
67
+ def no_prompt(option_types, options={})
68
+ options = options.merge({:edit_mode => true, :no_prompt => true})
69
+ options.delete(:no_prompt) if options[:always_prompt] # --prompt to always prompt
70
+ return prompt(option_types, options)
71
+ end
72
+
73
+ # Prompt for a single input and return only the value
74
+ # @param [Hash] option_type the OptionType input record to prompt for, contains fieldName, fieldLabel, etc.
75
+ # @param [Hash] options the standard command options
76
+ # @see {#prompt} method for details on the supported options
77
+ # @return [String, Number, Hash, Array, nil] value provided by the options or user input, usually a string or a number or nil if no value is provided.
78
+ #
79
+ # @example Prompt for name value
80
+ #
81
+ # chosen_name = prompt_value({'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true}, options)
82
+ # puts "Chosen Name: #{chosen_name}"
83
+ #
84
+ def prompt_value(option_type, options={})
85
+ # this does not need with fieldContext, so get rid of it
86
+ # option_types = [option_type.merge({'fieldContext' => nil})]
87
+ # results = prompt(Array(option_type), options)
88
+ # return results[option_type['fieldName']]
89
+
90
+ # this works with fieldContext now, hooray
91
+ # use get_object_value() to traverse object to get the value
92
+ option_types = [option_type] # Array(option_type)
93
+ results = prompt(option_types, options)
94
+ return get_option_type_value(results, option_types.first)
95
+ end
96
+
97
+ # Process inputs of a form, prompting for its options and field groups
98
+ # @param [Hash] form the OptionTypeForm to process
99
+ # @param [Hash] options the standard command options
100
+ # @see {#prompt} method for defailts on the supported options
101
+ # @return Hash containing the values provided for each option type, the key is the fieldName
102
+ #
103
+ def prompt_form(form, options={})
104
+ form_results = {}
105
+ results = prompt(Array(form['options']), options.merge(form_results))
106
+ form_results.deep_merge!(results)
107
+ # prompt for each field group, merging results into options context as we go
108
+ Array(form['fieldGroups']).each do |field_group|
109
+ # todo: look at isCollapsible, defaultCollapsed and visibleOnCode to see if the group should
110
+ # if collapsed then just use options.merge({:no_prompt => true}) and maybe need to set :required => false ?
111
+ results = prompt(field_group['options'], options.merge(form_results))
112
+ form_results.deep_merge!(results)
113
+ end
114
+ return form_results
115
+ end
116
+
117
+ protected
118
+
119
+ # construct options parameter for Morpheus::Cli::OptionTypes.prompt
120
+ # This options Hash is a mixture of Symbols that provide some common functionality like :no_prompt, and :context_map, etc.
121
+ # String keys are used to lookup values being passed into the command in order to prevent prompting the user
122
+ # The precedence for providing option values is as follows:
123
+ # 1. options['foo'] 2. options[:params]['foo'] 3. options[:options]['foo']
124
+ #
125
+ def construct_prompt_options(options)
126
+ passed_options = Hash(options[:options]).select {|k,v| k.is_a?(String) }
127
+ params = Hash(options[:params]).select {|k,v| k.is_a?(String) }
128
+ return passed_options.deep_merge(params).deep_merge!(options)
129
+ end
130
+
131
+ def get_option_type_value(results, option_type)
132
+ field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
133
+ get_object_value(results, field_key)
134
+ end
135
+
15
136
  end
@@ -217,6 +217,12 @@ module Morpheus
217
217
  context_map = context_map[ns.to_s]
218
218
  end
219
219
 
220
+ # CLI only options that need some do some inflection to decide how to prompt
221
+ # defaultValue is it right now..
222
+ if option_type[:preprocesser].is_a?(Proc)
223
+ option_type[:preprocesser].call(option_type, api_client, option_params)
224
+ end
225
+
220
226
  # credential type
221
227
  handle_credential_type = -> {
222
228
  credential_type = select_prompt(option_type.merge({'defaultValue' => value}), api_client, option_params.merge({'credentialTypes' => option_type['config']['credentialTypes']}), !value.nil?, nil, paging_enabled, ignore_empty, options[:edit_mode])
@@ -326,7 +332,9 @@ module Morpheus
326
332
  end
327
333
 
328
334
  if !value_found
329
- if option_type['type'] == 'number'
335
+ if option_type['type'] == 'text'
336
+ value = generic_prompt(option_type)
337
+ elsif option_type['type'] == 'number'
330
338
  value = number_prompt(option_type)
331
339
  elsif option_type['type'] == 'password'
332
340
  value = password_prompt(option_type)
@@ -380,8 +388,10 @@ module Morpheus
380
388
  end
381
389
  elsif option_type['type'] == 'file'
382
390
  value = file_prompt(option_type)
383
- elsif option_type['type'] == 'file-content'
391
+ elsif option_type['type'] == 'file-content' || option_type['type'] == 'fileContent'
384
392
  value = file_content_prompt(option_type, options, api_client, {})
393
+ elsif option_type['type'] == 'logoSelector'
394
+ value = file_prompt(option_type)
385
395
  elsif option_type['type'] == 'multiText'
386
396
  value = multitext_prompt(option_type)
387
397
  elsif option_type['type'] == 'azureMarketplace'
@@ -396,7 +406,6 @@ module Morpheus
396
406
  value = generic_prompt(option_type)
397
407
  end
398
408
  end
399
-
400
409
  # --labels x,y,z uses processValue proc to convert strings to an array
401
410
  if option_type['processValue'].is_a?(Proc)
402
411
  value = option_type['processValue'].call(value)
@@ -409,6 +418,61 @@ module Morpheus
409
418
  if value && value.is_a?(String)
410
419
  value = value.split(",").collect {|it| it.strip }
411
420
  end
421
+ # todo: Handle these types added with the new form fields:
422
+ #
423
+ # byteSize
424
+ # code-editor
425
+ # fileContent
426
+ # logoSelector
427
+ # keyValue
428
+ # textArray
429
+ # typeahead
430
+ # group
431
+ # cloud
432
+ # environment
433
+ # diskManager
434
+ # layout
435
+ # networkManager
436
+ # plan
437
+ # resourcePool
438
+ # secGroup
439
+ # tag
440
+ # httpHeader
441
+ elsif option_type['type'] == 'byteSize'
442
+ if value.to_s.empty?
443
+ value = 0 # nil
444
+ elsif value.is_a?(String)
445
+ if value.to_s.upcase.include?("G")
446
+ value = value.to_i * 1024 * 1024 * 1024
447
+ elsif value.to_s.upcase.include?("M")
448
+ value = value * 1024 * 1024
449
+ else
450
+ # assume bytes by default..
451
+ value = value.to_i
452
+ end
453
+ end
454
+ elsif option_type['type'] == 'keyValue'
455
+ value = try_as_json(value)
456
+ if value.is_a?(String)
457
+ map = {}
458
+ value.split(",").each do |it|
459
+ pair = it.split("=");
460
+ map[pair[0].to_s.strip] = pair[1..-1].join("=").strip
461
+ end
462
+ value = map
463
+ end
464
+ elsif option_type['type'] == 'textArray'
465
+ value = try_as_json(value)
466
+ if value.is_a?(String)
467
+ value = value.split(",").collect {|it| it.to_s.strip }
468
+ end
469
+ else
470
+ # default translation
471
+ # for non text inputs, try to parse value as JSON
472
+ # if option_type['type'] == 'group' || option_type['type'] == 'cloud' etc..
473
+ if value.is_a?(String) && option_type['type'] != 'text'
474
+ value = try_as_json(value)
475
+ end
412
476
  end
413
477
  context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
414
478
  parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
@@ -526,7 +590,12 @@ module Morpheus
526
590
  if matched_options.size > 1
527
591
  print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{use_value}]\n\n", Term::ANSIColor.reset
528
592
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
529
- display_select_options(option_type, matched_options)
593
+ if matched_options && matched_options.size > 10
594
+ display_select_options(option_type, matched_options.first(10))
595
+ puts " (#{matched_options.size-10} more)"
596
+ else
597
+ display_select_options(option_type, matched_options)
598
+ end
530
599
  print "The value '#{input}' matched #{matched_options.size()} options.\n"
531
600
  # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
532
601
  print "Try using value instead of name.\n"
@@ -595,7 +664,12 @@ module Morpheus
595
664
  if matched_options.size > 1
596
665
  print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{default_value}]\n\n", Term::ANSIColor.reset
597
666
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
598
- display_select_options(option_type, matched_options)
667
+ if matched_options && matched_options.size > 10
668
+ display_select_options(option_type, matched_options.first(10))
669
+ puts " (#{matched_options.size-10} more)"
670
+ else
671
+ display_select_options(option_type, matched_options)
672
+ end
599
673
  print "The value '#{default_value}' matched #{matched_options.size()} options.\n"
600
674
  # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
601
675
  print "Try using value instead of name.\n"
@@ -679,7 +753,12 @@ module Morpheus
679
753
  if matched_options.size > 1
680
754
  print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{input}]\n\n", Term::ANSIColor.reset
681
755
  print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
682
- display_select_options(option_type, matched_options)
756
+ if matched_options && matched_options.size > 10
757
+ display_select_options(option_type, matched_options.first(10))
758
+ puts " (#{matched_options.size-10} more)"
759
+ else
760
+ display_select_options(option_type, matched_options)
761
+ end
683
762
  print "The value '#{input}' matched #{matched_options.size()} options.\n"
684
763
  # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
685
764
  print "Try using value instead of name.\n"
@@ -867,7 +946,12 @@ module Morpheus
867
946
  exit 1
868
947
  else
869
948
  #help_prompt(option_type)
870
- display_select_options(option_type, select_options)
949
+ if select_options && select_options.size > 10
950
+ display_select_options(option_type, select_options.first(10))
951
+ puts " (#{select_options.size-10} more)"
952
+ else
953
+ display_select_options(option_type, select_options)
954
+ end
871
955
  print "\n"
872
956
  if select_options.empty?
873
957
  print "The value '#{input}' matched 0 options.\n"
@@ -1381,6 +1465,19 @@ module Morpheus
1381
1465
  end
1382
1466
  rtn
1383
1467
  end
1468
+
1469
+ def self.try_as_json(val)
1470
+ if val.is_a?(String)
1471
+ if (val.to_s[0] == '{' && val.to_s[-1] == '}') || (val.to_s[0] == '[' && val.to_s[-1] == ']')
1472
+ begin
1473
+ val = JSON.parse(val)
1474
+ rescue
1475
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if Morpheus::Logging.debug?
1476
+ end
1477
+ end
1478
+ end
1479
+ return val
1480
+ end
1384
1481
  end
1385
1482
  end
1386
1483
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "6.2.3"
4
+ VERSION = "6.3.1"
5
5
  end
6
6
  end
@@ -50,9 +50,12 @@ module Morpheus::Routes
50
50
  "#!app-templates", # App Blueprints (blueprints)
51
51
  "#!catalog-items",
52
52
  "#!compute-type-layouts", # Cluster Layouts
53
+ "#!compute-type-packages", # Cluster Packages
53
54
  ],
54
55
  :'virtual-images' => {},
55
56
  options: [
57
+ "#!forms", # Forms
58
+ "#!option-types", # Inputs
56
59
  "#!option-type-lists", # Option Lists
57
60
  ],
58
61
  templates: [
@@ -181,6 +184,12 @@ module Morpheus::Routes
181
184
 
182
185
  # map well known aliases
183
186
  case(path.dasherize.pluralize)
187
+ # when "forms"
188
+ # path = "/library/options/#!forms"
189
+ when "inputs"
190
+ path = "/library/options/#!option-types"
191
+ when "option-lists"
192
+ path = "/library/options/#!option-type-lists"
184
193
  when "backups"
185
194
  path = id ? "/backups/show" : "/backups/list"
186
195
  when "backup-jobs"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.3
4
+ version: 6.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Estes
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2023-10-13 00:00:00.000000000 Z
14
+ date: 2023-11-14 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -251,6 +251,7 @@ files:
251
251
  - lib/morpheus/api/jobs_interface.rb
252
252
  - lib/morpheus/api/key_pairs_interface.rb
253
253
  - lib/morpheus/api/library_cluster_layouts_interface.rb
254
+ - lib/morpheus/api/library_cluster_packages_interface.rb
254
255
  - lib/morpheus/api/library_container_scripts_interface.rb
255
256
  - lib/morpheus/api/library_container_templates_interface.rb
256
257
  - lib/morpheus/api/library_container_types_interface.rb
@@ -301,6 +302,7 @@ files:
301
302
  - lib/morpheus/api/network_subnets_interface.rb
302
303
  - lib/morpheus/api/network_types_interface.rb
303
304
  - lib/morpheus/api/networks_interface.rb
305
+ - lib/morpheus/api/option_type_forms_interface.rb
304
306
  - lib/morpheus/api/option_type_lists_interface.rb
305
307
  - lib/morpheus/api/option_types_interface.rb
306
308
  - lib/morpheus/api/options_interface.rb
@@ -430,9 +432,11 @@ files:
430
432
  - lib/morpheus/cli/commands/jobs_command.rb
431
433
  - lib/morpheus/cli/commands/key_pairs.rb
432
434
  - lib/morpheus/cli/commands/library_cluster_layouts_command.rb
435
+ - lib/morpheus/cli/commands/library_cluster_packages_command.rb
433
436
  - lib/morpheus/cli/commands/library_container_scripts_command.rb
434
437
  - lib/morpheus/cli/commands/library_container_templates_command.rb
435
438
  - lib/morpheus/cli/commands/library_container_types_command.rb
439
+ - lib/morpheus/cli/commands/library_forms_command.rb
436
440
  - lib/morpheus/cli/commands/library_instance_types_command.rb
437
441
  - lib/morpheus/cli/commands/library_layouts_command.rb
438
442
  - lib/morpheus/cli/commands/library_option_lists_command.rb
@@ -507,6 +511,7 @@ files:
507
511
  - lib/morpheus/cli/commands/security_package_types.rb
508
512
  - lib/morpheus/cli/commands/security_packages.rb
509
513
  - lib/morpheus/cli/commands/security_scans.rb
514
+ - lib/morpheus/cli/commands/self_service_command.rb
510
515
  - lib/morpheus/cli/commands/service_catalog_command.rb
511
516
  - lib/morpheus/cli/commands/service_plans_command.rb
512
517
  - lib/morpheus/cli/commands/set_prompt_command.rb