morpheus-cli 4.2.12 → 4.2.17

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