morpheus-cli 2.10.3 → 2.11.0

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