morpheus-cli 6.2.3 → 6.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.
@@ -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