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.
- checksums.yaml +4 -4
- data/bin/morpheus +5 -96
- data/lib/morpheus/api/api_client.rb +23 -1
- data/lib/morpheus/api/checks_interface.rb +106 -0
- data/lib/morpheus/api/incidents_interface.rb +102 -0
- data/lib/morpheus/api/monitoring_apps_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_contacts_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_groups_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_interface.rb +36 -0
- data/lib/morpheus/api/option_type_lists_interface.rb +47 -0
- data/lib/morpheus/api/option_types_interface.rb +47 -0
- data/lib/morpheus/api/roles_interface.rb +0 -1
- data/lib/morpheus/api/setup_interface.rb +19 -9
- data/lib/morpheus/cli.rb +20 -1
- data/lib/morpheus/cli/accounts.rb +8 -3
- data/lib/morpheus/cli/app_templates.rb +19 -11
- data/lib/morpheus/cli/apps.rb +52 -37
- data/lib/morpheus/cli/cli_command.rb +229 -53
- data/lib/morpheus/cli/cli_registry.rb +48 -40
- data/lib/morpheus/cli/clouds.rb +55 -26
- data/lib/morpheus/cli/command_error.rb +12 -0
- data/lib/morpheus/cli/credentials.rb +68 -26
- data/lib/morpheus/cli/curl_command.rb +98 -0
- data/lib/morpheus/cli/dashboard_command.rb +2 -7
- data/lib/morpheus/cli/deployments.rb +4 -4
- data/lib/morpheus/cli/deploys.rb +1 -2
- data/lib/morpheus/cli/dot_file.rb +5 -8
- data/lib/morpheus/cli/error_handler.rb +179 -15
- data/lib/morpheus/cli/groups.rb +21 -13
- data/lib/morpheus/cli/hosts.rb +220 -110
- data/lib/morpheus/cli/instance_types.rb +2 -2
- data/lib/morpheus/cli/instances.rb +257 -167
- data/lib/morpheus/cli/key_pairs.rb +15 -9
- data/lib/morpheus/cli/library.rb +673 -27
- data/lib/morpheus/cli/license.rb +2 -2
- data/lib/morpheus/cli/load_balancers.rb +4 -4
- data/lib/morpheus/cli/log_level_command.rb +6 -4
- data/lib/morpheus/cli/login.rb +17 -3
- data/lib/morpheus/cli/logout.rb +25 -11
- data/lib/morpheus/cli/man_command.rb +388 -0
- data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/monitoring_helper.rb +434 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +620 -112
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -1
- data/lib/morpheus/cli/monitoring_apps_command.rb +29 -0
- data/lib/morpheus/cli/monitoring_checks_command.rb +427 -0
- data/lib/morpheus/cli/monitoring_contacts_command.rb +373 -0
- data/lib/morpheus/cli/monitoring_groups_command.rb +29 -0
- data/lib/morpheus/cli/monitoring_incidents_command.rb +711 -0
- data/lib/morpheus/cli/option_types.rb +10 -1
- data/lib/morpheus/cli/recent_activity_command.rb +2 -5
- data/lib/morpheus/cli/remote.rb +874 -134
- data/lib/morpheus/cli/roles.rb +54 -27
- data/lib/morpheus/cli/security_group_rules.rb +2 -2
- data/lib/morpheus/cli/security_groups.rb +23 -19
- data/lib/morpheus/cli/set_prompt_command.rb +50 -0
- data/lib/morpheus/cli/shell.rb +222 -157
- data/lib/morpheus/cli/tasks.rb +19 -15
- data/lib/morpheus/cli/users.rb +27 -17
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +28 -13
- data/lib/morpheus/cli/whoami.rb +131 -52
- data/lib/morpheus/cli/workflows.rb +24 -9
- data/lib/morpheus/formatters.rb +195 -3
- data/lib/morpheus/logging.rb +86 -0
- data/lib/morpheus/terminal.rb +371 -0
- data/scripts/generate_morpheus_commands_help.morpheus +60 -0
- metadata +21 -2
data/lib/morpheus/cli/deploys.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
44
|
+
Morpheus::Logging::DarkPrinter.puts "executing source file #{@filename}" if Morpheus::Logging.debug?
|
48
45
|
end
|
49
|
-
file_contents
|
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
|
-
|
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
|
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,
|
24
|
+
when ::OptionParser::InvalidOption, ::OptionParser::AmbiguousOption,
|
25
|
+
::OptionParser::MissingArgument, ::OptionParser::InvalidArgument,
|
26
|
+
::OptionParser::NeedlessArgument
|
15
27
|
# raise err
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
#
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
data/lib/morpheus/cli/groups.rb
CHANGED
@@ -52,7 +52,7 @@ class Morpheus::Cli::Groups
|
|
52
52
|
return
|
53
53
|
end
|
54
54
|
groups = json_response['groups']
|
55
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
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
|
-
|
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
|
{}
|
data/lib/morpheus/cli/hosts.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
91
|
-
print "\n" ,cyan, bold, title, (subtitle.empty? ? "" : " - #{subtitle}"), "\n", "==================", reset, "\n\n"
|
146
|
+
print_h1 title, subtitles
|
92
147
|
if servers.empty?
|
93
|
-
|
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
|
121
|
-
print_dry_run @servers_interface.dry.get(
|
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:
|
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(
|
237
|
+
server = find_host_by_name_or_id(arg)
|
128
238
|
json_response = @servers_interface.get(server['id'])
|
129
239
|
if options[:json]
|
130
|
-
|
131
|
-
|
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
|
-
|
259
|
+
title = "Host Details"
|
260
|
+
print_h1 title
|
138
261
|
print cyan
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
193
|
-
print_dry_run @servers_interface.dry.get(
|
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:
|
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(
|
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
|
-
|
210
|
-
puts "
|
211
|
-
puts "
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
-
|
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([
|
371
|
+
print_dry_run @logs_interface.dry.server_logs([server['id']], params)
|
254
372
|
return
|
255
373
|
end
|
256
|
-
logs = @logs_interface.server_logs([
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
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
|
-
|
437
|
+
print_h1 "Morpheus Server Types - Cloud: #{zone['name']}"
|
309
438
|
if cloud_server_types.nil? || cloud_server_types.empty?
|
310
|
-
|
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
|
-
|
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
|
-
#
|
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 = ""
|