morpheus-cli 4.2.19 → 5.0.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 (72) 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/cloud_resource_pools_command.rb +16 -0
  23. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  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 -0
  29. data/lib/morpheus/cli/deploy.rb +199 -90
  30. data/lib/morpheus/cli/deployments.rb +342 -29
  31. data/lib/morpheus/cli/deploys.rb +206 -41
  32. data/lib/morpheus/cli/error_handler.rb +7 -0
  33. data/lib/morpheus/cli/forgot_password.rb +133 -0
  34. data/lib/morpheus/cli/health_command.rb +2 -2
  35. data/lib/morpheus/cli/hosts.rb +193 -28
  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_instance_types_command.rb +17 -3
  40. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  41. data/lib/morpheus/cli/logs_command.rb +9 -6
  42. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  43. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  44. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  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 +24 -4
  49. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  50. data/lib/morpheus/cli/option_types.rb +266 -17
  51. data/lib/morpheus/cli/ping.rb +0 -1
  52. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  53. data/lib/morpheus/cli/remote.rb +35 -12
  54. data/lib/morpheus/cli/reports_command.rb +99 -30
  55. data/lib/morpheus/cli/roles.rb +305 -3
  56. data/lib/morpheus/cli/search_command.rb +182 -0
  57. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  58. data/lib/morpheus/cli/setup.rb +1 -1
  59. data/lib/morpheus/cli/shell.rb +33 -11
  60. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  61. data/lib/morpheus/cli/tasks.rb +20 -21
  62. data/lib/morpheus/cli/tenants_command.rb +1 -1
  63. data/lib/morpheus/cli/usage_command.rb +203 -0
  64. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  65. data/lib/morpheus/cli/users.rb +12 -1
  66. data/lib/morpheus/cli/version.rb +1 -1
  67. data/lib/morpheus/cli/virtual_images.rb +280 -199
  68. data/lib/morpheus/cli/whoami.rb +6 -6
  69. data/lib/morpheus/cli/workflows.rb +34 -41
  70. data/lib/morpheus/formatters.rb +48 -5
  71. data/lib/morpheus/terminal.rb +6 -2
  72. metadata +13 -2
@@ -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
 
@@ -53,9 +54,13 @@ 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
57
+ opts.on('-a', '--all', "Display all details: memory and storage usage used / max values." ) do
58
+ options[:details] = true
59
+ end
60
+ opts.on('--details', "Display all details: alias for --all" ) do
61
+ options[:details] = true
58
62
  end
63
+ opts.add_hidden_option('--details')
59
64
  opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
60
65
  options[:group] = val
61
66
  end
@@ -105,20 +110,35 @@ class Morpheus::Cli::Hosts
105
110
  opts.on( '--created-by USER', "Created By User Username or ID" ) do |val|
106
111
  options[:created_by] = val
107
112
  end
113
+ opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
114
+ options[:account] = val
115
+ end
108
116
  opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
109
117
  options[:details] = true
110
118
  end
119
+ opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
120
+ val.split(",").each do |value_pair|
121
+ k,v = value_pair.strip.split("=")
122
+ options[:tags] ||= {}
123
+ options[:tags][k] ||= []
124
+ options[:tags][k] << (v || '')
125
+ end
126
+ end
111
127
  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
128
  params[:tagCompliant] = true
113
129
  end
114
130
  opts.on('--non-tag-compliant', "Displays only servers with tag compliance warnings." ) do
115
131
  params[:tagCompliant] = false
116
132
  end
117
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
133
+ build_standard_list_options(opts, options)
118
134
  opts.footer = "List hosts."
119
135
  end
120
136
  optparse.parse!(args)
121
137
  connect(options)
138
+ # verify_args!(args:args, optparse:optparse, count:0)
139
+ if args.count > 0
140
+ options[:phrase] = args.join(" ")
141
+ end
122
142
  begin
123
143
  params.merge!(parse_list_options(options))
124
144
  account = nil
@@ -159,6 +179,11 @@ class Morpheus::Cli::Hosts
159
179
  params['clusterId'] = cluster['id']
160
180
  end
161
181
  end
182
+ if options[:tags] && !options[:tags].empty?
183
+ options[:tags].each do |k,v|
184
+ params['tags.' + k] = v
185
+ end
186
+ end
162
187
 
163
188
  @servers_interface.setopts(options)
164
189
  if options[:dry_run]
@@ -190,6 +215,9 @@ class Morpheus::Cli::Hosts
190
215
  multi_tenant = json_response['multiTenant'] == true
191
216
  title = "Morpheus Hosts"
192
217
  subtitles = []
218
+ if account
219
+ subtitles << "Tenant: #{account['name']}".strip
220
+ end
193
221
  if group
194
222
  subtitles << "Group: #{group['name']}".strip
195
223
  end
@@ -240,31 +268,66 @@ class Morpheus::Cli::Hosts
240
268
  end
241
269
  row = {
242
270
  id: server['id'],
243
- tenant: server['account'] ? server['account']['name'] : server['accountId'],
244
271
  name: server['name'],
272
+ hostname: server['hostname'],
245
273
  platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
246
- cloud: server['zone'] ? server['zone']['name'] : '',
247
274
  type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
275
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
276
+ owner: server['owner'] ? server['owner']['username'] : server['owner'],
277
+ cloud: server['zone'] ? server['zone']['name'] : '',
278
+ ip: server['externalIp'],
279
+ internal_ip: server['internalIp'],
248
280
  nodes: server['containers'] ? server['containers'].size : '',
249
- status: format_server_status(server, cyan),
281
+ # status: format_server_status(server, cyan),
282
+ status: (options[:details]||options[:all_fields]) ? format_server_status(server, cyan) : format_server_status_friendly(server, cyan),
250
283
  power: format_server_power_state(server, cyan),
251
284
  cpu: cpu_usage_str + cyan,
252
285
  memory: memory_usage_str + cyan,
253
- storage: storage_usage_str + cyan
286
+ storage: storage_usage_str + cyan,
287
+ created: format_local_dt(server['dateCreated']),
288
+ updated: format_local_dt(server['lastUpdated']),
254
289
  }
255
290
  row
256
291
  }
257
- columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
258
- if multi_tenant
259
- columns.insert(4, :tenant)
292
+ # columns = [:id, :name, :type, :cloud, :ip, :internal_ip, :nodes, :status, :power]
293
+ columns = {
294
+ "ID" => :id,
295
+ "Name" => :name,
296
+ "Hostname" => :hostname,
297
+ "Type" => :type,
298
+ "Owner" => :owner,
299
+ "Tenant" => :tenant,
300
+ "Cloud" => :cloud,
301
+ "IP" => :ip,
302
+ "Private IP" => :internal_ip,
303
+ "Nodes" => :nodes,
304
+ "Status" => :status,
305
+ "Power" => :power,
306
+ "CPU" => :cpu,
307
+ "Memory" => :memory,
308
+ "Storage" => :storage,
309
+ "Created" => :created,
310
+ "Updated" => :updated,
311
+ }
312
+ if options[:details] != true
313
+ columns.delete("Hostname")
314
+ columns.delete("Private IP")
315
+ columns.delete("Owner")
316
+ columns.delete("Tenant")
317
+ columns.delete("Power")
318
+ columns.delete("Created")
319
+ columns.delete("Updated")
260
320
  end
261
- columns += [:cpu, :memory, :storage]
262
- # custom pretty table columns ...
263
- if options[:include_fields]
264
- columns = options[:include_fields]
321
+ if !multi_tenant
322
+ columns.delete("Tenant")
265
323
  end
324
+ # columns += [:cpu, :memory, :storage]
325
+ # # custom pretty table columns ...
326
+ # if options[:include_fields]
327
+ # columns = options[:include_fields]
328
+ # end
266
329
  print cyan
267
- print as_pretty_table(rows, columns, options)
330
+ print as_pretty_table(rows, columns.upcase_keys!, options)
268
331
  print reset
269
332
  print_results_pagination(json_response)
270
333
  end
@@ -392,6 +455,9 @@ class Morpheus::Cli::Hosts
392
455
  options = {}
393
456
  optparse = Morpheus::Cli::OptionParser.new do |opts|
394
457
  opts.banner = subcommand_usage("[name]")
458
+ opts.on( nil, '--costs', "Display Cost and Price" ) do
459
+ options[:include_costs] = true
460
+ end
395
461
  opts.on('--refresh [SECONDS]', String, "Refresh until status is provisioned,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
396
462
  options[:refresh_until_status] ||= "provisioned,failed"
397
463
  if !val.to_s.empty?
@@ -453,22 +519,37 @@ class Morpheus::Cli::Hosts
453
519
  title = "Host Details"
454
520
  print_h1 title, [], options
455
521
  print cyan
456
- print_description_list({
522
+ server_columns = {
457
523
  "ID" => 'id',
458
524
  "Name" => 'name',
525
+ "Hostname" => 'hostname',
459
526
  "Description" => 'description',
460
- "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
527
+ "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
528
+ "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
461
529
  #"Group" => lambda {|it| it['group'] ? it['group']['name'] : '' },
462
530
  "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
531
+ "IP" => lambda {|it| it['externalIp'] },
532
+ "Private IP" => lambda {|it| it['internalIp'] },
463
533
  "Type" => lambda {|it| it['computeServerType'] ? it['computeServerType']['name'] : 'unmanaged' },
464
534
  "Platform" => lambda {|it| it['serverOs'] ? it['serverOs']['name'].upcase : 'N/A' },
465
535
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
536
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
537
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
466
538
  "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
539
  "Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
469
- "Power" => lambda {|it| format_server_power_state(it) },
470
- }, server)
471
-
540
+ # "Status" => lambda {|it| format_server_status(it) },
541
+ # "Power" => lambda {|it| format_server_power_state(it) },
542
+ "Status" => lambda {|it| format_server_status_friendly(it) }, # combo
543
+ }
544
+ server_columns.delete("Hostname") if server['hostname'].to_s.empty? || server['hostname'] == server['name']
545
+ server_columns.delete("IP") if server['externalIp'].to_s.empty?
546
+ server_columns.delete("Private IP") if server['internalIp'].to_s.empty?
547
+ # server_columns.delete("Tenant") if multi_tenant != true
548
+ server_columns.delete("Cost") if server['hourlyCost'].to_f == 0
549
+ server_columns.delete("Price") if server['hourlyPrice'].to_f == 0 || server['hourlyPrice'] == server['hourlyCost']
550
+
551
+ print_description_list(server_columns, server)
552
+
472
553
  if server['statusMessage']
473
554
  print_h2 "Status Message", options
474
555
  if server['status'] == 'failed'
@@ -485,6 +566,16 @@ class Morpheus::Cli::Hosts
485
566
 
486
567
  print_h2 "Host Usage", options
487
568
  print_stats_usage(stats)
569
+
570
+ if options[:include_costs]
571
+ print_h2 "Host Cost"
572
+ cost_columns = {
573
+ "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
574
+ "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
575
+ }
576
+ print_description_list(cost_columns, server)
577
+ end
578
+
488
579
  print reset, "\n"
489
580
 
490
581
 
@@ -1781,6 +1872,53 @@ class Morpheus::Cli::Hosts
1781
1872
  end
1782
1873
  end
1783
1874
 
1875
+ def snapshots(args)
1876
+ options = {}
1877
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1878
+ opts.banner = subcommand_usage("[host]")
1879
+ # no pagination yet
1880
+ # build_standard_list_options(opts, options)
1881
+ build_standard_get_options(opts, options)
1882
+ end
1883
+ optparse.parse!(args)
1884
+ verify_args!(args:args, optparse:optparse, count:1)
1885
+ connect(options)
1886
+ begin
1887
+ server = find_host_by_name_or_id(args[0])
1888
+ return 1 if server.nil?
1889
+ params = {}
1890
+ @servers_interface.setopts(options)
1891
+ if options[:dry_run]
1892
+ print_dry_run @servers_interface.dry.snapshots(server['id'], params)
1893
+ return
1894
+ end
1895
+ json_response = @servers_interface.snapshots(server['id'], params)
1896
+ snapshots = json_response['snapshots']
1897
+ render_response(json_response, options, 'snapshots') do
1898
+ print_h1 "Snapshots: #{server['name']}", [], options
1899
+ if snapshots.empty?
1900
+ print cyan,"No snapshots found",reset,"\n"
1901
+ else
1902
+ snapshot_column_definitions = {
1903
+ "ID" => lambda {|it| it['id'] },
1904
+ "Name" => lambda {|it| it['name'] },
1905
+ "Description" => lambda {|it| it['snapshotType'] ? (it['snapshotType']['name'] || it['snapshotType']['code']) : '' },
1906
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
1907
+ "Status" => lambda {|it| format_snapshot_status(it) }
1908
+ }
1909
+ print cyan
1910
+ print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
1911
+ print_results_pagination({size: snapshots.size, total: snapshots.size})
1912
+ end
1913
+ print reset, "\n"
1914
+ end
1915
+ return 0
1916
+ rescue RestClient::Exception => e
1917
+ print_rest_exception(e, options)
1918
+ exit 1
1919
+ end
1920
+ end
1921
+
1784
1922
  private
1785
1923
 
1786
1924
  def find_host_by_id(id)
@@ -1897,11 +2035,38 @@ class Morpheus::Cli::Hosts
1897
2035
 
1898
2036
  def format_server_status(server, return_color=cyan)
1899
2037
  out = ""
1900
- status_string = server['status']
1901
- # todo: colorize, upcase?
1902
- out << status_string.to_s
2038
+ status_string = server['status'].to_s.downcase
2039
+ if status_string == 'provisioned'
2040
+ out = "#{cyan}#{status_string.upcase}#{return_color}"
2041
+ elsif status_string == 'provisioning'
2042
+ out = "#{cyan}#{status_string.upcase}#{cyan}"
2043
+ elsif status_string == 'failed' or status_string == 'error'
2044
+ out = "#{red}#{status_string.upcase}#{return_color}"
2045
+ else
2046
+ out = "#{yellow}#{status_string.upcase}#{return_color}"
2047
+ end
2048
+ out
2049
+ end
2050
+
2051
+ def format_server_status_friendly(server, return_color=cyan)
2052
+ out = ""
2053
+ status_string = server['status'].to_s.downcase
2054
+ if status_string == 'provisioned'
2055
+ # out = format_server_power_state(server, return_color)
2056
+ # make it looks like format_instance_status
2057
+ if server['powerState'] == 'on'
2058
+ out << "#{green}RUNNING#{return_color}"
2059
+ elsif server['powerState'] == 'off'
2060
+ out << "#{red}STOPPED#{return_color}"
2061
+ else
2062
+ out << "#{white}#{server['powerState'].to_s.upcase}#{return_color}"
2063
+ end
2064
+ else
2065
+ out = format_server_status(server, return_color)
2066
+ end
1903
2067
  out
1904
2068
  end
2069
+
1905
2070
 
1906
2071
  def make_managed_option_types(connected=true)
1907
2072
  [
@@ -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