morpheus-cli 4.2.8 → 4.2.10

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api.rb +1 -1
  4. data/lib/morpheus/api/activity_interface.rb +9 -0
  5. data/lib/morpheus/api/api_client.rb +83 -27
  6. data/lib/morpheus/api/apps_interface.rb +21 -0
  7. data/lib/morpheus/api/dashboard_interface.rb +5 -21
  8. data/lib/morpheus/api/instances_interface.rb +3 -10
  9. data/lib/morpheus/api/invoice_line_items_interface.rb +14 -0
  10. data/lib/morpheus/api/invoices_interface.rb +7 -12
  11. data/lib/morpheus/api/library_layouts_interface.rb +8 -0
  12. data/lib/morpheus/api/ping_interface.rb +20 -0
  13. data/lib/morpheus/api/projects_interface.rb +33 -0
  14. data/lib/morpheus/api/setup_interface.rb +19 -36
  15. data/lib/morpheus/api/user_settings_interface.rb +0 -6
  16. data/lib/morpheus/api/whoami_interface.rb +4 -8
  17. data/lib/morpheus/benchmarking.rb +16 -26
  18. data/lib/morpheus/cli.rb +10 -5
  19. data/lib/morpheus/cli/access_token_command.rb +5 -8
  20. data/lib/morpheus/cli/activity_command.rb +146 -0
  21. data/lib/morpheus/cli/apps.rb +312 -121
  22. data/lib/morpheus/cli/archives_command.rb +1 -1
  23. data/lib/morpheus/cli/auth_command.rb +4 -11
  24. data/lib/morpheus/cli/blueprints_command.rb +196 -137
  25. data/lib/morpheus/cli/change_password_command.rb +1 -1
  26. data/lib/morpheus/cli/cli_command.rb +225 -72
  27. data/lib/morpheus/cli/cli_registry.rb +2 -2
  28. data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
  29. data/lib/morpheus/cli/clouds.rb +5 -20
  30. data/lib/morpheus/cli/clusters.rb +4 -28
  31. data/lib/morpheus/cli/commands/standard/alias_command.rb +2 -9
  32. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +2 -0
  33. data/lib/morpheus/cli/commands/standard/curl_command.rb +2 -3
  34. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -6
  35. data/lib/morpheus/cli/commands/standard/man_command.rb +10 -7
  36. data/lib/morpheus/cli/commands/standard/ssl_verification_command.rb +10 -9
  37. data/lib/morpheus/cli/containers_command.rb +3 -3
  38. data/lib/morpheus/cli/credentials.rb +13 -16
  39. data/lib/morpheus/cli/error_handler.rb +18 -12
  40. data/lib/morpheus/cli/errors.rb +45 -0
  41. data/lib/morpheus/cli/execute_schedules_command.rb +1 -1
  42. data/lib/morpheus/cli/execution_request_command.rb +4 -4
  43. data/lib/morpheus/cli/groups.rb +84 -132
  44. data/lib/morpheus/cli/hosts.rb +6 -16
  45. data/lib/morpheus/cli/instances.rb +100 -183
  46. data/lib/morpheus/cli/invoices_command.rb +505 -71
  47. data/lib/morpheus/cli/library_layouts_command.rb +254 -166
  48. data/lib/morpheus/cli/library_option_lists_command.rb +0 -87
  49. data/lib/morpheus/cli/library_option_types_command.rb +0 -96
  50. data/lib/morpheus/cli/license.rb +3 -0
  51. data/lib/morpheus/cli/login.rb +17 -37
  52. data/lib/morpheus/cli/logout.rb +9 -5
  53. data/lib/morpheus/cli/mixins/accounts_helper.rb +83 -7
  54. data/lib/morpheus/cli/mixins/operations_helper.rb +41 -0
  55. data/lib/morpheus/cli/mixins/option_source_helper.rb +255 -0
  56. data/lib/morpheus/cli/mixins/print_helper.rb +18 -4
  57. data/lib/morpheus/cli/mixins/provisioning_helper.rb +222 -13
  58. data/lib/morpheus/cli/mixins/remote_helper.rb +139 -0
  59. data/lib/morpheus/cli/monitoring_checks_command.rb +11 -3
  60. data/lib/morpheus/cli/network_groups_command.rb +8 -2
  61. data/lib/morpheus/cli/option_types.rb +1 -1
  62. data/lib/morpheus/cli/ping.rb +252 -0
  63. data/lib/morpheus/cli/price_sets_command.rb +16 -27
  64. data/lib/morpheus/cli/prices_command.rb +34 -27
  65. data/lib/morpheus/cli/processes_command.rb +81 -7
  66. data/lib/morpheus/cli/projects_command.rb +607 -0
  67. data/lib/morpheus/cli/recent_activity_command.rb +87 -65
  68. data/lib/morpheus/cli/remote.rb +965 -974
  69. data/lib/morpheus/cli/reports_command.rb +3 -15
  70. data/lib/morpheus/cli/roles.rb +8 -31
  71. data/lib/morpheus/cli/service_plans_command.rb +25 -31
  72. data/lib/morpheus/cli/setup.rb +392 -0
  73. data/lib/morpheus/cli/shell.rb +144 -56
  74. data/lib/morpheus/cli/subnets_command.rb +71 -11
  75. data/lib/morpheus/cli/tasks.rb +3 -3
  76. data/lib/morpheus/cli/user_sources_command.rb +4 -4
  77. data/lib/morpheus/cli/users.rb +135 -109
  78. data/lib/morpheus/cli/version.rb +1 -1
  79. data/lib/morpheus/cli/whitelabel_settings_command.rb +7 -7
  80. data/lib/morpheus/cli/whoami.rb +90 -129
  81. data/lib/morpheus/cli/wiki_command.rb +2 -14
  82. data/lib/morpheus/ext/rest_client.rb +36 -0
  83. data/lib/morpheus/formatters.rb +42 -5
  84. data/lib/morpheus/rest_client.rb +0 -10
  85. data/lib/morpheus/terminal.rb +41 -1
  86. data/lib/morpheus/util.rb +24 -0
  87. metadata +16 -3
  88. data/lib/morpheus/cli/command_error.rb +0 -22
@@ -3,14 +3,18 @@ require 'date'
3
3
 
4
4
  class Morpheus::Cli::InvoicesCommand
5
5
  include Morpheus::Cli::CliCommand
6
+ include Morpheus::Cli::ProvisioningHelper
7
+ include Morpheus::Cli::OptionSourceHelper
6
8
 
7
9
  set_command_name :'invoices'
8
10
 
9
- register_subcommands :list, :get
11
+ register_subcommands :list, :get, :refresh,
12
+ :list_line_items, :get_line_item
10
13
 
11
14
  def connect(opts)
12
15
  @api_client = establish_remote_appliance_connection(opts)
13
16
  @invoices_interface = @api_client.invoices
17
+ @invoice_line_items_interface = @api_client.invoice_line_items
14
18
  end
15
19
 
16
20
  def handle(args)
@@ -24,17 +28,17 @@ class Morpheus::Cli::InvoicesCommand
24
28
  optparse = Morpheus::Cli::OptionParser.new do |opts|
25
29
  opts.banner = subcommand_usage()
26
30
  opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
27
- options[:show_actual_costs] = true
28
- options[:show_costs] = true
31
+ options[:show_estimates] = true
32
+ # options[:show_costs] = true
29
33
  options[:show_prices] = true
30
34
  options[:show_raw_data] = true
31
35
  end
32
- opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Memory, Storage, etc." ) do
33
- options[:show_actual_costs] = true
34
- end
35
- opts.on('--costs', '--costs', "Display all costs: Compute, Memory, Storage, etc." ) do
36
- options[:show_costs] = true
36
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Memory, Storage, etc." ) do
37
+ options[:show_estimates] = true
37
38
  end
39
+ # opts.on('--costs', '--costs', "Display all costs: Compute, Memory, Storage, etc." ) do
40
+ # options[:show_costs] = true
41
+ # end
38
42
  opts.on('--prices', '--prices', "Display prices: Total, Compute, Memory, Storage, etc." ) do
39
43
  options[:show_prices] = true
40
44
  end
@@ -63,28 +67,32 @@ class Morpheus::Cli::InvoicesCommand
63
67
  end
64
68
  opts.add_hidden_option('--ref-id')
65
69
  opts.on('--group ID', String, "Filter by Group") do |val|
66
- params['siteId'] ||= []
67
- params['siteId'] << val
70
+ options[:groups] ||= []
71
+ options[:groups] << val
68
72
  end
69
- opts.on('--cloud ID', String, "Filter by Cloud") do |val|
70
- params['zoneId'] ||= []
71
- params['zoneId'] << val
73
+ opts.on( '-c', '--cloud CLOUD', "Filter by Cloud" ) do |val|
74
+ options[:clouds] ||= []
75
+ options[:clouds] << val
72
76
  end
73
77
  opts.on('--instance ID', String, "Filter by Instance") do |val|
74
- params['instanceId'] ||= []
75
- params['instanceId'] << val
78
+ options[:instances] ||= []
79
+ options[:instances] << val
76
80
  end
77
81
  opts.on('--container ID', String, "Filter by Container") do |val|
78
82
  params['containerId'] ||= []
79
83
  params['containerId'] << val
80
84
  end
81
85
  opts.on('--server ID', String, "Filter by Server (Host)") do |val|
82
- params['serverId'] ||= []
83
- params['serverId'] << val
86
+ options[:servers] ||= []
87
+ options[:servers] << val
84
88
  end
85
89
  opts.on('--user ID', String, "Filter by User") do |val|
86
- params['userId'] ||= []
87
- params['userId'] << val
90
+ options[:users] ||= []
91
+ options[:users] << val
92
+ end
93
+ opts.on('--project PROJECT', String, "View invoices for a project.") do |val|
94
+ options[:projects] ||= []
95
+ options[:projects] << val
88
96
  end
89
97
  # opts.on('--cluster ID', String, "Filter by Cluster") do |val|
90
98
  # params['clusterId'] ||= []
@@ -116,15 +124,45 @@ class Morpheus::Cli::InvoicesCommand
116
124
  end
117
125
  optparse.parse!(args)
118
126
  connect(options)
127
+ # verify_args!(args:args, optparse:optparse, count:0)
119
128
  if args.count > 0
120
- print_error Morpheus::Terminal.angry_prompt
121
- puts_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
122
- return 1
129
+ options[:phrase] = args.join(" ")
123
130
  end
124
131
  begin
132
+ # construct params
133
+ params.merge!(parse_list_options(options))
134
+ if options[:clouds]
135
+ cloud_ids = parse_cloud_id_list(options[:clouds])
136
+ return 1, "clouds not found for #{options[:clouds]}" if cloud_ids.nil?
137
+ params['zoneId'] = cloud_ids
138
+ end
139
+ if options[:groups]
140
+ group_ids = parse_group_id_list(options[:groups])
141
+ return 1, "groups not found for #{options[:groups]}" if group_ids.nil?
142
+ params['siteId'] = group_ids
143
+ end
144
+ if options[:instances]
145
+ instance_ids = parse_instance_id_list(options[:instances])
146
+ return 1, "instances not found for #{options[:instances]}" if instance_ids.nil?
147
+ params['instanceId'] = instance_ids
148
+ end
149
+ if options[:servers]
150
+ server_ids = parse_server_id_list(options[:servers])
151
+ return 1, "servers not found for #{options[:servers]}" if server_ids.nil?
152
+ params['serverId'] = server_ids
153
+ end
154
+ if options[:users]
155
+ user_ids = parse_user_id_list(options[:users])
156
+ return 1, "users not found for #{options[:users]}" if user_ids.nil?
157
+ params['userId'] = user_ids
158
+ end
159
+ if options[:projects]
160
+ project_ids = parse_project_id_list(options[:projects])
161
+ return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
162
+ params['projectId'] = project_ids
163
+ end
125
164
  params['rawData'] = true if options[:show_raw_data]
126
165
  params['refId'] = ref_ids unless ref_ids.empty?
127
- params.merge!(parse_list_options(options))
128
166
  @invoices_interface.setopts(options)
129
167
  if options[:dry_run]
130
168
  print_dry_run @invoices_interface.dry.list(params)
@@ -136,12 +174,6 @@ class Morpheus::Cli::InvoicesCommand
136
174
  invoices = json_response['invoices']
137
175
  title = "Morpheus Invoices"
138
176
  subtitles = []
139
- if params['status']
140
- subtitles << "Status: #{params['status']}"
141
- end
142
- if params['alarmStatus'] == 'acknowledged'
143
- subtitles << "(Acknowledged)"
144
- end
145
177
  if params['startDate']
146
178
  subtitles << "Start Date: #{params['startDate']}"
147
179
  end
@@ -155,11 +187,17 @@ class Morpheus::Cli::InvoicesCommand
155
187
  else
156
188
  # current_date = Time.now
157
189
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
190
+ show_projects = invoices.find {|it| it['project'] } || (params['projectId'] || params['projectName'] || params['projectTag'])
158
191
  columns = [
159
192
  {"INVOICE ID" => lambda {|it| it['id'] } },
160
193
  {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
161
194
  {"REF ID" => lambda {|it| it['refId'] } },
162
- {"REF NAME" => lambda {|it| it['refName'] } },
195
+ {"REF NAME" => lambda {|it| it['refName'] } }
196
+ ] + (show_projects ? [
197
+ {"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
198
+ {"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
199
+ {"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } }
200
+ ] : []) + [
163
201
  #{"INTERVAL" => lambda {|it| it['interval'] } },
164
202
  {"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
165
203
  {"ACCOUNT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
@@ -176,35 +214,16 @@ class Morpheus::Cli::InvoicesCommand
176
214
  else
177
215
  format_money(it['totalCost'])
178
216
  end
179
- } },
180
- {"ACTUAL MTD" => lambda {|it| format_money(it['actualRunningCost']) } },
181
- {"ACTUAL TOTAL" => lambda {|it|
182
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['actualTotalCost'].to_f > 0
183
- format_money(it['actualTotalCost']) + " (Projected)"
184
- else
185
- format_money(it['actualTotalCost'])
186
- end
187
217
  } }
188
218
  ]
189
- if options[:show_costs]
190
- columns += [
191
- {"COMPUTE" => lambda {|it| format_money(it['computeCost']) } },
192
- # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
193
- {"STORAGE" => lambda {|it| format_money(it['storageCost']) } },
194
- {"NETWORK" => lambda {|it| format_money(it['networkCost']) } },
195
- {"OTHER" => lambda {|it| format_money(it['extraCost']) } },
196
- ]
197
- end
198
- if options[:show_actual_costs]
199
- columns += [
200
- {"ACTUAL COMPUTE" => lambda {|it| format_money(it['actualComputeCost']) } },
201
- # {"ACTUAL MEMORY" => lambda {|it| format_money(it['actualMemoryCost']) } },
202
- {"ACTUAL STORAGE" => lambda {|it| format_money(it['actualStorageCost']) } },
203
- {"ACTUAL NETWORK" => lambda {|it| format_money(it['actualNetworkCost']) } },
204
- {"ACTUAL OTHER" => lambda {|it| format_money(it['actualExtraCost']) } },
205
- ]
206
- end
207
-
219
+
220
+ columns += [
221
+ {"COMPUTE" => lambda {|it| format_money(it['computeCost']) } },
222
+ # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
223
+ {"STORAGE" => lambda {|it| format_money(it['storageCost']) } },
224
+ {"NETWORK" => lambda {|it| format_money(it['networkCost']) } },
225
+ {"OTHER" => lambda {|it| format_money(it['extraCost']) } },
226
+ ]
208
227
  if options[:show_prices]
209
228
  columns += [
210
229
  {"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice']) } },
@@ -222,7 +241,23 @@ class Morpheus::Cli::InvoicesCommand
222
241
  } }
223
242
  ]
224
243
  end
225
-
244
+ if options[:show_estimates]
245
+ columns += [
246
+ {"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost']) } },
247
+ {"TOTAL EST." => lambda {|it|
248
+ if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['estimatedTotalCost'].to_f > 0
249
+ format_money(it['estimatedTotalCost']) + " (Projected)"
250
+ else
251
+ format_money(it['estimatedTotalCost'])
252
+ end
253
+ } },
254
+ {"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost']) } },
255
+ # {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost']) } },
256
+ {"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost']) } },
257
+ {"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost']) } },
258
+ {"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost']) } },
259
+ ]
260
+ end
226
261
  if options[:show_raw_data]
227
262
  columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
228
263
  end
@@ -240,17 +275,30 @@ class Morpheus::Cli::InvoicesCommand
240
275
  options = {}
241
276
  optparse = Morpheus::Cli::OptionParser.new do |opts|
242
277
  opts.banner = subcommand_usage("[id]")
278
+ opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
279
+ options[:show_estimates] = true
280
+ # options[:show_costs] = true
281
+ options[:show_prices] = true
282
+ options[:show_raw_data] = true
283
+ end
284
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Memory, Storage, etc." ) do
285
+ options[:show_estimates] = true
286
+ end
243
287
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
244
288
  options[:show_raw_data] = true
245
289
  end
290
+ opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
291
+ options[:hide_line_items] = true
292
+ end
246
293
  build_standard_get_options(opts, options)
247
294
  opts.footer = "Get details about a specific invoice."
295
+ opts.footer = <<-EOT
296
+ Get details about a specific invoice.
297
+ [id] is required. This is the id of an invoice.
298
+ EOT
248
299
  end
249
300
  optparse.parse!(args)
250
- if args.count < 1
251
- puts optparse
252
- return 1
253
- end
301
+ verify_args!(args:args, optparse:optparse, min:1)
254
302
  connect(options)
255
303
  id_list = parse_id_list(args)
256
304
  return run_command_for_each_arg(id_list) do |arg|
@@ -284,21 +332,31 @@ class Morpheus::Cli::InvoicesCommand
284
332
  "Ref ID" => lambda {|it| it['refId'] },
285
333
  "Ref Name" => lambda {|it| it['refName'] },
286
334
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
335
+ "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
336
+ "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
337
+ "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
287
338
  "Power State" => lambda {|it| format_server_power_state(it) },
288
339
  "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
289
340
  "Active" => lambda {|it| format_boolean(it['active']) },
290
341
  "Period" => lambda {|it| format_invoice_period(it) },
342
+ "Estimate" => lambda {|it| format_boolean(it['estimate']) },
291
343
  #"Interval" => lambda {|it| it['interval'] },
292
344
  "Start" => lambda {|it| format_date(it['startDate']) },
293
345
  "End" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' },
294
- "Estimate" => lambda {|it| format_boolean(it['estimate']) },
346
+ "Ref Start" => lambda {|it| format_local_dt(it['refStart']) },
347
+ "Ref End" => lambda {|it| it['refEnd'] ? format_local_dt(it['refEnd']) : '' },
295
348
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
296
349
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
297
350
  }
298
351
  # remove columns that do not apply
299
- if !invoice['plan']
352
+ if invoice['plan'].nil?
300
353
  description_cols.delete("Plan")
301
354
  end
355
+ if invoice['project'].nil?
356
+ description_cols.delete("Project ID")
357
+ description_cols.delete("Project Name")
358
+ description_cols.delete("Project Tags")
359
+ end
302
360
  if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
303
361
  description_cols.delete("Power State")
304
362
  end
@@ -337,10 +395,15 @@ class Morpheus::Cli::InvoicesCommand
337
395
  print "\n"
338
396
  # print_h2 "Costs"
339
397
  cost_rows = [
340
- {label: 'Usage Price'.upcase, compute: invoice['computePrice'], memory: invoice['memoryPrice'], storage: invoice['storagePrice'], network: invoice['networkPrice'], license: invoice['licensePrice'], extra: invoice['extraPrice'], running: invoice['runningPrice'], total: invoice['totalPrice']},
341
- {label: 'Usage Cost'.upcase, compute: invoice['computeCost'], memory: invoice['memoryCost'], storage: invoice['storageCost'], network: invoice['networkCost'], license: invoice['licenseCost'], extra: invoice['extraCost'], running: invoice['runningCost'], total: invoice['totalCost']},
342
- {label: 'Actual Cost'.upcase, compute: invoice['actualComputeCost'], memory: invoice['actualMemoryCost'], storage: invoice['actualStorageCost'], network: invoice['actualNetworkCost'], license: invoice['actualLicenseCost'], extra: invoice['actualExtraCost'], running: invoice['actualRunningCost'], total: invoice['actualTotalCost']},
398
+ {label: 'Price'.upcase, compute: invoice['computePrice'], memory: invoice['memoryPrice'], storage: invoice['storagePrice'], network: invoice['networkPrice'], license: invoice['licensePrice'], extra: invoice['extraPrice'], running: invoice['runningPrice'], total: invoice['totalPrice']},
399
+ {label: 'Cost'.upcase, compute: invoice['computeCost'], memory: invoice['memoryCost'], storage: invoice['storageCost'], network: invoice['networkCost'], license: invoice['licenseCost'], extra: invoice['extraCost'], running: invoice['runningCost'], total: invoice['totalCost']},
343
400
  ]
401
+ if options[:show_estimates]
402
+ cost_rows += [
403
+ {label: 'Estimated Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
404
+ {label: 'Estimated Price'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
405
+ ]
406
+ end
344
407
  cost_columns = {
345
408
  "" => lambda {|it| it[:label] },
346
409
  "Compute".upcase => lambda {|it| format_money(it[:compute]) },
@@ -375,6 +438,38 @@ class Morpheus::Cli::InvoicesCommand
375
438
  puts invoice['rawData']
376
439
  end
377
440
 
441
+ # Line Items
442
+ line_items = invoice['lineItems']
443
+ if line_items && line_items.size > 0 && options[:hide_line_items] != true
444
+
445
+ line_items_columns = [
446
+ {"INVOICE ID" => lambda {|it| it['invoiceId'] } },
447
+ {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
448
+ {"REF ID" => lambda {|it| it['refId'] } },
449
+ {"REF NAME" => lambda {|it| it['refName'] } },
450
+ #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
451
+ {"START" => lambda {|it| format_date(it['startDate']) } },
452
+ {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
453
+ {"USAGE TYPE" => lambda {|it| it['usageType'] } },
454
+ {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
455
+ {"USAGE" => lambda {|it| it['itemUsage'] } },
456
+ {"RATE" => lambda {|it| it['itemRate'] } },
457
+ {"COST" => lambda {|it| format_money(it['itemCost']) } },
458
+ {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
459
+ {"TAX" => lambda {|it| format_money(it['itemTax']) } },
460
+ # {"TERM" => lambda {|it| it['itemTerm'] } },
461
+ "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
462
+ "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
463
+ ]
464
+
465
+ if options[:show_raw_data]
466
+ columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
467
+ end
468
+
469
+ print_h2 "Line Items"
470
+ print as_pretty_table(line_items, line_items_columns, options)
471
+ print_results_pagination({total: line_items.size, size: line_items.size})
472
+ end
378
473
 
379
474
  print reset,"\n"
380
475
  return 0
@@ -384,6 +479,347 @@ class Morpheus::Cli::InvoicesCommand
384
479
  end
385
480
  end
386
481
 
482
+ def refresh(args)
483
+ options = {}
484
+ params = {}
485
+ payload = {}
486
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
487
+ opts.banner = subcommand_usage("[--daily] [--costing] [--current] [-c CLOUD]")
488
+ opts.on( '--daily', "Refresh Daily Invoices" ) do
489
+ payload[:daily] = true
490
+ end
491
+ opts.on( '--costing', "Refresh Costing Data" ) do
492
+ payload[:costing] = true
493
+ end
494
+ opts.on( '--current', "Collect the most up to date costing data." ) do
495
+ payload[:current] = true
496
+ end
497
+ opts.on( '--date DATE', String, "Date to collect costing for. By default the cost data is collected for the end of the previous period." ) do |val|
498
+ payload[:date] = val.to_s
499
+ end
500
+ opts.on( '-c', '--cloud CLOUD', "Specify cloud(s) to refresh costing for." ) do |val|
501
+ payload[:clouds] ||= []
502
+ payload[:clouds] << val
503
+ end
504
+ opts.on( '--all', "Refresh costing for all clouds." ) do
505
+ payload[:all] = true
506
+ end
507
+ # opts.on( '-f', '--force', "Force Refresh" ) do
508
+ # query_params[:force] = 'true'
509
+ # end
510
+ build_standard_update_options(opts, options, [:query, :auto_confirm])
511
+ opts.footer = <<-EOT
512
+ Refresh invoices.
513
+ By default, nothing is changed.
514
+ Include --daily to regenerate invoice records.
515
+ Include --costing to refresh actual costing data.
516
+ Include --current to refresh costing data for the actual current time.
517
+ To get the latest invoice costing data, include --daily --costing --current --all
518
+ EOT
519
+ end
520
+ optparse.parse!(args)
521
+ verify_args!(args:args, optparse:optparse, count:0)
522
+ connect(options)
523
+ params.merge!(parse_query_options(options))
524
+ if options[:payload]
525
+ payload = options[:payload]
526
+ end
527
+ payload.deep_merge!(parse_passed_options(options))
528
+ # --clouds
529
+ if payload[:clouds]
530
+ payload[:clouds] = parse_id_list(payload[:clouds]).collect {|cloud_id|
531
+ if cloud_id.to_s =~ /\A\d{1,}\Z/
532
+ cloud_id
533
+ else
534
+ cloud = find_cloud_option(cloud_id)
535
+ return 1 if cloud.nil?
536
+ cloud['id']
537
+ end
538
+ }
539
+ end
540
+ # are you sure?
541
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to refresh invoices?")
542
+ return 9, "aborted command"
543
+ end
544
+ # ok, make the request
545
+ @invoices_interface.setopts(options)
546
+ if options[:dry_run]
547
+ print_dry_run @invoices_interface.dry.refresh(params, payload)
548
+ return
549
+ end
550
+ json_response = @invoices_interface.refresh(params, payload)
551
+ # render the result
552
+ render_result = render_with_format(json_response, options)
553
+ return 0 if render_result
554
+ # print output
555
+ print_green_success(json_response['msg'] || "Refreshing invoices")
556
+ return 0
557
+ end
558
+
559
+ def list_line_items(args)
560
+ options = {}
561
+ params = {}
562
+ ref_ids = []
563
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
564
+ opts.banner = subcommand_usage()
565
+ opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
566
+ options[:show_actual_costs] = true
567
+ options[:show_costs] = true
568
+ options[:show_prices] = true
569
+ options[:show_raw_data] = true
570
+ end
571
+ # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Memory, Storage, etc." ) do
572
+ # options[:show_actual_costs] = true
573
+ # end
574
+ # opts.on('--costs', '--costs', "Display all costs: Compute, Memory, Storage, etc." ) do
575
+ # options[:show_costs] = true
576
+ # end
577
+ # opts.on('--prices', '--prices', "Display prices: Total, Compute, Memory, Storage, etc." ) do
578
+ # options[:show_prices] = true
579
+ # end
580
+ opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
581
+ if val.to_s.downcase == 'cloud' || val.to_s.downcase == 'zone'
582
+ params['refType'] = 'ComputeZone'
583
+ elsif val.to_s.downcase == 'instance'
584
+ params['refType'] = 'Instance'
585
+ elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
586
+ params['refType'] = 'ComputeServer'
587
+ elsif val.to_s.downcase == 'cluster'
588
+ params['refType'] = 'ComputeServerGroup'
589
+ elsif val.to_s.downcase == 'group'
590
+ params['refType'] = 'ComputeSite'
591
+ elsif val.to_s.downcase == 'user'
592
+ params['refType'] = 'User'
593
+ else
594
+ params['refType'] = val
595
+ end
596
+ end
597
+ opts.on('--id ID', String, "Filter by Ref ID") do |val|
598
+ ref_ids << val
599
+ end
600
+ opts.on('--ref-id ID', String, "Filter by Ref ID") do |val|
601
+ ref_ids << val
602
+ end
603
+ opts.add_hidden_option('--ref-id')
604
+ opts.on('--group ID', String, "Filter by Group") do |val|
605
+ options[:groups] ||= []
606
+ options[:groups] << val
607
+ end
608
+ opts.on( '-c', '--cloud CLOUD', "Filter by Cloud" ) do |val|
609
+ options[:clouds] ||= []
610
+ options[:clouds] << val
611
+ end
612
+ opts.on('--instance ID', String, "Filter by Instance") do |val|
613
+ options[:instances] ||= []
614
+ options[:instances] << val
615
+ end
616
+ opts.on('--container ID', String, "Filter by Container") do |val|
617
+ params['containerId'] ||= []
618
+ params['containerId'] << val
619
+ end
620
+ opts.on('--server ID', String, "Filter by Server (Host)") do |val|
621
+ options[:servers] ||= []
622
+ options[:servers] << val
623
+ end
624
+ opts.on('--user ID', String, "Filter by User") do |val|
625
+ options[:users] ||= []
626
+ options[:users] << val
627
+ end
628
+ opts.on('--project PROJECT', String, "View invoices for a project.") do |val|
629
+ options[:projects] ||= []
630
+ options[:projects] << val
631
+ end
632
+ # opts.on('--cluster ID', String, "Filter by Cluster") do |val|
633
+ # params['clusterId'] ||= []
634
+ # params['clusterId'] << val
635
+ # end
636
+ opts.on('--start DATE', String, "Start date in the format YYYY-MM-DD.") do |val|
637
+ params['startDate'] = val #parse_time(val).utc.iso8601
638
+ end
639
+ opts.on('--end DATE', String, "End date in the format YYYY-MM-DD. Default is now.") do |val|
640
+ params['endDate'] = val #parse_time(val).utc.iso8601
641
+ end
642
+ opts.on('--period PERIOD', String, "Period in the format YYYYMM. This can be used instead of start/end.") do |val|
643
+ params['period'] = parse_period(val)
644
+ end
645
+ opts.on('--active [true|false]',String, "Filter by active.") do |val|
646
+ params['active'] = (val.to_s != 'false' && val.to_s != 'off')
647
+ end
648
+ opts.on('--estimate [true|false]',String, "Filter by estimate.") do |val|
649
+ params['estimate'] = (val.to_s != 'false' && val.to_s != 'off')
650
+ end
651
+ opts.on('--tenant ID', String, "View invoice line items for a tenant. Default is your own account.") do |val|
652
+ params['accountId'] = val
653
+ end
654
+ opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
655
+ options[:show_raw_data] = true
656
+ end
657
+ build_standard_list_options(opts, options)
658
+ opts.footer = "List invoice line items."
659
+ end
660
+ optparse.parse!(args)
661
+ connect(options)
662
+ # verify_args!(args:args, optparse:optparse, count:0)
663
+ if args.count > 0
664
+ options[:phrase] = args.join(" ")
665
+ end
666
+
667
+ # construct params
668
+ params.merge!(parse_list_options(options))
669
+ if options[:clouds]
670
+ cloud_ids = parse_cloud_id_list(options[:clouds])
671
+ return 1, "clouds not found for #{options[:clouds]}" if cloud_ids.nil?
672
+ params['zoneId'] = cloud_ids
673
+ end
674
+ if options[:groups]
675
+ group_ids = parse_group_id_list(options[:groups])
676
+ return 1, "groups not found for #{options[:groups]}" if group_ids.nil?
677
+ params['siteId'] = group_ids
678
+ end
679
+ if options[:instances]
680
+ instance_ids = parse_instance_id_list(options[:instances])
681
+ return 1, "instances not found for #{options[:instances]}" if instance_ids.nil?
682
+ params['instanceId'] = instance_ids
683
+ end
684
+ if options[:servers]
685
+ server_ids = parse_server_id_list(options[:servers])
686
+ return 1, "servers not found for #{options[:servers]}" if server_ids.nil?
687
+ params['serverId'] = server_ids
688
+ end
689
+ if options[:users]
690
+ user_ids = parse_user_id_list(options[:users])
691
+ return 1, "users not found for #{options[:users]}" if user_ids.nil?
692
+ params['userId'] = user_ids
693
+ end
694
+ if options[:projects]
695
+ project_ids = parse_project_id_list(options[:projects])
696
+ return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
697
+ params['projectId'] = project_ids
698
+ end
699
+ params['rawData'] = true if options[:show_raw_data]
700
+ params['refId'] = ref_ids unless ref_ids.empty?
701
+ @invoice_line_items_interface.setopts(options)
702
+ if options[:dry_run]
703
+ print_dry_run @invoice_line_items_interface.dry.list(params)
704
+ return
705
+ end
706
+ json_response = @invoice_line_items_interface.list(params)
707
+ line_items = json_response['lineItems']
708
+ render_response(json_response, options, 'lineItems') do
709
+ title = "Morpheus Line Items"
710
+ subtitles = []
711
+ if params['startDate']
712
+ subtitles << "Start Date: #{params['startDate']}"
713
+ end
714
+ if params['endDate']
715
+ subtitles << "End Date: #{params['endDate']}"
716
+ end
717
+ subtitles += parse_list_subtitles(options)
718
+ print_h1 title, subtitles
719
+ if line_items.empty?
720
+ print yellow,"No line items found.",reset,"\n"
721
+ else
722
+ # current_date = Time.now
723
+ # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
724
+ columns = [
725
+ {"INVOICE ID" => lambda {|it| it['invoiceId'] } },
726
+ {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
727
+ {"REF ID" => lambda {|it| it['refId'] } },
728
+ {"REF NAME" => lambda {|it| it['refName'] } },
729
+ #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
730
+ {"START" => lambda {|it| format_date(it['startDate']) } },
731
+ {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
732
+ {"USAGE TYPE" => lambda {|it| it['usageType'] } },
733
+ {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
734
+ {"USAGE" => lambda {|it| it['itemUsage'] } },
735
+ {"RATE" => lambda {|it| it['itemRate'] } },
736
+ {"COST" => lambda {|it| format_money(it['itemCost']) } },
737
+ {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
738
+ {"TAX" => lambda {|it| format_money(it['itemTax']) } },
739
+ # {"TERM" => lambda {|it| it['itemTerm'] } },
740
+ "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
741
+ "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
742
+ ]
743
+
744
+ if options[:show_raw_data]
745
+ columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
746
+ end
747
+ print as_pretty_table(line_items, columns, options)
748
+ print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
749
+ end
750
+ print reset,"\n"
751
+ end
752
+ if line_items.empty?
753
+ return 1, "no line items found"
754
+ else
755
+ return 0, nil
756
+ end
757
+ end
758
+
759
+ def get_line_item(args)
760
+ options = {}
761
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
762
+ opts.banner = subcommand_usage("[id]")
763
+ opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
764
+ options[:show_raw_data] = true
765
+ end
766
+ build_standard_get_options(opts, options)
767
+ opts.footer = "Get details about a specific invoice line item."
768
+ opts.footer = <<-EOT
769
+ Get details about a specific invoice line item.
770
+ [id] is required. This is the id of an invoice line item.
771
+ EOT
772
+ end
773
+ optparse.parse!(args)
774
+ verify_args!(args:args, optparse:optparse, min:1)
775
+ connect(options)
776
+ id_list = parse_id_list(args)
777
+ return run_command_for_each_arg(id_list) do |arg|
778
+ _get(arg, options)
779
+ end
780
+ end
781
+
782
+ def _get_line_item(id, options)
783
+ params = {}
784
+ if options[:show_raw_data]
785
+ params['rawData'] = true
786
+ end
787
+ @invoice_line_items_interface.setopts(options)
788
+ if options[:dry_run]
789
+ print_dry_run @invoice_line_items_interface.dry.get(id, params)
790
+ return
791
+ end
792
+ json_response = @invoice_line_items_interface.get(id, params)
793
+ line_item = json_response['lineItem']
794
+ render_response(json_response, options, 'lineItem') do
795
+ print_h1 "Line Item Details"
796
+ print cyan
797
+ description_cols = {
798
+ "ID" => lambda {|it| it['id'] },
799
+ "Invoice ID" => lambda {|it| it['invoiceId'] },
800
+ "Type" => lambda {|it| format_invoice_ref_type(it) },
801
+ "Ref ID" => lambda {|it| it['refId'] },
802
+ "Ref Name" => lambda {|it| it['refName'] },
803
+ "Start" => lambda {|it| format_date(it['startDate']) },
804
+ "End" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' },
805
+ "Usage Type" => lambda {|it| it['usageType'] },
806
+ "Usage Category" => lambda {|it| it['usageCategory'] },
807
+ "Item Usage" => lambda {|it| it['itemUsage'] },
808
+ "Item Rate" => lambda {|it| it['itemRate'] },
809
+ "Item Cost" => lambda {|it| format_money(it['itemCost']) },
810
+ "Item Price" => lambda {|it| format_money(it['itemrPrice']) },
811
+ "Item Tax" => lambda {|it| format_money(it['itemTax']) },
812
+ "Item Term" => lambda {|it| it['itemTerm'] },
813
+ #"Tax Type" => lambda {|it| it['taxType'] },
814
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
815
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
816
+ }
817
+ print_description_list(description_cols, line_item, options)
818
+ print reset,"\n"
819
+ end
820
+ return 0, nil
821
+ end
822
+
387
823
  private
388
824
 
389
825
  # def find_invoice_by_name_or_id(val)
@@ -426,10 +862,8 @@ class Morpheus::Cli::InvoicesCommand
426
862
  def format_invoice_ref_type(it)
427
863
  if it['refType'] == 'ComputeZone'
428
864
  "Cloud"
429
- elsif it['refType'] == 'Instance'
430
- "Instance"
431
- elsif it['refType'] == 'ComputeServer'
432
- "Host"
865
+ # elsif it['refType'] == 'ComputeServer'
866
+ # "Host"
433
867
  elsif it['refType'] == 'ComputeServerGroup'
434
868
  "Cluster"
435
869
  elsif it['refType'] == 'ComputeSite'