morpheus-cli 4.2.12 → 4.2.17

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/README.md +8 -6
  4. data/lib/morpheus/api/api_client.rb +32 -14
  5. data/lib/morpheus/api/auth_interface.rb +4 -2
  6. data/lib/morpheus/api/backup_jobs_interface.rb +9 -0
  7. data/lib/morpheus/api/backups_interface.rb +16 -0
  8. data/lib/morpheus/api/deploy_interface.rb +25 -56
  9. data/lib/morpheus/api/deployments_interface.rb +43 -54
  10. data/lib/morpheus/api/doc_interface.rb +57 -0
  11. data/lib/morpheus/api/instances_interface.rb +5 -0
  12. data/lib/morpheus/api/rest_interface.rb +40 -0
  13. data/lib/morpheus/api/user_sources_interface.rb +0 -15
  14. data/lib/morpheus/api/users_interface.rb +2 -3
  15. data/lib/morpheus/benchmarking.rb +2 -2
  16. data/lib/morpheus/cli.rb +3 -1
  17. data/lib/morpheus/cli/access_token_command.rb +27 -10
  18. data/lib/morpheus/cli/apps.rb +23 -16
  19. data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
  20. data/lib/morpheus/cli/backups_command.rb +271 -0
  21. data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
  22. data/lib/morpheus/cli/cli_command.rb +176 -45
  23. data/lib/morpheus/cli/cli_registry.rb +10 -1
  24. data/lib/morpheus/cli/clusters.rb +2 -19
  25. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +23 -20
  26. data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
  27. data/lib/morpheus/cli/containers_command.rb +2 -1
  28. data/lib/morpheus/cli/credentials.rb +14 -10
  29. data/lib/morpheus/cli/deploy.rb +374 -0
  30. data/lib/morpheus/cli/deployments.rb +521 -197
  31. data/lib/morpheus/cli/deploys.rb +271 -126
  32. data/lib/morpheus/cli/doc.rb +182 -0
  33. data/lib/morpheus/cli/error_handler.rb +23 -8
  34. data/lib/morpheus/cli/errors.rb +3 -2
  35. data/lib/morpheus/cli/health_command.rb +4 -3
  36. data/lib/morpheus/cli/hosts.rb +2 -1
  37. data/lib/morpheus/cli/image_builder_command.rb +2 -2
  38. data/lib/morpheus/cli/instances.rb +138 -18
  39. data/lib/morpheus/cli/invoices_command.rb +338 -223
  40. data/lib/morpheus/cli/library_layouts_command.rb +1 -1
  41. data/lib/morpheus/cli/library_option_lists_command.rb +61 -125
  42. data/lib/morpheus/cli/library_option_types_command.rb +32 -37
  43. data/lib/morpheus/cli/login.rb +9 -3
  44. data/lib/morpheus/cli/logs_command.rb +3 -2
  45. data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
  46. data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
  47. data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
  48. data/lib/morpheus/cli/mixins/library_helper.rb +32 -0
  49. data/lib/morpheus/cli/mixins/logs_helper.rb +18 -9
  50. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  51. data/lib/morpheus/cli/mixins/print_helper.rb +149 -84
  52. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
  53. data/lib/morpheus/cli/mixins/whoami_helper.rb +19 -6
  54. data/lib/morpheus/cli/network_routers_command.rb +1 -1
  55. data/lib/morpheus/cli/option_parser.rb +48 -5
  56. data/lib/morpheus/cli/option_types.rb +46 -10
  57. data/lib/morpheus/cli/price_sets_command.rb +1 -1
  58. data/lib/morpheus/cli/remote.rb +8 -10
  59. data/lib/morpheus/cli/roles.rb +49 -92
  60. data/lib/morpheus/cli/security_groups.rb +7 -1
  61. data/lib/morpheus/cli/service_plans_command.rb +10 -10
  62. data/lib/morpheus/cli/setup.rb +1 -1
  63. data/lib/morpheus/cli/shell.rb +7 -6
  64. data/lib/morpheus/cli/subnets_command.rb +1 -1
  65. data/lib/morpheus/cli/tenants_command.rb +133 -163
  66. data/lib/morpheus/cli/user_groups_command.rb +20 -65
  67. data/lib/morpheus/cli/user_settings_command.rb +115 -13
  68. data/lib/morpheus/cli/user_sources_command.rb +57 -24
  69. data/lib/morpheus/cli/users.rb +210 -186
  70. data/lib/morpheus/cli/version.rb +1 -1
  71. data/lib/morpheus/cli/whitelabel_settings_command.rb +29 -5
  72. data/lib/morpheus/cli/whoami.rb +113 -6
  73. data/lib/morpheus/cli/workflows.rb +1 -1
  74. data/lib/morpheus/ext/hash.rb +21 -0
  75. data/lib/morpheus/formatters.rb +7 -19
  76. data/lib/morpheus/terminal.rb +1 -0
  77. metadata +12 -3
  78. data/lib/morpheus/cli/auth_command.rb +0 -105
@@ -0,0 +1,271 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::BackupsCommand
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::BackupsHelper
6
+ include Morpheus::Cli::ProvisioningHelper
7
+ include Morpheus::Cli::OptionSourceHelper
8
+
9
+ set_command_hidden # hide until ready
10
+
11
+ set_command_name :'backups'
12
+
13
+ register_subcommands :list, :get, :add, :update, :remove, :run, :restore
14
+
15
+ def connect(opts)
16
+ @api_client = establish_remote_appliance_connection(opts)
17
+ @backups_interface = @api_client.backups
18
+ @backup_jobs_interface = @api_client.backup_jobs
19
+ end
20
+
21
+ def handle(args)
22
+ handle_subcommand(args)
23
+ end
24
+
25
+ def list(args)
26
+ options = {}
27
+ params = {}
28
+ ref_ids = []
29
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
30
+ opts.banner = subcommand_usage("[search]")
31
+ build_standard_list_options(opts, options)
32
+ opts.footer = "List backups."
33
+ end
34
+ optparse.parse!(args)
35
+ connect(options)
36
+ # verify_args!(args:args, optparse:optparse, count:0)
37
+ if args.count > 0
38
+ options[:phrase] = args.join(" ")
39
+ end
40
+ params.merge!(parse_list_options(options))
41
+ @backups_interface.setopts(options)
42
+ if options[:dry_run]
43
+ print_dry_run @backups_interface.dry.list(params)
44
+ return
45
+ end
46
+ json_response = @backups_interface.list(params)
47
+ backups = json_response['backups']
48
+ render_response(json_response, options, 'backups') do
49
+ print_h1 "Morpheus Backups", parse_list_subtitles(options), options
50
+ if backups.empty?
51
+ print cyan,"No backups found.",reset,"\n"
52
+ else
53
+ print as_pretty_table(backups, backup_column_definitions.upcase_keys!, options)
54
+ print_results_pagination(json_response)
55
+ end
56
+ print reset,"\n"
57
+ end
58
+ if backups.empty?
59
+ return 1, "no backups found"
60
+ else
61
+ return 0, nil
62
+ end
63
+ end
64
+
65
+ def get(args)
66
+ params = {}
67
+ options = {}
68
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
69
+ opts.banner = subcommand_usage("[backup]")
70
+ build_standard_get_options(opts, options)
71
+ opts.footer = <<-EOT
72
+ Get details about a specific backup.
73
+ [backup] is required. This is the name or id of a backup.
74
+ EOT
75
+ end
76
+ optparse.parse!(args)
77
+ verify_args!(args:args, optparse:optparse, min:1)
78
+ connect(options)
79
+ id_list = parse_id_list(args)
80
+ return run_command_for_each_arg(id_list) do |arg|
81
+ _get(arg, params, options)
82
+ end
83
+ end
84
+
85
+ def _get(id, options)
86
+ params = {}
87
+ @backups_interface.setopts(options)
88
+ if options[:dry_run]
89
+ print_dry_run @backups_interface.dry.get(id, params)
90
+ return
91
+ end
92
+ json_response = @backups_interface.get(id, params)
93
+ backup = json_response['backup']
94
+ render_response(json_response, options, 'backup') do
95
+ print_h1 "Backup Details", [], options
96
+ print cyan
97
+ print_description_list(backup_column_definitions, backup)
98
+ print reset,"\n"
99
+ end
100
+ return 0, nil
101
+ end
102
+
103
+ def add(args)
104
+ options = {}
105
+ params = {}
106
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
107
+ opts.banner = subcommand_usage("[name] [options]")
108
+ build_option_type_options(opts, options, add_backup_option_types)
109
+ build_option_type_options(opts, options, add_backup_advanced_option_types)
110
+ build_standard_add_options(opts, options)
111
+ opts.footer = <<-EOT
112
+ Create a new backup.
113
+ EOT
114
+ end
115
+ optparse.parse!(args)
116
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
117
+ options[:options]['name'] = args[0] if args[0]
118
+ connect(options)
119
+ payload = {}
120
+ if options[:payload]
121
+ payload = options[:payload]
122
+ payload.deep_merge!({'backup' => parse_passed_options(options)})
123
+ else
124
+ payload.deep_merge!({'backup' => parse_passed_options(options)})
125
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(add_backup_option_types(), options[:options], @api_client, options[:params])
126
+ params.deep_merge!(v_prompt)
127
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_backup_advanced_option_types, options[:options], @api_client, options[:params])
128
+ advanced_config.deep_compact!
129
+ params.deep_merge!(advanced_config)
130
+ payload['backup'].deep_merge!(params)
131
+ end
132
+ @backups_interface.setopts(options)
133
+ if options[:dry_run]
134
+ print_dry_run @backups_interface.dry.create(payload)
135
+ return 0, nil
136
+ end
137
+ json_response = @backups_interface.create(payload)
138
+ backup = json_response['backup']
139
+ render_response(json_response, options, 'backup') do
140
+ print_green_success "Added backup #{backup['name']}"
141
+ return _get(backup["id"], {}, options)
142
+ end
143
+ return 0, nil
144
+ end
145
+
146
+ def update(args)
147
+ options = {}
148
+ params = {}
149
+ payload = {}
150
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
151
+ opts.banner = subcommand_usage("[backup] [options]")
152
+ build_option_type_options(opts, options, update_backup_option_types)
153
+ build_option_type_options(opts, options, update_backup_advanced_option_types)
154
+ build_standard_update_options(opts, options)
155
+ opts.footer = <<-EOT
156
+ Update a backup.
157
+ [backup] is required. This is the name or id of a backup.
158
+ EOT
159
+ end
160
+ optparse.parse!(args)
161
+ verify_args!(args:args, optparse:optparse, count:1)
162
+ connect(options)
163
+ backup = find_backup_by_name_or_id(args[0])
164
+ return 1 if backup.nil?
165
+ payload = {}
166
+ if options[:payload]
167
+ payload = options[:payload]
168
+ payload.deep_merge!({'backup' => parse_passed_options(options)})
169
+ else
170
+ payload.deep_merge!({'backup' => parse_passed_options(options)})
171
+ # do not prompt on update
172
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_backup_option_types, options[:options], @api_client, options[:params])
173
+ v_prompt.deep_compact!
174
+ params.deep_merge!(v_prompt)
175
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_backup_advanced_option_types, options[:options], @api_client, options[:params])
176
+ advanced_config.deep_compact!
177
+ params.deep_merge!(advanced_config)
178
+ payload.deep_merge!({'backup' => params})
179
+ if payload['backup'].empty? # || options[:no_prompt]
180
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
181
+ end
182
+ end
183
+ @backups_interface.setopts(options)
184
+ if options[:dry_run]
185
+ print_dry_run @backups_interface.dry.update(backup['id'], payload)
186
+ return
187
+ end
188
+ json_response = @backups_interface.update(backup['id'], payload)
189
+ backup = json_response['backup']
190
+ render_response(json_response, options, 'backup') do
191
+ print_green_success "Updated backup #{backup['name']}"
192
+ return _get(backup["id"], {}, options)
193
+ end
194
+ return 0, nil
195
+ end
196
+
197
+ def remove(args)
198
+ options = {}
199
+ params = {}
200
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
201
+ opts.banner = subcommand_usage("[backup] [options]")
202
+ build_standard_remove_options(opts, options)
203
+ opts.footer = <<-EOT
204
+ Delete a backup.
205
+ [backup] is required. This is the name or id of a backup.
206
+ EOT
207
+ end
208
+ optparse.parse!(args)
209
+ verify_args!(args:args, optparse:optparse, count:1)
210
+ connect(options)
211
+ backup = find_backup_by_name_or_id(args[0])
212
+ return 1 if backup.nil?
213
+ @backups_interface.setopts(options)
214
+ if options[:dry_run]
215
+ print_dry_run @backups_interface.dry.destroy(backup['id'], params)
216
+ return
217
+ end
218
+ json_response = @backups_interface.destroy(backup['id'], params)
219
+ render_response(json_response, options) do
220
+ print_green_success "Removed backup #{backup['name']}"
221
+ end
222
+ return 0, nil
223
+ end
224
+
225
+ private
226
+
227
+ def backup_column_definitions()
228
+ {
229
+ "ID" => 'id',
230
+ "Name" => 'name',
231
+ "Schedule" => lambda {|it| it['schedule']['name'] rescue '' },
232
+ "Backup Job" => lambda {|it| it['job']['name'] rescue '' },
233
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
234
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
235
+ }
236
+ end
237
+
238
+ # this is not so simple, need to first choose select instance, host or provider
239
+ def add_backup_option_types
240
+ [
241
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
242
+ {'fieldName' => 'jobId', 'fieldLabel' => 'Backup Job', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
243
+ # @options_interface.options_for_source("licenseTypes", {})['data']
244
+ @backup_jobs_interface.list({max:10000})['backupJobs'].collect {|backup_job|
245
+ {'name' => backup_job['name'], 'value' => backup_job['id'], 'id' => backup_job['id']}
246
+ }
247
+ }, 'required' => true, 'displayOrder' => 3},
248
+ ]
249
+ end
250
+
251
+ def add_backup_advanced_option_types
252
+ []
253
+ end
254
+
255
+ def update_backup_option_types
256
+ add_backup_option_types.collect {|it|
257
+ it.delete('required')
258
+ it.delete('defaultValue')
259
+ it
260
+ }
261
+ end
262
+
263
+ def update_backup_advanced_option_types
264
+ add_backup_advanced_option_types.collect {|it|
265
+ it.delete('required')
266
+ it.delete('defaultValue')
267
+ it
268
+ }
269
+ end
270
+
271
+ end
@@ -279,7 +279,7 @@ class Morpheus::Cli::BootScriptsCommand
279
279
  options = {}
280
280
  optparse = Morpheus::Cli::OptionParser.new do |opts|
281
281
  opts.banner = subcommand_usage("[boot-script]")
282
- build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :remote])
282
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
283
283
  end
284
284
  optparse.parse!(args)
285
285
 
@@ -1,5 +1,6 @@
1
1
  require 'yaml'
2
2
  require 'json'
3
+ require 'fileutils'
3
4
  require 'morpheus/logging'
4
5
  require 'morpheus/benchmarking'
5
6
  require 'morpheus/cli/option_parser'
@@ -169,14 +170,18 @@ module Morpheus
169
170
  full_field_name = "#{field_namespace.join('.')}.#{field_name}"
170
171
  end
171
172
 
172
- description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{option_type['defaultValue'] ? ' Default: '+option_type['defaultValue'].to_s : ''}"
173
+ description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}"
173
174
  if option_type['description']
174
175
  # description << "\n #{option_type['description']}"
175
176
  description << " - #{option_type['description']}"
176
177
  end
178
+ if option_type['defaultValue']
179
+ description << ". Default: #{option_type['defaultValue']}"
180
+ end
177
181
  if option_type['helpBlock']
178
182
  description << "\n #{option_type['helpBlock']}"
179
183
  end
184
+
180
185
  # description = option_type['description'].to_s
181
186
  # if option_type['defaultValue']
182
187
  # description = "#{description} Default: #{option_type['defaultValue']}"
@@ -228,11 +233,11 @@ module Morpheus
228
233
  ## the standard options for a command that makes api requests (most of them)
229
234
 
230
235
  def build_standard_get_options(opts, options, includes=[], excludes=[])
231
- build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :quiet, :dry_run, :remote] + includes, excludes)
236
+ build_common_options(opts, options, includes + [:query, :json, :yaml, :csv, :fields, :quiet, :dry_run, :remote], excludes)
232
237
  end
233
238
 
234
239
  def build_standard_post_options(opts, options, includes=[], excludes=[])
235
- build_common_options(opts, options, [:options, :payload, :json, :quiet, :dry_run, :remote] + includes, excludes)
240
+ build_common_options(opts, options, includes + [:options, :payload, :json, :quiet, :dry_run, :remote], excludes)
236
241
  end
237
242
 
238
243
  def build_standard_put_options(opts, options, includes=[], excludes=[])
@@ -240,7 +245,7 @@ module Morpheus
240
245
  end
241
246
 
242
247
  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)
248
+ build_common_options(opts, options, includes + [:auto_confirm, :query, :json, :quiet, :dry_run, :remote], excludes)
244
249
  end
245
250
 
246
251
  # list is GET that supports phrase,max,offset,sort,direction
@@ -281,13 +286,24 @@ module Morpheus
281
286
  while (option_key = option_keys.shift) do
282
287
  case option_key.to_sym
283
288
 
284
- when :account
285
- opts.on('-a','--account ACCOUNT', "Account Name or ID") do |val|
289
+ when :tenant, :account
290
+ # todo: let's deprecate this in favor of :tenant --tenant to keep -a reserved for --all perhaps?
291
+ opts.on('--tenant TENANT', String, "Tenant (Account) Name or ID") do |val|
292
+ options[:account] = val
293
+ end
294
+ opts.on('--tenant-id ID', String, "Tenant (Account) ID") do |val|
295
+ options[:account_id] = val
296
+ end
297
+ # todo: let's deprecate this in favor of :tenant --tenant to keep -a reserved for --all perhaps?
298
+ opts.on('-a','--account ACCOUNT', "Alias for --tenant") do |val|
286
299
  options[:account] = val
287
300
  end
288
- opts.on('-A','--account-id ID', "Account ID") do |val|
301
+ opts.on('-A','--account-id ID', "Tenant (Account) ID") do |val|
289
302
  options[:account_id] = val
290
303
  end
304
+ opts.add_hidden_option('--tenant-id') if opts.is_a?(Morpheus::Cli::OptionParser)
305
+ opts.add_hidden_option('-a, --account') if opts.is_a?(Morpheus::Cli::OptionParser)
306
+ opts.add_hidden_option('-A, --account-id') if opts.is_a?(Morpheus::Cli::OptionParser)
291
307
 
292
308
  when :options
293
309
  options[:options] ||= {}
@@ -455,7 +471,7 @@ module Morpheus
455
471
  end
456
472
 
457
473
  # arbitrary query parameters in the format -Q "category=web&phrase=nginx"
458
- # opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'phrase=foobar&category=web'" ) do |val|
474
+ # opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'foo=bar&category=web'" ) do |val|
459
475
  # options[:query_filters_raw] = val
460
476
  # options[:query_filters] = {}
461
477
  # # todo: smarter parsing
@@ -473,7 +489,7 @@ module Morpheus
473
489
 
474
490
  when :query, :query_filters
475
491
  # arbitrary query parameters in the format -Q "category=web&phrase=nginx"
476
- opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'phrase=foobar&category=web'" ) do |val|
492
+ opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'foo=bar&category=web'" ) do |val|
477
493
  options[:query_filters_raw] = val
478
494
  options[:query_filters] = {}
479
495
  # todo: smarter parsing
@@ -599,9 +615,17 @@ module Morpheus
599
615
  opts.add_hidden_option('json-raw') if opts.is_a?(Morpheus::Cli::OptionParser)
600
616
 
601
617
  when :yaml
602
- opts.on(nil, '--yaml', "YAML Output") do
603
- options[:yaml] = true
604
- options[:format] = :yaml
618
+ # -y for --yes and for --yaml
619
+ if includes.include?(:auto_confirm)
620
+ opts.on(nil, '--yaml', "YAML Output") do
621
+ options[:yaml] = true
622
+ options[:format] = :yaml
623
+ end
624
+ else
625
+ opts.on('-y', '--yaml', "YAML Output") do
626
+ options[:yaml] = true
627
+ options[:format] = :yaml
628
+ end
605
629
  end
606
630
  opts.on(nil, '--yml', "alias for --yaml") do
607
631
  options[:yaml] = true
@@ -779,7 +803,11 @@ module Morpheus
779
803
  end
780
804
  opts.add_hidden_option('--no-debug') if opts.is_a?(Morpheus::Cli::OptionParser)
781
805
 
782
-
806
+ opts.on('--hidden-help', "Print help that includes all the hidden options, like this one." ) do
807
+ puts opts.full_help_message({show_hidden_options:true})
808
+ exit # return 0 maybe?
809
+ end
810
+ opts.add_hidden_option('--hidden-help') if opts.is_a?(Morpheus::Cli::OptionParser)
783
811
  opts.on('-h', '--help', "Print this help" ) do
784
812
  puts opts
785
813
  exit # return 0 maybe?
@@ -804,6 +832,20 @@ module Morpheus
804
832
  self.class.subcommand_aliases
805
833
  end
806
834
 
835
+ # def subcommand_descriptions
836
+ # self.class.subcommand_descriptions
837
+ # end
838
+
839
+ def get_subcommand_description(subcmd)
840
+ self.class.get_subcommand_description(subcmd)
841
+ end
842
+
843
+ def subcommand_description()
844
+ calling_method = caller[0][/`([^']*)'/, 1].to_s.sub('block in ', '')
845
+ subcommand_name = subcommands.key(calling_method)
846
+ subcommand_name ? get_subcommand_description(subcommand_name) : nil
847
+ end
848
+
807
849
  def default_subcommand
808
850
  self.class.default_subcommand
809
851
  end
@@ -845,8 +887,11 @@ module Morpheus
845
887
  if !subcommands.empty?
846
888
  out << "Commands:"
847
889
  out << "\n"
848
- subcommands.sort.each {|cmd, method|
849
- out << "\t#{cmd.to_s}\n"
890
+ subcommands.sort.each {|subcmd, method|
891
+ desc = get_subcommand_description(subcmd)
892
+ out << "\t#{subcmd.to_s}"
893
+ out << "\t#{desc}" if desc
894
+ out << "\n"
850
895
  }
851
896
  end
852
897
  # out << "\n"
@@ -990,7 +1035,7 @@ module Morpheus
990
1035
  # raise_command_error "Please specify a remote appliance with -r or see the command `remote use`"
991
1036
  # end
992
1037
 
993
- Morpheus::Logging::DarkPrinter.puts "establishing connection to remote #{display_appliance(@appliance_name, @appliance_url)}" if Morpheus::Logging.debug?
1038
+ Morpheus::Logging::DarkPrinter.puts "establishing connection to remote #{display_appliance(@appliance_name, @appliance_url)}" if Morpheus::Logging.debug? # && !options[:quiet]
994
1039
 
995
1040
  if options[:no_authorization]
996
1041
  # maybe handle this here..
@@ -1061,7 +1106,7 @@ module Morpheus
1061
1106
  else
1062
1107
  if opts[:min]
1063
1108
  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])
1109
+ raise_args_error("not enough arguments, expected #{opts[:min] || '0'}-#{opts[:max] || 'N'} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
1065
1110
  end
1066
1111
  end
1067
1112
  if opts[:max]
@@ -1152,22 +1197,32 @@ module Morpheus
1152
1197
  payload
1153
1198
  end
1154
1199
 
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
1200
+ def validate_outfile(outfile, options)
1201
+ full_filename = File.expand_path(outfile)
1202
+ outdir = File.dirname(full_filename)
1203
+ if Dir.exists?(full_filename)
1204
+ print_red_alert "[local-file] is invalid. It is the name of an existing directory: #{outfile}"
1205
+ return false
1206
+ end
1207
+ if !Dir.exists?(outdir)
1208
+ if options[:mkdir]
1209
+ print cyan,"Creating local directory #{outdir}",reset,"\n"
1210
+ FileUtils.mkdir_p(outdir)
1162
1211
  else
1163
- return 0, nil
1212
+ print_red_alert "[local-file] is invalid. Directory not found: #{outdir}"
1213
+ return false
1164
1214
  end
1165
1215
  end
1216
+ if File.exists?(full_filename) && !options[:overwrite]
1217
+ print_red_alert "[local-file] is invalid. File already exists: #{outfile}\nUse -f to overwrite the existing file."
1218
+ return false
1219
+ end
1220
+ return true
1166
1221
  end
1167
1222
 
1168
1223
  # basic rendering for options :json, :yml, :csv, :quiet, and :outfile
1169
1224
  # returns the string rendered, or nil if nothing was rendered.
1170
- def render_with_format(json_response, options, object_key=nil, &block)
1225
+ def render_response(json_response, options, object_key=nil, &block)
1171
1226
  output = nil
1172
1227
  if options[:json]
1173
1228
  output = as_json(json_response, options, object_key)
@@ -1180,32 +1235,49 @@ module Morpheus
1180
1235
  else
1181
1236
  output = records_as_csv([row], options)
1182
1237
  end
1183
- elsif options[:quiet]
1184
- # note: returning non nil means the calling function knows to return rght away.. kinda weird..
1185
- # but means we need less if options[:quiet] blocks in every action.
1186
- return ""
1187
1238
  end
1188
- if output
1189
- if options[:outfile]
1239
+ if options[:outfile]
1240
+ if output
1190
1241
  print_to_file(output, options[:outfile], options[:overwrite])
1242
+ print "#{cyan}Wrote output to file #{options[:outfile]} (#{File.size(options[:outfile])} B)\n" unless options[:quiet]
1191
1243
  else
1192
- puts output
1244
+ # uhhh ok lets try this
1245
+ Morpheus::Logging::DarkPrinter.puts "using experimental feature: --outfile without a common format like json, yml or csv" if Morpheus::Logging.debug?
1246
+ result = with_stdout_to_file(options[:outfile], options[:overwrite], 'w+', &block)
1247
+ print "#{cyan}Wrote output to file #{options[:outfile]} (#{File.size(options[:outfile])} B)\n" unless options[:quiet]
1248
+ if result
1249
+ return result
1250
+ end
1251
+ return 0, nil
1193
1252
  end
1194
1253
  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
1254
+ # --quiet means do not render, still want to print to outfile though
1255
+ if options[:quiet]
1256
+ return 0, nil
1257
+ end
1258
+ # render ouput generated above
1259
+ if output
1260
+ puts output
1261
+ return 0, nil
1262
+ else
1263
+ # no render happened, so calling the block if given
1264
+ if block_given?
1265
+ result = yield
1266
+ if result
1267
+ return result
1268
+ else
1269
+ return 0, nil
1270
+ end
1271
+ else
1272
+ # nil means nothing was rendered, some methods still using render_with_format() are relying on this
1273
+ return nil
1274
+ end
1275
+ end
1205
1276
  end
1206
- return output
1207
1277
  end
1208
1278
 
1279
+ alias :render_with_format :render_response
1280
+
1209
1281
  module ClassMethods
1210
1282
 
1211
1283
  def set_command_name(cmd_name)
@@ -1277,12 +1349,46 @@ module Morpheus
1277
1349
  v = cmd.to_s.gsub('-', '_')
1278
1350
  register_subcommands({(k) => v})
1279
1351
  else
1280
- raise "Unable to register command of type: #{cmd.class} #{cmd}"
1352
+ raise Morpheus::Cli::CliRegistry::BadCommandDefinition.new("Unable to register command of type: #{cmd.class} #{cmd}")
1281
1353
  end
1282
1354
  }
1283
1355
  return
1284
1356
  end
1285
1357
 
1358
+ # this might be the new hotness
1359
+ # register_subcommand(:show) # do not do this, always define a description!
1360
+ # register_subcommand(:list, "List things")
1361
+ # register_subcommand("update-all", "update_all", "Update all things")
1362
+ # If the command name =~ method, no need to pass both
1363
+ # command names will have "-" swapped in for "_" and vice versa for method names.
1364
+ def register_subcommand(*args)
1365
+ args = args.flatten
1366
+ cmd_name = args[0]
1367
+ cmd_method = nil
1368
+ cmd_desc = nil
1369
+ if args.count == 1
1370
+ cmd_method = cmd_name
1371
+ elsif args.count == 2
1372
+ if args[1].is_a?(Symbol)
1373
+ cmd_method = args[1]
1374
+ else
1375
+ cmd_method = cmd_name
1376
+ cmd_desc = args[1]
1377
+ end
1378
+ elsif args.count == 3
1379
+ cmd_method = args[1]
1380
+ cmd_desc = args[2]
1381
+ else
1382
+ raise Morpheus::Cli::CliRegistry::BadCommandDefinition.new("register_subcommand expects 1-3 arguments, got #{args.size} #{args.inspect}")
1383
+ end
1384
+ cmd_name = cmd_name.to_s.gsub("_", "-").to_sym
1385
+ cmd_method = (cmd_method || cmd_name).to_s.gsub("-", "_").to_sym
1386
+ cmd_definition = {(cmd_name) => cmd_method}
1387
+ register_subcommands(cmd_definition)
1388
+ add_subcommand_description(cmd_name, cmd_desc)
1389
+ return
1390
+ end
1391
+
1286
1392
  def set_default_subcommand(cmd)
1287
1393
  @default_subcommand = cmd
1288
1394
  end
@@ -1341,6 +1447,31 @@ module Morpheus
1341
1447
  @subcommand_aliases.delete(alias_cmd_name.to_s)
1342
1448
  end
1343
1449
 
1450
+ def subcommand_descriptions
1451
+ @subcommand_descriptions ||= {}
1452
+ end
1453
+
1454
+ def add_subcommand_description(cmd_name, description)
1455
+ @subcommand_descriptions ||= {}
1456
+ @subcommand_descriptions[cmd_name.to_s.gsub('_', '-')] = description
1457
+ end
1458
+
1459
+ def get_subcommand_description(cmd_name)
1460
+ desc = subcommand_descriptions[cmd_name.to_s.gsub('_', '-')]
1461
+ if desc
1462
+ return desc
1463
+ else
1464
+ cmd_method = subcommands.key(cmd_name)
1465
+ return cmd_method ? subcommand_descriptions[cmd_method.to_s.gsub('_', '-')] : nil
1466
+ end
1467
+ end
1468
+
1469
+ def set_subcommand_descriptions(cmd_map)
1470
+ cmd_map.each do |cmd_name, description|
1471
+ add_subcommand_description(cmd_name, description)
1472
+ end
1473
+ end
1474
+
1344
1475
  end
1345
1476
  end
1346
1477
  end