morpheus-cli 4.2.20 → 5.0.2

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +26 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +7 -0
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +7 -0
  14. data/lib/morpheus/api/usage_interface.rb +18 -0
  15. data/lib/morpheus/cli.rb +6 -3
  16. data/lib/morpheus/cli/apps.rb +3 -4
  17. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  18. data/lib/morpheus/cli/backups_command.rb +3 -0
  19. data/lib/morpheus/cli/budgets_command.rb +4 -4
  20. data/lib/morpheus/cli/catalog_command.rb +507 -0
  21. data/lib/morpheus/cli/cli_command.rb +45 -20
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -0
  28. data/lib/morpheus/cli/deploy.rb +199 -90
  29. data/lib/morpheus/cli/deployments.rb +342 -29
  30. data/lib/morpheus/cli/deploys.rb +206 -41
  31. data/lib/morpheus/cli/error_handler.rb +7 -0
  32. data/lib/morpheus/cli/forgot_password.rb +133 -0
  33. data/lib/morpheus/cli/groups.rb +1 -1
  34. data/lib/morpheus/cli/health_command.rb +2 -2
  35. data/lib/morpheus/cli/hosts.rb +181 -26
  36. data/lib/morpheus/cli/instances.rb +102 -33
  37. data/lib/morpheus/cli/invoices_command.rb +33 -16
  38. data/lib/morpheus/cli/jobs_command.rb +28 -6
  39. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  40. data/lib/morpheus/cli/logs_command.rb +9 -6
  41. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  42. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  43. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  44. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  45. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  46. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  47. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  48. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  49. data/lib/morpheus/cli/option_types.rb +266 -17
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +305 -3
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  57. data/lib/morpheus/cli/setup.rb +1 -1
  58. data/lib/morpheus/cli/shell.rb +33 -11
  59. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  60. data/lib/morpheus/cli/tasks.rb +20 -21
  61. data/lib/morpheus/cli/tenants_command.rb +1 -1
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +280 -199
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +48 -5
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +13 -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)
@@ -475,8 +475,8 @@ class Morpheus::Cli::HealthCommand
475
475
  opts.footer = "List health logs. These are the logs of the morpheus appliance itself."
476
476
  end
477
477
  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}"
478
+ if args.count > 0
479
+ options[:phrase] = args.join(" ")
480
480
  end
481
481
  connect(options)
482
482
  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,
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
 
@@ -108,17 +109,29 @@ class Morpheus::Cli::Hosts
108
109
  opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
109
110
  options[:details] = true
110
111
  end
112
+ opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
113
+ val.split(",").each do |value_pair|
114
+ k,v = value_pair.strip.split("=")
115
+ options[:tags] ||= {}
116
+ options[:tags][k] ||= []
117
+ options[:tags][k] << (v || '')
118
+ end
119
+ end
111
120
  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
121
  params[:tagCompliant] = true
113
122
  end
114
123
  opts.on('--non-tag-compliant', "Displays only servers with tag compliance warnings." ) do
115
124
  params[:tagCompliant] = false
116
125
  end
117
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
126
+ build_standard_list_options(opts, options)
118
127
  opts.footer = "List hosts."
119
128
  end
120
129
  optparse.parse!(args)
121
130
  connect(options)
131
+ # verify_args!(args:args, optparse:optparse, count:0)
132
+ if args.count > 0
133
+ options[:phrase] = args.join(" ")
134
+ end
122
135
  begin
123
136
  params.merge!(parse_list_options(options))
124
137
  account = nil
@@ -159,6 +172,11 @@ class Morpheus::Cli::Hosts
159
172
  params['clusterId'] = cluster['id']
160
173
  end
161
174
  end
175
+ if options[:tags] && !options[:tags].empty?
176
+ options[:tags].each do |k,v|
177
+ params['tags.' + k] = v
178
+ end
179
+ end
162
180
 
163
181
  @servers_interface.setopts(options)
164
182
  if options[:dry_run]
@@ -240,31 +258,66 @@ class Morpheus::Cli::Hosts
240
258
  end
241
259
  row = {
242
260
  id: server['id'],
243
- tenant: server['account'] ? server['account']['name'] : server['accountId'],
244
261
  name: server['name'],
262
+ hostname: server['hostname'],
245
263
  platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
246
- cloud: server['zone'] ? server['zone']['name'] : '',
247
264
  type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
265
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
266
+ owner: server['owner'] ? server['owner']['username'] : server['owner'],
267
+ cloud: server['zone'] ? server['zone']['name'] : '',
268
+ ip: server['externalIp'],
269
+ internal_ip: server['internalIp'],
248
270
  nodes: server['containers'] ? server['containers'].size : '',
249
- status: format_server_status(server, cyan),
271
+ # status: format_server_status(server, cyan),
272
+ status: (options[:details]||options[:all_fields]) ? format_server_status(server, cyan) : format_server_status_friendly(server, cyan),
250
273
  power: format_server_power_state(server, cyan),
251
274
  cpu: cpu_usage_str + cyan,
252
275
  memory: memory_usage_str + cyan,
253
- storage: storage_usage_str + cyan
276
+ storage: storage_usage_str + cyan,
277
+ created: format_local_dt(server['dateCreated']),
278
+ updated: format_local_dt(server['lastUpdated']),
254
279
  }
255
280
  row
256
281
  }
257
- columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
258
- if multi_tenant
259
- columns.insert(4, :tenant)
282
+ # columns = [:id, :name, :type, :cloud, :ip, :internal_ip, :nodes, :status, :power]
283
+ columns = {
284
+ "ID" => :id,
285
+ "Name" => :name,
286
+ "Hostname" => :hostname,
287
+ "Type" => :type,
288
+ "Owner" => :owner,
289
+ "Tenant" => :tenant,
290
+ "Cloud" => :cloud,
291
+ "IP" => :ip,
292
+ "Private IP" => :internal_ip,
293
+ "Nodes" => :nodes,
294
+ "Status" => :status,
295
+ "Power" => :power,
296
+ "CPU" => :cpu,
297
+ "Memory" => :memory,
298
+ "Storage" => :storage,
299
+ "Created" => :created,
300
+ "Updated" => :updated,
301
+ }
302
+ if options[:details] != true
303
+ columns.delete("Hostname")
304
+ columns.delete("Private IP")
305
+ columns.delete("Owner")
306
+ columns.delete("Tenant")
307
+ columns.delete("Power")
308
+ columns.delete("Created")
309
+ columns.delete("Updated")
260
310
  end
261
- columns += [:cpu, :memory, :storage]
262
- # custom pretty table columns ...
263
- if options[:include_fields]
264
- columns = options[:include_fields]
311
+ if !multi_tenant
312
+ columns.delete("Tenant")
265
313
  end
314
+ # columns += [:cpu, :memory, :storage]
315
+ # # custom pretty table columns ...
316
+ # if options[:include_fields]
317
+ # columns = options[:include_fields]
318
+ # end
266
319
  print cyan
267
- print as_pretty_table(rows, columns, options)
320
+ print as_pretty_table(rows, columns.upcase_keys!, options)
268
321
  print reset
269
322
  print_results_pagination(json_response)
270
323
  end
@@ -392,6 +445,9 @@ class Morpheus::Cli::Hosts
392
445
  options = {}
393
446
  optparse = Morpheus::Cli::OptionParser.new do |opts|
394
447
  opts.banner = subcommand_usage("[name]")
448
+ opts.on( nil, '--costs', "Display Cost and Price" ) do
449
+ options[:include_costs] = true
450
+ end
395
451
  opts.on('--refresh [SECONDS]', String, "Refresh until status is provisioned,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
396
452
  options[:refresh_until_status] ||= "provisioned,failed"
397
453
  if !val.to_s.empty?
@@ -453,22 +509,37 @@ class Morpheus::Cli::Hosts
453
509
  title = "Host Details"
454
510
  print_h1 title, [], options
455
511
  print cyan
456
- print_description_list({
512
+ server_columns = {
457
513
  "ID" => 'id',
458
514
  "Name" => 'name',
515
+ "Hostname" => 'hostname',
459
516
  "Description" => 'description',
460
- "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
517
+ "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
518
+ "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
461
519
  #"Group" => lambda {|it| it['group'] ? it['group']['name'] : '' },
462
520
  "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
521
+ "IP" => lambda {|it| it['externalIp'] },
522
+ "Private IP" => lambda {|it| it['internalIp'] },
463
523
  "Type" => lambda {|it| it['computeServerType'] ? it['computeServerType']['name'] : 'unmanaged' },
464
524
  "Platform" => lambda {|it| it['serverOs'] ? it['serverOs']['name'].upcase : 'N/A' },
465
525
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
526
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
527
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
466
528
  "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
529
  "Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
469
- "Power" => lambda {|it| format_server_power_state(it) },
470
- }, server)
471
-
530
+ # "Status" => lambda {|it| format_server_status(it) },
531
+ # "Power" => lambda {|it| format_server_power_state(it) },
532
+ "Status" => lambda {|it| format_server_status_friendly(it) }, # combo
533
+ }
534
+ server_columns.delete("Hostname") if server['hostname'].to_s.empty? || server['hostname'] == server['name']
535
+ server_columns.delete("IP") if server['externalIp'].to_s.empty?
536
+ server_columns.delete("Private IP") if server['internalIp'].to_s.empty?
537
+ # server_columns.delete("Tenant") if multi_tenant != true
538
+ server_columns.delete("Cost") if server['hourlyCost'].to_f == 0
539
+ server_columns.delete("Price") if server['hourlyPrice'].to_f == 0 || server['hourlyPrice'] == server['hourlyCost']
540
+
541
+ print_description_list(server_columns, server)
542
+
472
543
  if server['statusMessage']
473
544
  print_h2 "Status Message", options
474
545
  if server['status'] == 'failed'
@@ -485,6 +556,16 @@ class Morpheus::Cli::Hosts
485
556
 
486
557
  print_h2 "Host Usage", options
487
558
  print_stats_usage(stats)
559
+
560
+ if options[:include_costs]
561
+ print_h2 "Host Cost"
562
+ cost_columns = {
563
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
564
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
565
+ }
566
+ print_description_list(cost_columns, server)
567
+ end
568
+
488
569
  print reset, "\n"
489
570
 
490
571
 
@@ -1781,6 +1862,53 @@ class Morpheus::Cli::Hosts
1781
1862
  end
1782
1863
  end
1783
1864
 
1865
+ def snapshots(args)
1866
+ options = {}
1867
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1868
+ opts.banner = subcommand_usage("[host]")
1869
+ # no pagination yet
1870
+ # build_standard_list_options(opts, options)
1871
+ build_standard_get_options(opts, options)
1872
+ end
1873
+ optparse.parse!(args)
1874
+ verify_args!(args:args, optparse:optparse, count:1)
1875
+ connect(options)
1876
+ begin
1877
+ server = find_host_by_name_or_id(args[0])
1878
+ return 1 if server.nil?
1879
+ params = {}
1880
+ @servers_interface.setopts(options)
1881
+ if options[:dry_run]
1882
+ print_dry_run @servers_interface.dry.snapshots(server['id'], params)
1883
+ return
1884
+ end
1885
+ json_response = @servers_interface.snapshots(server['id'], params)
1886
+ snapshots = json_response['snapshots']
1887
+ render_response(json_response, options, 'snapshots') do
1888
+ print_h1 "Snapshots: #{server['name']}", [], options
1889
+ if snapshots.empty?
1890
+ print cyan,"No snapshots found",reset,"\n"
1891
+ else
1892
+ snapshot_column_definitions = {
1893
+ "ID" => lambda {|it| it['id'] },
1894
+ "Name" => lambda {|it| it['name'] },
1895
+ "Description" => lambda {|it| it['snapshotType'] ? (it['snapshotType']['name'] || it['snapshotType']['code']) : '' },
1896
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
1897
+ "Status" => lambda {|it| format_snapshot_status(it) }
1898
+ }
1899
+ print cyan
1900
+ print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
1901
+ print_results_pagination({size: snapshots.size, total: snapshots.size})
1902
+ end
1903
+ print reset, "\n"
1904
+ end
1905
+ return 0
1906
+ rescue RestClient::Exception => e
1907
+ print_rest_exception(e, options)
1908
+ exit 1
1909
+ end
1910
+ end
1911
+
1784
1912
  private
1785
1913
 
1786
1914
  def find_host_by_id(id)
@@ -1897,11 +2025,38 @@ class Morpheus::Cli::Hosts
1897
2025
 
1898
2026
  def format_server_status(server, return_color=cyan)
1899
2027
  out = ""
1900
- status_string = server['status']
1901
- # todo: colorize, upcase?
1902
- out << status_string.to_s
2028
+ status_string = server['status'].to_s.downcase
2029
+ if status_string == 'provisioned'
2030
+ out = "#{cyan}#{status_string.upcase}#{return_color}"
2031
+ elsif status_string == 'provisioning'
2032
+ out = "#{cyan}#{status_string.upcase}#{cyan}"
2033
+ elsif status_string == 'failed' or status_string == 'error'
2034
+ out = "#{red}#{status_string.upcase}#{return_color}"
2035
+ else
2036
+ out = "#{yellow}#{status_string.upcase}#{return_color}"
2037
+ end
2038
+ out
2039
+ end
2040
+
2041
+ def format_server_status_friendly(server, return_color=cyan)
2042
+ out = ""
2043
+ status_string = server['status'].to_s.downcase
2044
+ if status_string == 'provisioned'
2045
+ # out = format_server_power_state(server, return_color)
2046
+ # make it looks like format_instance_status
2047
+ if server['powerState'] == 'on'
2048
+ out << "#{green}RUNNING#{return_color}"
2049
+ elsif server['powerState'] == 'off'
2050
+ out << "#{red}STOPPED#{return_color}"
2051
+ else
2052
+ out << "#{white}#{server['powerState'].to_s.upcase}#{return_color}"
2053
+ end
2054
+ else
2055
+ out = format_server_status(server, return_color)
2056
+ end
1903
2057
  out
1904
2058
  end
2059
+
1905
2060
 
1906
2061
  def make_managed_option_types(connected=true)
1907
2062
  [
@@ -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,
22
+ :security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshots,
23
23
  :console, :status_check, {:containers => :list_containers},
24
24
  :scaling, {:'scaling-update' => :scaling_update},
25
25
  :wiki, :update_wiki,
@@ -92,11 +92,28 @@ class Morpheus::Cli::Instances
92
92
  opts.on('--pending-removal-only', "Only instances pending removal.") do
93
93
  options[:deleted] = true
94
94
  end
95
+ opts.on('--labels label',String, "Filter by labels (keywords).") do |val|
96
+ val.split(",").each do |k|
97
+ options[:labels] ||= []
98
+ options[:labels] << k.strip
99
+ end
100
+ end
101
+ opts.on('--tags Name=Value',String, "Filter by tags (metadata name value pairs).") do |val|
102
+ val.split(",").each do |value_pair|
103
+ k,v = value_pair.strip.split("=")
104
+ options[:tags] ||= {}
105
+ options[:tags][k] ||= []
106
+ options[:tags][k] << (v || '')
107
+ end
108
+ end
95
109
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
96
110
  opts.footer = "List instances."
97
111
  end
98
112
  optparse.parse!(args)
99
- verify_args!(args:args, count:0, optparse:optparse)
113
+ # verify_args!(args:args, optparse:optparse, count:0)
114
+ if args.count > 0
115
+ options[:phrase] = args.join(" ")
116
+ end
100
117
  connect(options)
101
118
  begin
102
119
  params.merge!(parse_list_options(options))
@@ -129,7 +146,13 @@ class Morpheus::Cli::Instances
129
146
 
130
147
  params['showDeleted'] = true if options[:showDeleted]
131
148
  params['deleted'] = true if options[:deleted]
132
-
149
+ params['labels'] = options[:labels] if options[:labels]
150
+ if options[:tags]
151
+ options[:tags].each do |k,v|
152
+ params['tags.' + k] = v
153
+ end
154
+ end
155
+
133
156
  @instances_interface.setopts(options)
134
157
  if options[:dry_run]
135
158
  print_dry_run @instances_interface.dry.list(params)
@@ -344,18 +367,16 @@ class Morpheus::Cli::Instances
344
367
  opts.on("--environment ENV", String, "Environment code") do |val|
345
368
  options[:environment] = val.to_s
346
369
  end
347
- opts.on('--metadata LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
370
+ opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
348
371
  options[:metadata] = val
349
372
  end
350
- opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
351
- #options[:tags] = val
352
- options[:tags] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
373
+ opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
374
+ options[:metadata] = val
353
375
  end
354
- opts.on('--tags LIST', String, "Tags") do |val|
355
- #options[:tags] = val
356
- options[:tags] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
376
+ opts.add_hidden_option('--metadata')
377
+ opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
378
+ options[:labels] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
357
379
  end
358
- opts.add_hidden_option('--tags')
359
380
  opts.on("--copies NUMBER", Integer, "Number of copies to provision") do |val|
360
381
  options[:copies] = val.to_i
361
382
  end
@@ -525,18 +546,17 @@ class Morpheus::Cli::Instances
525
546
  opts.on('--group GROUP', String, "Group Name or ID") do |val|
526
547
  options[:group] = val
527
548
  end
528
- opts.on('--metadata LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
549
+ opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
529
550
  options[:metadata] = val
530
551
  end
531
- opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
532
- #params['tags'] = val
533
- params['tags'] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
552
+ opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
553
+ options[:metadata] = val
534
554
  end
535
- opts.on('--tags LIST', String, "Tags") do |val|
536
- #params['tags'] = val
555
+ opts.add_hidden_option('--metadata')
556
+ opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
557
+ # todo switch this from 'tags' to 'labels'
537
558
  params['tags'] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
538
559
  end
539
- opts.add_hidden_option('--tags')
540
560
  opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
541
561
  params['powerScheduleType'] = val == "null" ? nil : val
542
562
  end
@@ -607,6 +627,9 @@ class Morpheus::Cli::Instances
607
627
  metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
608
628
  metadata_list = metadata_list.collect do |it|
609
629
  metadata_pair = it.split(":")
630
+ if metadata_pair.size == 1 && it.include?("=")
631
+ metadata_pair = it.split("=")
632
+ end
610
633
  row = {}
611
634
  row['name'] = metadata_pair[0].to_s.strip
612
635
  row['value'] = metadata_pair[1].to_s.strip
@@ -1148,6 +1171,9 @@ class Morpheus::Cli::Instances
1148
1171
  opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
1149
1172
  options[:include_scaling] = true
1150
1173
  end
1174
+ opts.on( nil, '--costs', "Display Cost and Price" ) do
1175
+ options[:include_costs] = true
1176
+ end
1151
1177
  opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
1152
1178
  options[:refresh_until_status] ||= "running,failed"
1153
1179
  if !val.to_s.empty?
@@ -1249,6 +1275,8 @@ class Morpheus::Cli::Instances
1249
1275
  "Layout" => lambda {|it| it['layout'] ? it['layout']['name'] : '' },
1250
1276
  "Version" => lambda {|it| it['instanceVersion'] },
1251
1277
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
1278
+ # "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1279
+ # "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1252
1280
  "Environment" => 'instanceContext',
1253
1281
  "Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
1254
1282
  "Metadata" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
@@ -1304,6 +1332,16 @@ class Morpheus::Cli::Instances
1304
1332
  print_h2 "Instance Usage", options
1305
1333
  print_stats_usage(stats)
1306
1334
  end
1335
+
1336
+ if options[:include_costs]
1337
+ print_h2 "Instance Cost"
1338
+ cost_columns = {
1339
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1340
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1341
+ }
1342
+ print_description_list(cost_columns, instance)
1343
+ end
1344
+
1307
1345
  print reset, "\n"
1308
1346
 
1309
1347
  # if options[:include_lb]
@@ -2880,6 +2918,52 @@ class Morpheus::Cli::Instances
2880
2918
  end
2881
2919
  end
2882
2920
 
2921
+ def snapshots(args)
2922
+ options = {}
2923
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2924
+ opts.banner = subcommand_usage("[instance]")
2925
+ # no pagination yet
2926
+ # build_standard_list_options(opts, options)
2927
+ build_standard_get_options(opts, options)
2928
+ end
2929
+ optparse.parse!(args)
2930
+ verify_args!(args:args, optparse:optparse, count:1)
2931
+ connect(options)
2932
+ begin
2933
+ instance = find_instance_by_name_or_id(args[0])
2934
+ params = {}
2935
+ @instances_interface.setopts(options)
2936
+ if options[:dry_run]
2937
+ print_dry_run @instances_interface.dry.snapshots(instance['id'], params)
2938
+ return
2939
+ end
2940
+ json_response = @instances_interface.snapshots(instance['id'], params)
2941
+ snapshots = json_response['snapshots']
2942
+ render_response(json_response, options, 'snapshots') do
2943
+ print_h1 "Snapshots: #{instance['name']} (#{instance['instanceType']['name']})", [], options
2944
+ if snapshots.empty?
2945
+ print cyan,"No snapshots found",reset,"\n"
2946
+ else
2947
+ snapshot_column_definitions = {
2948
+ "ID" => lambda {|it| it['id'] },
2949
+ "Name" => lambda {|it| it['name'] },
2950
+ "Description" => lambda {|it| it['snapshotType'] ? (it['snapshotType']['name'] || it['snapshotType']['code']) : '' },
2951
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
2952
+ "Status" => lambda {|it| format_snapshot_status(it) }
2953
+ }
2954
+ print cyan
2955
+ print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
2956
+ print_results_pagination({size: snapshots.size, total: snapshots.size})
2957
+ end
2958
+ print reset, "\n"
2959
+ end
2960
+ return 0
2961
+ rescue RestClient::Exception => e
2962
+ print_rest_exception(e, options)
2963
+ exit 1
2964
+ end
2965
+ end
2966
+
2883
2967
  def import_snapshot(args)
2884
2968
  options = {}
2885
2969
  query_params = {}
@@ -4006,19 +4090,4 @@ private
4006
4090
  }
4007
4091
  end
4008
4092
 
4009
- def format_app_deploy_status(status, return_color=cyan)
4010
- out = ""
4011
- s = status.to_s.downcase
4012
- if s == 'deployed'
4013
- out << "#{green}#{s.upcase}#{return_color}"
4014
- elsif s == 'open' || s == 'archived' || s == 'committed'
4015
- out << "#{cyan}#{s.upcase}#{return_color}"
4016
- elsif s == 'failed'
4017
- out << "#{red}#{s.upcase}#{return_color}"
4018
- else
4019
- out << "#{yellow}#{s.upcase}#{return_color}"
4020
- end
4021
- out
4022
- end
4023
-
4024
4093
  end