morpheus-cli 4.2.15 → 4.2.16

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6215b2bb521606b6de3580787cca558fa2925f2c037f5c6bfbf2f1d952e0f81
4
- data.tar.gz: 64f921b6c079eabda7fdad9bf70f55af423c0cc1a42f6f5ccead6442737a684b
3
+ metadata.gz: 3481d47679761310fbffd07ad968588ace4b92d9f428f68983f90a3ef4e13799
4
+ data.tar.gz: 5c900c15f68c14d5ab10d89b148e2bee5216653c123004b528f3d8bf1c1740cb
5
5
  SHA512:
6
- metadata.gz: ba237f9f75cb4f64d04699ae8feaf3840c87b2cb62c1f0ec224a70aece0a23c76105de608f493f019e44f43ebd72590df226cf61654aee6fa34881754695a82b
7
- data.tar.gz: 92f3c614362d4dfe3385aecdb8f52847a336d47aa41afc3a9ee6909f221a672454bc98a2e3bfdbe94a48208cebbb9f0293abc7bd9d9c177e7f11ec8b0989a9b6
6
+ metadata.gz: 384148f22d822c664456d5de65813d8ff6e4ea07b9a26a68ab8803131aa43247e093f4cae9304cb591d58f6f075bebccdbbdd2ca614f615b33ebc46b242a8cf2
7
+ data.tar.gz: 8ae2af56264a5bb3163ae5b8bb04747f58ef4e96f3ecdfed5b3a66e14ebce5147ef73623624fc22ab16536d4834a21b7de8a7b57952b86a9ad128abb712cf37f
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.5.1
2
2
 
3
- RUN gem install morpheus-cli -v 4.2.14
3
+ RUN gem install morpheus-cli -v 4.2.16
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -169,14 +169,18 @@ module Morpheus
169
169
  full_field_name = "#{field_namespace.join('.')}.#{field_name}"
170
170
  end
171
171
 
172
- description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{option_type['defaultValue'] ? ' Default: '+option_type['defaultValue'].to_s : ''}"
172
+ description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}"
173
173
  if option_type['description']
174
174
  # description << "\n #{option_type['description']}"
175
175
  description << " - #{option_type['description']}"
176
176
  end
177
+ if option_type['defaultValue']
178
+ description << ". Default: #{option_type['defaultValue']}"
179
+ end
177
180
  if option_type['helpBlock']
178
181
  description << "\n #{option_type['helpBlock']}"
179
182
  end
183
+
180
184
  # description = option_type['description'].to_s
181
185
  # if option_type['defaultValue']
182
186
  # description = "#{description} Default: #{option_type['defaultValue']}"
@@ -804,6 +808,20 @@ module Morpheus
804
808
  self.class.subcommand_aliases
805
809
  end
806
810
 
811
+ # def subcommand_descriptions
812
+ # self.class.subcommand_descriptions
813
+ # end
814
+
815
+ def get_subcommand_description(subcmd)
816
+ self.class.get_subcommand_description(subcmd)
817
+ end
818
+
819
+ def subcommand_description()
820
+ calling_method = caller[0][/`([^']*)'/, 1].to_s.sub('block in ', '')
821
+ subcommand_name = subcommands.key(calling_method)
822
+ subcommand_name ? get_subcommand_description(subcommand_name) : nil
823
+ end
824
+
807
825
  def default_subcommand
808
826
  self.class.default_subcommand
809
827
  end
@@ -845,8 +863,11 @@ module Morpheus
845
863
  if !subcommands.empty?
846
864
  out << "Commands:"
847
865
  out << "\n"
848
- subcommands.sort.each {|cmd, method|
849
- out << "\t#{cmd.to_s}\n"
866
+ subcommands.sort.each {|subcmd, method|
867
+ desc = get_subcommand_description(subcmd)
868
+ out << "\t#{subcmd.to_s}"
869
+ out << "\t#{desc}" if desc
870
+ out << "\n"
850
871
  }
851
872
  end
852
873
  # out << "\n"
@@ -1277,12 +1298,46 @@ module Morpheus
1277
1298
  v = cmd.to_s.gsub('-', '_')
1278
1299
  register_subcommands({(k) => v})
1279
1300
  else
1280
- raise "Unable to register command of type: #{cmd.class} #{cmd}"
1301
+ raise Morpheus::Cli::CliRegistry::BadCommandDefinition.new("Unable to register command of type: #{cmd.class} #{cmd}")
1281
1302
  end
1282
1303
  }
1283
1304
  return
1284
1305
  end
1285
1306
 
1307
+ # this might be the new hotness
1308
+ # register_subcommand(:show) # do not do this, always define a description!
1309
+ # register_subcommand(:list, "List things")
1310
+ # register_subcommand("update-all", "update_all", "Update all things")
1311
+ # If the command name =~ method, no need to pass both
1312
+ # command names will have "-" swapped in for "_" and vice versa for method names.
1313
+ def register_subcommand(*args)
1314
+ args = args.flatten
1315
+ cmd_name = args[0]
1316
+ cmd_method = nil
1317
+ cmd_desc = nil
1318
+ if args.count == 1
1319
+ cmd_method = cmd_name
1320
+ elsif args.count == 2
1321
+ if args[1].is_a?(Symbol)
1322
+ cmd_method = args[1]
1323
+ else
1324
+ cmd_method = cmd_name
1325
+ cmd_desc = args[1]
1326
+ end
1327
+ elsif args.count == 3
1328
+ cmd_method = args[1]
1329
+ cmd_desc = args[2]
1330
+ else
1331
+ raise Morpheus::Cli::CliRegistry::BadCommandDefinition.new("register_subcommand expects 1-3 arguments, got #{args.size} #{args.inspect}")
1332
+ end
1333
+ cmd_name = cmd_name.to_s.gsub("_", "-").to_sym
1334
+ cmd_method = (cmd_method || cmd_name).to_s.gsub("-", "_").to_sym
1335
+ cmd_definition = {(cmd_name) => cmd_method}
1336
+ register_subcommands(cmd_definition)
1337
+ add_subcommand_description(cmd_name, cmd_desc)
1338
+ return
1339
+ end
1340
+
1286
1341
  def set_default_subcommand(cmd)
1287
1342
  @default_subcommand = cmd
1288
1343
  end
@@ -1341,6 +1396,31 @@ module Morpheus
1341
1396
  @subcommand_aliases.delete(alias_cmd_name.to_s)
1342
1397
  end
1343
1398
 
1399
+ def subcommand_descriptions
1400
+ @subcommand_descriptions ||= {}
1401
+ end
1402
+
1403
+ def add_subcommand_description(cmd_name, description)
1404
+ @subcommand_descriptions ||= {}
1405
+ @subcommand_descriptions[cmd_name.to_s.gsub('_', '-')] = description
1406
+ end
1407
+
1408
+ def get_subcommand_description(cmd_name)
1409
+ desc = subcommand_descriptions[cmd_name.to_s.gsub('_', '-')]
1410
+ if desc
1411
+ return desc
1412
+ else
1413
+ cmd_method = subcommands.key(cmd_name)
1414
+ return cmd_method ? subcommand_descriptions[cmd_method.to_s.gsub('_', '-')] : nil
1415
+ end
1416
+ end
1417
+
1418
+ def set_subcommand_descriptions(cmd_map)
1419
+ cmd_map.each do |cmd_name, description|
1420
+ add_subcommand_description(cmd_name, description)
1421
+ end
1422
+ end
1423
+
1344
1424
  end
1345
1425
  end
1346
1426
  end
@@ -9,6 +9,9 @@ module Morpheus
9
9
  module Cli
10
10
  class CliRegistry
11
11
 
12
+ class BadCommandDefinition < StandardError
13
+ end
14
+
12
15
  class BadAlias < StandardError
13
16
  end
14
17
 
@@ -7,11 +7,14 @@ class Morpheus::Cli::BenchmarkCommand
7
7
  include Morpheus::Cli::CliCommand
8
8
  set_command_name :'benchmark'
9
9
 
10
- # control global benchmark toggle
11
- register_subcommands :on, :off, :on?, :off?
12
- # record your own benchmarks
13
- register_subcommands :start, :stop, :status, :exec => :execute
14
-
10
+ register_subcommand :on, "Enable global benchmarking."
11
+ register_subcommand :off, "Disable global benchmarking."
12
+ register_subcommand :on?, "Print the value of the global benchmark setting. Exit 0 if on."
13
+ register_subcommand :off?, "Print the value of the global benchmark setting. Exit 0 if off."
14
+ register_subcommand :start, "Start recording a benchmark."
15
+ register_subcommand :stop, "Stop recording a benchmark."
16
+ register_subcommand :status, "Print status of benchmark."
17
+ register_subcommand :exec, :execute, "Benchmark a specified command or expression."
15
18
 
16
19
  # this would be cool, we should store all benchmarking results in memory or on disk =o
17
20
  # register_subcommands :list, :get, :put, :remove
@@ -35,7 +38,7 @@ class Morpheus::Cli::BenchmarkCommand
35
38
  opts.banner = subcommand_usage("")
36
39
  build_common_options(opts, options, [:quiet])
37
40
  opts.footer = <<-EOT
38
- Enable global benchmarking.
41
+ #{subcommand_description}
39
42
  This behaves the same as if you were to add the -B switch to every command.
40
43
  EOT
41
44
  end
@@ -65,7 +68,7 @@ EOT
65
68
  opts.banner = subcommand_usage("")
66
69
  build_common_options(opts, options, [:quiet])
67
70
  opts.footer = <<-EOT
68
- Disable global benchmarking.
71
+ #{subcommand_description}
69
72
  The default state for this setting is off.
70
73
  EOT
71
74
  end
@@ -95,7 +98,7 @@ EOT
95
98
  opts.banner = subcommand_usage("")
96
99
  build_common_options(opts, options, [:quiet])
97
100
  opts.footer = <<-EOT
98
- Print the value of the global benchmark setting.
101
+ #{subcommand_description}
99
102
  Exit 0 if on.
100
103
  EOT
101
104
  end
@@ -121,7 +124,7 @@ EOT
121
124
  opts.banner = subcommand_usage("")
122
125
  build_common_options(opts, options, [:quiet])
123
126
  opts.footer = <<-EOT
124
- Print the value of the global benchmark setting.
127
+ #{subcommand_description}
125
128
  Exit 0 if off.
126
129
  EOT
127
130
  end
@@ -149,7 +152,7 @@ EOT
149
152
  opts.banner = subcommand_usage("[name]")
150
153
  build_common_options(opts, options, [:quiet])
151
154
  opts.footer = <<-EOT
152
- Start recording a benchmark.
155
+ #{subcommand_description}
153
156
  [name] is required. This is just a name for the routine.
154
157
  This allows you to record how long it takes to run a series of commands.
155
158
  Just run `benchmark stop` when you are finished.
@@ -194,7 +197,7 @@ EOT
194
197
  end
195
198
  build_common_options(opts, options, [:quiet])
196
199
  opts.footer = <<-EOT
197
- Stop recording a benchmark.
200
+ #{subcommand_description}
198
201
  [name] is optional. This is the name of the benchmark to stop.
199
202
  The last benchmark is used by default.
200
203
  EOT
@@ -254,7 +257,7 @@ EOT
254
257
  opts.banner = subcommand_usage("[name]")
255
258
  build_common_options(opts, options, [:quiet])
256
259
  opts.footer = <<-EOT
257
- Print status of benchmark.
260
+ #{subcommand_description}
258
261
  [name] is optional. This is the name of the benchmark to inspect.
259
262
  The last benchmark is used by default.
260
263
  EOT
@@ -309,7 +312,7 @@ EOT
309
312
  end
310
313
  build_common_options(opts, options, [:quiet])
311
314
  opts.footer = <<-EOT
312
- Benchmark a specified command.
315
+ #{subcommand_description}
313
316
  [command] is required. This is the command to execute
314
317
  EOT
315
318
  end
@@ -27,37 +27,26 @@ class Morpheus::Cli::InvoicesCommand
27
27
  ref_ids = []
28
28
  optparse = Morpheus::Cli::OptionParser.new do |opts|
29
29
  opts.banner = subcommand_usage()
30
- opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
30
+ opts.on('-a', '--all', "Display all details, costs and prices." ) do
31
+ options[:show_all] = true
31
32
  options[:show_estimates] = true
32
33
  # options[:show_costs] = true
33
34
  options[:show_prices] = true
34
- options[:show_raw_data] = true
35
+ # options[:show_raw_data] = true
35
36
  end
36
- opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Memory, Storage, etc." ) do
37
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Other" ) do
37
38
  options[:show_estimates] = true
38
39
  end
39
- # opts.on('--costs', '--costs', "Display all costs: Compute, Memory, Storage, etc." ) do
40
+ # opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Other" ) do
40
41
  # options[:show_costs] = true
41
42
  # end
42
- opts.on('--prices', '--prices', "Display prices: Total, Compute, Memory, Storage, etc." ) do
43
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
43
44
  options[:show_prices] = true
44
45
  end
45
46
  opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
46
- if val.to_s.downcase == 'cloud' || val.to_s.downcase == 'zone'
47
- params['refType'] = 'ComputeZone'
48
- elsif val.to_s.downcase == 'instance'
49
- params['refType'] = 'Instance'
50
- elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
51
- params['refType'] = 'ComputeServer'
52
- elsif val.to_s.downcase == 'cluster'
53
- params['refType'] = 'ComputeServerGroup'
54
- elsif val.to_s.downcase == 'group'
55
- params['refType'] = 'ComputeSite'
56
- elsif val.to_s.downcase == 'user'
57
- params['refType'] = 'User'
58
- else
59
- params['refType'] = val
60
- end
47
+ params['refType'] ||= []
48
+ values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
49
+ values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
61
50
  end
62
51
  opts.on('--id ID', String, "Filter by Ref ID") do |val|
63
52
  ref_ids << val
@@ -123,6 +112,14 @@ class Morpheus::Cli::InvoicesCommand
123
112
  params['includeTotals'] = true
124
113
  options[:show_invoice_totals] = true
125
114
  end
115
+ opts.on('--totals-only', "View totals only") do |val|
116
+ params['includeTotals'] = true
117
+ options[:show_invoice_totals] = true
118
+ options[:totals_only] = true
119
+ end
120
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
121
+ options[:sigdig] = val.to_i
122
+ end
126
123
  build_standard_list_options(opts, options)
127
124
  opts.footer = "List invoices."
128
125
  end
@@ -185,7 +182,9 @@ class Morpheus::Cli::InvoicesCommand
185
182
  subtitles += parse_list_subtitles(options)
186
183
  print_h1 title, subtitles
187
184
  if invoices.empty?
188
- print cyan,"No invoices found.",reset,"\n"
185
+ unless options[:totals_only]
186
+ print cyan,"No invoices found.",reset,"\n"
187
+ end
189
188
  else
190
189
  # current_date = Time.now
191
190
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
@@ -194,98 +193,146 @@ class Morpheus::Cli::InvoicesCommand
194
193
  {"INVOICE ID" => lambda {|it| it['id'] } },
195
194
  {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
196
195
  {"REF ID" => lambda {|it| it['refId'] } },
197
- {"REF NAME" => lambda {|it| it['refName'] } }
198
- ] + (show_projects ? [
199
- {"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
200
- {"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
201
- {"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } }
202
- ] : []) + [
196
+ {"REF NAME" => lambda {|it|
197
+ if options[:show_all]
198
+ it['refName']
199
+ else
200
+ truncate_string_right(it['refName'], 100)
201
+ end
202
+ } },
203
203
  #{"INTERVAL" => lambda {|it| it['interval'] } },
204
204
  {"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
205
- {"ACCOUNT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
206
- {"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
207
- #{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
205
+ #{"TENANT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
206
+
207
+ #{"COST TYPE" => lambda {|it| it['costType'].to_s.capitalize } },
208
208
  {"PERIOD" => lambda {|it| format_invoice_period(it) } },
209
209
  {"START" => lambda {|it| format_date(it['startDate']) } },
210
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
211
- {"MTD" => lambda {|it| format_money(it['runningCost']) } },
210
+ {"END" => lambda {|it| format_date(it['endDate']) } },
211
+ ] + (options[:show_all] ? [
212
+ {"REF START" => lambda {|it| format_dt(it['refStart']) } },
213
+ {"REF END" => lambda {|it| format_dt(it['refEnd']) } },
214
+ ] : []) + [
215
+ {"COMPUTE" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) } },
216
+ # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
217
+ {"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
218
+ {"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
219
+ {"OTHER" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
220
+ {"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
212
221
  {"TOTAL" => lambda {|it|
213
-
214
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['totalCost'].to_f > 0
215
- format_money(it['totalCost']) + " (Projected)"
216
- else
217
- format_money(it['totalCost'])
218
- end
222
+ format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
219
223
  } }
220
224
  ]
221
225
 
222
- columns += [
223
- {"COMPUTE" => lambda {|it| format_money(it['computeCost']) } },
224
- # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
225
- {"STORAGE" => lambda {|it| format_money(it['storageCost']) } },
226
- {"NETWORK" => lambda {|it| format_money(it['networkCost']) } },
227
- {"OTHER" => lambda {|it| format_money(it['extraCost']) } },
228
- ]
229
226
  if options[:show_prices]
230
227
  columns += [
231
- {"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice']) } },
232
- # {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice']) } },
233
- {"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice']) } },
234
- {"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice']) } },
235
- {"OTHER PRICE" => lambda {|it| format_money(it['extraPrice']) } },
236
- {"MTD PRICE" => lambda {|it| format_money(it['runningPrice']) } },
228
+ {"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) } },
229
+ # {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
230
+ {"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
231
+ {"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
232
+ {"OTHER PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
233
+ {"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
237
234
  {"TOTAL PRICE" => lambda {|it|
238
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['totalPrice'].to_f > 0
239
- format_money(it['totalPrice']) + " (Projected)"
240
- else
241
- format_money(it['totalPrice'])
242
- end
235
+ format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
243
236
  } }
244
237
  ]
245
238
  end
246
239
  if options[:show_estimates]
247
240
  columns += [
248
- {"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost']) } },
241
+ {"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
242
+ # {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
243
+ {"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
244
+ {"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
245
+ {"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
246
+ {"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
249
247
  {"TOTAL EST." => lambda {|it|
250
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['estimatedTotalCost'].to_f > 0
251
- format_money(it['estimatedTotalCost']) + " (Projected)"
252
- else
253
- format_money(it['estimatedTotalCost'])
254
- end
248
+ format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
255
249
  } },
256
- {"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost']) } },
257
- # {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost']) } },
258
- {"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost']) } },
259
- {"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost']) } },
260
- {"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost']) } },
261
250
  ]
262
251
  end
252
+ columns += [
253
+ {"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
254
+ {"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
255
+ {"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
256
+ ]
257
+ if show_projects
258
+ columns += [
259
+ {"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
260
+ {"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
261
+ {"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } },
262
+ ]
263
+ end
264
+ columns += [
265
+ {"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
266
+ {"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
267
+ ]
263
268
  if options[:show_raw_data]
264
269
  columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
265
270
  end
266
- print as_pretty_table(invoices, columns, options)
267
- print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
271
+ unless options[:totals_only]
272
+ print as_pretty_table(invoices, columns, options)
273
+ print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
274
+ end
268
275
 
269
276
  if options[:show_invoice_totals]
270
277
  invoice_totals = json_response['invoiceTotals']
278
+ print_h2 "Line Item Totals" unless options[:totals_only]
279
+ invoice_totals_columns = [
280
+ {"Invoices" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
281
+ {"Compute" => lambda {|it| format_money(it['actualComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
282
+ {"Storage" => lambda {|it| format_money(it['actualStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
283
+ {"Network" => lambda {|it| format_money(it['actualNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
284
+ {"Extra" => lambda {|it| format_money(it['actualExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
285
+ ] + (options[:show_prices] ? [
286
+ {"Compute Price" => lambda {|it| format_money(it['actualComputePrice'], 'usd', {sigdig:options[:sigdig]}) } },
287
+ {"Storage Price" => lambda {|it| format_money(it['actualStoragePrice'], 'usd', {sigdig:options[:sigdig]}) } },
288
+ {"Network Price" => lambda {|it| format_money(it['actualNetworkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
289
+ {"Extra Price" => lambda {|it| format_money(it['actualExtraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
290
+ ] : [])
291
+ print_description_list(invoice_totals_columns, line_item_totals)
292
+ end
293
+ if options[:show_invoice_totals]
294
+ invoice_totals = json_response['invoiceTotals']
295
+ print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
296
+
271
297
  if invoice_totals
272
- print_h2 "Invoice Totals"
273
- invoice_totals_columns = {
274
- "# Invoices" => lambda {|it| format_number(json_response['meta']['total']) rescue '' },
275
- "Total Price" => lambda {|it| format_money(it['actualTotalPrice']) },
276
- "Total Cost" => lambda {|it| format_money(it['actualTotalCost']) },
277
- "Running Price" => lambda {|it| format_money(it['actualRunningPrice']) },
278
- "Running Cost" => lambda {|it| format_money(it['actualRunningCost']) },
279
- # "Invoice Total Price" => lambda {|it| format_money(it['invoiceTotalPrice']) },
280
- # "Invoice Total Cost" => lambda {|it| format_money(it['invoiceTotalCost']) },
281
- # "Invoice Running Price" => lambda {|it| format_money(it['invoiceRunningPrice']) },
282
- # "Invoice Running Cost" => lambda {|it| format_money(it['invoiceRunningCost']) },
283
- # "Estimated Total Price" => lambda {|it| format_money(it['estimatedTotalPrice']) },
284
- # "Estimated Total Cost" => lambda {|it| format_money(it['estimatedTotalCost']) },
285
- # "Compute Price" => lambda {|it| format_money(it['computePrice']) },
286
- # "Compute Cost" => lambda {|it| format_money(it['computeCost']) },
298
+ cost_rows = [
299
+ {label: 'Cost', compute: invoice_totals['actualComputeCost'], memory: invoice_totals['actualMemoryCost'], storage: invoice_totals['actualStorageCost'], network: invoice_totals['actualNetworkCost'], license: invoice_totals['actualLicenseCost'], extra: invoice_totals['actualExtraCost'], running: invoice_totals['actualRunningCost'], total: invoice_totals['actualTotalCost']},
300
+ ]
301
+ if options[:show_prices]
302
+ cost_rows += [
303
+ {label: 'Price', compute: invoice_totals['actualComputePrice'], memory: invoice_totals['actualMemoryPrice'], storage: invoice_totals['actualStoragePrice'], network: invoice_totals['actualNetworkPrice'], license: invoice_totals['actualLicensePrice'], extra: invoice_totals['actualExtraPrice'], running: invoice_totals['actualRunningPrice'], total: invoice_totals['actualTotalPrice']},
304
+ ]
305
+ end
306
+ if options[:show_estimates]
307
+ cost_rows += [
308
+ {label: 'Estimated Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
309
+ {label: 'Estimated Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
310
+ ]
311
+ end
312
+ cost_columns = {
313
+ "" => lambda {|it| it[:label] },
314
+ "Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
315
+ "Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
316
+ "Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
317
+ "Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
318
+ "License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
319
+ "Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
320
+ "MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
321
+ "Total".upcase => lambda {|it|
322
+ format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
323
+ },
287
324
  }
288
- print_description_list(invoice_totals_columns, invoice_totals)
325
+ # remove columns that rarely have data...
326
+ if cost_rows.sum { |it| it[:memory].to_f } == 0
327
+ cost_columns.delete("Memory".upcase)
328
+ end
329
+ if cost_rows.sum { |it| it[:license].to_f } == 0
330
+ cost_columns.delete("License".upcase)
331
+ end
332
+ if cost_rows.sum { |it| it[:extra].to_f } == 0
333
+ cost_columns.delete("Other".upcase)
334
+ end
335
+ print as_pretty_table(cost_rows, cost_columns, options)
289
336
  else
290
337
  print "\n"
291
338
  print yellow, "No invoice totals data", reset, "\n"
@@ -301,14 +348,17 @@ class Morpheus::Cli::InvoicesCommand
301
348
  options = {}
302
349
  optparse = Morpheus::Cli::OptionParser.new do |opts|
303
350
  opts.banner = subcommand_usage("[id]")
304
- opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
351
+ opts.on('-a', '--all', "Display all details, costs and prices." ) do
305
352
  options[:show_estimates] = true
306
353
  # options[:show_costs] = true
307
354
  options[:show_prices] = true
308
- options[:show_raw_data] = true
355
+ # options[:show_raw_data] = true
309
356
  options[:max_line_items] = 10000
310
357
  end
311
- opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Memory, Storage, etc." ) do
358
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
359
+ options[:show_prices] = true
360
+ end
361
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Other" ) do
312
362
  options[:show_estimates] = true
313
363
  end
314
364
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
@@ -318,12 +368,12 @@ class Morpheus::Cli::InvoicesCommand
318
368
  options[:show_raw_data] = true
319
369
  options[:pretty_json] = true
320
370
  end
321
- opts.on('-m', '--max-line-items NUMBER', "Maximum number of line items to display. Default is 5.") do |val|
322
- options[:max_line_items] = val.to_i
323
- end
324
371
  opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
325
372
  options[:hide_line_items] = true
326
373
  end
374
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
375
+ options[:sigdig] = val.to_i
376
+ end
327
377
  build_standard_get_options(opts, options)
328
378
  opts.footer = "Get details about a specific invoice."
329
379
  opts.footer = <<-EOT
@@ -366,19 +416,21 @@ EOT
366
416
  "Ref ID" => lambda {|it| it['refId'] },
367
417
  "Ref Name" => lambda {|it| it['refName'] },
368
418
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
369
- "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
370
- "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
371
- "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
372
419
  "Power State" => lambda {|it| format_server_power_state(it) },
373
- "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
420
+ "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
374
421
  "Active" => lambda {|it| format_boolean(it['active']) },
375
- "Period" => lambda {|it| format_invoice_period(it) },
376
422
  "Estimate" => lambda {|it| format_boolean(it['estimate']) },
423
+ #"Cost Type" => lambda {|it| it['costType'].to_s.capitalize },
424
+ "Period" => lambda {|it| format_invoice_period(it) },
377
425
  #"Interval" => lambda {|it| it['interval'] },
378
426
  "Start" => lambda {|it| format_date(it['startDate']) },
379
- "End" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' },
380
- "Ref Start" => lambda {|it| format_local_dt(it['refStart']) },
381
- "Ref End" => lambda {|it| it['refEnd'] ? format_local_dt(it['refEnd']) : '' },
427
+ "End" => lambda {|it| format_date(it['endDate']) },
428
+ "Ref Start" => lambda {|it| format_dt(it['refStart']) },
429
+ "Ref End" => lambda {|it| format_dt(it['refEnd']) },
430
+ "Items" => lambda {|it| it['lineItems'].size rescue '' },
431
+ "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
432
+ "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
433
+ "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
382
434
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
383
435
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
384
436
  }
@@ -398,27 +450,27 @@ EOT
398
450
  =begin
399
451
  print_h2 "Costs"
400
452
  cost_columns = {
401
- "Compute" => lambda {|it| format_money(it['computeCost']) },
402
- "Memory" => lambda {|it| format_money(it['memoryCost']) },
403
- "Storage" => lambda {|it| format_money(it['storageCost']) },
404
- "Network" => lambda {|it| format_money(it['networkCost']) },
405
- "License" => lambda {|it| format_money(it['licenseCost']) },
406
- "Other" => lambda {|it| format_money(it['extraCost']) },
407
- "Running" => lambda {|it| format_money(it['runningCost']) },
408
- "Total Cost" => lambda {|it| format_money(it['totalCost']) },
453
+ "Compute" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) },
454
+ "Memory" => lambda {|it| format_money(it['memoryCost'], 'usd', {sigdig:options[:sigdig]}) },
455
+ "Storage" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) },
456
+ "Network" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) },
457
+ "License" => lambda {|it| format_money(it['licenseCost'], 'usd', {sigdig:options[:sigdig]}) },
458
+ "Other" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
459
+ "Running" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) },
460
+ "Total Cost" => lambda {|it| format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) },
409
461
  }
410
462
  print as_pretty_table([invoice], cost_columns, options)
411
463
 
412
464
  print_h2 "Prices"
413
465
  price_columns = {
414
- "Compute" => lambda {|it| format_money(it['computePrice']) },
415
- "Memory" => lambda {|it| format_money(it['memoryPrice']) },
416
- "Storage" => lambda {|it| format_money(it['storagePrice']) },
417
- "Network" => lambda {|it| format_money(it['networkPrice']) },
418
- "License" => lambda {|it| format_money(it['licensePrice']) },
419
- "Other" => lambda {|it| format_money(it['extraPrice']) },
420
- "Running" => lambda {|it| format_money(it['runningPrice']) },
421
- "Total Price" => lambda {|it| format_money(it['totalPrice']) },
466
+ "Compute" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) },
467
+ "Memory" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) },
468
+ "Storage" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) },
469
+ "Network" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) },
470
+ "License" => lambda {|it| format_money(it['licensePrice'], 'usd', {sigdig:options[:sigdig]}) },
471
+ "Other" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
472
+ "Running" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) },
473
+ "Total Price" => lambda {|it| format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) },
422
474
  }
423
475
  print as_pretty_table([invoice], price_columns, options)
424
476
  =end
@@ -426,33 +478,76 @@ EOT
426
478
  # current_date = Time.now
427
479
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
428
480
 
429
- print "\n"
430
- # print_h2 "Costs"
481
+
482
+
483
+ # Line Items
484
+ line_items = invoice['lineItems']
485
+ if line_items && line_items.size > 0 && options[:hide_line_items] != true
486
+ line_items_columns = [
487
+ {"ID" => lambda {|it| it['id'] } },
488
+ #{"REF TYPE" => lambda {|it| format_invoice_ref_type(it) } },
489
+ #{"REF ID" => lambda {|it| it['refId'] } },
490
+ #{"REF NAME" => lambda {|it| it['refName'] } },
491
+ #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
492
+ {"START" => lambda {|it| format_dt(it['startDate']) } },
493
+ {"END" => lambda {|it| format_dt(it['endDate']) } },
494
+ {"USAGE TYPE" => lambda {|it| it['usageType'] } },
495
+ {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
496
+ {"USAGE" => lambda {|it| it['itemUsage'] } },
497
+ {"RATE" => lambda {|it| it['itemRate'] } },
498
+ {"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
499
+ {"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
500
+ #{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
501
+ # {"TERM" => lambda {|it| it['itemTerm'] } },
502
+ {"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
503
+ {"ITEM NAME" => lambda {|it| it['itemName'] } },
504
+ {"ITEM TYPE" => lambda {|it| it['itemType'] } },
505
+ {"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
506
+ {"PRODUCT CODE" => lambda {|it| it['productCode'] } },
507
+ {"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
508
+ {"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
509
+ ]
510
+ if options[:show_raw_data]
511
+ line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
512
+ end
513
+ print_h2 "Line Items"
514
+ #max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
515
+ paged_line_items = line_items #.first(max_line_items)
516
+ print as_pretty_table(paged_line_items, line_items_columns, options)
517
+ print_results_pagination({total: line_items.size, size: paged_line_items.size}, {:label => "line item", :n_label => "line items"})
518
+ end
519
+
520
+ # cost_types = ["Costs"]
521
+ # cost_types << "Prices" if options[:show_prices]
522
+ # cost_types << "Estimates" if options[:show_estimates]
523
+ # print_h2 cost_types.size == 1 ? "Totals" : "Total #{anded_list(cost_types)}"
524
+ print_h2 "Invoice Totals"
525
+
431
526
  cost_rows = [
432
- {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']},
433
527
  {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']},
434
528
  ]
529
+ if options[:show_prices]
530
+ cost_rows += [
531
+ {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']},
532
+ ]
533
+ end
435
534
  if options[:show_estimates]
436
535
  cost_rows += [
437
536
  {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']},
438
- {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']},
537
+ {label: 'Estimated Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
439
538
  ]
440
539
  end
441
540
  cost_columns = {
442
541
  "" => lambda {|it| it[:label] },
443
- "Compute".upcase => lambda {|it| format_money(it[:compute]) },
444
- "Memory".upcase => lambda {|it| format_money(it[:memory]) },
445
- "Storage".upcase => lambda {|it| format_money(it[:storage]) },
446
- "Network".upcase => lambda {|it| format_money(it[:network]) },
447
- "License".upcase => lambda {|it| format_money(it[:license]) },
448
- "Other".upcase => lambda {|it| format_money(it[:extra]) },
449
- "MTD" => lambda {|it| format_money(it[:running]) },
542
+ "Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
543
+ "Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
544
+ "Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
545
+ "Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
546
+ "License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
547
+ "Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
548
+ "MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
450
549
  "Total".upcase => lambda {|it|
451
- if invoice['runningMultiplier'] && invoice['runningMultiplier'].to_i != 1 && it[:total].to_f.to_f > 0
452
- format_money(it[:total]) + " (Projected)"
453
- else
454
- format_money(it[:total])
455
- end
550
+ format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
456
551
  },
457
552
  }
458
553
  # remove columns that rarely have data...
@@ -471,41 +566,6 @@ EOT
471
566
  print_h2 "Raw Data"
472
567
  puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
473
568
  end
474
-
475
- # Line Items
476
- line_items = invoice['lineItems']
477
- if line_items && line_items.size > 0 && options[:hide_line_items] != true
478
-
479
- line_items_columns = [
480
- {"ID" => lambda {|it| it['id'] } },
481
- {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
482
- {"REF ID" => lambda {|it| it['refId'] } },
483
- {"REF NAME" => lambda {|it| it['refName'] } },
484
- #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
485
- {"START" => lambda {|it| format_date(it['startDate']) } },
486
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
487
- {"USAGE TYPE" => lambda {|it| it['usageType'] } },
488
- {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
489
- {"USAGE" => lambda {|it| it['itemUsage'] } },
490
- {"RATE" => lambda {|it| it['itemRate'] } },
491
- {"COST" => lambda {|it| format_money(it['itemCost']) } },
492
- {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
493
- {"TAX" => lambda {|it| format_money(it['itemTax']) } },
494
- # {"TERM" => lambda {|it| it['itemTerm'] } },
495
- "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
496
- "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
497
- ]
498
-
499
- if options[:show_raw_data]
500
- line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
501
- end
502
-
503
- print_h2 "Line Items"
504
- max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
505
- paged_line_items = line_items.first(max_line_items)
506
- print as_pretty_table(paged_line_items, line_items_columns, options)
507
- print_results_pagination({total: line_items.size, size: paged_line_items.size}, {:label => "line item", :n_label => "line items"})
508
- end
509
569
 
510
570
  print reset,"\n"
511
571
  return 0
@@ -598,41 +658,33 @@ EOT
598
658
  ref_ids = []
599
659
  optparse = Morpheus::Cli::OptionParser.new do |opts|
600
660
  opts.banner = subcommand_usage()
601
- opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
661
+ opts.on('-a', '--all', "Display all details, costs and prices." ) do
602
662
  options[:show_actual_costs] = true
603
663
  options[:show_costs] = true
604
664
  options[:show_prices] = true
605
- options[:show_raw_data] = true
665
+ # options[:show_raw_data] = true
606
666
  end
607
- # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Memory, Storage, etc." ) do
667
+ # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Other" ) do
608
668
  # options[:show_actual_costs] = true
609
669
  # end
610
- # opts.on('--costs', '--costs', "Display all costs: Compute, Memory, Storage, etc." ) do
670
+ # opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Other" ) do
611
671
  # options[:show_costs] = true
612
672
  # end
613
- # opts.on('--prices', '--prices', "Display prices: Total, Compute, Memory, Storage, etc." ) do
614
- # options[:show_prices] = true
615
- # end
673
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
674
+ options[:show_prices] = true
675
+ end
616
676
  opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
617
677
  params['invoiceId'] ||= []
618
678
  params['invoiceId'] << val
619
679
  end
680
+ opts.on('--external-id ID', String, "Filter by External ID") do |val|
681
+ params['externalId'] ||= []
682
+ params['externalId'] << val
683
+ end
620
684
  opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
621
- if val.to_s.downcase == 'cloud' || val.to_s.downcase == 'zone'
622
- params['refType'] = 'ComputeZone'
623
- elsif val.to_s.downcase == 'instance'
624
- params['refType'] = 'Instance'
625
- elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
626
- params['refType'] = 'ComputeServer'
627
- elsif val.to_s.downcase == 'cluster'
628
- params['refType'] = 'ComputeServerGroup'
629
- elsif val.to_s.downcase == 'group'
630
- params['refType'] = 'ComputeSite'
631
- elsif val.to_s.downcase == 'user'
632
- params['refType'] = 'User'
633
- else
634
- params['refType'] = val
635
- end
685
+ params['refType'] ||= []
686
+ values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
687
+ values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
636
688
  end
637
689
  opts.on('--id ID', String, "Filter by Ref ID") do |val|
638
690
  ref_ids << val
@@ -698,6 +750,14 @@ EOT
698
750
  params['includeTotals'] = true
699
751
  options[:show_invoice_totals] = true
700
752
  end
753
+ opts.on('--totals-only', "View totals only") do |val|
754
+ params['includeTotals'] = true
755
+ options[:show_invoice_totals] = true
756
+ options[:totals_only] = true
757
+ end
758
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
759
+ options[:sigdig] = val.to_i
760
+ end
701
761
  build_standard_list_options(opts, options)
702
762
  opts.footer = "List invoice line items."
703
763
  end
@@ -773,15 +833,21 @@ EOT
773
833
  {"REF NAME" => lambda {|it| it['refName'] } },
774
834
  #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
775
835
  {"START" => lambda {|it| format_date(it['startDate']) } },
776
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
836
+ {"END" => lambda {|it| format_date(it['endDate']) } },
777
837
  {"USAGE TYPE" => lambda {|it| it['usageType'] } },
778
838
  {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
779
839
  {"USAGE" => lambda {|it| it['itemUsage'] } },
780
840
  {"RATE" => lambda {|it| it['itemRate'] } },
781
- {"COST" => lambda {|it| format_money(it['itemCost']) } },
782
- {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
783
- {"TAX" => lambda {|it| format_money(it['itemTax']) } },
784
- # {"TERM" => lambda {|it| it['itemTerm'] } },
841
+ {"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
842
+ ] + (options[:show_prices] ? [
843
+ {"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
844
+ {"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
845
+ ] : []) + [
846
+ {"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
847
+ {"ITEM NAME" => lambda {|it| it['itemName'] } },
848
+ {"ITEM TYPE" => lambda {|it| it['itemType'] } },
849
+ {"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
850
+ {"PRODUCT CODE" => lambda {|it| it['productCode'] } },
785
851
  "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
786
852
  "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
787
853
  ]
@@ -789,35 +855,39 @@ EOT
789
855
  if options[:show_raw_data]
790
856
  columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
791
857
  end
792
- if options[:show_invoice_totals]
793
- line_item_totals = json_response['lineItemTotals']
794
- if line_item_totals
795
- totals_row = line_item_totals.clone
796
- totals_row['id'] = 'TOTAL:'
797
- #totals_row['usageCategory'] = 'TOTAL:'
798
- line_items = line_items + [totals_row]
799
- end
800
- end
801
- print as_pretty_table(line_items, columns, options)
802
- print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
803
-
804
858
  # if options[:show_invoice_totals]
805
859
  # line_item_totals = json_response['lineItemTotals']
806
860
  # if line_item_totals
807
- # print_h2 "Line Items Totals"
808
- # invoice_totals_columns = {
809
- # "# Line Items" => lambda {|it| format_number(json_response['meta']['total']) rescue '' },
810
- # "Cost" => lambda {|it| format_money(it['itemCost']) },
811
- # "Price" => lambda {|it| format_money(it['itemPrice']) },
812
- # "Tax" => lambda {|it| format_money(it['itemTax']) },
813
- # "Usage" => lambda {|it| it['itemUsage'] },
814
- # }
815
- # print_description_list(invoice_totals_columns, line_item_totals)
816
- # else
817
- # print "\n"
818
- # print yellow, "No line item totals data", reset, "\n"
861
+ # totals_row = line_item_totals.clone
862
+ # totals_row['id'] = 'TOTAL:'
863
+ # #totals_row['usageCategory'] = 'TOTAL:'
864
+ # line_items = line_items + [totals_row]
819
865
  # end
820
866
  # end
867
+ unless options[:totals_only]
868
+ print as_pretty_table(line_items, columns, options)
869
+ print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
870
+ end
871
+
872
+ if options[:show_invoice_totals]
873
+ line_item_totals = json_response['lineItemTotals']
874
+ if line_item_totals
875
+ print_h2 "Line Item Totals" unless options[:totals_only]
876
+ invoice_totals_columns = [
877
+ {"Items" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
878
+ #{"Usage" => lambda {|it| it['itemUsage'] } },
879
+ {"Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
880
+ ] + (options[:show_prices] ? [
881
+ {"Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
882
+ #{"Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
883
+
884
+ ] : [])
885
+ print_description_list(invoice_totals_columns, line_item_totals)
886
+ else
887
+ print "\n"
888
+ print yellow, "No line item totals data", reset, "\n"
889
+ end
890
+ end
821
891
 
822
892
  end
823
893
  print reset,"\n"
@@ -840,6 +910,9 @@ EOT
840
910
  options[:show_raw_data] = true
841
911
  options[:pretty_json] = true
842
912
  end
913
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
914
+ options[:sigdig] = val.to_i
915
+ end
843
916
  build_standard_get_options(opts, options)
844
917
  opts.footer = "Get details about a specific invoice line item."
845
918
  opts.footer = <<-EOT
@@ -883,11 +956,16 @@ EOT
883
956
  "Usage Category" => lambda {|it| it['usageCategory'] },
884
957
  "Item Usage" => lambda {|it| it['itemUsage'] },
885
958
  "Item Rate" => lambda {|it| it['itemRate'] },
886
- "Item Cost" => lambda {|it| format_money(it['itemCost']) },
887
- "Item Price" => lambda {|it| format_money(it['itemrPrice']) },
888
- "Item Tax" => lambda {|it| format_money(it['itemTax']) },
889
- "Item Term" => lambda {|it| it['itemTerm'] },
959
+ "Item Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) },
960
+ "Item Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) },
961
+ #"Item Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) },
890
962
  #"Tax Type" => lambda {|it| it['taxType'] },
963
+ "Item Term" => lambda {|it| it['itemTerm'] },
964
+ "Item ID" => lambda {|it| it['itemId'] },
965
+ "Item Name" => lambda {|it| it['itemName'] },
966
+ "Item Type" => lambda {|it| it['itemType'] },
967
+ "Item Description" => lambda {|it| it['itemDescription'] },
968
+ "Product Code" => lambda {|it| it['productCode'] },
891
969
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
892
970
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
893
971
  }
@@ -956,6 +1034,25 @@ EOT
956
1034
  end
957
1035
  end
958
1036
 
1037
+ def parse_invoice_ref_type(ref_type)
1038
+ val = ref_type.to_s.downcase
1039
+ if val == 'cloud' || val == 'zone'
1040
+ 'ComputeZone'
1041
+ elsif val == 'instance'
1042
+ 'Instance'
1043
+ elsif val == 'server' || val == 'host'
1044
+ 'ComputeServer'
1045
+ elsif val == 'cluster'
1046
+ 'ComputeServerGroup'
1047
+ elsif val == 'group' || val == 'site'
1048
+ 'ComputeSite'
1049
+ elsif val == 'user'
1050
+ 'User'
1051
+ else
1052
+ ref_type
1053
+ end
1054
+ end
1055
+
959
1056
  # convert "202003" to "March 2020"
960
1057
  def format_invoice_period(it)
961
1058
  interval = it['interval']
@@ -1002,6 +1099,11 @@ EOT
1002
1099
  end
1003
1100
  end
1004
1101
 
1102
+ def get_current_period()
1103
+ now = Time.now.utc
1104
+ now.year.to_s + now.month.to_s.rjust(2,'0')
1105
+ end
1106
+
1005
1107
  def format_server_power_state(server, return_color=cyan)
1006
1108
  out = ""
1007
1109
  if server['powerState'] == 'on'