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.
@@ -203,7 +203,13 @@ module Morpheus
203
203
  # value_label = 'SELECT'
204
204
  # elsif option['type'] == 'select'
205
205
  end
206
- opts.on("--#{full_field_name} #{value_label}", String, description) do |val|
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?
@@ -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('--details', "Display more details: memory and storage usage used / max values." ) do
110
- options[:details] = true
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( '-a', '--account ACCOUNT', "Account Name or ID" ) do |val|
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', 'required' => true},
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: !instance['group'].nil? ? instance['group']['name'] : nil,
238
- cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil,
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
- # if options[:include_fields]
256
- # columns = options[:include_fields]
257
- # end
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
- "Metadata" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
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']}" },