morpheus-cli 5.2.1 → 5.3.0

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.
@@ -466,11 +466,16 @@ module Morpheus
466
466
 
467
467
  when :list
468
468
  opts.on( '-m', '--max MAX', "Max Results" ) do |val|
469
- max = val.to_i
470
- if max <= 0
471
- raise ::OptionParser::InvalidArgument.new("must be a positive integer")
469
+ # api supports max=-1 for all at the moment..
470
+ if val.to_s == "all" || val.to_s == "-1"
471
+ options[:max] = "-1"
472
+ else
473
+ max = val.to_i
474
+ if max <= 0
475
+ raise ::OptionParser::InvalidArgument.new("must be a positive integer")
476
+ end
477
+ options[:max] = max
472
478
  end
473
- options[:max] = max
474
479
  end
475
480
 
476
481
  opts.on( '-o', '--offset OFFSET', "Offset Results" ) do |val|
@@ -486,12 +491,17 @@ module Morpheus
486
491
  end
487
492
 
488
493
  opts.on( '-S', '--sort ORDER', "Sort Order. DIRECTION may be included as \"ORDER [asc|desc]\"." ) do |v|
489
- v_parts = v.to_s.split(" ")
490
- if v_parts.size > 1
491
- options[:sort] = v_parts[0]
492
- options[:direction] = (v_parts[1].strip == "desc") ? "desc" : "asc"
493
- else
494
+ if v.to_s.include?(",")
495
+ # sorting on multiple properties, just pass it as is, newer api supports multiple fields
494
496
  options[:sort] = v
497
+ else
498
+ v_parts = v.to_s.split(" ")
499
+ if v_parts.size > 1
500
+ options[:sort] = v_parts[0]
501
+ options[:direction] = (v_parts[1].strip == "desc") ? "desc" : "asc"
502
+ else
503
+ options[:sort] = v
504
+ end
495
505
  end
496
506
  end
497
507
 
@@ -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
@@ -134,29 +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] || options[:json]
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
- puts as_json(JSON.parse(last_line), options)
158
+ json_response = JSON.parse(last_line)
151
159
  rescue => ex
152
- Morpheus::Logging::DarkPrinter.puts "failed to parse curl result as JSON data Error: #{ex.message}" if Morpheus::Logging.debug?
153
- 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
154
166
  end
155
167
  else
156
- puts curl_output
168
+ other_output = curl_output
157
169
  end
158
- return $?.success?
159
-
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
160
175
  end
161
176
 
162
177
  def command_available?(cmd)
@@ -15,7 +15,7 @@ class Morpheus::Cli::HistoryCommand
15
15
  def handle(args)
16
16
  options = {show_pagination:false}
17
17
  optparse = Morpheus::Cli::OptionParser.new do|opts|
18
- opts.banner = "Usage: morpheus #{command_name}"
18
+ opts.banner = "Usage: morpheus #{command_name} [search]"
19
19
  # -n is a hidden alias for -m
20
20
  opts.on( '-n', '--max-commands MAX', "Alias for -m, --max option." ) do |val|
21
21
  options[:max] = val
@@ -35,6 +35,7 @@ The --flush option can be used to purge the history.
35
35
  Examples:
36
36
  history
37
37
  history -m 100
38
+ history "instances list"
38
39
  history --flush
39
40
 
40
41
  The most recently executed commands are seen by default. Use --reverse to see the oldest commands.
@@ -42,7 +43,10 @@ EOT
42
43
  end
43
44
  raw_cmd = "#{command_name} #{args.join(' ')}"
44
45
  optparse.parse!(args)
45
- 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
46
50
  if options[:do_flush]
47
51
  command_count = Morpheus::Cli::Shell.instance.history_commands_count
48
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'})?")
@@ -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
+ options[:details] ? {"RESOURCE" => lambda {|record| "#{record['objectType']} #{record['objectId']}" } } : nil,
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'] || options[:details]
282
+ "#{format_local_dt(record['ts'] || record['timestamp'])}"
283
+ else
284
+ "#{format_duration_ago(record['ts'] || record['timestamp'])}"
285
+ end
286
+
287
+ } },
288
+ ].compact
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
@@ -31,6 +31,10 @@ class Morpheus::Cli::ExecutionRequestCommand
31
31
  handle_subcommand(args)
32
32
  end
33
33
 
34
+ def default_refresh_interval
35
+ 5
36
+ end
37
+
34
38
  def get(args)
35
39
  raw_args = args
36
40
  options = {}
@@ -134,7 +138,7 @@ class Morpheus::Cli::ExecutionRequestCommand
134
138
  options = {}
135
139
  params = {}
136
140
  script_content = nil
137
- do_refresh = true
141
+ options[:refresh_until_finished] = true
138
142
  optparse = Morpheus::Cli::OptionParser.new do|opts|
139
143
  opts.banner = subcommand_usage("[options]")
140
144
  opts.on('--server ID', String, "Server ID") do |val|
@@ -161,8 +165,14 @@ class Morpheus::Cli::ExecutionRequestCommand
161
165
  exit 1
162
166
  end
163
167
  end
164
- opts.on(nil, '--no-refresh', "Do not refresh until finished" ) do
165
- do_refresh = false
168
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is finished. Default interval is #{default_refresh_interval} seconds.") do |val|
169
+ options[:refresh_until_finished] = true
170
+ if !val.to_s.empty?
171
+ options[:refresh_interval] = val.to_f
172
+ end
173
+ end
174
+ opts.on(nil, '--no-refresh', "Do not refresh. The default behavior is to refresh until finished." ) do
175
+ options[:refresh_until_finished] = false
166
176
  end
167
177
  #build_option_type_options(opts, options, add_user_source_option_types())
168
178
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
@@ -212,8 +222,8 @@ class Morpheus::Cli::ExecutionRequestCommand
212
222
  end
213
223
  execution_request = json_response['executionRequest']
214
224
  print_green_success "Executing request #{execution_request['uniqueId']}"
215
- if do_refresh
216
- get([execution_request['uniqueId'], "--refresh"] + (options[:remote] ? ["-r",options[:remote]] : []))
225
+ if options[:refresh_until_finished]
226
+ get([execution_request['uniqueId'], "--refresh", options[:refresh_interval] ? options[:refresh_interval].to_s : nil].compact + (options[:remote] ? ["-r",options[:remote]] : []))
217
227
  else
218
228
  get([execution_request['uniqueId']] + (options[:remote] ? ["-r",options[:remote]] : []))
219
229
  end