morpheus-cli 5.0.0 → 5.2.2

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Dockerfile +1 -1
  4. data/lib/morpheus/api/api_client.rb +16 -0
  5. data/lib/morpheus/api/billing_interface.rb +1 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +16 -2
  10. data/lib/morpheus/api/invoices_interface.rb +12 -3
  11. data/lib/morpheus/api/search_interface.rb +13 -0
  12. data/lib/morpheus/api/servers_interface.rb +14 -0
  13. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  14. data/lib/morpheus/api/usage_interface.rb +18 -0
  15. data/lib/morpheus/cli.rb +6 -2
  16. data/lib/morpheus/cli/apps.rb +3 -23
  17. data/lib/morpheus/cli/budgets_command.rb +389 -319
  18. data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
  19. data/lib/morpheus/cli/cli_command.rb +51 -10
  20. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -13
  21. data/lib/morpheus/cli/commands/standard/history_command.rb +9 -3
  22. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  23. data/lib/morpheus/cli/containers_command.rb +0 -24
  24. data/lib/morpheus/cli/cypher_command.rb +6 -2
  25. data/lib/morpheus/cli/dashboard_command.rb +260 -20
  26. data/lib/morpheus/cli/deploy.rb +199 -90
  27. data/lib/morpheus/cli/deployments.rb +341 -28
  28. data/lib/morpheus/cli/deploys.rb +206 -41
  29. data/lib/morpheus/cli/error_handler.rb +7 -0
  30. data/lib/morpheus/cli/forgot_password.rb +133 -0
  31. data/lib/morpheus/cli/groups.rb +1 -1
  32. data/lib/morpheus/cli/health_command.rb +59 -2
  33. data/lib/morpheus/cli/hosts.rb +271 -39
  34. data/lib/morpheus/cli/instances.rb +228 -129
  35. data/lib/morpheus/cli/invoices_command.rb +100 -20
  36. data/lib/morpheus/cli/jobs_command.rb +94 -92
  37. data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
  38. data/lib/morpheus/cli/library_option_types_command.rb +10 -5
  39. data/lib/morpheus/cli/logs_command.rb +9 -6
  40. data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
  41. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
  42. data/lib/morpheus/cli/mixins/print_helper.rb +13 -27
  43. data/lib/morpheus/cli/mixins/provisioning_helper.rb +108 -5
  44. data/lib/morpheus/cli/option_types.rb +271 -22
  45. data/lib/morpheus/cli/remote.rb +35 -10
  46. data/lib/morpheus/cli/reports_command.rb +99 -30
  47. data/lib/morpheus/cli/roles.rb +193 -155
  48. data/lib/morpheus/cli/search_command.rb +182 -0
  49. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  50. data/lib/morpheus/cli/setup.rb +1 -1
  51. data/lib/morpheus/cli/shell.rb +33 -11
  52. data/lib/morpheus/cli/tasks.rb +29 -32
  53. data/lib/morpheus/cli/usage_command.rb +64 -11
  54. data/lib/morpheus/cli/version.rb +1 -1
  55. data/lib/morpheus/cli/virtual_images.rb +429 -254
  56. data/lib/morpheus/cli/whoami.rb +6 -6
  57. data/lib/morpheus/cli/workflows.rb +33 -40
  58. data/lib/morpheus/formatters.rb +75 -18
  59. data/lib/morpheus/terminal.rb +6 -2
  60. metadata +10 -4
  61. data/lib/morpheus/cli/mixins/catalog_helper.rb +0 -66
@@ -26,8 +26,8 @@ class Morpheus::Cli::CurlCommand
26
26
  options = {}
27
27
  optparse = Morpheus::Cli::OptionParser.new do|opts|
28
28
  opts.banner = "Usage: morpheus curl [path] -- [*args]"
29
- opts.on( '-p', '--pretty', "Print result as parsed JSON." ) do
30
- options[:pretty] = true
29
+ opts.on( '-p', '--pretty', "Print result as parsed JSON. Alias for -j" ) do
30
+ options[:json] = true
31
31
  end
32
32
  opts.on( '-X', '--request METHOD', "HTTP request method. Default is GET" ) do |val|
33
33
  curl_method = val
@@ -41,7 +41,7 @@ class Morpheus::Cli::CurlCommand
41
41
  opts.on( '--progress', '--progress', "Display progress output by excluding the -s option." ) do
42
42
  show_progress = true
43
43
  end
44
- build_common_options(opts, options, [:dry_run, :remote])
44
+ build_common_options(opts, options, [:dry_run, :json, :remote])
45
45
  opts.add_hidden_option('--curl')
46
46
  #opts.add_hidden_option('--scrub')
47
47
  opts.footer = <<-EOT
@@ -134,31 +134,44 @@ EOT
134
134
  print reset
135
135
  return 0
136
136
  end
137
+ exit_code, err = 0, nil
137
138
  # print cyan
138
139
  # print "#{cyan}#{curl_cmd_str}#{reset}"
139
140
  # print "\n\n"
140
141
  print reset
141
142
  # print result
142
143
  curl_output = `#{curl_cmd}`
143
- if options[:pretty]
144
+
145
+ if $?.success? != true
146
+ exit_code = $?.exitstatus
147
+ err = "curl command exited non-zero"
148
+ end
149
+ json_response = {}
150
+ other_output = nil
151
+ if options[:json] || options[:yaml] || options[:csv]
144
152
  output_lines = curl_output.split("\n")
145
153
  last_line = output_lines.pop
146
154
  if output_lines.size > 0
147
- puts output_lines.join("\n")
155
+ other_output = output_lines.join("\n")
148
156
  end
149
157
  begin
150
- json_data = JSON.parse(last_line)
151
- json_string = JSON.pretty_generate(json_data)
152
- puts json_string
158
+ json_response = JSON.parse(last_line)
153
159
  rescue => ex
154
- Morpheus::Logging::DarkPrinter.puts "failed to parse curl result as JSON data Error: #{ex.message}" if Morpheus::Logging.debug?
155
- puts last_line
160
+ puts_error curl_output
161
+ print_red_alert "failed to parse curl result as JSON data Error: #{ex.message}"
162
+
163
+ exit_code = 2
164
+ err = "failed to parse curl result as JSON data Error: #{ex.message}"
165
+ return exit_code, err
156
166
  end
157
167
  else
158
- puts curl_output
168
+ other_output = curl_output
159
169
  end
160
- return $?.success?
161
-
170
+ curl_object_key = nil # json_response.keys.first
171
+ render_response(json_response, options, curl_object_key) do
172
+ puts other_output
173
+ end
174
+ return exit_code, err
162
175
  end
163
176
 
164
177
  def command_available?(cmd)
@@ -6,14 +6,16 @@ require 'morpheus/cli/cli_command'
6
6
  class Morpheus::Cli::HistoryCommand
7
7
  include Morpheus::Cli::CliCommand
8
8
  set_command_name :'history'
9
- set_command_hidden
9
+ set_command_description "View morpheus shell command history"
10
+ # hidden for now because it is a shell command, shouldnt just be shell command though
11
+ set_command_hidden
10
12
 
11
13
  # todo: support all the other :list options too, not just max
12
14
  # AND start logging every terminal command, not just shell...
13
15
  def handle(args)
14
16
  options = {show_pagination:false}
15
17
  optparse = Morpheus::Cli::OptionParser.new do|opts|
16
- opts.banner = "Usage: morpheus #{command_name}"
18
+ opts.banner = "Usage: morpheus #{command_name} [search]"
17
19
  # -n is a hidden alias for -m
18
20
  opts.on( '-n', '--max-commands MAX', "Alias for -m, --max option." ) do |val|
19
21
  options[:max] = val
@@ -33,6 +35,7 @@ The --flush option can be used to purge the history.
33
35
  Examples:
34
36
  history
35
37
  history -m 100
38
+ history "instances list"
36
39
  history --flush
37
40
 
38
41
  The most recently executed commands are seen by default. Use --reverse to see the oldest commands.
@@ -40,7 +43,10 @@ EOT
40
43
  end
41
44
  raw_cmd = "#{command_name} #{args.join(' ')}"
42
45
  optparse.parse!(args)
43
- verify_args!(args:args, count: 0, optparse:optparse)
46
+ # verify_args!(args:args, count: 0, optparse:optparse)
47
+ if args.count > 0
48
+ options[:phrase] = args.join(" ")
49
+ end
44
50
  if options[:do_flush]
45
51
  command_count = Morpheus::Cli::Shell.instance.history_commands_count
46
52
  unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to flush your command history (#{format_number(command_count)} #{command_count == 1 ? 'command' : 'commands'})?")
@@ -9,7 +9,7 @@ class Morpheus::Cli::ManCommand
9
9
 
10
10
  # this should be read only anyway...
11
11
  @@default_editor = "less" # ENV['EDITOR']
12
-
12
+
13
13
  def handle(args)
14
14
  options = {}
15
15
  regenerate = false
@@ -28,6 +28,12 @@ class Morpheus::Cli::ManCommand
28
28
  opts.on('-g','--generate', "Regenerate the manual file") do
29
29
  regenerate = true
30
30
  end
31
+ opts.on('-o','--out FILE', "Write manual file to a custom location") do |val|
32
+ options[:outfile] = val
33
+ end
34
+ opts.on('--overwrite', '--overwrite', "Overwrite output file if it already exists.") do |val|
35
+ options[:overwrite] = true
36
+ end
31
37
  opts.on('-q','--quiet', "Do not open manual, for use with the -g option.") do
32
38
  options[:quiet] = true
33
39
  end
@@ -47,7 +53,8 @@ class Morpheus::Cli::ManCommand
47
53
  end
48
54
  opts.footer = <<-EOT
49
55
  Open the morpheus manual located at #{Morpheus::Cli::ManCommand.man_file_path}
50
- The -g switch be used to regenerate the file.
56
+ The -g option can be used to regenerate the file.
57
+ The --out FILE option be used to write the manual file to a custom location.
51
58
  EOT
52
59
  end
53
60
  optparse.parse!(args)
@@ -65,18 +72,35 @@ EOT
65
72
  end
66
73
 
67
74
  fn = Morpheus::Cli::ManCommand.man_file_path
75
+ if options[:outfile]
76
+ regenerate = true
77
+ fn = File.expand_path(options[:outfile])
78
+ if File.directory?(fn)
79
+ # if you give me a directory, could still work and use the default filename
80
+ # fn = File.join(fn, "CLI-Manual-#{Morpheus::Cli::VERSION}.md")
81
+ # raise_command_error "outfile is invalid. It is the name of an existing directory: #{fn}"
82
+ print_error "#{red}Output file '#{fn}' is invalid.#{reset}\n"
83
+ print_error "#{red}It is the name of an existing directory.#{reset}\n"
84
+ return 1
85
+ end
86
+ if File.exists?(fn) && options[:overwrite] != true
87
+ print_error "#{red}Output file '#{fn}' already exists.#{reset}\n"
88
+ print_error "#{red}Use --overwrite to overwrite the existing file.#{reset}\n"
89
+ return 1
90
+ end
91
+ end
92
+ exit_code, err = 0, nil
68
93
  if regenerate || !File.exists?(fn)
69
94
  #Morpheus::Logging::DarkPrinter.puts "generating manual #{fn} ..." if Morpheus::Logging.debug? && !options[:quiet]
70
- Morpheus::Cli::ManCommand.generate_manual(options)
95
+ exit_code, err = Morpheus::Cli::ManCommand.generate_manual(options)
71
96
  end
72
97
 
73
98
  if options[:quiet]
74
- return 0, nil
99
+ return exit_code, err
75
100
  end
76
101
 
77
102
  Morpheus::Logging::DarkPrinter.puts "opening manual file #{fn}" if Morpheus::Logging.debug? && !options[:quiet]
78
103
 
79
-
80
104
  if open_as_link # not used atm
81
105
  link = "file://#{fn}"
82
106
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@@ -138,6 +162,14 @@ EOT
138
162
  # todo: use pandoc or something else to convert the CLI-Manual.md to a man page
139
163
  # and install it, so the os command `man morpheus` will work too.
140
164
  fn = man_file_path()
165
+ if options[:outfile]
166
+ fn = File.expand_path(options[:outfile])
167
+ if File.exists?(fn) && options[:overwrite] != true
168
+ print_error "#{red}Output file '#{options[:outfile]}' already exists.#{reset}\n"
169
+ print_error "#{red}Use --overwrite to overwrite the existing file.#{reset}\n"
170
+ return 1, "output file already exists"
171
+ end
172
+ end
141
173
  if !Dir.exists?(File.dirname(fn))
142
174
  FileUtils.mkdir_p(File.dirname(fn))
143
175
  end
@@ -153,7 +185,7 @@ EOT
153
185
  begin
154
186
 
155
187
  manpage.print <<-ENDTEXT
156
- morpheus v#{Morpheus::Cli::VERSION}
188
+ #{prog_name} v#{Morpheus::Cli::VERSION}
157
189
 
158
190
  ## NAME
159
191
 
@@ -170,17 +202,17 @@ morpheus v#{Morpheus::Cli::VERSION}
170
202
  This is a command line interface for managing a Morpheus Appliance.
171
203
  All communication with the remote appliance is done via the Morpheus API.
172
204
 
173
- To get started, see the command `remote add` command.
205
+ Use the command `#{prog_name} remote add` to connect to your Morpheus appliance.
174
206
 
175
207
  To learn more, visit https://github.com/gomorpheus/morpheus-cli/wiki/Getting-Started
176
208
 
177
- To learn more about the Morpheus Appliance, visit https://www.morpheusdata.com/features
209
+ To learn more about the Morpheus Appliance, visit https://www.morpheusdata.com
178
210
 
179
211
  To learn more about the Morpheus API, visit https://apidocs.morpheusdata.com
180
212
 
181
213
  ## GLOBAL OPTIONS
182
214
 
183
- Morpheus supports a few global options.
215
+ There are several global options available.
184
216
 
185
217
  -v, --version Print the version.
186
218
  --noprofile Do not read and execute the personal initialization script .morpheus_profile
@@ -190,30 +222,42 @@ morpheus v#{Morpheus::Cli::VERSION}
190
222
 
191
223
  ## COMMON OPTIONS
192
224
 
193
- There are some common options that many commands support. They work the same way for each command.
225
+ There are many common options that are supported by a most commands.
194
226
 
195
227
  -O, --option OPTION Option value in the format -O var="value" (deprecated soon in favor of first class options)
196
228
  -N, --no-prompt Skip prompts. Use default values for all optional fields.
229
+ --payload FILE Payload from a local JSON or YAML file, skip all prompting
230
+ --payload-dir DIRECTORY Payload from a local directory containing 1-N JSON or YAML files, skip all prompting
231
+ --payload-json JSON Payload JSON, skip all prompting
232
+ --payload-yaml YAML Payload YAML, skip all prompting
197
233
  -j, --json JSON Output
198
234
  -d, --dry-run Dry Run, print the API request instead of executing it
199
- -r, --remote REMOTE Remote Appliance Name to use for this command. The active appliance is used by default.
200
- -I, --insecure Allow for insecure HTTPS communication i.e. bad SSL certificate
201
- -y, --yes Auto confirm, skip any 'Are you sure?' confirmations.
202
- -r, --quiet No Output, when successful.
235
+ --curl Dry Run to output API request as a curl command.
236
+ --scrub Mask secrets in output, such as the Authorization header. For use with --curl and --dry-run.
237
+ -r, --remote REMOTE Remote name. The current remote is used by default.
238
+ --remote-url URL Remote url. This allows adhoc requests instead of using a configured remote.
239
+ -T, --token TOKEN Access token for authentication with --remote. Saved credentials are used by default.
240
+ -U, --username USERNAME Username for authentication.
241
+ -P, --password PASSWORD Password for authentication.
242
+ -I, --insecure Allow insecure HTTPS communication. i.e. bad SSL certificate.
243
+ -H, --header HEADER Additional HTTP header to include with requests.
244
+ --timeout SECONDS Timeout for api requests. Default is typically 30 seconds.
245
+ -y, --yes Auto Confirm
246
+ -q, --quiet No Output, do not print to stdout
203
247
 
204
248
  ## MORPHEUS COMMANDS
205
249
 
206
- We divide morpheus into commands.
207
- Every morpheus command may have 0-N sub-commands that it supports.
250
+ The morpheus executable is divided into commands.
251
+ Each morpheus command may have 0-N sub-commands that it supports.
208
252
  Commands generally map to the functionality provided in the Morpheus UI.
209
253
 
210
254
  You can get help for any morpheus command by using the -h option.
211
255
 
212
- The available commands and their options are also documented below.
256
+ The available commands and their options are documented below.
213
257
  ENDTEXT
214
258
 
215
259
  terminal = Morpheus::Terminal.new($stdin, manpage)
216
- Morpheus::Logging::DarkPrinter.puts "appending command help `morpheus --help`" if Morpheus::Logging.debug? && !options[:quiet]
260
+ Morpheus::Logging::DarkPrinter.puts "appending command help `#{prog_name} --help`" if Morpheus::Logging.debug? && !options[:quiet]
217
261
 
218
262
  manpage.print "\n"
219
263
  manpage.print "## morpheus\n"
@@ -226,7 +270,7 @@ ENDTEXT
226
270
  Morpheus::Cli::CliRegistry.all.keys.sort.each do |cmd|
227
271
  cmd_klass = Morpheus::Cli::CliRegistry.instance.get(cmd)
228
272
  cmd_instance = cmd_klass.new
229
- Morpheus::Logging::DarkPrinter.puts "appending command help `morpheus #{cmd} --help`" if Morpheus::Logging.debug? && !options[:quiet]
273
+ Morpheus::Logging::DarkPrinter.puts "appending command help `#{prog_name} #{cmd} --help`" if Morpheus::Logging.debug? && !options[:quiet]
230
274
  #help_cmd = "morpheus #{cmd} --help"
231
275
  #help_output = `#{help_cmd}`
232
276
  manpage.print "\n"
@@ -243,7 +287,7 @@ ENDTEXT
243
287
  subcommands = cmd_klass.visible_subcommands
244
288
  if subcommands && subcommands.size > 0
245
289
  subcommands.sort.each do |subcommand, subcommand_method|
246
- Morpheus::Logging::DarkPrinter.puts "appending command help `morpheus #{cmd} #{subcommand} --help`" if Morpheus::Logging.debug? && !options[:quiet]
290
+ Morpheus::Logging::DarkPrinter.puts "appending command help `#{prog_name} #{cmd} #{subcommand} --help`" if Morpheus::Logging.debug? && !options[:quiet]
247
291
  manpage.print "\n"
248
292
  manpage.print "#### morpheus #{cmd} #{subcommand}\n"
249
293
  manpage.print "\n"
@@ -289,7 +333,7 @@ morpheus> remote list
289
333
  Morpheus Appliances
290
334
  ==================
291
335
 
292
- You have no appliances configured. See the `remote add` command.
336
+ You have no appliances configured. See the `#{prog_name} remote add` command.
293
337
 
294
338
  ```
295
339
 
@@ -319,11 +363,10 @@ The `appliances` YAML file contains a list of known appliances, keyed by name.
319
363
  Example:
320
364
  ```yaml
321
365
  :qa:
322
- :host: https://qa.mycoolsite.com
366
+ :host: https://qa.mymorpheus.com
323
367
  :active: true
324
368
  :production:
325
- :host: https://morpheus.mycoolsite.com
326
- :active: false
369
+ :host: https://mymorpheus.com
327
370
  ```
328
371
 
329
372
  ### credentials file
@@ -349,7 +392,7 @@ This may be inhibited by using the `--noprofile` option.
349
392
 
350
393
  ### .morpheusrc file
351
394
 
352
- When started as an interactive shell with the `morpheus shell` command,
395
+ When started as an interactive shell with the `#{prog_name} shell` command,
353
396
  Morpheus reads and executes `$MORPHEUS_CLI_HOME/.morpheusrc` (if it exists). This may be inhibited by using the `--norc` option.
354
397
 
355
398
  An example startup script might look like this:
@@ -357,20 +400,11 @@ An example startup script might look like this:
357
400
  ```
358
401
  # .morpheusrc
359
402
 
360
- # aliases
361
- alias our-instances='instances list -c "Our Cloud"'
362
-
363
- # switch to our appliance that we created with `remote add morphapp1`
364
- remote use morphapp1
365
-
366
- # greeting
367
- echo "Welcome back human, have fun!"
368
-
369
- # print current user information
370
- whoami
371
-
372
- # print the list of instances in our cloud
373
- our-instances
403
+ set-prompt "%cyan%username%reset@%magenta%remote %cyanmorpheus> %reset"
404
+ version
405
+ remote current
406
+ echo "Welcome back %username"
407
+ echo
374
408
 
375
409
  ```
376
410
 
@@ -383,7 +417,7 @@ ENDTEXT
383
417
  terminal = Morpheus::Terminal.new()
384
418
  end
385
419
 
386
- return true
420
+ return 0, nil
387
421
  end
388
422
 
389
423
  end
@@ -699,28 +699,4 @@ private
699
699
  end
700
700
  end
701
701
 
702
- def format_container_status(container, return_color=cyan)
703
- out = ""
704
- status_string = container['status'].to_s
705
- if status_string == 'running'
706
- out << "#{green}#{status_string.upcase}#{return_color}"
707
- elsif status_string == 'stopped' or status_string == 'failed'
708
- out << "#{red}#{status_string.upcase}#{return_color}"
709
- elsif status_string == 'unknown'
710
- out << "#{white}#{status_string.upcase}#{return_color}"
711
- else
712
- out << "#{yellow}#{status_string.upcase}#{return_color}"
713
- end
714
- out
715
- end
716
-
717
- def format_container_connection_string(container)
718
- if !container['ports'].nil? && container['ports'].empty? == false
719
- connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
720
- else
721
- # eh? more logic needed here i think, see taglib morph:containerLocationMenu
722
- connection_string = "#{container['ip']}"
723
- end
724
- end
725
-
726
702
  end
@@ -265,6 +265,9 @@ EOT
265
265
  opts.on( '-v', '--value VALUE', "Secret value. This can be used to store a string instead of an object, and can also be passed as the second argument." ) do |val|
266
266
  item_value = val
267
267
  end
268
+ # opts.on( '--type VALUE', String, "Type, default is based on key engine, string or object" ) do |val|
269
+ # params['type'] = val
270
+ # end
268
271
  opts.on( '-t', '--ttl SECONDS', "Time to live, the lease duration before this key expires." ) do |val|
269
272
  ttl = val
270
273
  if val.to_s.empty? || val.to_s == '0'
@@ -276,7 +279,7 @@ EOT
276
279
  # opts.on( '--no-overwrite', '--no-overwrite', "Do not overwrite existing keys. Existing keys are overwritten by default." ) do
277
280
  # params['overwrite'] = false
278
281
  # end
279
- build_common_options(opts, options, [:auto_confirm, :options, :payload, :json, :yaml, :csv, :fields, :outfile, :dry_run, :quiet, :remote])
282
+ build_common_options(opts, options, [:auto_confirm, :options, :payload, :query, :json, :yaml, :csv, :fields, :outfile, :dry_run, :quiet, :remote])
280
283
  opts.footer = "Create or update a cypher key." + "\n" +
281
284
  "[key] is required. This is the key of the cypher being created or updated. The key includes the mount prefix eg. secret/hello" + "\n" +
282
285
  "[value] is required for some cypher engines, such as secret. This is the secret value or k=v pairs being stored. Supports 1-N arguments." + "\n" +
@@ -292,7 +295,8 @@ EOT
292
295
  # return 1
293
296
  # end
294
297
  connect(options)
295
-
298
+ params.merge!(parse_query_options(options))
299
+
296
300
  # parse arguments like [value] or [k=v]
297
301
  if args.count == 0
298
302
  # prompt for key and value
@@ -4,8 +4,9 @@ require 'json'
4
4
 
5
5
  class Morpheus::Cli::DashboardCommand
6
6
  include Morpheus::Cli::CliCommand
7
+ include Morpheus::Cli::ProvisioningHelper
7
8
  set_command_name :dashboard
8
- set_command_hidden # remove once this is done
9
+ set_command_description "View Morpheus Dashboard"
9
10
 
10
11
  def initialize()
11
12
  # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
@@ -28,34 +29,273 @@ class Morpheus::Cli::DashboardCommand
28
29
  options = {}
29
30
  optparse = Morpheus::Cli::OptionParser.new do |opts|
30
31
  opts.banner = usage
31
- build_common_options(opts, options, [:json, :dry_run]) # todo: support :account
32
+ opts.on('-a', '--details', "Display all details: more instance usage stats, etc" ) do
33
+ options[:details] = true
34
+ end
35
+ build_standard_list_options(opts, options)
36
+ opts.footer = <<-EOT
37
+ View Morpheus Dashboard.
38
+ This includes instance and backup counts, favorite instances, monitoring and recent activity.
39
+ EOT
32
40
  end
33
41
  optparse.parse!(args)
34
-
42
+ verify_args!(args:args, optparse:optparse, count:0)
35
43
  connect(options)
36
- begin
37
- params = {}
38
- @dashboard_interface.setopts(options)
39
- if options[:dry_run]
40
- print_dry_run @dashboard_interface.dry.get(params)
41
- return
44
+ params = {}
45
+ params.merge!(parse_list_options(options))
46
+ @dashboard_interface.setopts(options)
47
+ if options[:dry_run]
48
+ print_dry_run @dashboard_interface.dry.get(params)
49
+ return
50
+ end
51
+ json_response = @dashboard_interface.get(params)
52
+ render_response(json_response, options) do
53
+ print_h1 "Morpheus Dashboard", [], options
54
+
55
+ ## STATUS
56
+
57
+ status_column_definitions = {
58
+ "Instances" => lambda {|it|
59
+ format_number(it['instanceStats']['total']) rescue nil
60
+ },
61
+ "Running" => lambda {|it|
62
+ format_number(it['instanceStats']['running']) rescue nil
63
+ },
64
+ # "Used Storage" => lambda {|it|
65
+ # ((it['instanceStats']['maxStorage'].to_i > 0) ? ((it['instanceStats']['usedStorage'].to_f / it['instanceStats']['maxStorage'].to_f) * 100).round(1) : 0).to_s + '%' rescue nil
66
+ # },
67
+ }
68
+ print as_description_list(json_response, status_column_definitions, options)
69
+ # print reset,"\n"
70
+
71
+ stats = json_response['instanceStats']
72
+ if stats
73
+ print_h2 "Instance Usage", options
74
+ print_stats_usage(stats, {include: [:max_cpu, :avg_cpu, :memory, :storage]})
42
75
  end
43
- json_response = @dashboard_interface.get(params)
44
- if options[:json]
45
- print JSON.pretty_generate(json_response)
46
- print "\n"
76
+
77
+
78
+
79
+ open_incident_count = json_response['monitoring']['openIncidents'] rescue (json_response['appStatus']['openIncidents'] rescue nil)
80
+
81
+ avg_response_time = json_response['monitoring']['avgResponseTime'] rescue nil
82
+ warning_apps = json_response['monitoring']['warningApps'] rescue 0
83
+ warning_checks = json_response['monitoring']['warningChecks'] rescue 0
84
+ fail_checks = json_response['monitoring']['failChecks'] rescue 0
85
+ fail_apps = json_response['monitoring']['failApps'] rescue 0
86
+ success_checks = json_response['monitoring']['successChecks'] rescue 0
87
+ success_apps = json_response['monitoring']['successApps'] rescue 0
88
+ monitoring_status_color = cyan
89
+ if fail_checks > 0 || fail_apps > 0
90
+ monitoring_status_color = red
91
+ elsif warning_checks > 0 || warning_apps > 0
92
+ monitoring_status_color = yellow
93
+ end
94
+
95
+ print_h2 "Monitoring"
96
+
97
+ monitoring_column_definitions = {
98
+ "Status" => lambda {|it|
99
+ if fail_apps > 0 # || fail_checks > 0
100
+ # check_summary = [fail_apps > 0 ? "#{fail_apps} Apps" : nil,fail_checks > 0 ? "#{fail_checks} Checks" : nil].compact.join(", ")
101
+ # red + "ERROR" + " (" + check_summary + ")" + cyan
102
+ red + "ERROR" + cyan
103
+ elsif warning_apps > 0 || warning_checks > 0
104
+ # check_summary = [warning_apps > 0 ? "#{warning_apps} Apps" : nil,warning_checks > 0 ? "#{warning_checks} Checks" : nil].compact.join(", ")
105
+ # red + "WARNING" + " (" + check_summary + ")" + cyan
106
+ yellow + "WARNING" + cyan
107
+ else
108
+ cyan + "HEALTHY" + cyan
109
+ end
110
+ },
111
+ # "Availability" => lambda {|it|
112
+ # # todo
113
+ # },
114
+ "Response Time" => lambda {|it|
115
+ # format_number(avg_response_time).to_s + "ms"
116
+ (avg_response_time.round).to_s + "ms"
117
+ },
118
+ "Open Incidents" => lambda {|it|
119
+ monitoring_status_color = cyan
120
+ # if fail_checks > 0 || fail_apps > 0
121
+ # monitoring_status_color = red
122
+ # elsif warning_checks > 0 || warning_apps > 0
123
+ # monitoring_status_color = yellow
124
+ # end
125
+ if open_incident_count.nil?
126
+ yellow + "n/a" + cyan + "\n"
127
+ elsif open_incident_count == 0
128
+ monitoring_status_color + "0 Open Incidents" + cyan
129
+ elsif open_incident_count == 1
130
+ monitoring_status_color + "1 Open Incident" + cyan
131
+ else
132
+ monitoring_status_color + "#{open_incident_count} Open Incidents" + cyan
133
+ end
134
+ }
135
+ }
136
+ #print as_description_list(json_response, monitoring_column_definitions, options)
137
+ print as_pretty_table([json_response], monitoring_column_definitions.upcase_keys!, options)
138
+
139
+
140
+ if json_response['logStats']
141
+ # todo: should come from monitoring.startMs-endMs
142
+ log_period_display = "7 Days"
143
+ print_h2 "Logs (#{log_period_display})", options
144
+ error_log_data = json_response['logStats']['data'].find {|it| it['key'].to_s.upcase == 'ERROR' }
145
+ error_count = error_log_data["count"] rescue 0
146
+ fatal_log_data = json_response['logStats']['data'].find {|it| it['key'].to_s.upcase == 'FATAL' }
147
+ fatal_count = fatal_log_data["count"] rescue 0
148
+ # total error is actaully error + fatal
149
+ total_error_count = error_count + fatal_count
150
+ # if total_error_count.nil?
151
+ # print yellow + "n/a" + cyan + "\n"
152
+ # elsif total_error_count == 0
153
+ # print cyan + "0 Errors" + cyan + "\n"
154
+ # elsif total_error_count == 1
155
+ # print red + "1 Error" + cyan + "\n"
156
+ # else
157
+ # print red + "#{total_error_count} Errors" + cyan + "\n"
158
+ # end
159
+ if total_error_count == 0
160
+ print cyan + "(0 Errors)" + cyan + "\n"
161
+ #print cyan + "0-0-0-0-0-0-0-0 (0 Errors)" + cyan + "\n"
162
+ end
163
+ if error_count > 0
164
+ if error_log_data["values"]
165
+ log_plot = ""
166
+ plot_index = 0
167
+ error_log_data["values"].each do |k, v|
168
+ if v.to_i == 0
169
+ log_plot << cyan + v.to_s
170
+ else
171
+ log_plot << red + v.to_s
172
+ end
173
+ if plot_index != error_log_data["values"].size - 1
174
+ log_plot << cyan + "-"
175
+ end
176
+ plot_index +=1
177
+ end
178
+ print log_plot
179
+ print " "
180
+ if error_count == 0
181
+ print cyan + "(0 Errors)" + cyan
182
+ elsif error_count == 1
183
+ print red + "(1 Errors)" + cyan
184
+ else
185
+ print red + "(#{error_count} Errors)" + cyan
186
+ end
187
+ print reset + "\n"
188
+ end
189
+ end
190
+ if fatal_count > 0
191
+ if fatal_log_data["values"]
192
+ log_plot = ""
193
+ plot_index = 0
194
+ fatal_log_data["values"].each do |k, v|
195
+ if v.to_i == 0
196
+ log_plot << cyan + v.to_s
197
+ else
198
+ log_plot << red + v.to_s
199
+ end
200
+ if plot_index != fatal_log_data["values"].size - 1
201
+ log_plot << cyan + "-"
202
+ end
203
+ plot_index +=1
204
+ end
205
+ print log_plot
206
+ print " "
207
+ if fatal_count == 0
208
+ print cyan + "(0 FATAL)" + cyan
209
+ elsif fatal_count == 1
210
+ print red + "(1 FATAL)" + cyan
211
+ else
212
+ print red + "(#{fatal_count} FATAL)" + cyan
213
+ end
214
+ print reset + "\n"
215
+ end
216
+ end
217
+ end
218
+
219
+ print_h2 "Backups (7 Days)"
220
+ backup_status_column_definitions = {
221
+ # "Total" => lambda {|it|
222
+ # it['backups']['accountStats']['lastSevenDays']['completed'] rescue nil
223
+ # },
224
+ "Successful" => lambda {|it|
225
+ it['backups']['accountStats']['lastSevenDays']['successful'] rescue nil
226
+ },
227
+ "Failed" => lambda {|it|
228
+ n = it['backups']['accountStats']['lastSevenDays']['failed'] rescue nil
229
+ if n == 0
230
+ cyan + n.to_s + reset
231
+ else
232
+ red + n.to_s + reset
233
+ end
234
+ }
235
+ }
236
+ print as_description_list(json_response, backup_status_column_definitions, options)
237
+ #print as_pretty_table([json_response], backup_status_column_definitions, options)
238
+ # print reset,"\n"
239
+
240
+ favorite_instances = json_response["provisioning"]["favoriteInstances"] || [] rescue []
241
+ if favorite_instances.empty?
242
+ # print cyan, "No favorite instances.",reset,"\n"
47
243
  else
244
+ print_h2 "My Instances"
245
+ favorite_instances_columns = {
246
+ "ID" => lambda {|instance|
247
+ instance['id']
248
+ },
249
+ "Name" => lambda {|instance|
250
+ instance['name']
251
+ },
252
+ "Type" => lambda {|instance|
253
+ instance['instanceType']['name'] rescue nil
254
+ },
255
+ "IP/PORT" => lambda {|instance|
256
+ format_instance_connection_string(instance)
257
+ },
258
+ "Status" => lambda {|it| format_instance_status(it) }
259
+ }
260
+ #print as_description_list(json_response, status_column_definitions, options)
261
+ print as_pretty_table(favorite_instances, favorite_instances_columns, options)
262
+ # print reset,"\n"
263
+ end
48
264
 
49
- print_h1 "Dashboard"
50
- print cyan
51
- puts "Coming soon... see --json"
52
- print reset,"\n"
265
+ # RECENT ACTIVITY
266
+ activity = json_response["activity"] || json_response["recentActivity"] || []
267
+ print_h2 "Recent Activity", [], options
268
+ if activity.empty?
269
+ print cyan, "No activity found.",reset,"\n"
270
+ else
271
+ columns = [
272
+ # {"SEVERITY" => lambda {|record| format_activity_severity(record['severity']) } },
273
+ {"TYPE" => lambda {|record| record['activityType'] } },
274
+ {"NAME" => lambda {|record| record['name'] } },
275
+ {"RESOURCE" => lambda {|record| "#{record['objectType']} #{record['objectId']}" } },
276
+ {"MESSAGE" => lambda {|record| record['message'] || '' } },
277
+ {"USER" => lambda {|record| record['user'] ? record['user']['username'] : record['userName'] } },
278
+ #{"DATE" => lambda {|record| "#{format_duration_ago(record['ts'] || record['timestamp'])}" } },
279
+ {"DATE" => lambda {|record|
280
+ # show full time if searching for custom timerange, otherwise the default is to show relative time
281
+ if params['start'] || params['end'] || params['timeframe']
282
+ "#{format_local_dt(record['ts'] || record['timestamp'])}"
283
+ else
284
+ "#{format_duration_ago(record['ts'] || record['timestamp'])}"
285
+ end
286
+
287
+ } },
288
+ ]
289
+ print as_pretty_table(activity, columns, options)
290
+ # print_results_pagination(json_response)
291
+ # print reset,"\n"
53
292
 
54
293
  end
55
- rescue RestClient::Exception => e
56
- print_rest_exception(e, options)
57
- exit 1
294
+
58
295
  end
296
+ print reset,"\n"
297
+ return 0, nil
59
298
  end
60
299
 
300
+
61
301
  end