morpheus-cli 4.2.8 → 4.2.10

Sign up to get free protection for your applications and to get access to all the features.
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]