morpheus-cli 4.2.8 → 4.2.10

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api.rb +1 -1
  4. data/lib/morpheus/api/activity_interface.rb +9 -0
  5. data/lib/morpheus/api/api_client.rb +83 -27
  6. data/lib/morpheus/api/apps_interface.rb +21 -0
  7. data/lib/morpheus/api/dashboard_interface.rb +5 -21
  8. data/lib/morpheus/api/instances_interface.rb +3 -10
  9. data/lib/morpheus/api/invoice_line_items_interface.rb +14 -0
  10. data/lib/morpheus/api/invoices_interface.rb +7 -12
  11. data/lib/morpheus/api/library_layouts_interface.rb +8 -0
  12. data/lib/morpheus/api/ping_interface.rb +20 -0
  13. data/lib/morpheus/api/projects_interface.rb +33 -0
  14. data/lib/morpheus/api/setup_interface.rb +19 -36
  15. data/lib/morpheus/api/user_settings_interface.rb +0 -6
  16. data/lib/morpheus/api/whoami_interface.rb +4 -8
  17. data/lib/morpheus/benchmarking.rb +16 -26
  18. data/lib/morpheus/cli.rb +10 -5
  19. data/lib/morpheus/cli/access_token_command.rb +5 -8
  20. data/lib/morpheus/cli/activity_command.rb +146 -0
  21. data/lib/morpheus/cli/apps.rb +312 -121
  22. data/lib/morpheus/cli/archives_command.rb +1 -1
  23. data/lib/morpheus/cli/auth_command.rb +4 -11
  24. data/lib/morpheus/cli/blueprints_command.rb +196 -137
  25. data/lib/morpheus/cli/change_password_command.rb +1 -1
  26. data/lib/morpheus/cli/cli_command.rb +225 -72
  27. data/lib/morpheus/cli/cli_registry.rb +2 -2
  28. data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
  29. data/lib/morpheus/cli/clouds.rb +5 -20
  30. data/lib/morpheus/cli/clusters.rb +4 -28
  31. data/lib/morpheus/cli/commands/standard/alias_command.rb +2 -9
  32. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +2 -0
  33. data/lib/morpheus/cli/commands/standard/curl_command.rb +2 -3
  34. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -6
  35. data/lib/morpheus/cli/commands/standard/man_command.rb +10 -7
  36. data/lib/morpheus/cli/commands/standard/ssl_verification_command.rb +10 -9
  37. data/lib/morpheus/cli/containers_command.rb +3 -3
  38. data/lib/morpheus/cli/credentials.rb +13 -16
  39. data/lib/morpheus/cli/error_handler.rb +18 -12
  40. data/lib/morpheus/cli/errors.rb +45 -0
  41. data/lib/morpheus/cli/execute_schedules_command.rb +1 -1
  42. data/lib/morpheus/cli/execution_request_command.rb +4 -4
  43. data/lib/morpheus/cli/groups.rb +84 -132
  44. data/lib/morpheus/cli/hosts.rb +6 -16
  45. data/lib/morpheus/cli/instances.rb +100 -183
  46. data/lib/morpheus/cli/invoices_command.rb +505 -71
  47. data/lib/morpheus/cli/library_layouts_command.rb +254 -166
  48. data/lib/morpheus/cli/library_option_lists_command.rb +0 -87
  49. data/lib/morpheus/cli/library_option_types_command.rb +0 -96
  50. data/lib/morpheus/cli/license.rb +3 -0
  51. data/lib/morpheus/cli/login.rb +17 -37
  52. data/lib/morpheus/cli/logout.rb +9 -5
  53. data/lib/morpheus/cli/mixins/accounts_helper.rb +83 -7
  54. data/lib/morpheus/cli/mixins/operations_helper.rb +41 -0
  55. data/lib/morpheus/cli/mixins/option_source_helper.rb +255 -0
  56. data/lib/morpheus/cli/mixins/print_helper.rb +18 -4
  57. data/lib/morpheus/cli/mixins/provisioning_helper.rb +222 -13
  58. data/lib/morpheus/cli/mixins/remote_helper.rb +139 -0
  59. data/lib/morpheus/cli/monitoring_checks_command.rb +11 -3
  60. data/lib/morpheus/cli/network_groups_command.rb +8 -2
  61. data/lib/morpheus/cli/option_types.rb +1 -1
  62. data/lib/morpheus/cli/ping.rb +252 -0
  63. data/lib/morpheus/cli/price_sets_command.rb +16 -27
  64. data/lib/morpheus/cli/prices_command.rb +34 -27
  65. data/lib/morpheus/cli/processes_command.rb +81 -7
  66. data/lib/morpheus/cli/projects_command.rb +607 -0
  67. data/lib/morpheus/cli/recent_activity_command.rb +87 -65
  68. data/lib/morpheus/cli/remote.rb +965 -974
  69. data/lib/morpheus/cli/reports_command.rb +3 -15
  70. data/lib/morpheus/cli/roles.rb +8 -31
  71. data/lib/morpheus/cli/service_plans_command.rb +25 -31
  72. data/lib/morpheus/cli/setup.rb +392 -0
  73. data/lib/morpheus/cli/shell.rb +144 -56
  74. data/lib/morpheus/cli/subnets_command.rb +71 -11
  75. data/lib/morpheus/cli/tasks.rb +3 -3
  76. data/lib/morpheus/cli/user_sources_command.rb +4 -4
  77. data/lib/morpheus/cli/users.rb +135 -109
  78. data/lib/morpheus/cli/version.rb +1 -1
  79. data/lib/morpheus/cli/whitelabel_settings_command.rb +7 -7
  80. data/lib/morpheus/cli/whoami.rb +90 -129
  81. data/lib/morpheus/cli/wiki_command.rb +2 -14
  82. data/lib/morpheus/ext/rest_client.rb +36 -0
  83. data/lib/morpheus/formatters.rb +42 -5
  84. data/lib/morpheus/rest_client.rb +0 -10
  85. data/lib/morpheus/terminal.rb +41 -1
  86. data/lib/morpheus/util.rb +24 -0
  87. metadata +16 -3
  88. data/lib/morpheus/cli/command_error.rb +0 -22
@@ -65,7 +65,7 @@ class Morpheus::Cli::ChangePasswordCommand
65
65
  raise_command_error "No current appliance, see `remote use`."
66
66
  end
67
67
  if !@current_remote[:username]
68
- raise_command_error "You are not currently logged in to #{@current_remote[:name]} - #{@current_remote[:url] || @current_remote[:host]}"
68
+ raise_command_error "You are not currently logged in to #{display_appliance(@current_remote[:name], @current_remote[:url])}"
69
69
  end
70
70
  username = @current_remote[:username]
71
71
  end
@@ -97,8 +97,12 @@ module Morpheus
97
97
  @no_prompt != true
98
98
  end
99
99
 
100
- def raise_command_error(msg)
101
- raise Morpheus::Cli::CommandError.new(msg)
100
+ def raise_command_error(msg, args=[], optparse=nil, exit_code=nil)
101
+ raise Morpheus::Cli::CommandError.new(msg, args, optparse, exit_code)
102
+ end
103
+
104
+ def raise_args_error(msg, args=[], optparse=nil, exit_code=nil)
105
+ raise Morpheus::Cli::CommandArgumentsError.new(msg, args, optparse, exit_code)
102
106
  end
103
107
 
104
108
  # parse_id_list splits returns the given id_list with its values split on a comma
@@ -128,6 +132,11 @@ module Morpheus
128
132
  raise_command_error "Invalid value for #{option} option"
129
133
  end
130
134
 
135
+ # this returns all the options passed in by -O, parsed all nicely into objects.
136
+ def parse_passed_options(options)
137
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
138
+ return passed_options
139
+ end
131
140
  # Appends Array of OptionType definitions to an OptionParser instance
132
141
  # This adds an option like --fieldContext.fieldName="VALUE"
133
142
  # @param opts [OptionParser]
@@ -216,24 +225,39 @@ module Morpheus
216
225
  opts
217
226
  end
218
227
 
219
- def build_standard_list_options(opts, options, includes=[], excludes=[])
220
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote] + includes, excludes)
221
- end
228
+ ## the standard options for a command that makes api requests (most of them)
222
229
 
223
230
  def build_standard_get_options(opts, options, includes=[], excludes=[])
224
- build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote] + includes, excludes)
231
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :quiet, :dry_run, :remote] + includes, excludes)
232
+ end
233
+
234
+ def build_standard_post_options(opts, options, includes=[], excludes=[])
235
+ build_common_options(opts, options, [:options, :payload, :json, :quiet, :dry_run, :remote] + includes, excludes)
236
+ end
237
+
238
+ def build_standard_put_options(opts, options, includes=[], excludes=[])
239
+ build_standard_post_options(opts, options, includes, excludes)
240
+ end
241
+
242
+ def build_standard_delete_options(opts, options, includes=[], excludes=[])
243
+ build_common_options(opts, options, [:auto_confirm, :query, :json, :quiet, :dry_run, :remote] + includes, excludes)
244
+ end
245
+
246
+ # list is GET that supports phrase,max,offset,sort,direction
247
+ def build_standard_list_options(opts, options, includes=[], excludes=[])
248
+ build_standard_get_options(opts, options, [:list] + includes, excludes=[])
225
249
  end
226
250
 
227
251
  def build_standard_add_options(opts, options, includes=[], excludes=[])
228
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote] + includes, excludes)
252
+ build_standard_post_options(opts, options, includes, excludes)
229
253
  end
230
254
 
231
255
  def build_standard_update_options(opts, options, includes=[], excludes=[])
232
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote] + includes, excludes)
256
+ build_standard_put_options(opts, options, includes, excludes)
233
257
  end
234
258
 
235
259
  def build_standard_remove_options(opts, options, includes=[], excludes=[])
236
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote] + includes, excludes)
260
+ build_standard_delete_options(opts, options, includes, excludes)
237
261
  end
238
262
 
239
263
  # appends to the passed OptionParser all the generic options
@@ -494,12 +518,33 @@ module Morpheus
494
518
  opts.on( '-T', '--token TOKEN', "Access token for authentication with --remote. Saved credentials are used by default." ) do |val|
495
519
  options[:remote_token] = val
496
520
  end unless excludes.include?(:remote_token)
521
+ opts.on( '--token-file FILE', String, "Token File, read a file containing the access token." ) do |val|
522
+ token_file = File.expand_path(val)
523
+ if !File.exists?(token_file) || !File.file?(token_file)
524
+ raise ::OptionParser::InvalidOption.new("File not found: #{token_file}")
525
+ end
526
+ options[:remote_token] = File.read(token_file).to_s.split("\n").first.strip
527
+ end
528
+ opts.add_hidden_option('--token-file') if opts.is_a?(Morpheus::Cli::OptionParser)
497
529
  opts.on( '-U', '--username USERNAME', "Username for authentication." ) do |val|
498
530
  options[:remote_username] = val
499
531
  end unless excludes.include?(:remote_username)
500
- opts.on( '-P', '--password PASSWORD', "Password for authentication." ) do |val|
501
- options[:remote_password] = val
502
- end unless excludes.include?(:remote_password)
532
+
533
+
534
+ unless excludes.include?(:remote_password)
535
+ opts.on( '-P', '--password PASSWORD', "Password for authentication." ) do |val|
536
+ options[:remote_password] = val
537
+ end
538
+ opts.on( '--password-file FILE', String, "Password File, read a file containing the password for authentication." ) do |val|
539
+ password_file = File.expand_path(val)
540
+ if !File.exists?(password_file) || !File.file?(password_file)
541
+ raise ::OptionParser::InvalidOption.new("File not found: #{password_file}")
542
+ end
543
+ file_content = File.read(password_file) #.strip
544
+ options[:remote_password] = File.read(password_file).to_s.split("\n").first
545
+ end
546
+ opts.add_hidden_option('--password-file') if opts.is_a?(Morpheus::Cli::OptionParser)
547
+ end
503
548
 
504
549
  # todo: also require this for talking to plain old HTTP
505
550
  opts.on('-I','--insecure', "Allow insecure HTTPS communication. i.e. bad SSL certificate.") do |val|
@@ -622,7 +667,7 @@ module Morpheus
622
667
  end
623
668
  #opts.add_hidden_option('--all-fields') if opts.is_a?(Morpheus::Cli::OptionParser)
624
669
  opts.on(nil, '--wrap', "Wrap table columns instead hiding them when terminal is not wide enough.") do
625
- options[:responsive_table] = false
670
+ options[:wrap] = true
626
671
  end
627
672
  when :thin
628
673
  opts.on( '--thin', '--thin', "Format headers and columns with thin borders." ) do |val|
@@ -701,7 +746,8 @@ module Morpheus
701
746
 
702
747
 
703
748
  # Benchmark this command?
704
- opts.on('-B','--benchmark', "Print benchmark time after the command is finished.") do
749
+ # Also useful for seeing exit status for every command.
750
+ opts.on('-B','--benchmark', "Print benchmark time and exit/error after the command is finished.") do
705
751
  options[:benchmark] = true
706
752
  # this is hacky, but working!
707
753
  # shell handles returning to false
@@ -725,6 +771,15 @@ module Morpheus
725
771
  # end
726
772
  end
727
773
 
774
+ # A way to ensure debugging is off, it should go back on after the command is complete.
775
+ opts.on('--no-debug','--no-debug', "Disable debugging.") do
776
+ options[:debug] = false
777
+ Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::INFO)
778
+ ::RestClient.log = Morpheus::Logging.debug? ? Morpheus::Logging::DarkPrinter.instance : nil
779
+ end
780
+ opts.add_hidden_option('--no-debug') if opts.is_a?(Morpheus::Cli::OptionParser)
781
+
782
+
728
783
  opts.on('-h', '--help', "Print this help" ) do
729
784
  puts opts
730
785
  exit # return 0 maybe?
@@ -741,6 +796,10 @@ module Morpheus
741
796
  self.class.subcommands
742
797
  end
743
798
 
799
+ def visible_subcommands
800
+ self.class.visible_subcommands
801
+ end
802
+
744
803
  def subcommand_aliases
745
804
  self.class.subcommand_aliases
746
805
  end
@@ -821,10 +880,8 @@ module Morpheus
821
880
  end
822
881
  cmd_method = subcommands[subcommand_name]
823
882
  if !cmd_method
824
- print_error Morpheus::Terminal.angry_prompt
825
- #puts_error "'#{subcommand_name}' is not recognized. See '#{my_help_command}'"
826
- puts_error "'#{subcommand_name}' is not recognized.\n#{full_command_usage}"
827
- return 127
883
+ error_msg = "'#{command_name} #{subcommand_name}' is not a morpheus command.\n#{full_command_usage}"
884
+ raise CommandNotFoundError.new(error_msg)
828
885
  end
829
886
  self.send(cmd_method, args[1..-1])
830
887
  end
@@ -857,96 +914,129 @@ module Morpheus
857
914
  return failed_result ? failed_result : cmd_results.last
858
915
  end
859
916
 
917
+ # def connect(options={})
918
+ # Morpheus::Logging::DarkPrinter.puts "#{command_name} has not defined connect()" if Morpheus::Logging.debug?
919
+ # end
920
+
860
921
  # This supports the simple remote option eg. `instances add --remote "qa"`
861
922
  # It will establish a connection to the pre-configured appliance named "qa"
862
- # The calling command can populate @appliances and/or @appliance_name
863
- # Otherwise, the current active appliance is used...
923
+ # By default it will connect to the active (current) remote appliance
864
924
  # This returns a new instance of Morpheus::APIClient (and sets @access_token, and @appliance)
865
925
  # Your command should be ready to make api requests after this.
926
+ # This will prompt for credentials if none are found, use :skip_login
927
+ # Credentials will be saved unless --remote-url or --token is being used.
866
928
  def establish_remote_appliance_connection(options)
867
929
  # todo: probably refactor and don't rely on this method to set these instance vars
930
+ @remote_appliance = nil
868
931
  @appliance_name, @appliance_url, @access_token = nil, nil, nil
869
932
  @api_client = nil
870
-
871
- appliance = nil # @appliance..why not? laff
872
- if options[:remote]
933
+ @do_save_credentials = true
934
+ # skip saving if --remote-url or --username or --password are passed in
935
+ if options[:remote_url] || options[:remote_token] || options[:remote_username] || options[:remote_password]
936
+ @do_save_credentials = false
937
+ end
938
+ appliance = nil
939
+ if options[:remote_url]
940
+ # --remote-url means use an arbitrary url, do not save any appliance config
941
+ # appliance = {name:'remote-url', url:options[:remote_url]}
942
+ appliance = {url:options[:remote_url]}
943
+ appliance[:temporary] = true
944
+ #appliance[:status] = "ready" # or "unknown"
945
+ # appliance[:last_check] = nil
946
+ elsif options[:remote]
947
+ # --remote means use the specified remote
873
948
  appliance = ::Morpheus::Cli::Remote.load_remote(options[:remote])
874
- if !appliance
949
+ if appliance.nil?
875
950
  if ::Morpheus::Cli::Remote.appliances.empty?
876
- raise_command_error "You have no appliances configured. See the `remote add` command."
951
+ raise_command_error "No remote appliances exist, see the command `remote add`."
877
952
  else
878
- raise_command_error "Remote appliance not found by the name '#{options[:remote]}'"
953
+ raise_command_error "Remote appliance not found by the name '#{options[:remote]}', see `remote list`"
879
954
  end
880
955
  end
881
956
  else
957
+ # use active remote
882
958
  appliance = ::Morpheus::Cli::Remote.load_active_remote()
883
959
  if !appliance
884
960
  if ::Morpheus::Cli::Remote.appliances.empty?
885
- raise_command_error "You have no appliances configured. See the `remote add` command."
961
+ raise_command_error "No remote appliances exist, see the command `remote add`"
886
962
  else
887
- raise_command_error "No current appliance, see `remote use`."
963
+ raise_command_error "#{command_name} requires a remote to be specified, try the option -r [remote] or see the command `remote use`"
888
964
  end
889
965
  end
890
966
  end
967
+ @remote_appliance = appliance
891
968
  @appliance_name = appliance[:name]
892
- @appliance_url = appliance[:host] || appliance[:url] # it's :host in the YAML..heh
893
-
969
+ @appliance_url = appliance[:url] || appliance[:host] # it used to store :host in the YAML
970
+ # set enable_ssl_verification
894
971
  # instead of toggling this global value
895
972
  # this should just be an attribute of the api client
896
973
  # for now, this fixes the issue where passing --insecure or --remote
897
974
  # would then apply to all subsequent commands...
898
- if !Morpheus::Cli::Shell.insecure
899
- if options[:insecure]
900
- Morpheus::RestClient.enable_ssl_verification = false
901
- else
902
- if appliance[:insecure] && Morpheus::RestClient.ssl_verification_enabled?
903
- Morpheus::RestClient.enable_ssl_verification = false
904
- elsif !appliance[:insecure] && !Morpheus::RestClient.ssl_verification_enabled?
905
- Morpheus::RestClient.enable_ssl_verification = true
906
- end
907
- end
975
+ allow_insecure = false
976
+ if options[:insecure] || appliance[:insecure] || Morpheus::Cli::Shell.insecure
977
+ allow_insecure = true
978
+ end
979
+ # Morpheus::RestClient.enable_ssl_verification = allow_insecure != true
980
+ if allow_insecure && Morpheus::RestClient.ssl_verification_enabled?
981
+ Morpheus::RestClient.enable_ssl_verification = false
982
+ elsif !allow_insecure && !Morpheus::RestClient.ssl_verification_enabled?
983
+ Morpheus::RestClient.enable_ssl_verification = true
908
984
  end
909
985
 
910
- # todo: support old way of accepting --username and --password on the command line
986
+ # always support accepting --username and --password on the command line
911
987
  # it's probably better not to do that tho, just so it stays out of history files
912
-
913
988
 
914
989
  # if !@appliance_name && !@appliance_url
915
990
  # raise_command_error "Please specify a remote appliance with -r or see the command `remote use`"
916
991
  # end
917
992
 
918
- Morpheus::Logging::DarkPrinter.puts "establishing connection to [#{@appliance_name}] #{@appliance_url}" if options[:debug]
919
- #puts "#{dark} #=> establishing connection to [#{@appliance_name}] #{@appliance_url}#{reset}\n" if options[:debug]
993
+ Morpheus::Logging::DarkPrinter.puts "establishing connection to remote #{display_appliance(@appliance_name, @appliance_url)}" if Morpheus::Logging.debug?
920
994
 
995
+ if options[:no_authorization]
996
+ # maybe handle this here..
997
+ options[:skip_login] = true
998
+ options[:skip_verify_access_token] = true
999
+ end
921
1000
 
922
1001
  # ok, get some credentials.
923
- # this prompts for username, password without options[:no_prompt]
924
- # uses saved credentials by default.
925
- # passing --remote-url or --token or --username will skip loading saved credentials and trigger prompting
1002
+ # use saved credentials by default or prompts for username, password.
1003
+ # passing --remote-url will skip loading saved credentials and prompt for login to use with the url
1004
+ # passing --token skips login prompting and uses the provided token.
1005
+ # passing --token or --username will skip saving credentials to appliance config, they are just used for one command
1006
+ # ideally this should not prompt now and wait until the client is used on a protected endpoint.
1007
+ # @wallet = nil
926
1008
  if options[:remote_token]
927
- @access_token = options[:remote_token]
1009
+ @wallet = {'access_token' => options[:remote_token]} #'username' => 'anonymous'
1010
+ elsif options[:remote_url]
1011
+ credentials = Morpheus::Cli::Credentials.new(@appliance_name, @appliance_url)
1012
+ unless options[:skip_login]
1013
+ @wallet = credentials.request_credentials(options, @do_save_credentials)
1014
+ end
928
1015
  else
929
1016
  credentials = Morpheus::Cli::Credentials.new(@appliance_name, @appliance_url)
930
- # @wallet = credentials.load_saved_credentials()
931
- # @wallet = credentials.request_credentials(options)
932
- if options[:remote_token]
933
- @wallet = credentials.request_credentials(options, false)
934
- elsif options[:remote_url] || options[:remote_username]
935
- @wallet = credentials.request_credentials(options, false)
936
- else
937
- #@wallet = credentials.request_credentials(options)
1017
+ # use saved credentials unless --username or passed
1018
+ unless options[:remote_username]
938
1019
  @wallet = credentials.load_saved_credentials()
939
1020
  end
940
- @access_token = @wallet ? @wallet['access_token'] : nil
941
- # if @access_token.to_s.empty?
942
- # unless options[:no_prompt]
943
- # @wallet = credentials.request_credentials(options)
944
- # @access_token = @wallet ? @wallet['access_token'] : nil
945
- # end
946
- # end
947
- # bail if we got nothing still
948
- unless options[:skip_verify_access_token]
949
- verify_access_token!
1021
+ # using active remote OR --remote flag
1022
+ # used saved credentials or login
1023
+ # ideally this sould not prompt now and wait until the client is used on a protected endpoint.
1024
+
1025
+
1026
+ if @wallet.nil? || @wallet['access_token'].nil?
1027
+ unless options[:skip_login]
1028
+ @wallet = credentials.request_credentials(options, @do_save_credentials)
1029
+ end
1030
+ end
1031
+
1032
+ end
1033
+ @access_token = @wallet ? @wallet['access_token'] : nil
1034
+
1035
+ # validate we have a token
1036
+ # hrm...
1037
+ unless options[:skip_verify_access_token]
1038
+ if @access_token.empty?
1039
+ raise AuthorizationRequiredError.new("Failed to acquire access token for #{display_appliance(@appliance_name, @appliance_url)}. Verify your credentials are correct.")
950
1040
  end
951
1041
  end
952
1042
 
@@ -956,9 +1046,29 @@ module Morpheus
956
1046
  return api_client
957
1047
  end
958
1048
 
959
- def verify_access_token!
960
- if @access_token.empty?
961
- raise_command_error "Unable to acquire access token. Please verify your credentials and try again."
1049
+ # verify_args! verifies that the right number of commands were passed
1050
+ # and raises a command error if not.
1051
+ # Example: verify_args!(args:args, count:1, optparse:optparse)
1052
+ # this could go be done in optparse.parse instead perhaps
1053
+ def verify_args!(opts={})
1054
+ args = opts[:args] || []
1055
+ if opts[:count]
1056
+ if args.count < opts[:count]
1057
+ raise_args_error("not enough arguments, expected #{opts[:count]} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
1058
+ elsif args.count > opts[:count]
1059
+ raise_args_error("too many arguments, expected #{opts[:count]} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
1060
+ end
1061
+ else
1062
+ if opts[:min]
1063
+ if args.count < opts[:min]
1064
+ raise_args_error("not many arguments, expected #{opts[:min] || '0'}-#{opts[:max] || 'N'} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
1065
+ end
1066
+ end
1067
+ if opts[:max]
1068
+ if args.count > opts[:max]
1069
+ raise_args_error("too many arguments, expected #{opts[:min] || '0'}-#{opts[:max] || 'N'} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
1070
+ end
1071
+ end
962
1072
  end
963
1073
  true
964
1074
  end
@@ -1042,9 +1152,22 @@ module Morpheus
1042
1152
  payload
1043
1153
  end
1044
1154
 
1045
- # basic rendering for options :json, :yaml, :csv, :fields, and :outfile
1155
+ def render_response(json_response, options, object_key=nil, &block)
1156
+ render_result = render_with_format(json_response, options, object_key)
1157
+ if render_result
1158
+ return 0, nil
1159
+ else
1160
+ if block_given?
1161
+ return yield
1162
+ else
1163
+ return 0, nil
1164
+ end
1165
+ end
1166
+ end
1167
+
1168
+ # basic rendering for options :json, :yml, :csv, :quiet, and :outfile
1046
1169
  # returns the string rendered, or nil if nothing was rendered.
1047
- def render_with_format(json_response, options, object_key=nil)
1170
+ def render_with_format(json_response, options, object_key=nil, &block)
1048
1171
  output = nil
1049
1172
  if options[:json]
1050
1173
  output = as_json(json_response, options, object_key)
@@ -1060,7 +1183,7 @@ module Morpheus
1060
1183
  elsif options[:quiet]
1061
1184
  # note: returning non nil means the calling function knows to return rght away.. kinda weird..
1062
1185
  # but means we need less if options[:quiet] blocks in every action.
1063
- output = ""
1186
+ return ""
1064
1187
  end
1065
1188
  if output
1066
1189
  if options[:outfile]
@@ -1068,6 +1191,17 @@ module Morpheus
1068
1191
  else
1069
1192
  puts output
1070
1193
  end
1194
+ else
1195
+ if block_given?
1196
+ # invoke the user given block to render (print output)
1197
+ # hope it returned something well formed, there's a parse method for that..
1198
+ cmd_render_result = yield
1199
+ # could try to support writing output to options[:outfile] here too..
1200
+ # output is already printed inside block though
1201
+ # if cmd_render_result
1202
+ # return output
1203
+ # end
1204
+ end
1071
1205
  end
1072
1206
  return output
1073
1207
  end
@@ -1099,6 +1233,14 @@ module Morpheus
1099
1233
  !!@hidden_command
1100
1234
  end
1101
1235
 
1236
+ def set_subcommands_hidden(*cmds)
1237
+ @hidden_subcommands ||= []
1238
+ cmds.flatten.each do |cmd|
1239
+ @hidden_subcommands << cmd.to_sym
1240
+ end
1241
+ @hidden_subcommands
1242
+ end
1243
+
1102
1244
  def command_description
1103
1245
  @command_description
1104
1246
  end
@@ -1153,6 +1295,17 @@ module Morpheus
1153
1295
  @subcommands ||= {}
1154
1296
  end
1155
1297
 
1298
+ def visible_subcommands
1299
+ cmds = subcommands.clone
1300
+ if @hidden_subcommands && !@hidden_subcommands.empty?
1301
+ @hidden_subcommands.each do |hidden_cmd|
1302
+ cmds.delete(hidden_cmd.to_s)
1303
+ cmds.delete(hidden_cmd.to_sym)
1304
+ end
1305
+ end
1306
+ cmds
1307
+ end
1308
+
1156
1309
  def has_subcommand?(cmd_name)
1157
1310
  return false if cmd_name.empty?
1158
1311
  @subcommands && @subcommands[cmd_name.to_s]