morpheus-cli 4.2.22 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Dockerfile +1 -1
  4. data/lib/morpheus/api/api_client.rb +30 -0
  5. data/lib/morpheus/api/billing_interface.rb +34 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  7. data/lib/morpheus/api/deploy_interface.rb +1 -1
  8. data/lib/morpheus/api/deployments_interface.rb +20 -1
  9. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  10. data/lib/morpheus/api/instances_interface.rb +16 -2
  11. data/lib/morpheus/api/rest_interface.rb +0 -6
  12. data/lib/morpheus/api/roles_interface.rb +14 -0
  13. data/lib/morpheus/api/search_interface.rb +13 -0
  14. data/lib/morpheus/api/servers_interface.rb +14 -0
  15. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  16. data/lib/morpheus/api/usage_interface.rb +18 -0
  17. data/lib/morpheus/cli.rb +7 -3
  18. data/lib/morpheus/cli/apps.rb +6 -27
  19. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  20. data/lib/morpheus/cli/backups_command.rb +3 -0
  21. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  22. data/lib/morpheus/cli/cli_command.rb +70 -21
  23. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  24. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  25. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  26. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  27. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  28. data/lib/morpheus/cli/containers_command.rb +14 -24
  29. data/lib/morpheus/cli/cypher_command.rb +6 -2
  30. data/lib/morpheus/cli/deploy.rb +199 -90
  31. data/lib/morpheus/cli/deployments.rb +341 -28
  32. data/lib/morpheus/cli/deploys.rb +206 -41
  33. data/lib/morpheus/cli/error_handler.rb +7 -0
  34. data/lib/morpheus/cli/forgot_password.rb +133 -0
  35. data/lib/morpheus/cli/groups.rb +1 -1
  36. data/lib/morpheus/cli/health_command.rb +59 -2
  37. data/lib/morpheus/cli/hosts.rb +295 -35
  38. data/lib/morpheus/cli/instances.rb +247 -130
  39. data/lib/morpheus/cli/invoices_command.rb +37 -19
  40. data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
  41. data/lib/morpheus/cli/library_option_types_command.rb +5 -2
  42. data/lib/morpheus/cli/logs_command.rb +9 -6
  43. data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
  44. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  45. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  46. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  47. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +108 -5
  49. data/lib/morpheus/cli/option_types.rb +271 -22
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/remote.rb +35 -12
  52. data/lib/morpheus/cli/reports_command.rb +99 -30
  53. data/lib/morpheus/cli/roles.rb +453 -113
  54. data/lib/morpheus/cli/search_command.rb +182 -0
  55. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  56. data/lib/morpheus/cli/setup.rb +1 -1
  57. data/lib/morpheus/cli/shell.rb +33 -11
  58. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  59. data/lib/morpheus/cli/tasks.rb +29 -32
  60. data/lib/morpheus/cli/usage_command.rb +203 -0
  61. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  62. data/lib/morpheus/cli/users.rb +12 -1
  63. data/lib/morpheus/cli/version.rb +1 -1
  64. data/lib/morpheus/cli/virtual_images.rb +429 -254
  65. data/lib/morpheus/cli/whoami.rb +6 -6
  66. data/lib/morpheus/cli/workflows.rb +33 -40
  67. data/lib/morpheus/formatters.rb +75 -7
  68. data/lib/morpheus/terminal.rb +6 -2
  69. metadata +14 -2
@@ -52,7 +52,7 @@ class Morpheus::Cli::Groups
52
52
  end
53
53
  json_response = @groups_interface.list(params)
54
54
  exit_code, err = 0, nil
55
- render_response(json_response, options) do
55
+ render_response(json_response, options, "groups") do
56
56
  groups = json_response['groups']
57
57
  subtitles = []
58
58
  subtitles += parse_list_subtitles(options)
@@ -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?
@@ -475,8 +532,8 @@ class Morpheus::Cli::HealthCommand
475
532
  opts.footer = "List health logs. These are the logs of the morpheus appliance itself."
476
533
  end
477
534
  optparse.parse!(args)
478
- if args.count != 0
479
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
535
+ if args.count > 0
536
+ options[:phrase] = args.join(" ")
480
537
  end
481
538
  connect(options)
482
539
  begin
@@ -16,10 +16,11 @@ class Morpheus::Cli::Hosts
16
16
  include Morpheus::Cli::LogsHelper
17
17
  set_command_name :hosts
18
18
  set_command_description "View and manage hosts (servers)."
19
- register_subcommands :list, :count, :get, :view, :stats, :add, :update, :remove, :logs, :start, :stop, :resize, :run_workflow, :make_managed, :upgrade_agent
20
- register_subcommands :'types' => :list_types
21
- register_subcommands :exec => :execution_request
22
- register_subcommands :wiki, :update_wiki
19
+ register_subcommands :list, :count, :get, :view, :stats, :add, :update, :remove, :logs, :start, :stop, :resize,
20
+ :run_workflow, :make_managed, :upgrade_agent, :snapshots, :software,
21
+ {:'types' => :list_types},
22
+ {:exec => :execution_request},
23
+ :wiki, :update_wiki
23
24
  alias_subcommand :details, :get
24
25
  set_default_subcommand :list
25
26
 
@@ -53,9 +54,6 @@ class Morpheus::Cli::Hosts
53
54
  params = {}
54
55
  optparse = Morpheus::Cli::OptionParser.new do |opts|
55
56
  opts.banner = subcommand_usage()
56
- opts.on( '-a', '--account ACCOUNT', "Account Name or ID" ) do |val|
57
- options[:account] = val
58
- end
59
57
  opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
60
58
  options[:group] = val
61
59
  end
@@ -81,6 +79,17 @@ class Morpheus::Cli::Hosts
81
79
  # params[:clusterId] = val
82
80
  options[:cluster] = val
83
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
84
93
  opts.on( '', '--vm', "Show only virtual machines" ) do |val|
85
94
  params[:vm] = true
86
95
  end
@@ -105,8 +114,16 @@ class Morpheus::Cli::Hosts
105
114
  opts.on( '--created-by USER', "Created By User Username or ID" ) do |val|
106
115
  options[:created_by] = val
107
116
  end
108
- opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
109
- options[:details] = true
117
+ opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
118
+ options[:account] = val
119
+ end
120
+ opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
121
+ val.split(",").each do |value_pair|
122
+ k,v = value_pair.strip.split("=")
123
+ options[:tags] ||= {}
124
+ options[:tags][k] ||= []
125
+ options[:tags][k] << (v || '')
126
+ end
110
127
  end
111
128
  opts.on('--tag-compliant', "Displays only servers that are valid according to applied tag policies. Does not show servers that do not have tag policies." ) do
112
129
  params[:tagCompliant] = true
@@ -114,11 +131,21 @@ class Morpheus::Cli::Hosts
114
131
  opts.on('--non-tag-compliant', "Displays only servers with tag compliance warnings." ) do
115
132
  params[:tagCompliant] = false
116
133
  end
117
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
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
140
+ build_standard_list_options(opts, options)
118
141
  opts.footer = "List hosts."
119
142
  end
120
143
  optparse.parse!(args)
121
144
  connect(options)
145
+ # verify_args!(args:args, optparse:optparse, count:0)
146
+ if args.count > 0
147
+ options[:phrase] = args.join(" ")
148
+ end
122
149
  begin
123
150
  params.merge!(parse_list_options(options))
124
151
  account = nil
@@ -159,6 +186,11 @@ class Morpheus::Cli::Hosts
159
186
  params['clusterId'] = cluster['id']
160
187
  end
161
188
  end
189
+ if options[:tags] && !options[:tags].empty?
190
+ options[:tags].each do |k,v|
191
+ params['tags.' + k] = v
192
+ end
193
+ end
162
194
 
163
195
  @servers_interface.setopts(options)
164
196
  if options[:dry_run]
@@ -190,6 +222,9 @@ class Morpheus::Cli::Hosts
190
222
  multi_tenant = json_response['multiTenant'] == true
191
223
  title = "Morpheus Hosts"
192
224
  subtitles = []
225
+ if account
226
+ subtitles << "Tenant: #{account['name']}".strip
227
+ end
193
228
  if group
194
229
  subtitles << "Group: #{group['name']}".strip
195
230
  end
@@ -230,7 +265,7 @@ class Morpheus::Cli::Hosts
230
265
  cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
231
266
  memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
232
267
  storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
233
- if options[:details]
268
+ if options[:details] || options[:stats]
234
269
  if stats['maxMemory'] && stats['maxMemory'].to_i != 0
235
270
  memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
236
271
  end
@@ -240,31 +275,76 @@ class Morpheus::Cli::Hosts
240
275
  end
241
276
  row = {
242
277
  id: server['id'],
243
- tenant: server['account'] ? server['account']['name'] : server['accountId'],
244
278
  name: server['name'],
279
+ external_name: server['externalName'],
280
+ hostname: server['hostname'],
245
281
  platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
246
- cloud: server['zone'] ? server['zone']['name'] : '',
247
282
  type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
283
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
284
+ owner: server['owner'] ? server['owner']['username'] : server['owner'],
285
+ cloud: server['zone'] ? server['zone']['name'] : '',
286
+ plan: server['plan'] ? server['plan']['name'] : '',
287
+ ip: server['externalIp'],
288
+ internal_ip: server['internalIp'],
248
289
  nodes: server['containers'] ? server['containers'].size : '',
249
- status: format_server_status(server, cyan),
290
+ # status: format_server_status(server, cyan),
291
+ status: (options[:details]||options[:all_fields]) ? format_server_status(server, cyan) : format_server_status_friendly(server, cyan),
250
292
  power: format_server_power_state(server, cyan),
251
293
  cpu: cpu_usage_str + cyan,
252
294
  memory: memory_usage_str + cyan,
253
- storage: storage_usage_str + cyan
295
+ storage: storage_usage_str + cyan,
296
+ created: format_local_dt(server['dateCreated']),
297
+ updated: format_local_dt(server['lastUpdated']),
254
298
  }
255
299
  row
256
300
  }
257
- columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
258
- if multi_tenant
259
- columns.insert(4, :tenant)
301
+ # columns = [:id, :name, :type, :cloud, :ip, :internal_ip, :nodes, :status, :power]
302
+ columns = {
303
+ "ID" => :id,
304
+ "Name" => :name,
305
+ "External Name" => :external_name,
306
+ "Hostname" => :hostname,
307
+ "Type" => :type,
308
+ "Owner" => :owner,
309
+ "Tenant" => :tenant,
310
+ "Cloud" => :cloud,
311
+ "Plan" => :plan,
312
+ "IP" => :ip,
313
+ "Private IP" => :internal_ip,
314
+ "Nodes" => :nodes,
315
+ "Status" => :status,
316
+ "Power" => :power,
317
+ "CPU" => :cpu,
318
+ "Memory" => :memory,
319
+ "Storage" => :storage,
320
+ "Created" => :created,
321
+ "Updated" => :updated,
322
+ }
323
+ if options[:details] != true
324
+ columns.delete("External Name")
325
+ columns.delete("Hostname")
326
+ columns.delete("Plan")
327
+ columns.delete("Private IP")
328
+ columns.delete("Owner")
329
+ columns.delete("Tenant")
330
+ columns.delete("Power")
331
+ columns.delete("Created")
332
+ columns.delete("Updated")
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")
260
337
  end
261
- columns += [:cpu, :memory, :storage]
262
- # custom pretty table columns ...
263
- if options[:include_fields]
264
- columns = options[:include_fields]
338
+ if !multi_tenant
339
+ columns.delete("Tenant")
265
340
  end
341
+ # columns += [:cpu, :memory, :storage]
342
+ # # custom pretty table columns ...
343
+ # if options[:include_fields]
344
+ # columns = options[:include_fields]
345
+ # end
266
346
  print cyan
267
- print as_pretty_table(rows, columns, options)
347
+ print as_pretty_table(rows, columns.upcase_keys!, options)
268
348
  print reset
269
349
  print_results_pagination(json_response)
270
350
  end
@@ -281,7 +361,7 @@ class Morpheus::Cli::Hosts
281
361
  options = {}
282
362
  optparse = Morpheus::Cli::OptionParser.new do |opts|
283
363
  opts.banner = subcommand_usage("[options]")
284
- opts.on( '-a', '--account ACCOUNT', "Account Name or ID" ) do |val|
364
+ opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
285
365
  options[:account] = val
286
366
  end
287
367
  opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
@@ -392,6 +472,9 @@ class Morpheus::Cli::Hosts
392
472
  options = {}
393
473
  optparse = Morpheus::Cli::OptionParser.new do |opts|
394
474
  opts.banner = subcommand_usage("[name]")
475
+ opts.on( nil, '--costs', "Display Cost and Price" ) do
476
+ options[:include_costs] = true
477
+ end
395
478
  opts.on('--refresh [SECONDS]', String, "Refresh until status is provisioned,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
396
479
  options[:refresh_until_status] ||= "provisioned,failed"
397
480
  if !val.to_s.empty?
@@ -447,28 +530,46 @@ class Morpheus::Cli::Hosts
447
530
  puts records_as_csv([json_response['server']], options)
448
531
  return 0
449
532
  end
450
- server = json_response['server']
533
+ server = json_response['server'] || json_response['host'] || {}
451
534
  #stats = server['stats'] || json_response['stats'] || {}
452
535
  stats = json_response['stats'] || {}
536
+ tags = server['tags'] || server['metadata']
453
537
  title = "Host Details"
454
538
  print_h1 title, [], options
455
539
  print cyan
456
- print_description_list({
540
+ server_columns = {
457
541
  "ID" => 'id',
458
542
  "Name" => 'name',
543
+ "Hostname" => 'hostname',
459
544
  "Description" => 'description',
460
- "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
545
+ "Tags" => lambda {|it| tags ? format_metadata(tags) : '' },
546
+ "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
547
+ "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
461
548
  #"Group" => lambda {|it| it['group'] ? it['group']['name'] : '' },
462
549
  "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
550
+ "IP" => lambda {|it| it['externalIp'] },
551
+ "Private IP" => lambda {|it| it['internalIp'] },
463
552
  "Type" => lambda {|it| it['computeServerType'] ? it['computeServerType']['name'] : 'unmanaged' },
464
553
  "Platform" => lambda {|it| it['serverOs'] ? it['serverOs']['name'].upcase : 'N/A' },
465
554
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
555
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
556
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
466
557
  "Agent" => lambda {|it| it['agentInstalled'] ? "#{server['agentVersion'] || ''} updated at #{format_local_dt(server['lastAgentUpdate'])}" : '(not installed)' },
467
- "Status" => lambda {|it| format_server_status(it) },
468
558
  "Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
469
- "Power" => lambda {|it| format_server_power_state(it) },
470
- }, server)
471
-
559
+ # "Status" => lambda {|it| format_server_status(it) },
560
+ # "Power" => lambda {|it| format_server_power_state(it) },
561
+ "Status" => lambda {|it| format_server_status_friendly(it) }, # combo
562
+ }
563
+ server_columns.delete("Hostname") if server['hostname'].to_s.empty? || server['hostname'] == server['name']
564
+ server_columns.delete("IP") if server['externalIp'].to_s.empty?
565
+ server_columns.delete("Private IP") if server['internalIp'].to_s.empty?
566
+ # server_columns.delete("Tenant") if multi_tenant != true
567
+ server_columns.delete("Cost") if server['hourlyCost'].to_f == 0
568
+ server_columns.delete("Price") if server['hourlyPrice'].to_f == 0 || server['hourlyPrice'] == server['hourlyCost']
569
+ server_columns.delete("Tags") if tags.nil? || tags.empty?
570
+
571
+ print_description_list(server_columns, server)
572
+
472
573
  if server['statusMessage']
473
574
  print_h2 "Status Message", options
474
575
  if server['status'] == 'failed'
@@ -485,6 +586,16 @@ class Morpheus::Cli::Hosts
485
586
 
486
587
  print_h2 "Host Usage", options
487
588
  print_stats_usage(stats)
589
+
590
+ if options[:include_costs]
591
+ print_h2 "Host Cost"
592
+ cost_columns = {
593
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
594
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
595
+ }
596
+ print_description_list(cost_columns, server)
597
+ end
598
+
488
599
  print reset, "\n"
489
600
 
490
601
 
@@ -882,6 +993,19 @@ class Morpheus::Cli::Hosts
882
993
  opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
883
994
  params['powerScheduleType'] = val == "null" ? nil : val
884
995
  end
996
+ opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
997
+ options[:tags] = val
998
+ end
999
+ opts.on('--metadata LIST', String, "Alias for --tags.") do |val|
1000
+ options[:tags] = val
1001
+ end
1002
+ opts.add_hidden_option('--metadata')
1003
+ opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
1004
+ options[:add_tags] = val
1005
+ end
1006
+ opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
1007
+ options[:remove_tags] = val
1008
+ end
885
1009
  # opts.on('--created-by ID', String, "Created By User ID") do |val|
886
1010
  # params['createdById'] = val
887
1011
  # end
@@ -900,6 +1024,18 @@ class Morpheus::Cli::Hosts
900
1024
  new_group = nil
901
1025
  passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
902
1026
  params.deep_merge!(passed_options) unless passed_options.empty?
1027
+ # metadata tags
1028
+ if options[:tags]
1029
+ params['tags'] = parse_metadata(options[:tags])
1030
+ else
1031
+ # params['tags'] = prompt_metadata(options)
1032
+ end
1033
+ if options[:add_tags]
1034
+ params['addTags'] = parse_metadata(options[:add_tags])
1035
+ end
1036
+ if options[:remove_tags]
1037
+ params['removeTags'] = parse_metadata(options[:remove_tags])
1038
+ end
903
1039
  payload = nil
904
1040
  if options[:payload]
905
1041
  payload = options[:payload]
@@ -1781,6 +1917,103 @@ class Morpheus::Cli::Hosts
1781
1917
  end
1782
1918
  end
1783
1919
 
1920
+ def snapshots(args)
1921
+ options = {}
1922
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1923
+ opts.banner = subcommand_usage("[host]")
1924
+ # no pagination yet
1925
+ # build_standard_list_options(opts, options)
1926
+ build_standard_get_options(opts, options)
1927
+ end
1928
+ optparse.parse!(args)
1929
+ verify_args!(args:args, optparse:optparse, count:1)
1930
+ connect(options)
1931
+ begin
1932
+ server = find_host_by_name_or_id(args[0])
1933
+ return 1 if server.nil?
1934
+ params = {}
1935
+ @servers_interface.setopts(options)
1936
+ if options[:dry_run]
1937
+ print_dry_run @servers_interface.dry.snapshots(server['id'], params)
1938
+ return
1939
+ end
1940
+ json_response = @servers_interface.snapshots(server['id'], params)
1941
+ snapshots = json_response['snapshots']
1942
+ render_response(json_response, options, 'snapshots') do
1943
+ print_h1 "Snapshots: #{server['name']}", [], options
1944
+ if snapshots.empty?
1945
+ print cyan,"No snapshots found",reset,"\n"
1946
+ else
1947
+ snapshot_column_definitions = {
1948
+ "ID" => lambda {|it| it['id'] },
1949
+ "Name" => lambda {|it| it['name'] },
1950
+ "Description" => lambda {|it| it['description'] },
1951
+ # "Type" => lambda {|it| it['snapshotType'] },
1952
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
1953
+ "Status" => lambda {|it| format_snapshot_status(it) }
1954
+ }
1955
+ print cyan
1956
+ print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
1957
+ print_results_pagination({size: snapshots.size, total: snapshots.size})
1958
+ end
1959
+ print reset, "\n"
1960
+ end
1961
+ return 0
1962
+ rescue RestClient::Exception => e
1963
+ print_rest_exception(e, options)
1964
+ exit 1
1965
+ end
1966
+ end
1967
+
1968
+ def software(args)
1969
+ options = {}
1970
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1971
+ opts.banner = subcommand_usage("[host]")
1972
+ build_standard_list_options(opts, options)
1973
+ end
1974
+ optparse.parse!(args)
1975
+ verify_args!(args:args, optparse:optparse, count:1)
1976
+ connect(options)
1977
+ begin
1978
+ server = find_host_by_name_or_id(args[0])
1979
+ return 1 if server.nil?
1980
+ params = {}
1981
+ params.merge!(parse_list_options(options))
1982
+ @servers_interface.setopts(options)
1983
+ if options[:dry_run]
1984
+ print_dry_run @servers_interface.dry.software(server['id'], params)
1985
+ return
1986
+ end
1987
+ json_response = @servers_interface.software(server['id'], params)
1988
+ software = json_response['software']
1989
+ render_response(json_response, options, 'software') do
1990
+ print_h1 "Software: #{server['name']}", [], options
1991
+ if software.empty?
1992
+ print cyan,"No software found",reset,"\n"
1993
+ else
1994
+ software_column_definitions = {
1995
+ # "ID" => lambda {|it| it['id'] },
1996
+ "Name" => lambda {|it| it['name'] },
1997
+ "Version" => lambda {|it| it['packageVersion'] },
1998
+ "Publisher" => lambda {|it| it['packagePublisher'] },
1999
+ # "Release" => lambda {|it| it['packageRelease'] },
2000
+ # "Type" => lambda {|it| it['packageType'] },
2001
+ # "Architecture" => lambda {|it| it['architecture'] },
2002
+ # "Install Date" => lambda {|it| format_local_dt(it['installDate']) },
2003
+ }
2004
+ print cyan
2005
+ print as_pretty_table(software, software_column_definitions.upcase_keys!, options)
2006
+ print_results_pagination({size: software.size, total: software.size})
2007
+ end
2008
+ print reset, "\n"
2009
+ end
2010
+ return 0
2011
+ rescue RestClient::Exception => e
2012
+ print_rest_exception(e, options)
2013
+ exit 1
2014
+ end
2015
+ end
2016
+
1784
2017
  private
1785
2018
 
1786
2019
  def find_host_by_id(id)
@@ -1897,16 +2130,43 @@ class Morpheus::Cli::Hosts
1897
2130
 
1898
2131
  def format_server_status(server, return_color=cyan)
1899
2132
  out = ""
1900
- status_string = server['status']
1901
- # todo: colorize, upcase?
1902
- out << status_string.to_s
2133
+ status_string = server['status'].to_s.downcase
2134
+ if status_string == 'provisioned'
2135
+ out = "#{cyan}#{status_string.upcase}#{return_color}"
2136
+ elsif status_string == 'provisioning'
2137
+ out = "#{cyan}#{status_string.upcase}#{cyan}"
2138
+ elsif status_string == 'failed' or status_string == 'error'
2139
+ out = "#{red}#{status_string.upcase}#{return_color}"
2140
+ else
2141
+ out = "#{yellow}#{status_string.upcase}#{return_color}"
2142
+ end
1903
2143
  out
1904
2144
  end
1905
2145
 
2146
+ def format_server_status_friendly(server, return_color=cyan)
2147
+ out = ""
2148
+ status_string = server['status'].to_s.downcase
2149
+ if status_string == 'provisioned'
2150
+ # out = format_server_power_state(server, return_color)
2151
+ # make it looks like format_instance_status
2152
+ if server['powerState'] == 'on'
2153
+ out << "#{green}RUNNING#{return_color}"
2154
+ elsif server['powerState'] == 'off'
2155
+ out << "#{red}STOPPED#{return_color}"
2156
+ else
2157
+ out << "#{white}#{server['powerState'].to_s.upcase}#{return_color}"
2158
+ end
2159
+ else
2160
+ out = format_server_status(server, return_color)
2161
+ end
2162
+ out
2163
+ end
2164
+
2165
+
1906
2166
  def make_managed_option_types(connected=true)
1907
2167
  [
1908
2168
  #{'fieldName' => 'account', 'fieldLabel' => 'Account', 'type' => 'select', 'optionSource' => 'accounts', 'required' => true},
1909
- {'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text', 'required' => true},
2169
+ {'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text'},
1910
2170
  {'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false},
1911
2171
  {'fieldName' => 'serverOs', 'fieldLabel' => 'OS Type', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false},
1912
2172
  ]