morpheus-cli 5.0.2 → 5.2.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/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/instances_interface.rb +9 -2
- data/lib/morpheus/api/servers_interface.rb +7 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/cli.rb +2 -1
- data/lib/morpheus/cli/apps.rb +3 -23
- data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
- data/lib/morpheus/cli/cli_command.rb +25 -1
- data/lib/morpheus/cli/containers_command.rb +0 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/health_command.rb +57 -0
- data/lib/morpheus/cli/hosts.rb +85 -9
- data/lib/morpheus/cli/instances.rb +85 -68
- data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +76 -0
- data/lib/morpheus/cli/option_types.rb +10 -10
- data/lib/morpheus/cli/roles.rb +193 -155
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/tasks.rb +9 -11
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +162 -68
- data/lib/morpheus/formatters.rb +34 -9
- metadata +5 -4
- data/lib/morpheus/cli/mixins/catalog_helper.rb +0 -66
@@ -203,7 +203,13 @@ module Morpheus
|
|
203
203
|
# value_label = 'SELECT'
|
204
204
|
# elsif option['type'] == 'select'
|
205
205
|
end
|
206
|
-
|
206
|
+
full_option = "--#{full_field_name} #{value_label}"
|
207
|
+
shorthand_option = option_type['shorthand']
|
208
|
+
arg1, arg2 = full_option, String
|
209
|
+
if option_type['shorthand']
|
210
|
+
arg1, arg2 = full_option, option_type['shorthand']
|
211
|
+
end
|
212
|
+
opts.on(arg1, arg2, description) do |val|
|
207
213
|
if option_type['type'] == 'checkbox'
|
208
214
|
val = (val.to_s != 'false' && val.to_s != 'off')
|
209
215
|
else
|
@@ -269,6 +275,11 @@ module Morpheus
|
|
269
275
|
build_standard_delete_options(opts, options, includes, excludes)
|
270
276
|
end
|
271
277
|
|
278
|
+
# number of decimal places to show with curreny
|
279
|
+
def default_sigdig
|
280
|
+
2
|
281
|
+
end
|
282
|
+
|
272
283
|
# appends to the passed OptionParser all the generic options
|
273
284
|
# @param opts [OptionParser] the option parser object being constructed
|
274
285
|
# @param options [Hash] the output Hash that is to being modified
|
@@ -318,6 +329,11 @@ module Morpheus
|
|
318
329
|
end
|
319
330
|
opts.add_hidden_option('--details')
|
320
331
|
|
332
|
+
when :sigdig
|
333
|
+
opts.on('--sigdig DIGITS', "Significant digits to display for prices (currency). Default is #{default_sigdig}.") do |val|
|
334
|
+
options[:sigdig] = val.to_i
|
335
|
+
end
|
336
|
+
|
321
337
|
when :options
|
322
338
|
options[:options] ||= {}
|
323
339
|
opts.on( '-O', '--option OPTION', "Option in the format -O field=\"value\"" ) do |option|
|
@@ -847,6 +863,10 @@ module Morpheus
|
|
847
863
|
self.class.command_name
|
848
864
|
end
|
849
865
|
|
866
|
+
def command_description
|
867
|
+
self.class.command_description
|
868
|
+
end
|
869
|
+
|
850
870
|
def subcommands
|
851
871
|
self.class.subcommands
|
852
872
|
end
|
@@ -921,6 +941,10 @@ module Morpheus
|
|
921
941
|
out << "\n"
|
922
942
|
}
|
923
943
|
end
|
944
|
+
if command_description
|
945
|
+
out << "\n"
|
946
|
+
out << "#{command_description}\n"
|
947
|
+
end
|
924
948
|
# out << "\n"
|
925
949
|
out
|
926
950
|
end
|
@@ -699,28 +699,4 @@ private
|
|
699
699
|
end
|
700
700
|
end
|
701
701
|
|
702
|
-
def format_container_status(container, return_color=cyan)
|
703
|
-
out = ""
|
704
|
-
status_string = container['status'].to_s
|
705
|
-
if status_string == 'running'
|
706
|
-
out << "#{green}#{status_string.upcase}#{return_color}"
|
707
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
708
|
-
out << "#{red}#{status_string.upcase}#{return_color}"
|
709
|
-
elsif status_string == 'unknown'
|
710
|
-
out << "#{white}#{status_string.upcase}#{return_color}"
|
711
|
-
else
|
712
|
-
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
713
|
-
end
|
714
|
-
out
|
715
|
-
end
|
716
|
-
|
717
|
-
def format_container_connection_string(container)
|
718
|
-
if !container['ports'].nil? && container['ports'].empty? == false
|
719
|
-
connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
|
720
|
-
else
|
721
|
-
# eh? more logic needed here i think, see taglib morph:containerLocationMenu
|
722
|
-
connection_string = "#{container['ip']}"
|
723
|
-
end
|
724
|
-
end
|
725
|
-
|
726
702
|
end
|
@@ -265,6 +265,9 @@ EOT
|
|
265
265
|
opts.on( '-v', '--value VALUE', "Secret value. This can be used to store a string instead of an object, and can also be passed as the second argument." ) do |val|
|
266
266
|
item_value = val
|
267
267
|
end
|
268
|
+
# opts.on( '--type VALUE', String, "Type, default is based on key engine, string or object" ) do |val|
|
269
|
+
# params['type'] = val
|
270
|
+
# end
|
268
271
|
opts.on( '-t', '--ttl SECONDS', "Time to live, the lease duration before this key expires." ) do |val|
|
269
272
|
ttl = val
|
270
273
|
if val.to_s.empty? || val.to_s == '0'
|
@@ -276,7 +279,7 @@ EOT
|
|
276
279
|
# opts.on( '--no-overwrite', '--no-overwrite', "Do not overwrite existing keys. Existing keys are overwritten by default." ) do
|
277
280
|
# params['overwrite'] = false
|
278
281
|
# end
|
279
|
-
build_common_options(opts, options, [:auto_confirm, :options, :payload, :json, :yaml, :csv, :fields, :outfile, :dry_run, :quiet, :remote])
|
282
|
+
build_common_options(opts, options, [:auto_confirm, :options, :payload, :query, :json, :yaml, :csv, :fields, :outfile, :dry_run, :quiet, :remote])
|
280
283
|
opts.footer = "Create or update a cypher key." + "\n" +
|
281
284
|
"[key] is required. This is the key of the cypher being created or updated. The key includes the mount prefix eg. secret/hello" + "\n" +
|
282
285
|
"[value] is required for some cypher engines, such as secret. This is the secret value or k=v pairs being stored. Supports 1-N arguments." + "\n" +
|
@@ -292,7 +295,8 @@ EOT
|
|
292
295
|
# return 1
|
293
296
|
# end
|
294
297
|
connect(options)
|
295
|
-
|
298
|
+
params.merge!(parse_query_options(options))
|
299
|
+
|
296
300
|
# parse arguments like [value] or [k=v]
|
297
301
|
if args.count == 0
|
298
302
|
# prompt for key and value
|
@@ -30,6 +30,7 @@ class Morpheus::Cli::HealthCommand
|
|
30
30
|
opts.on('-a', '--all', "Display all details: CPU, Memory, Database, etc." ) do
|
31
31
|
options[:details] = true
|
32
32
|
options[:show_cpu] = true
|
33
|
+
options[:show_threads] = true
|
33
34
|
options[:show_memory] = true
|
34
35
|
options[:show_database] = true
|
35
36
|
options[:show_elastic] = true
|
@@ -47,6 +48,9 @@ class Morpheus::Cli::HealthCommand
|
|
47
48
|
opts.on('--cpu', "Display CPU details" ) do
|
48
49
|
options[:show_cpu] = true
|
49
50
|
end
|
51
|
+
opts.on('--threads', "Display Thread details" ) do
|
52
|
+
options[:show_threads] = true
|
53
|
+
end
|
50
54
|
opts.on('--memory', "Display Memory details" ) do
|
51
55
|
options[:show_memory] = true
|
52
56
|
end
|
@@ -184,6 +188,59 @@ class Morpheus::Cli::HealthCommand
|
|
184
188
|
end
|
185
189
|
end
|
186
190
|
|
191
|
+
# Threads ()
|
192
|
+
if options[:show_threads]
|
193
|
+
print_h2 "Threads", options
|
194
|
+
if health['threads'].nil?
|
195
|
+
print yellow,"No thread information returned.",reset,"\n\n"
|
196
|
+
else
|
197
|
+
print cyan
|
198
|
+
|
199
|
+
thread_summary_columns = {
|
200
|
+
"Thread Count" => lambda {|it| it['totalThreads'].size rescue '' },
|
201
|
+
"Busy Threads" => lambda {|it| it['busyThreads'].size rescue '' },
|
202
|
+
"Running Threads" => lambda {|it| it['runningThreads'].size rescue '' },
|
203
|
+
"Blocked Threads" => lambda {|it| it['blockedThreads'].size rescue '' },
|
204
|
+
}
|
205
|
+
print_description_list(thread_summary_columns, health['threads'], options)
|
206
|
+
|
207
|
+
|
208
|
+
thread_columns = [
|
209
|
+
{"Name".upcase => lambda {|it| it['name']} },
|
210
|
+
{"Status".upcase => lambda {|it|
|
211
|
+
# hrmm
|
212
|
+
status_string = (it['status'] || it['state']).to_s.downcase
|
213
|
+
status_color = cyan
|
214
|
+
# if status_string.include?('waiting')
|
215
|
+
# status_color = yellow
|
216
|
+
# end
|
217
|
+
"#{status_color}#{status_string.upcase}#{cyan}"
|
218
|
+
} },
|
219
|
+
# {"CPU Time" => lambda {|it| it['cpuTime'].to_s } },
|
220
|
+
# {"CPU Time" => lambda {|it| format_human_duration(it['cpuTime'].to_f / 1000) rescue '' } },
|
221
|
+
{"CPU Percent" => lambda {|it| it['cpuPercent'].to_i.to_s + '%' } }
|
222
|
+
]
|
223
|
+
|
224
|
+
if health['threads']['busyThreads'] && health['threads']['busyThreads'].size > 0
|
225
|
+
print_h2 "Busy Threads"
|
226
|
+
print cyan
|
227
|
+
print as_pretty_table(health['threads']['busyThreads'], thread_columns, options)
|
228
|
+
end
|
229
|
+
|
230
|
+
if health['threads']['runningThreads'] && health['threads']['runningThreads'].size > 0
|
231
|
+
print_h2 "Running Threads"
|
232
|
+
print cyan
|
233
|
+
print as_pretty_table(health['threads']['runningThreads'], thread_columns, options)
|
234
|
+
end
|
235
|
+
|
236
|
+
if health['threads']['blockedThreads'] && health['threads']['blockedThreads'].size > 0
|
237
|
+
print_h2 "Blocked Threads"
|
238
|
+
print cyan
|
239
|
+
print as_pretty_table(health['threads']['blockedThreads'], thread_columns, options)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
187
244
|
# Memory
|
188
245
|
if options[:show_memory]
|
189
246
|
if health['memory'].nil?
|
data/lib/morpheus/cli/hosts.rb
CHANGED
@@ -17,7 +17,7 @@ class Morpheus::Cli::Hosts
|
|
17
17
|
set_command_name :hosts
|
18
18
|
set_command_description "View and manage hosts (servers)."
|
19
19
|
register_subcommands :list, :count, :get, :view, :stats, :add, :update, :remove, :logs, :start, :stop, :resize,
|
20
|
-
:run_workflow, :make_managed, :upgrade_agent, :snapshots,
|
20
|
+
:run_workflow, :make_managed, :upgrade_agent, :snapshots, :software,
|
21
21
|
{:'types' => :list_types},
|
22
22
|
{:exec => :execution_request},
|
23
23
|
:wiki, :update_wiki
|
@@ -54,9 +54,6 @@ class Morpheus::Cli::Hosts
|
|
54
54
|
params = {}
|
55
55
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
56
56
|
opts.banner = subcommand_usage()
|
57
|
-
opts.on( '-a', '--account ACCOUNT', "Account Name or ID" ) do |val|
|
58
|
-
options[:account] = val
|
59
|
-
end
|
60
57
|
opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
|
61
58
|
options[:group] = val
|
62
59
|
end
|
@@ -82,6 +79,17 @@ class Morpheus::Cli::Hosts
|
|
82
79
|
# params[:clusterId] = val
|
83
80
|
options[:cluster] = val
|
84
81
|
end
|
82
|
+
opts.on( '--plan NAME', String, "Filter by Plan name(s)" ) do |val|
|
83
|
+
# commas used in names a lot so use --plan one --plan two
|
84
|
+
params['plan'] ||= []
|
85
|
+
params['plan'] << val
|
86
|
+
end
|
87
|
+
opts.on( '--plan-id ID', String, "Filter by Plan id(s)" ) do |val|
|
88
|
+
params['planId'] = parse_id_list(val)
|
89
|
+
end
|
90
|
+
opts.on( '--plan-code CODE', String, "Filter by Plan code(s)" ) do |val|
|
91
|
+
params['planCode'] = parse_id_list(val)
|
92
|
+
end
|
85
93
|
opts.on( '', '--vm', "Show only virtual machines" ) do |val|
|
86
94
|
params[:vm] = true
|
87
95
|
end
|
@@ -106,8 +114,8 @@ class Morpheus::Cli::Hosts
|
|
106
114
|
opts.on( '--created-by USER', "Created By User Username or ID" ) do |val|
|
107
115
|
options[:created_by] = val
|
108
116
|
end
|
109
|
-
opts.on('--
|
110
|
-
options[:
|
117
|
+
opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
|
118
|
+
options[:account] = val
|
111
119
|
end
|
112
120
|
opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
|
113
121
|
val.split(",").each do |value_pair|
|
@@ -123,6 +131,12 @@ class Morpheus::Cli::Hosts
|
|
123
131
|
opts.on('--non-tag-compliant', "Displays only servers with tag compliance warnings." ) do
|
124
132
|
params[:tagCompliant] = false
|
125
133
|
end
|
134
|
+
opts.on('--stats', "Display values for memory and storage usage used / max values." ) do
|
135
|
+
options[:stats] = true
|
136
|
+
end
|
137
|
+
opts.on('-a', '--details', "Display all details: hostname, private ip, plan, stats, etc." ) do
|
138
|
+
options[:details] = true
|
139
|
+
end
|
126
140
|
build_standard_list_options(opts, options)
|
127
141
|
opts.footer = "List hosts."
|
128
142
|
end
|
@@ -208,6 +222,9 @@ class Morpheus::Cli::Hosts
|
|
208
222
|
multi_tenant = json_response['multiTenant'] == true
|
209
223
|
title = "Morpheus Hosts"
|
210
224
|
subtitles = []
|
225
|
+
if account
|
226
|
+
subtitles << "Tenant: #{account['name']}".strip
|
227
|
+
end
|
211
228
|
if group
|
212
229
|
subtitles << "Group: #{group['name']}".strip
|
213
230
|
end
|
@@ -248,7 +265,7 @@ class Morpheus::Cli::Hosts
|
|
248
265
|
cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
|
249
266
|
memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
|
250
267
|
storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
|
251
|
-
if options[:details]
|
268
|
+
if options[:details] || options[:stats]
|
252
269
|
if stats['maxMemory'] && stats['maxMemory'].to_i != 0
|
253
270
|
memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
|
254
271
|
end
|
@@ -259,12 +276,14 @@ class Morpheus::Cli::Hosts
|
|
259
276
|
row = {
|
260
277
|
id: server['id'],
|
261
278
|
name: server['name'],
|
279
|
+
external_name: server['externalName'],
|
262
280
|
hostname: server['hostname'],
|
263
281
|
platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
|
264
282
|
type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
|
265
283
|
tenant: server['account'] ? server['account']['name'] : server['accountId'],
|
266
284
|
owner: server['owner'] ? server['owner']['username'] : server['owner'],
|
267
285
|
cloud: server['zone'] ? server['zone']['name'] : '',
|
286
|
+
plan: server['plan'] ? server['plan']['name'] : '',
|
268
287
|
ip: server['externalIp'],
|
269
288
|
internal_ip: server['internalIp'],
|
270
289
|
nodes: server['containers'] ? server['containers'].size : '',
|
@@ -283,11 +302,13 @@ class Morpheus::Cli::Hosts
|
|
283
302
|
columns = {
|
284
303
|
"ID" => :id,
|
285
304
|
"Name" => :name,
|
305
|
+
"External Name" => :external_name,
|
286
306
|
"Hostname" => :hostname,
|
287
307
|
"Type" => :type,
|
288
308
|
"Owner" => :owner,
|
289
309
|
"Tenant" => :tenant,
|
290
310
|
"Cloud" => :cloud,
|
311
|
+
"Plan" => :plan,
|
291
312
|
"IP" => :ip,
|
292
313
|
"Private IP" => :internal_ip,
|
293
314
|
"Nodes" => :nodes,
|
@@ -300,7 +321,9 @@ class Morpheus::Cli::Hosts
|
|
300
321
|
"Updated" => :updated,
|
301
322
|
}
|
302
323
|
if options[:details] != true
|
324
|
+
columns.delete("External Name")
|
303
325
|
columns.delete("Hostname")
|
326
|
+
columns.delete("Plan")
|
304
327
|
columns.delete("Private IP")
|
305
328
|
columns.delete("Owner")
|
306
329
|
columns.delete("Tenant")
|
@@ -308,6 +331,10 @@ class Morpheus::Cli::Hosts
|
|
308
331
|
columns.delete("Created")
|
309
332
|
columns.delete("Updated")
|
310
333
|
end
|
334
|
+
# hide External Name if there are none
|
335
|
+
if !servers.find {|it| it['externalName'] && it['externalName'] != it['name']}
|
336
|
+
columns.delete("External Name")
|
337
|
+
end
|
311
338
|
if !multi_tenant
|
312
339
|
columns.delete("Tenant")
|
313
340
|
end
|
@@ -334,7 +361,7 @@ class Morpheus::Cli::Hosts
|
|
334
361
|
options = {}
|
335
362
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
336
363
|
opts.banner = subcommand_usage("[options]")
|
337
|
-
opts.on( '
|
364
|
+
opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
|
338
365
|
options[:account] = val
|
339
366
|
end
|
340
367
|
opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
|
@@ -1909,6 +1936,55 @@ class Morpheus::Cli::Hosts
|
|
1909
1936
|
end
|
1910
1937
|
end
|
1911
1938
|
|
1939
|
+
def software(args)
|
1940
|
+
options = {}
|
1941
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1942
|
+
opts.banner = subcommand_usage("[host]")
|
1943
|
+
build_standard_list_options(opts, options)
|
1944
|
+
end
|
1945
|
+
optparse.parse!(args)
|
1946
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1947
|
+
connect(options)
|
1948
|
+
begin
|
1949
|
+
server = find_host_by_name_or_id(args[0])
|
1950
|
+
return 1 if server.nil?
|
1951
|
+
params = {}
|
1952
|
+
params.merge!(parse_list_options(options))
|
1953
|
+
@servers_interface.setopts(options)
|
1954
|
+
if options[:dry_run]
|
1955
|
+
print_dry_run @servers_interface.dry.software(server['id'], params)
|
1956
|
+
return
|
1957
|
+
end
|
1958
|
+
json_response = @servers_interface.software(server['id'], params)
|
1959
|
+
software = json_response['software']
|
1960
|
+
render_response(json_response, options, 'software') do
|
1961
|
+
print_h1 "Software: #{server['name']}", [], options
|
1962
|
+
if software.empty?
|
1963
|
+
print cyan,"No software found",reset,"\n"
|
1964
|
+
else
|
1965
|
+
software_column_definitions = {
|
1966
|
+
# "ID" => lambda {|it| it['id'] },
|
1967
|
+
"Name" => lambda {|it| it['name'] },
|
1968
|
+
"Version" => lambda {|it| it['packageVersion'] },
|
1969
|
+
"Publisher" => lambda {|it| it['packagePublisher'] },
|
1970
|
+
# "Release" => lambda {|it| it['packageRelease'] },
|
1971
|
+
# "Type" => lambda {|it| it['packageType'] },
|
1972
|
+
# "Architecture" => lambda {|it| it['architecture'] },
|
1973
|
+
# "Install Date" => lambda {|it| format_local_dt(it['installDate']) },
|
1974
|
+
}
|
1975
|
+
print cyan
|
1976
|
+
print as_pretty_table(software, software_column_definitions.upcase_keys!, options)
|
1977
|
+
print_results_pagination({size: software.size, total: software.size})
|
1978
|
+
end
|
1979
|
+
print reset, "\n"
|
1980
|
+
end
|
1981
|
+
return 0
|
1982
|
+
rescue RestClient::Exception => e
|
1983
|
+
print_rest_exception(e, options)
|
1984
|
+
exit 1
|
1985
|
+
end
|
1986
|
+
end
|
1987
|
+
|
1912
1988
|
private
|
1913
1989
|
|
1914
1990
|
def find_host_by_id(id)
|
@@ -2061,7 +2137,7 @@ class Morpheus::Cli::Hosts
|
|
2061
2137
|
def make_managed_option_types(connected=true)
|
2062
2138
|
[
|
2063
2139
|
#{'fieldName' => 'account', 'fieldLabel' => 'Account', 'type' => 'select', 'optionSource' => 'accounts', 'required' => true},
|
2064
|
-
{'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text'
|
2140
|
+
{'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text'},
|
2065
2141
|
{'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false},
|
2066
2142
|
{'fieldName' => 'serverOs', 'fieldLabel' => 'OS Type', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false},
|
2067
2143
|
]
|
@@ -19,7 +19,7 @@ class Morpheus::Cli::Instances
|
|
19
19
|
:history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details},
|
20
20
|
:stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :stop_service, :start_service, :restart_service,
|
21
21
|
:backup, :backups, :resize, :clone, :envs, :setenv, :delenv,
|
22
|
-
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshots,
|
22
|
+
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshot, :snapshots,
|
23
23
|
:console, :status_check, {:containers => :list_containers},
|
24
24
|
:scaling, {:'scaling-update' => :scaling_update},
|
25
25
|
:wiki, :update_wiki,
|
@@ -80,9 +80,6 @@ class Morpheus::Cli::Instances
|
|
80
80
|
options[:owner] = val
|
81
81
|
end
|
82
82
|
opts.add_hidden_option('--created-by')
|
83
|
-
opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
|
84
|
-
options[:details] = true
|
85
|
-
end
|
86
83
|
opts.on('--status STATUS', "Filter by status i.e. provisioning,running,starting,stopping") do |val|
|
87
84
|
params['status'] = (params['status'] || []) + val.to_s.split(',').collect {|s| s.strip }.select {|s| s != "" }
|
88
85
|
end
|
@@ -92,6 +89,17 @@ class Morpheus::Cli::Instances
|
|
92
89
|
opts.on('--pending-removal-only', "Only instances pending removal.") do
|
93
90
|
options[:deleted] = true
|
94
91
|
end
|
92
|
+
opts.on( '--plan NAME', String, "Filter by Plan name(s)" ) do |val|
|
93
|
+
# commas used in names a lot so use --plan one --plan two
|
94
|
+
params['plan'] ||= []
|
95
|
+
params['plan'] << val
|
96
|
+
end
|
97
|
+
opts.on( '--plan-id ID', String, "Filter by Plan id(s)" ) do |val|
|
98
|
+
params['planId'] = parse_id_list(val)
|
99
|
+
end
|
100
|
+
opts.on( '--plan-code CODE', String, "Filter by Plan code(s)" ) do |val|
|
101
|
+
params['planCode'] = parse_id_list(val)
|
102
|
+
end
|
95
103
|
opts.on('--labels label',String, "Filter by labels (keywords).") do |val|
|
96
104
|
val.split(",").each do |k|
|
97
105
|
options[:labels] ||= []
|
@@ -106,6 +114,12 @@ class Morpheus::Cli::Instances
|
|
106
114
|
options[:tags][k] << (v || '')
|
107
115
|
end
|
108
116
|
end
|
117
|
+
opts.on('--stats', "Display values for memory and storage usage used / max values." ) do
|
118
|
+
options[:stats] = true
|
119
|
+
end
|
120
|
+
opts.on('-a', '--details', "Display all details: plan, stats, etc" ) do
|
121
|
+
options[:details] = true
|
122
|
+
end
|
109
123
|
build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
110
124
|
opts.footer = "List instances."
|
111
125
|
end
|
@@ -216,7 +230,7 @@ class Morpheus::Cli::Instances
|
|
216
230
|
cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
|
217
231
|
memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
|
218
232
|
storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
|
219
|
-
if options[:details]
|
233
|
+
if options[:details] || options[:stats]
|
220
234
|
if stats['maxMemory'] && stats['maxMemory'].to_i != 0
|
221
235
|
memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
|
222
236
|
end
|
@@ -234,8 +248,9 @@ class Morpheus::Cli::Instances
|
|
234
248
|
nodes: instance['containers'].count,
|
235
249
|
status: format_instance_status(instance, cyan),
|
236
250
|
type: instance['instanceType']['name'],
|
237
|
-
group:
|
238
|
-
cloud:
|
251
|
+
group: instance['group'] ? instance['group']['name'] : nil,
|
252
|
+
cloud: instance['cloud'] ? instance['cloud']['name'] : nil,
|
253
|
+
plan: instance['plan'] ? instance['plan']['name'] : '',
|
239
254
|
version: instance['instanceVersion'] ? instance['instanceVersion'] : '',
|
240
255
|
created: format_local_dt(instance['dateCreated']),
|
241
256
|
cpu: cpu_usage_str + cyan,
|
@@ -249,12 +264,13 @@ class Morpheus::Cli::Instances
|
|
249
264
|
{:created => {:display_name => "CREATED"}},
|
250
265
|
# {:tenant => {:display_name => "TENANT"}},
|
251
266
|
{:user => {:display_name => "OWNER", :max_width => 20}},
|
267
|
+
:plan,
|
252
268
|
:nodes, {:connection => {:max_width => 30}}, :status, :cpu, :memory, :storage]
|
253
269
|
# custom pretty table columns ... this is handled in as_pretty_table now(),
|
254
270
|
# todo: remove all these.. and try to always pass rows as the json data itself..
|
255
|
-
|
256
|
-
|
257
|
-
|
271
|
+
if options[:details] != true
|
272
|
+
columns.delete(:plan)
|
273
|
+
end
|
258
274
|
print cyan
|
259
275
|
print as_pretty_table(rows, columns, options)
|
260
276
|
print reset
|
@@ -1237,7 +1253,15 @@ class Morpheus::Cli::Instances
|
|
1237
1253
|
instance = json_response['instance']
|
1238
1254
|
stats = instance['stats'] || json_response['stats'] || {}
|
1239
1255
|
# load_balancers = json_response['loadBalancers'] || {}
|
1240
|
-
|
1256
|
+
# metadata tags used to be returned as metadata and are now returned as tags
|
1257
|
+
# the problem is tags is what we used to call Labels (keywords)
|
1258
|
+
# the api will change to tags and labels, so handle the old format as long as metadata is returned.
|
1259
|
+
tags, labels = nil, nil
|
1260
|
+
if instance.key?('metadata')
|
1261
|
+
tags, labels = instance['metadata'], instance['tags']
|
1262
|
+
else
|
1263
|
+
tags, labels = instance['tags'], instance['labels']
|
1264
|
+
end
|
1241
1265
|
# containers are fetched via separate api call
|
1242
1266
|
containers = nil
|
1243
1267
|
if options[:include_containers]
|
@@ -1279,7 +1303,7 @@ class Morpheus::Cli::Instances
|
|
1279
1303
|
# "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
1280
1304
|
"Environment" => 'instanceContext',
|
1281
1305
|
"Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
|
1282
|
-
"
|
1306
|
+
"Tags" => lambda {|it| tags ? tags.collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
1283
1307
|
"Owner" => lambda {|it|
|
1284
1308
|
if it['owner']
|
1285
1309
|
(it['owner']['username'] || it['owner']['id'])
|
@@ -1298,11 +1322,14 @@ class Morpheus::Cli::Instances
|
|
1298
1322
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
1299
1323
|
"Status" => lambda {|it| format_instance_status(it) }
|
1300
1324
|
}
|
1325
|
+
description_cols.delete("Labels") if labels.nil? || labels.empty?
|
1326
|
+
description_cols.delete("Tags") if tags.nil? || tags.empty?
|
1301
1327
|
description_cols.delete("Power Schedule") if instance['powerSchedule'].nil?
|
1302
1328
|
description_cols.delete("Expire Date") if instance['expireDate'].nil?
|
1303
1329
|
description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
|
1304
1330
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
1305
1331
|
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
1332
|
+
#description_cols.delete("Environment") if instance['instanceContext'].nil?
|
1306
1333
|
print_description_list(description_cols, instance)
|
1307
1334
|
|
1308
1335
|
if instance['statusMessage']
|
@@ -2616,6 +2643,48 @@ class Morpheus::Cli::Instances
|
|
2616
2643
|
end
|
2617
2644
|
end
|
2618
2645
|
|
2646
|
+
def snapshot(args)
|
2647
|
+
options = {}
|
2648
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2649
|
+
opts.banner = subcommand_usage("[instance]")
|
2650
|
+
opts.on( '--name VALUE', String, "Snapshot Name. Default is server name + timestamp" ) do |val|
|
2651
|
+
options[:options]['name'] = val
|
2652
|
+
end
|
2653
|
+
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2654
|
+
options[:options]['description'] = val
|
2655
|
+
end
|
2656
|
+
build_standard_add_options(opts, options, [:auto_confirm])
|
2657
|
+
opts.footer = <<-EOT
|
2658
|
+
Create a snapshot for an instance.
|
2659
|
+
[instance] is required. This is the name or id of an instance
|
2660
|
+
EOT
|
2661
|
+
end
|
2662
|
+
optparse.parse!(args)
|
2663
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2664
|
+
connect(options)
|
2665
|
+
instance = find_instance_by_name_or_id(args[0])
|
2666
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the instance '#{instance['name']}'?", options)
|
2667
|
+
exit 1
|
2668
|
+
end
|
2669
|
+
payload = {}
|
2670
|
+
if options[:payload]
|
2671
|
+
payload = options[:payload]
|
2672
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2673
|
+
else
|
2674
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2675
|
+
end
|
2676
|
+
@instances_interface.setopts(options)
|
2677
|
+
if options[:dry_run]
|
2678
|
+
print_dry_run @instances_interface.dry.snapshot(instance['id'], payload)
|
2679
|
+
return
|
2680
|
+
end
|
2681
|
+
json_response = @instances_interface.snapshot(instance['id'], payload)
|
2682
|
+
render_response(json_response, options, 'snapshots') do
|
2683
|
+
print_green_success "Snapshot initiated."
|
2684
|
+
end
|
2685
|
+
return 0, nil
|
2686
|
+
end
|
2687
|
+
|
2619
2688
|
def remove(args)
|
2620
2689
|
options = {}
|
2621
2690
|
query_params = {}
|
@@ -2925,6 +2994,10 @@ class Morpheus::Cli::Instances
|
|
2925
2994
|
# no pagination yet
|
2926
2995
|
# build_standard_list_options(opts, options)
|
2927
2996
|
build_standard_get_options(opts, options)
|
2997
|
+
opts.footer = <<-EOT
|
2998
|
+
List snapshots for an instance.
|
2999
|
+
[instance] is required. This is the name or id of an instance
|
3000
|
+
EOT
|
2928
3001
|
end
|
2929
3002
|
optparse.parse!(args)
|
2930
3003
|
verify_args!(args:args, optparse:optparse, count:1)
|
@@ -3876,51 +3949,6 @@ private
|
|
3876
3949
|
end
|
3877
3950
|
end
|
3878
3951
|
|
3879
|
-
def format_instance_status(instance, return_color=cyan)
|
3880
|
-
out = ""
|
3881
|
-
status_string = instance['status'].to_s
|
3882
|
-
if status_string == 'running'
|
3883
|
-
out << "#{green}#{status_string.upcase}#{return_color}"
|
3884
|
-
elsif status_string == 'provisioning'
|
3885
|
-
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
3886
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
3887
|
-
out << "#{red}#{status_string.upcase}#{return_color}"
|
3888
|
-
else
|
3889
|
-
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
3890
|
-
end
|
3891
|
-
out
|
3892
|
-
end
|
3893
|
-
|
3894
|
-
def format_instance_connection_string(instance)
|
3895
|
-
if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
|
3896
|
-
connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
|
3897
|
-
end
|
3898
|
-
end
|
3899
|
-
|
3900
|
-
def format_container_status(container, return_color=cyan)
|
3901
|
-
out = ""
|
3902
|
-
status_string = container['status'].to_s
|
3903
|
-
if status_string == 'running'
|
3904
|
-
out << "#{green}#{status_string.upcase}#{return_color}"
|
3905
|
-
elsif status_string == 'provisioning'
|
3906
|
-
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
3907
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
3908
|
-
out << "#{red}#{status_string.upcase}#{return_color}"
|
3909
|
-
else
|
3910
|
-
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
3911
|
-
end
|
3912
|
-
out
|
3913
|
-
end
|
3914
|
-
|
3915
|
-
def format_container_connection_string(container)
|
3916
|
-
if !container['ports'].nil? && container['ports'].empty? == false
|
3917
|
-
connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
|
3918
|
-
else
|
3919
|
-
# eh? more logic needed here i think, see taglib morph:containerLocationMenu
|
3920
|
-
connection_string = "#{container['ip']}"
|
3921
|
-
end
|
3922
|
-
end
|
3923
|
-
|
3924
3952
|
def instance_scaling_option_types(instance=nil)
|
3925
3953
|
|
3926
3954
|
# Group
|
@@ -3974,17 +4002,6 @@ private
|
|
3974
4002
|
list
|
3975
4003
|
end
|
3976
4004
|
|
3977
|
-
def format_instance_container_display_name(instance, plural=false)
|
3978
|
-
#<span class="info-label">${[null,'docker'].contains(instance.layout?.provisionType?.code) ? 'Containers' : 'Virtual Machines'}:</span> <span class="info-value">${instance.containers?.size()}</span>
|
3979
|
-
v = plural ? "Containers" : "Container"
|
3980
|
-
if instance && instance['layout'] && instance['layout'].key?("provisionTypeCode")
|
3981
|
-
if [nil, 'docker'].include?(instance['layout']["provisionTypeCode"])
|
3982
|
-
v = plural ? "Virtual Machines" : "Virtual Machine"
|
3983
|
-
end
|
3984
|
-
end
|
3985
|
-
return v
|
3986
|
-
end
|
3987
|
-
|
3988
4005
|
def print_instance_threshold_description_list(instance_threshold)
|
3989
4006
|
description_cols = {
|
3990
4007
|
# "Instance" => lambda {|it| "#{instance['id']} - #{instance['name']}" },
|