morpheus-cli 4.2.15 → 4.2.16

Sign up to get free protection for your applications and to get access to all the features.
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'