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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +16 -0
- data/lib/morpheus/api/billing_interface.rb +1 -0
- data/lib/morpheus/api/deploy_interface.rb +1 -1
- data/lib/morpheus/api/deployments_interface.rb +20 -1
- data/lib/morpheus/api/forgot_password_interface.rb +17 -0
- data/lib/morpheus/api/instances_interface.rb +16 -2
- data/lib/morpheus/api/invoices_interface.rb +12 -3
- data/lib/morpheus/api/search_interface.rb +13 -0
- data/lib/morpheus/api/servers_interface.rb +14 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/api/usage_interface.rb +18 -0
- data/lib/morpheus/cli.rb +6 -2
- data/lib/morpheus/cli/apps.rb +3 -23
- data/lib/morpheus/cli/budgets_command.rb +389 -319
- data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
- data/lib/morpheus/cli/cli_command.rb +51 -10
- data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -13
- data/lib/morpheus/cli/commands/standard/history_command.rb +9 -3
- data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
- data/lib/morpheus/cli/containers_command.rb +0 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/dashboard_command.rb +260 -20
- data/lib/morpheus/cli/deploy.rb +199 -90
- data/lib/morpheus/cli/deployments.rb +341 -28
- data/lib/morpheus/cli/deploys.rb +206 -41
- data/lib/morpheus/cli/error_handler.rb +7 -0
- data/lib/morpheus/cli/forgot_password.rb +133 -0
- data/lib/morpheus/cli/groups.rb +1 -1
- data/lib/morpheus/cli/health_command.rb +59 -2
- data/lib/morpheus/cli/hosts.rb +271 -39
- data/lib/morpheus/cli/instances.rb +228 -129
- data/lib/morpheus/cli/invoices_command.rb +100 -20
- data/lib/morpheus/cli/jobs_command.rb +94 -92
- data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/library_option_types_command.rb +10 -5
- data/lib/morpheus/cli/logs_command.rb +9 -6
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
- data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
- data/lib/morpheus/cli/mixins/print_helper.rb +13 -27
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +108 -5
- data/lib/morpheus/cli/option_types.rb +271 -22
- data/lib/morpheus/cli/remote.rb +35 -10
- data/lib/morpheus/cli/reports_command.rb +99 -30
- data/lib/morpheus/cli/roles.rb +193 -155
- data/lib/morpheus/cli/search_command.rb +182 -0
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +33 -11
- data/lib/morpheus/cli/tasks.rb +29 -32
- data/lib/morpheus/cli/usage_command.rb +64 -11
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +429 -254
- data/lib/morpheus/cli/whoami.rb +6 -6
- data/lib/morpheus/cli/workflows.rb +33 -40
- data/lib/morpheus/formatters.rb +75 -18
- data/lib/morpheus/terminal.rb +6 -2
- metadata +10 -4
- 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[:
|
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
|
-
|
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
|
-
|
155
|
+
other_output = output_lines.join("\n")
|
148
156
|
end
|
149
157
|
begin
|
150
|
-
|
151
|
-
json_string = JSON.pretty_generate(json_data)
|
152
|
-
puts json_string
|
158
|
+
json_response = JSON.parse(last_line)
|
153
159
|
rescue => ex
|
154
|
-
|
155
|
-
|
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
|
-
|
168
|
+
other_output = curl_output
|
159
169
|
end
|
160
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
200
|
-
|
201
|
-
-
|
202
|
-
|
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
|
-
|
207
|
-
|
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
|
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
|
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
|
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
|
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
|
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.
|
366
|
+
:host: https://qa.mymorpheus.com
|
323
367
|
:active: true
|
324
368
|
:production:
|
325
|
-
:host: https://
|
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
|
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
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|