morpheus-cli 2.10.3 → 2.11.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/bin/morpheus +5 -96
  3. data/lib/morpheus/api/api_client.rb +23 -1
  4. data/lib/morpheus/api/checks_interface.rb +106 -0
  5. data/lib/morpheus/api/incidents_interface.rb +102 -0
  6. data/lib/morpheus/api/monitoring_apps_interface.rb +47 -0
  7. data/lib/morpheus/api/monitoring_contacts_interface.rb +47 -0
  8. data/lib/morpheus/api/monitoring_groups_interface.rb +47 -0
  9. data/lib/morpheus/api/monitoring_interface.rb +36 -0
  10. data/lib/morpheus/api/option_type_lists_interface.rb +47 -0
  11. data/lib/morpheus/api/option_types_interface.rb +47 -0
  12. data/lib/morpheus/api/roles_interface.rb +0 -1
  13. data/lib/morpheus/api/setup_interface.rb +19 -9
  14. data/lib/morpheus/cli.rb +20 -1
  15. data/lib/morpheus/cli/accounts.rb +8 -3
  16. data/lib/morpheus/cli/app_templates.rb +19 -11
  17. data/lib/morpheus/cli/apps.rb +52 -37
  18. data/lib/morpheus/cli/cli_command.rb +229 -53
  19. data/lib/morpheus/cli/cli_registry.rb +48 -40
  20. data/lib/morpheus/cli/clouds.rb +55 -26
  21. data/lib/morpheus/cli/command_error.rb +12 -0
  22. data/lib/morpheus/cli/credentials.rb +68 -26
  23. data/lib/morpheus/cli/curl_command.rb +98 -0
  24. data/lib/morpheus/cli/dashboard_command.rb +2 -7
  25. data/lib/morpheus/cli/deployments.rb +4 -4
  26. data/lib/morpheus/cli/deploys.rb +1 -2
  27. data/lib/morpheus/cli/dot_file.rb +5 -8
  28. data/lib/morpheus/cli/error_handler.rb +179 -15
  29. data/lib/morpheus/cli/groups.rb +21 -13
  30. data/lib/morpheus/cli/hosts.rb +220 -110
  31. data/lib/morpheus/cli/instance_types.rb +2 -2
  32. data/lib/morpheus/cli/instances.rb +257 -167
  33. data/lib/morpheus/cli/key_pairs.rb +15 -9
  34. data/lib/morpheus/cli/library.rb +673 -27
  35. data/lib/morpheus/cli/license.rb +2 -2
  36. data/lib/morpheus/cli/load_balancers.rb +4 -4
  37. data/lib/morpheus/cli/log_level_command.rb +6 -4
  38. data/lib/morpheus/cli/login.rb +17 -3
  39. data/lib/morpheus/cli/logout.rb +25 -11
  40. data/lib/morpheus/cli/man_command.rb +388 -0
  41. data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
  42. data/lib/morpheus/cli/mixins/monitoring_helper.rb +434 -0
  43. data/lib/morpheus/cli/mixins/print_helper.rb +620 -112
  44. data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -1
  45. data/lib/morpheus/cli/monitoring_apps_command.rb +29 -0
  46. data/lib/morpheus/cli/monitoring_checks_command.rb +427 -0
  47. data/lib/morpheus/cli/monitoring_contacts_command.rb +373 -0
  48. data/lib/morpheus/cli/monitoring_groups_command.rb +29 -0
  49. data/lib/morpheus/cli/monitoring_incidents_command.rb +711 -0
  50. data/lib/morpheus/cli/option_types.rb +10 -1
  51. data/lib/morpheus/cli/recent_activity_command.rb +2 -5
  52. data/lib/morpheus/cli/remote.rb +874 -134
  53. data/lib/morpheus/cli/roles.rb +54 -27
  54. data/lib/morpheus/cli/security_group_rules.rb +2 -2
  55. data/lib/morpheus/cli/security_groups.rb +23 -19
  56. data/lib/morpheus/cli/set_prompt_command.rb +50 -0
  57. data/lib/morpheus/cli/shell.rb +222 -157
  58. data/lib/morpheus/cli/tasks.rb +19 -15
  59. data/lib/morpheus/cli/users.rb +27 -17
  60. data/lib/morpheus/cli/version.rb +1 -1
  61. data/lib/morpheus/cli/virtual_images.rb +28 -13
  62. data/lib/morpheus/cli/whoami.rb +131 -52
  63. data/lib/morpheus/cli/workflows.rb +24 -9
  64. data/lib/morpheus/formatters.rb +195 -3
  65. data/lib/morpheus/logging.rb +86 -0
  66. data/lib/morpheus/terminal.rb +371 -0
  67. data/scripts/generate_morpheus_commands_help.morpheus +60 -0
  68. metadata +21 -2
@@ -57,8 +57,7 @@ class Morpheus::Cli::Deploys
57
57
  end
58
58
  instance = instance_results['instances'][0]
59
59
  instance_id = instance['id']
60
- print "\n" ,cyan, bold, "Morpheus Deployment\n","==================", reset, "\n\n"
61
-
60
+ print_h1 "Morpheus Deployment"
62
61
  if !deploy_args['script'].nil?
63
62
  print cyan, bold, " - Executing Pre Deploy Script...", reset, "\n"
64
63
 
@@ -7,9 +7,6 @@ require 'term/ansicolor'
7
7
  class Morpheus::Cli::DotFile
8
8
  include Term::ANSIColor
9
9
 
10
- DEFAULT_EXEC_PROC = lambda {|args|
11
-
12
- }
13
10
  EXPORTED_ALIASES_HEADER = "# exported aliases"
14
11
 
15
12
  # the path of the profile source file
@@ -25,9 +22,9 @@ class Morpheus::Cli::DotFile
25
22
  end
26
23
 
27
24
  attr_reader :filename
28
- attr_reader :file_contents
29
- attr_reader :commands
30
- attr_reader :cmd_results
25
+ # attr_reader :file_contents
26
+ # attr_reader :commands
27
+ # attr_reader :cmd_results
31
28
 
32
29
  def initialize(fn)
33
30
  @filename = fn
@@ -44,9 +41,9 @@ class Morpheus::Cli::DotFile
44
41
  if !File.exists?(@filename)
45
42
  print "#{Term::ANSIColor.red}source file not found: #{@filename}#{Term::ANSIColor.reset}\n" # if Morpheus::Logging.debug?
46
43
  else
47
- print "#{dark} #=> executing source file #{@filename}#{reset}\n" if Morpheus::Logging.debug?
44
+ Morpheus::Logging::DarkPrinter.puts "executing source file #{@filename}" if Morpheus::Logging.debug?
48
45
  end
49
- file_contents ||= File.read(@filename)
46
+ file_contents = File.read(@filename)
50
47
  lines = file_contents.split("\n")
51
48
  cmd_results = []
52
49
  line_num = 0
@@ -1,40 +1,204 @@
1
1
  require 'term/ansicolor'
2
2
  require 'optparse'
3
- #require 'rest_client'
3
+ require 'json'
4
+ require 'rest_client'
5
+ require 'net/https'
4
6
  require 'morpheus/logging'
7
+ require 'morpheus/cli/command_error'
8
+
5
9
  class Morpheus::Cli::ErrorHandler
6
- include Morpheus::Cli::PrintHelper
10
+ include Term::ANSIColor
11
+
12
+ def initialize(io=$stderr)
13
+ @stderr = io
14
+ end
7
15
 
8
16
  def handle_error(err, options={})
17
+ exit_code = 1
9
18
  # heh
10
19
  if Morpheus::Logging.debug? && options[:debug].nil?
11
20
  options[:debug] = true
12
21
  end
22
+ do_print_stacktrace = true
13
23
  case (err)
14
- when OptionParser::InvalidOption, OptionParser::AmbiguousOption, OptionParser::MissingArgument, OptionParser::InvalidArgument
24
+ when ::OptionParser::InvalidOption, ::OptionParser::AmbiguousOption,
25
+ ::OptionParser::MissingArgument, ::OptionParser::InvalidArgument,
26
+ ::OptionParser::NeedlessArgument
15
27
  # raise err
16
- print_red_alert "#{err.message}"
17
- puts "Try -h for help with this command."
28
+ # @stderr.puts "#{red}#{err.message}#{reset}"
29
+ puts_angry_error err.message
30
+ @stderr.puts "Try -h for help with this command."
31
+ do_print_stacktrace = false
32
+ # exit_code = 127
33
+ when Morpheus::Cli::CommandError
34
+ # @stderr.puts "#{red}#{err.message}#{reset}"
35
+ puts_angry_error err.message
36
+ do_print_stacktrace = false
37
+ # @stderr.puts "Try -h for help with this command."
38
+ when SocketError
39
+ @stderr.puts "#{red}Error Communicating with the Appliance.#{reset}"
40
+ @stderr.puts "#{red}#{err.message}#{reset}"
41
+ when RestClient::Exceptions::Timeout
42
+ @stderr.puts "#{red}Error Communicating with the Appliance.#{reset}"
43
+ @stderr.puts "#{red}#{err.message}#{reset}"
18
44
  when Errno::ECONNREFUSED
19
- print_red_alert "#{err.message}"
20
- # more special errors?
45
+ @stderr.puts "#{red}Error Communicating with the Appliance.#{reset}"
46
+ @stderr.puts "#{red}#{err.message}#{reset}"
47
+ # @stderr.puts "Try -h for help with this command."
21
48
  when OpenSSL::SSL::SSLError
22
- print_red_alert "Error Communicating with the Appliance. #{err.message}"
49
+ @stderr.puts "#{red}Error Communicating with the Appliance.#{reset}"
50
+ @stderr.puts "#{red}#{err.message}#{reset}"
23
51
  when RestClient::Exception
24
52
  print_rest_exception(err, options)
25
53
  else
26
- print_red_alert "Unexpected Error."
27
- if !options[:debug]
28
- print "Use --debug for more information.\n"
54
+ @stderr.puts "#{red}Unexpected Error#{reset}"
55
+ end
56
+
57
+ if do_print_stacktrace
58
+ if options[:debug]
59
+ if err.is_a?(Exception)
60
+ print_stacktrace(err)
61
+ else
62
+ @stderr.puts err.to_s
63
+ end
64
+ else
65
+ @stderr.puts "Use --debug for more information."
29
66
  end
30
67
  end
31
68
 
32
- if options[:debug]
33
- print Term::ANSIColor.red, "\n", "#{err.class}: #{err.message}", "\n", Term::ANSIColor.reset
34
- print err.backtrace.join("\n"), "\n\n"
69
+ return exit_code
70
+
71
+ end
72
+
73
+ def print_stacktrace(err)
74
+ @stderr.print red, "\n", "#{err.class}: #{err.message}", "\n", reset
75
+ @stderr.print err.backtrace.join("\n"), "\n\n"
76
+ end
77
+
78
+ # def handle_rest_exception(err, io=@stderr)
79
+ # if !err.is_a?(RestClient::Exception)
80
+ # raise err
81
+ # end
82
+ # print_rest_exception(err, io=@stderr)
83
+ # end
84
+
85
+
86
+ def print_rest_exception(err, options)
87
+ e = err
88
+ if err.response
89
+ if options[:debug]
90
+ begin
91
+ print_rest_exception_request_and_response(e)
92
+ ensure
93
+ @stderr.print reset
94
+ end
95
+ return
96
+ end
97
+ if err.response.code == 400
98
+ response = JSON.parse(err.response.to_s)
99
+ print_rest_errors(response, options)
100
+ else
101
+ @stderr.print red, "Error Communicating with the Appliance. #{e}", reset, "\n"
102
+ if options[:json] || options[:debug]
103
+ begin
104
+ response = JSON.parse(e.response.to_s)
105
+ # @stderr.print red
106
+ @stderr.print JSON.pretty_generate(response)
107
+ @stderr.print reset, "\n"
108
+ rescue TypeError, JSON::ParserError => ex
109
+ @stderr.print red, "Failed to parse JSON response: #{ex}", reset, "\n"
110
+ # @stderr.print red
111
+ @stderr.print response.to_s
112
+ @stderr.print reset, "\n"
113
+ ensure
114
+ @stderr.print reset
115
+ end
116
+ else
117
+ @stderr.puts "Use --debug for more information."
118
+ end
119
+ end
35
120
  else
36
- #print "Use --debug for more information.\n"
121
+ @stderr.print red, "Error Communicating with the Appliance. #{e}", reset, "\n"
122
+ end
123
+ end
124
+
125
+ def print_rest_errors(response, options={})
126
+ begin
127
+ if options[:json]
128
+ @stderr.print red
129
+ @stderr.print JSON.pretty_generate(response)
130
+ @stderr.print reset, "\n"
131
+ else
132
+ if !response['success']
133
+ @stderr.print red,bold
134
+ if response['msg']
135
+ @stderr.puts response['msg']
136
+ end
137
+ if response['errors']
138
+ response['errors'].each do |key, value|
139
+ @stderr.print "* #{key}: #{value}\n"
140
+ end
141
+ end
142
+ @stderr.print reset
143
+ else
144
+ # this should not really happen
145
+ @stderr.print cyan,bold, "\nSuccess!"
146
+ end
147
+ end
148
+ ensure
149
+ @stderr.print reset
150
+ end
151
+ end
152
+
153
+ def print_rest_request(req)
154
+ @stderr.print "Request:"
155
+ @stderr.print "\n"
156
+ @stderr.print "#{req.method.to_s.upcase} #{req.url.inspect}"
157
+ @stderr.print "\n"
158
+ end
159
+
160
+ def print_rest_response(res)
161
+ # size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
162
+ size = (res.body.nil? ? 0 : res.body.size)
163
+ @stderr.print "Response:"
164
+ @stderr.print "\n"
165
+ display_size = Filesize.from("#{size} B").pretty rescue size
166
+ @stderr.print "HTTP #{res.net_http_res.code} - #{res.net_http_res.message} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{display_size}"
167
+ @stderr.print "\n"
168
+ begin
169
+ @stderr.print JSON.pretty_generate(JSON.parse(res.body))
170
+ rescue
171
+ @stderr.print res.body.to_s
37
172
  end
173
+ @stderr.print "\n"
38
174
  end
39
175
 
176
+ def print_rest_exception_request_and_response(e)
177
+ @stderr.puts "#{red}Error Communicating with the Appliance. (#{e.response.code})#{reset}"
178
+ response = e.response
179
+ request = response.instance_variable_get("@request")
180
+ @stderr.print red
181
+ print_rest_request(request)
182
+ @stderr.print "\n"
183
+ print_rest_response(response)
184
+ @stderr.print reset
185
+ end
186
+
187
+
188
+ protected
189
+
190
+ def puts_angry_error(*msgs)
191
+ # @stderr.print "#{Term::ANSIColor.red}morpheus: #{Term::ANSIColor.reset}#{msg}\n"
192
+ @stderr.print(Morpheus::Terminal.angry_prompt)
193
+ @stderr.puts(*msgs)
194
+ end
195
+
196
+ # def puts(*args)
197
+ # @stderr.puts(*args)
198
+ # end
199
+
200
+ # def print(*args)
201
+ # @stderr.print(*args)
202
+ # end
203
+
40
204
  end
@@ -52,7 +52,7 @@ class Morpheus::Cli::Groups
52
52
  return
53
53
  end
54
54
  groups = json_response['groups']
55
- print "\n" ,cyan, bold, "Morpheus Groups\n","==================", reset, "\n\n"
55
+ print_h1 "Morpheus Groups"
56
56
  if groups.empty?
57
57
  puts yellow,"No groups currently configured.",reset
58
58
  else
@@ -62,7 +62,7 @@ class Morpheus::Cli::Groups
62
62
  active_group = groups.find { |it| it['id'] == @active_group_id }
63
63
  active_group = active_group || find_group_by_name_or_id(@active_group_id)
64
64
  #unless @appliances.keys.size == 1
65
- print cyan, "\n# => Currently using #{active_group['name']}\n", reset
65
+ print cyan, "\n# => Currently using group #{active_group['name']}\n", reset
66
66
  #end
67
67
  else
68
68
  unless options[:remote]
@@ -99,17 +99,24 @@ class Morpheus::Cli::Groups
99
99
  return
100
100
  end
101
101
  group = json_response['group']
102
-
103
102
  is_active = @active_group_id && (@active_group_id == group['id'])
104
-
105
- print "\n" ,cyan, bold, "Group Details\n","==================", reset, "\n\n"
103
+ print_h1 "Group Details"
106
104
  print cyan
107
- puts "ID: #{group['id']}"
108
- puts "Name: #{group['name']}"
109
- puts "Code: #{group['code']}"
110
- puts "Location: #{group['location']}"
111
- puts "Clouds: #{group['zones'].collect {|it| it['name'] }.join(', ')}"
112
- puts "Hosts: #{group['serverCount']}"
105
+ description_cols = {
106
+ "ID" => 'id',
107
+ "Name" => 'name',
108
+ "Code" => 'code',
109
+ "Location" => 'location',
110
+ "Clouds" => lambda {|it| it['zones'].collect {|z| z['name'] }.join(', ') },
111
+ "Hosts" => 'serverCount'
112
+ }
113
+ print_description_list(description_cols, group)
114
+ # puts "ID: #{group['id']}"
115
+ # puts "Name: #{group['name']}"
116
+ # puts "Code: #{group['code']}"
117
+ # puts "Location: #{group['location']}"
118
+ # puts "Clouds: #{group['zones'].collect {|it| it['name'] }.join(', ')}"
119
+ # puts "Hosts: #{group['serverCount']}"
113
120
  if is_active
114
121
  puts "\n => This is the active group."
115
122
  end
@@ -441,7 +448,8 @@ class Morpheus::Cli::Groups
441
448
  if group
442
449
  print cyan,group['name'].to_s,"\n",reset
443
450
  else
444
- print dark,"No active group. See `groups use`","\n",reset
451
+ print yellow,"No active group. See `groups use`","\n",reset
452
+ return false
445
453
  end
446
454
  end
447
455
 
@@ -539,7 +547,7 @@ public
539
547
  def load_group_file
540
548
  fn = groups_file_path
541
549
  if File.exist? fn
542
- print "#{dark} #=> loading groups file #{fn}#{reset}\n" if Morpheus::Logging.debug?
550
+ Morpheus::Logging::DarkPrinter.puts "loading groups file #{fn}" if Morpheus::Logging.debug?
543
551
  return YAML.load_file(fn)
544
552
  else
545
553
  {}
@@ -44,7 +44,44 @@ class Morpheus::Cli::Hosts
44
44
  opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
45
45
  options[:cloud] = val
46
46
  end
47
- build_common_options(opts, options, [:list, :json, :dry_run, :remote])
47
+ opts.on( '-M', '--managed', "Show only Managed Servers" ) do |val|
48
+ params[:managed] = true
49
+ end
50
+ opts.on( '-U', '--unmanaged', "Show only Unmanaged Servers" ) do |val|
51
+ params[:managed] = false
52
+ end
53
+ opts.on( '-t', '--type TYPE', "Show only Certain Server Types" ) do |val|
54
+ params[:serverType] = val
55
+ end
56
+ opts.on( '-p', '--power STATE', "Filter by Power Status" ) do |val|
57
+ params[:powerState] = val
58
+ end
59
+ opts.on( '-i', '--ip IPADDRESS', "Filter by IP Address" ) do |val|
60
+ params[:ip] = val
61
+ end
62
+ opts.on( '', '--vm', "Show only virtual machines" ) do |val|
63
+ params[:vm] = true
64
+ end
65
+ opts.on( '', '--hypervisor', "Show only VM Hypervisors" ) do |val|
66
+ params[:vmHypervisor] = true
67
+ end
68
+ opts.on( '', '--container', "Show only Container Hypervisors" ) do |val|
69
+ params[:containerHypervisor] = true
70
+ end
71
+ opts.on( '', '--baremetal', "Show only Baremetal Servers" ) do |val|
72
+ params[:bareMetalHost] = true
73
+ end
74
+ opts.on( '', '--status STATUS', "Filter by Status" ) do |val|
75
+ params[:status] = val
76
+ end
77
+
78
+ opts.on( '', '--agent', "Show only Servers with the agent installed" ) do |val|
79
+ params[:agentInstalled] = true
80
+ end
81
+ opts.on( '', '--noagent', "Show only Servers with No agent" ) do |val|
82
+ params[:agentInstalled] = false
83
+ end
84
+ build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
48
85
  end
49
86
  optparse.parse!(args)
50
87
  connect(options)
@@ -72,8 +109,27 @@ class Morpheus::Cli::Hosts
72
109
  json_response = @servers_interface.get(params)
73
110
 
74
111
  if options[:json]
75
- print JSON.pretty_generate(json_response)
76
- print "\n"
112
+ if options[:include_fields]
113
+ json_response = {"servers" => filter_data(json_response["servers"], options[:include_fields]) }
114
+ end
115
+ puts as_json(json_response, options)
116
+ return 0
117
+ elsif options[:yaml]
118
+ if options[:include_fields]
119
+ json_response = {"servers" => filter_data(json_response["servers"], options[:include_fields]) }
120
+ end
121
+ puts as_yaml(json_response, options)
122
+ return 0
123
+ elsif options[:csv]
124
+ # merge stats to be nice here..
125
+ if json_response['servers']
126
+ all_stats = json_response['stats'] || {}
127
+ json_response['servers'].each do |it|
128
+ it['stats'] ||= all_stats[it['id'].to_s] || all_stats[it['id']]
129
+ end
130
+ end
131
+ puts records_as_csv(json_response['servers'], options)
132
+ return 0
77
133
  else
78
134
  servers = json_response['servers']
79
135
  title = "Morpheus Hosts"
@@ -87,12 +143,59 @@ class Morpheus::Cli::Hosts
87
143
  if params[:phrase]
88
144
  subtitles << "Search: #{params[:phrase]}".strip
89
145
  end
90
- subtitle = subtitles.join(', ')
91
- print "\n" ,cyan, bold, title, (subtitle.empty? ? "" : " - #{subtitle}"), "\n", "==================", reset, "\n\n"
146
+ print_h1 title, subtitles
92
147
  if servers.empty?
93
- puts yellow,"No hosts found.",reset
148
+ print yellow,"No hosts found.",reset,"\n"
94
149
  else
95
- print_servers_table(servers)
150
+ # print_servers_table(servers)
151
+ # server returns stats in a separate key stats => {"id" => {} }
152
+ # the id is a string right now..for some reason..
153
+ all_stats = json_response['stats'] || {}
154
+ servers.each do |it|
155
+ found_stats = all_stats[it['id'].to_s] || all_stats[it['id']]
156
+ if !it['stats']
157
+ it['stats'] = found_stats # || {}
158
+ else
159
+ it['stats'] = found_stats.merge!(it['stats'])
160
+ end
161
+ end
162
+
163
+ rows = servers.collect {|server|
164
+ stats = server['stats']
165
+
166
+ if !stats['maxMemory']
167
+ stats['maxMemory'] = stats['usedMemory'] + stats['freeMemory']
168
+ end
169
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
170
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
171
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
172
+ row = {
173
+ id: server['id'],
174
+ name: server['name'],
175
+ platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
176
+ cloud: server['zone'] ? server['zone']['name'] : '',
177
+ type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
178
+ nodes: server['containers'] ? server['containers'].size : '',
179
+ status: format_host_status(server, cyan),
180
+ power: format_server_power_state(server, cyan),
181
+ cpu: cpu_usage_str + cyan,
182
+ memory: memory_usage_str + cyan,
183
+ storage: storage_usage_str + cyan
184
+ }
185
+ row
186
+ }
187
+ columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
188
+ term_width = current_terminal_width()
189
+ if term_width > 170
190
+ columns += [:cpu, :memory, :storage]
191
+ end
192
+ # custom pretty table columns ...
193
+ if options[:include_fields]
194
+ columns = options[:include_fields]
195
+ end
196
+ print cyan
197
+ print as_pretty_table(rows, columns, options)
198
+ print reset
96
199
  print_results_pagination(json_response)
97
200
  end
98
201
  print reset,"\n"
@@ -107,7 +210,7 @@ class Morpheus::Cli::Hosts
107
210
  options = {}
108
211
  optparse = OptionParser.new do|opts|
109
212
  opts.banner = subcommand_usage("[name]")
110
- build_common_options(opts, options, [:json, :dry_run, :remote])
213
+ build_common_options(opts, options, [:json, :csv, :yaml, :fields, :dry_run, :remote])
111
214
  end
112
215
  optparse.parse!(args)
113
216
  if args.count < 1
@@ -115,58 +218,65 @@ class Morpheus::Cli::Hosts
115
218
  exit 1
116
219
  end
117
220
  connect(options)
221
+ id_list = parse_id_list(args)
222
+ return run_command_for_each_arg(id_list) do |arg|
223
+ _get(arg, options)
224
+ end
225
+ end
226
+
227
+ def _get(arg, options)
118
228
  begin
119
229
  if options[:dry_run]
120
- if args[0].to_s =~ /\A\d{1,}\Z/
121
- print_dry_run @servers_interface.dry.get(args[0].to_i)
230
+ if arg.to_s =~ /\A\d{1,}\Z/
231
+ print_dry_run @servers_interface.dry.get(arg.to_i)
122
232
  else
123
- print_dry_run @servers_interface.dry.get({name: args[0]})
233
+ print_dry_run @servers_interface.dry.get({name: arg})
124
234
  end
125
235
  return
126
236
  end
127
- server = find_host_by_name_or_id(args[0])
237
+ server = find_host_by_name_or_id(arg)
128
238
  json_response = @servers_interface.get(server['id'])
129
239
  if options[:json]
130
- print JSON.pretty_generate(json_response), "\n"
131
- return
240
+ if options[:include_fields]
241
+ json_response = {"server" => filter_data(json_response["server"], options[:include_fields]) }
242
+ end
243
+ puts as_json(json_response, options)
244
+ return 0
245
+ elsif options[:yaml]
246
+ if options[:include_fields]
247
+ json_response = {"server" => filter_data(json_response["server"], options[:include_fields]) }
248
+ end
249
+ puts as_yaml(json_response, options)
250
+ return 0
251
+ end
252
+ if options[:csv]
253
+ puts records_as_csv([json_response['server']], options)
254
+ return 0
132
255
  end
133
256
  server = json_response['server']
134
257
  #stats = server['stats'] || json_response['stats'] || {}
135
258
  stats = json_response['stats'] || {}
136
-
137
- print "\n" ,cyan, bold, "Host Details\n","==================", reset, "\n\n"
259
+ title = "Host Details"
260
+ print_h1 title
138
261
  print cyan
139
- puts "ID: #{server['id']}"
140
- puts "Name: #{server['name']}"
141
- puts "Description: #{server['description']}"
142
- puts "Account: #{server['account'] ? server['account']['name'] : ''}"
143
- #puts "Group: #{server['group'] ? server['group']['name'] : ''}"
144
- #puts "Cloud: #{server['cloud'] ? server['cloud']['name'] : ''}"
145
- puts "Cloud: #{server['zone'] ? server['zone']['name'] : ''}"
146
- puts "Nodes: #{server['containers'] ? server['containers'].size : ''}"
147
- puts "Type: #{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'}"
148
- puts "Platform: #{server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A'}"
149
- puts "Plan: #{server['plan'] ? server['plan']['name'] : ''}"
150
- if server['agentInstalled']
151
- puts "Agent: #{server['agentVersion'] || ''} updated at #{format_local_dt(server['lastAgentUpdate'])}"
152
- else
153
- puts "Agent: (not installed)"
154
- end
155
- puts "Status: #{format_host_status(server)}"
156
- puts "Power: #{format_server_power_state(server)}"
157
- if ((stats['maxMemory'].to_i != 0) || (stats['maxStorage'].to_i != 0))
158
- # stats_map = {}
159
- print "\n"
160
- #print "\n" ,cyan, bold, "Host Stats\n","==================", reset, "\n\n"
161
- # stats_map[:memory] = "#{Filesize.from("#{stats['usedMemory']} B").pretty} / #{Filesize.from("#{stats['maxMemory']} B").pretty}"
162
- # stats_map[:storage] = "#{Filesize.from("#{stats['usedStorage']} B").pretty} / #{Filesize.from("#{stats['maxStorage']} B").pretty}"
163
- # stats_map[:cpu] = "#{stats['cpuUsage'].to_f.round(2)}%"
164
- # tp [stats_map], :memory,:storage,:cpu
165
- print_stats_usage(stats)
166
- else
167
- #print yellow, "No stat data.", reset
168
- end
169
-
262
+ print_description_list({
263
+ "ID" => 'id',
264
+ "Name" => 'name',
265
+ "Description" => 'description',
266
+ "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
267
+ #"Group" => lambda {|it| it['group'] ? it['group']['name'] : '' },
268
+ "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
269
+ "Type" => lambda {|it| it['computeServerType'] ? it['computeServerType']['name'] : 'unmanaged' },
270
+ "Platform" => lambda {|it| it['serverOs'] ? it['serverOs']['name'].upcase : 'N/A' },
271
+ "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
272
+ "Agent" => lambda {|it| it['agentInstalled'] ? "#{server['agentVersion'] || ''} updated at #{format_local_dt(server['lastAgentUpdate'])}" : '(not installed)' },
273
+ "Status" => lambda {|it| format_host_status(it) },
274
+ "Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
275
+ "Power" => lambda {|it| format_server_power_state(it) },
276
+ }, server)
277
+
278
+ print_h2 "Host Usage"
279
+ print_stats_usage(stats)
170
280
  print reset, "\n"
171
281
 
172
282
  rescue RestClient::Exception => e
@@ -179,7 +289,7 @@ class Morpheus::Cli::Hosts
179
289
  options = {}
180
290
  optparse = OptionParser.new do|opts|
181
291
  opts.banner = subcommand_usage("[name]")
182
- build_common_options(opts, options, [:json, :dry_run, :remote])
292
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
183
293
  end
184
294
  optparse.parse!(args)
185
295
  if args.count < 1
@@ -187,43 +297,51 @@ class Morpheus::Cli::Hosts
187
297
  exit 1
188
298
  end
189
299
  connect(options)
300
+ ids = args
301
+ id_list = parse_id_list(args)
302
+ return run_command_for_each_arg(id_list) do |arg|
303
+ _stats(arg, options)
304
+ end
305
+ end
306
+
307
+ def _stats(arg, options)
190
308
  begin
191
309
  if options[:dry_run]
192
- if args[0].to_s =~ /\A\d{1,}\Z/
193
- print_dry_run @servers_interface.dry.get(args[0].to_i)
310
+ if arg.to_s =~ /\A\d{1,}\Z/
311
+ print_dry_run @servers_interface.dry.get(arg.to_i)
194
312
  else
195
- print_dry_run @servers_interface.dry.get({name: args[0]})
313
+ print_dry_run @servers_interface.dry.get({name: arg})
196
314
  end
197
315
  return
198
316
  end
199
- server = find_host_by_name_or_id(args[0])
317
+ server = find_host_by_name_or_id(arg)
200
318
  json_response = @servers_interface.get(server['id'])
201
319
  if options[:json]
202
320
  print JSON.pretty_generate(json_response), "\n"
203
- return
321
+ return 0
322
+ elsif options[:yaml]
323
+ if options[:include_fields]
324
+ json_response = {"stats" => filter_data(json_response["stats"], options[:include_fields]) }
325
+ end
326
+ puts as_yaml(json_response, options)
327
+ return 0
328
+ elsif options[:csv]
329
+ puts records_as_csv([json_response['stats']], options)
330
+ return 0
204
331
  end
205
332
  server = json_response['server']
206
333
  #stats = server['stats'] || json_response['stats'] || {}
207
334
  stats = json_response['stats'] || {}
208
-
209
- print "\n" ,cyan, bold, "Host Stats: #{server['name']} (#{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'})\n","==================", "\n\n", reset, cyan
210
- puts "Status: #{format_host_status(server)}"
211
- puts "Power: #{format_server_power_state(server)}"
212
- if ((stats['maxMemory'].to_i != 0) || (stats['maxStorage'].to_i != 0))
213
- # stats_map = {}
214
- print "\n"
215
- #print "\n" ,cyan, bold, "Host Stats\n","==================", reset, "\n\n"
216
- # stats_map[:memory] = "#{Filesize.from("#{stats['usedMemory']} B").pretty} / #{Filesize.from("#{stats['maxMemory']} B").pretty}"
217
- # stats_map[:storage] = "#{Filesize.from("#{stats['usedStorage']} B").pretty} / #{Filesize.from("#{stats['maxStorage']} B").pretty}"
218
- # stats_map[:cpu] = "#{stats['cpuUsage'].to_f.round(2)}%"
219
- # tp [stats_map], :memory,:storage,:cpu
220
- print_stats_usage(stats)
221
- else
222
- #print yellow, "No stat data.", reset
223
- end
335
+ title = "Host Stats: #{server['name']} (#{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'})"
336
+ print_h1 title
337
+ puts cyan + "Power: ".rjust(12) + format_server_power_state(server).to_s
338
+ puts cyan + "Status: ".rjust(12) + format_host_status(server).to_s
339
+ puts cyan + "Nodes: ".rjust(12) + (server['containers'] ? server['containers'].size : '').to_s
340
+ #print_h2 "Host Usage"
341
+ print_stats_usage(stats, {label_width: 10})
224
342
 
225
343
  print reset, "\n"
226
-
344
+ return 0
227
345
  rescue RestClient::Exception => e
228
346
  print_rest_exception(e, options)
229
347
  exit 1
@@ -243,36 +361,47 @@ class Morpheus::Cli::Hosts
243
361
  end
244
362
  connect(options)
245
363
  begin
246
- host = find_host_by_name_or_id(args[0])
364
+ server = find_host_by_name_or_id(args[0])
247
365
  params = {}
248
366
  [:phrase, :offset, :max, :sort, :direction].each do |k|
249
367
  params[k] = options[k] unless options[k].nil?
250
368
  end
251
369
  params[:query] = params.delete(:phrase) unless params[:phrase].nil?
252
370
  if options[:dry_run]
253
- print_dry_run @logs_interface.dry.server_logs([host['id']], params)
371
+ print_dry_run @logs_interface.dry.server_logs([server['id']], params)
254
372
  return
255
373
  end
256
- logs = @logs_interface.server_logs([host['id']], params)
374
+ logs = @logs_interface.server_logs([server['id']], params)
257
375
  output = ""
258
376
  if options[:json]
259
377
  output << JSON.pretty_generate(logs)
260
378
  else
261
- logs['data'].reverse.each do |log_entry|
262
- log_level = ''
263
- case log_entry['level']
264
- when 'INFO'
265
- log_level = "#{blue}#{bold}INFO#{reset}"
266
- when 'DEBUG'
267
- log_level = "#{white}#{bold}DEBUG#{reset}"
268
- when 'WARN'
269
- log_level = "#{yellow}#{bold}WARN#{reset}"
270
- when 'ERROR'
271
- log_level = "#{red}#{bold}ERROR#{reset}"
272
- when 'FATAL'
273
- log_level = "#{red}#{bold}FATAL#{reset}"
379
+ title = "Host Logs: #{server['name']} (#{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'})"
380
+ subtitles = []
381
+ if params[:query]
382
+ subtitles << "Search: #{params[:query]}".strip
383
+ end
384
+ # todo: startMs, endMs, sorts insteaad of sort..etc
385
+ print_h1 title, subtitles
386
+ if logs['data'].empty?
387
+ output << "#{cyan}No logs found.#{reset}\n"
388
+ else
389
+ logs['data'].reverse.each do |log_entry|
390
+ log_level = ''
391
+ case log_entry['level']
392
+ when 'INFO'
393
+ log_level = "#{blue}#{bold}INFO#{reset}"
394
+ when 'DEBUG'
395
+ log_level = "#{white}#{bold}DEBUG#{reset}"
396
+ when 'WARN'
397
+ log_level = "#{yellow}#{bold}WARN#{reset}"
398
+ when 'ERROR'
399
+ log_level = "#{red}#{bold}ERROR#{reset}"
400
+ when 'FATAL'
401
+ log_level = "#{red}#{bold}FATAL#{reset}"
402
+ end
403
+ output << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message']}\n"
274
404
  end
275
- output << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message']}\n"
276
405
  end
277
406
  end
278
407
  print output, reset, "\n"
@@ -305,9 +434,9 @@ class Morpheus::Cli::Hosts
305
434
  print JSON.pretty_generate(cloud_server_types)
306
435
  print "\n"
307
436
  else
308
- print "\n" ,cyan, bold, "Morpheus Server Types - Cloud: #{zone['name']}\n","==================", reset, "\n\n"
437
+ print_h1 "Morpheus Server Types - Cloud: #{zone['name']}"
309
438
  if cloud_server_types.nil? || cloud_server_types.empty?
310
- puts yellow,"No server types found for the selected cloud.",reset
439
+ print yellow,"No server types found for the selected cloud",reset,"\n"
311
440
  else
312
441
  cloud_server_types.each do |server_type|
313
442
  print cyan, "[#{server_type['code']}]".ljust(20), " - ", "#{server_type['name']}", "\n"
@@ -424,7 +553,7 @@ class Morpheus::Cli::Hosts
424
553
  payload[:networkInterfaces] = network_interfaces
425
554
  end
426
555
  rescue RestClient::Exception => e
427
- print_yellow_warning "Unable to load network options. Proceeding..."
556
+ print yellow,"Unable to load network options. Proceeding...",reset,"\n"
428
557
  print_rest_exception(e, options) if Morpheus::Logging.debug?
429
558
  end
430
559
  end
@@ -644,7 +773,7 @@ class Morpheus::Cli::Hosts
644
773
  # payload[:networkInterfaces] = network_interfaces
645
774
  # end
646
775
  # rescue RestClient::Exception => e
647
- # print_yellow_warning "Unable to load network options. Proceeding..."
776
+ # print yellow,"Unable to load network options. Proceeding...",reset,"\n"
648
777
  # print_rest_exception(e, options) if Morpheus::Logging.debug?
649
778
  # end
650
779
  # end
@@ -898,25 +1027,6 @@ class Morpheus::Cli::Hosts
898
1027
  end
899
1028
  end
900
1029
 
901
- def print_servers_table(servers, opts={})
902
- table_color = opts[:color] || cyan
903
- rows = servers.collect do |server|
904
- {
905
- id: server['id'],
906
- name: server['name'],
907
- platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
908
- cloud: server['zone'] ? server['zone']['name'] : '',
909
- type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
910
- nodes: server['containers'] ? server['containers'].size : '',
911
- status: format_host_status(server, table_color),
912
- power: format_server_power_state(server, table_color)
913
- }
914
- end
915
- columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
916
- print table_color
917
- tp rows, columns
918
- print reset
919
- end
920
1030
 
921
1031
  def format_server_power_state(server, return_color=cyan)
922
1032
  out = ""