morpheus-cli 4.2.11 → 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: c05a0d047f94e91857345c03ab934d64eef1b23620acf24b38057eba3d92cbcb
4
- data.tar.gz: 17a33813d17587e8686ef3ad1cbec018ef1e18049be40184aa76573d60eba469
3
+ metadata.gz: 3481d47679761310fbffd07ad968588ace4b92d9f428f68983f90a3ef4e13799
4
+ data.tar.gz: 5c900c15f68c14d5ab10d89b148e2bee5216653c123004b528f3d8bf1c1740cb
5
5
  SHA512:
6
- metadata.gz: 8450675f6ce45f28e42b3919ac5e6c65f210fde6deb14671a78eef72b0b7d6f0d0d008a108fc0af56a45af3e4485788bd6d91697ab544e868974df73d9ea4c54
7
- data.tar.gz: b0f5a5f626de331115ca8863b8e60305ec66ba34bfd6762697778f084bf7553618e37613e755c4ddc8cea425d8241faece71bbcf6a2a9483b94a7b1c0712c495
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.11
3
+ RUN gem install morpheus-cli -v 4.2.16
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -1303,7 +1303,7 @@ EOT
1303
1303
  # options[:interval] = parse_time(val).utc.iso8601
1304
1304
  # end
1305
1305
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
1306
- params['level'] = params['level'] ? [params['level'], val].flatten : val
1306
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
1307
1307
  end
1308
1308
  opts.on('--table', '--table', "Format ouput as a table.") do
1309
1309
  options[:table] = true
@@ -1346,6 +1346,7 @@ EOT
1346
1346
  end
1347
1347
  end
1348
1348
  params = {}
1349
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
1349
1350
  params.merge!(parse_list_options(options))
1350
1351
  params['query'] = params.delete('phrase') if params['phrase']
1351
1352
  params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
@@ -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
 
@@ -128,6 +131,11 @@ module Morpheus
128
131
  previous_command_result = nil
129
132
  current_operator = nil
130
133
  still_executing = true
134
+ # need to error before executing anything, could be dangerous otherwise!
135
+ # also maybe only pass flow commands if they have a space on either side..
136
+ if flow.include?("|")
137
+ raise Morpheus::Cli::ExpressionParser::InvalidExpression.new "The PIPE (|) operator is not yet supported. You can wrap your arguments in quotations."
138
+ end
131
139
  flow.each do |flow_cmd|
132
140
  if still_executing
133
141
  if flow_cmd == '&&'
@@ -144,7 +152,8 @@ module Morpheus
144
152
  still_executing = false
145
153
  end
146
154
  elsif flow_cmd == '|' # or with previous command
147
- raise Morpheus::Cli::ExpressionParser::InvalidExpression.new "The PIPE (|) operator is not yet supported =["
155
+ # todo, handle pipe!
156
+ raise Morpheus::Cli::ExpressionParser::InvalidExpression.new "The PIPE (|) operator is not yet supported. You can wrap your arguments in quotations."
148
157
  previous_command_result = nil
149
158
  still_executing = false
150
159
  # or just continue?
@@ -836,7 +836,7 @@ class Morpheus::Cli::Clusters
836
836
  options[:end] = parse_time(val) #.utc.iso8601
837
837
  end
838
838
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
839
- params['level'] = params['level'] ? [params['level'], val].flatten : val
839
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
840
840
  end
841
841
  opts.on('--table', '--table', "Format ouput as a table.") do
842
842
  options[:table] = true
@@ -854,6 +854,7 @@ class Morpheus::Cli::Clusters
854
854
  begin
855
855
  cluster = find_cluster_by_name_or_id(args[0])
856
856
  params = {}
857
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
857
858
  params.merge!(parse_list_options(options))
858
859
  params['query'] = params.delete('phrase') if params['phrase']
859
860
  params['startMs'] = (options[:start].to_i * 1000) if options[:start]
@@ -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
@@ -519,7 +519,7 @@ class Morpheus::Cli::ContainersCommand
519
519
  options[:end] = parse_time(val) #.utc.iso8601
520
520
  end
521
521
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
522
- params['level'] = params['level'] ? [params['level'], val].flatten : val
522
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
523
523
  end
524
524
  opts.on('--table', '--table', "Format ouput as a table.") do
525
525
  options[:table] = true
@@ -541,6 +541,7 @@ class Morpheus::Cli::ContainersCommand
541
541
  id_list = parse_id_list(args)
542
542
  begin
543
543
  containers = id_list # heh
544
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
544
545
  params.merge!(parse_list_options(options))
545
546
  params['query'] = params.delete('phrase') if params['phrase']
546
547
  params[:order] = params[:direction] unless params[:direction].nil? # old api version expects order instead of direction
@@ -57,7 +57,7 @@ module Morpheus
57
57
  # for now, it just stores the access token without other wallet info
58
58
  begin
59
59
  # @setup_interface = Morpheus::SetupInterface.new({url:@appliance_url,access_token:@access_token})
60
- whoami_interface = Morpheus::WhoamiInterface.new({url: @appliance_url, token: options[:remote_token]})
60
+ whoami_interface = Morpheus::WhoamiInterface.new({url: @appliance_url, access_token: options[:remote_token]})
61
61
  whoami_interface.setopts(options)
62
62
  if options[:dry_run]
63
63
  print_dry_run whoami_interface.dry.get()
@@ -457,7 +457,7 @@ class Morpheus::Cli::HealthCommand
457
457
  optparse = Morpheus::Cli::OptionParser.new do |opts|
458
458
  opts.banner = subcommand_usage()
459
459
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
460
- params['level'] = params['level'] ? [params['level'], val].flatten : val
460
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
461
461
  end
462
462
  opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start timestamp. Default is 30 days ago.") do |val|
463
463
  start_date = parse_time(val) #.utc.iso8601
@@ -465,7 +465,7 @@ class Morpheus::Cli::HealthCommand
465
465
  opts.on('--end TIMESTAMP','--end TIMESTAMP', "End timestamp. Default is now.") do |val|
466
466
  end_date = parse_time(val) #.utc.iso8601
467
467
  end
468
- opts.on('--table', '--table', "Format output as a table.") do
468
+ opts.on('-t', '--table', "Format output as a table.") do
469
469
  options[:table] = true
470
470
  end
471
471
  opts.on('-a', '--all', "Display all details: entire message." ) do
@@ -484,6 +484,7 @@ class Morpheus::Cli::HealthCommand
484
484
  # params['endDate'] = end_date.utc.iso8601 if end_date
485
485
  params['startMs'] = (start_date.to_i * 1000) if start_date
486
486
  params['endMs'] = (end_date.to_i * 1000) if end_date
487
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
487
488
  params.merge!(parse_list_options(options))
488
489
  @health_interface.setopts(options)
489
490
  if options[:dry_run]
@@ -497,7 +498,7 @@ class Morpheus::Cli::HealthCommand
497
498
  title = "Morpheus Health Logs"
498
499
  subtitles = []
499
500
  if params['level']
500
- subtitles << "Level: #{params['level']}"
501
+ subtitles << "Level: #{[params['level']].flatten.join(',')}"
501
502
  end
502
503
  if start_date
503
504
  subtitles << "Start: #{start_date}"
@@ -582,7 +582,7 @@ class Morpheus::Cli::Hosts
582
582
  options[:end] = parse_time(val) #.utc.iso8601
583
583
  end
584
584
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
585
- params['level'] = params['level'] ? [params['level'], val].flatten : val
585
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
586
586
  end
587
587
  opts.on('--table', '--table', "Format ouput as a table.") do
588
588
  options[:table] = true
@@ -600,6 +600,7 @@ class Morpheus::Cli::Hosts
600
600
  connect(options)
601
601
  begin
602
602
  server = find_host_by_name_or_id(args[0])
603
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
603
604
  params.merge!(parse_list_options(options))
604
605
  params['query'] = params.delete('phrase') if params['phrase']
605
606
  params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
@@ -1009,7 +1009,7 @@ class Morpheus::Cli::Instances
1009
1009
  # options[:interval] = parse_time(val).utc.iso8601
1010
1010
  # end
1011
1011
  opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
1012
- params['level'] = params['level'] ? [params['level'], val].flatten : val
1012
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
1013
1013
  end
1014
1014
  opts.on('--table', '--table', "Format ouput as a table.") do
1015
1015
  options[:table] = true
@@ -1036,6 +1036,7 @@ class Morpheus::Cli::Instances
1036
1036
  return 1
1037
1037
  end
1038
1038
  end
1039
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
1039
1040
  params.merge!(parse_list_options(options))
1040
1041
  params['query'] = params.delete('phrase') if params['phrase']
1041
1042
  params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
@@ -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
@@ -119,6 +108,18 @@ class Morpheus::Cli::InvoicesCommand
119
108
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
120
109
  options[:show_raw_data] = true
121
110
  end
111
+ opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
112
+ params['includeTotals'] = true
113
+ options[:show_invoice_totals] = true
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
122
123
  build_standard_list_options(opts, options)
123
124
  opts.footer = "List invoices."
124
125
  end
@@ -128,50 +129,48 @@ class Morpheus::Cli::InvoicesCommand
128
129
  if args.count > 0
129
130
  options[:phrase] = args.join(" ")
130
131
  end
131
- begin
132
- # construct params
133
- params.merge!(parse_list_options(options))
134
- if options[:clouds]
135
- cloud_ids = parse_cloud_id_list(options[:clouds])
136
- return 1, "clouds not found for #{options[:clouds]}" if cloud_ids.nil?
137
- params['zoneId'] = cloud_ids
138
- end
139
- if options[:groups]
140
- group_ids = parse_group_id_list(options[:groups])
141
- return 1, "groups not found for #{options[:groups]}" if group_ids.nil?
142
- params['siteId'] = group_ids
143
- end
144
- if options[:instances]
145
- instance_ids = parse_instance_id_list(options[:instances])
146
- return 1, "instances not found for #{options[:instances]}" if instance_ids.nil?
147
- params['instanceId'] = instance_ids
148
- end
149
- if options[:servers]
150
- server_ids = parse_server_id_list(options[:servers])
151
- return 1, "servers not found for #{options[:servers]}" if server_ids.nil?
152
- params['serverId'] = server_ids
153
- end
154
- if options[:users]
155
- user_ids = parse_user_id_list(options[:users])
156
- return 1, "users not found for #{options[:users]}" if user_ids.nil?
157
- params['userId'] = user_ids
158
- end
159
- if options[:projects]
160
- project_ids = parse_project_id_list(options[:projects])
161
- return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
162
- params['projectId'] = project_ids
163
- end
164
- params['rawData'] = true if options[:show_raw_data]
165
- params['refId'] = ref_ids unless ref_ids.empty?
166
- @invoices_interface.setopts(options)
167
- if options[:dry_run]
168
- print_dry_run @invoices_interface.dry.list(params)
169
- return
170
- end
171
- json_response = @invoices_interface.list(params)
172
- render_result = render_with_format(json_response, options, 'invoices')
173
- return 0 if render_result
174
- invoices = json_response['invoices']
132
+ # construct params
133
+ params.merge!(parse_list_options(options))
134
+ if options[:clouds]
135
+ cloud_ids = parse_cloud_id_list(options[:clouds])
136
+ return 1, "clouds not found for #{options[:clouds]}" if cloud_ids.nil?
137
+ params['zoneId'] = cloud_ids
138
+ end
139
+ if options[:groups]
140
+ group_ids = parse_group_id_list(options[:groups])
141
+ return 1, "groups not found for #{options[:groups]}" if group_ids.nil?
142
+ params['siteId'] = group_ids
143
+ end
144
+ if options[:instances]
145
+ instance_ids = parse_instance_id_list(options[:instances])
146
+ return 1, "instances not found for #{options[:instances]}" if instance_ids.nil?
147
+ params['instanceId'] = instance_ids
148
+ end
149
+ if options[:servers]
150
+ server_ids = parse_server_id_list(options[:servers])
151
+ return 1, "servers not found for #{options[:servers]}" if server_ids.nil?
152
+ params['serverId'] = server_ids
153
+ end
154
+ if options[:users]
155
+ user_ids = parse_user_id_list(options[:users])
156
+ return 1, "users not found for #{options[:users]}" if user_ids.nil?
157
+ params['userId'] = user_ids
158
+ end
159
+ if options[:projects]
160
+ project_ids = parse_project_id_list(options[:projects])
161
+ return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
162
+ params['projectId'] = project_ids
163
+ end
164
+ params['rawData'] = true if options[:show_raw_data]
165
+ params['refId'] = ref_ids unless ref_ids.empty?
166
+ @invoices_interface.setopts(options)
167
+ if options[:dry_run]
168
+ print_dry_run @invoices_interface.dry.list(params)
169
+ return
170
+ end
171
+ json_response = @invoices_interface.list(params)
172
+ invoices = json_response['invoices']
173
+ render_response(json_response, options, 'invoices') do
175
174
  title = "Morpheus Invoices"
176
175
  subtitles = []
177
176
  if params['startDate']
@@ -183,7 +182,9 @@ class Morpheus::Cli::InvoicesCommand
183
182
  subtitles += parse_list_subtitles(options)
184
183
  print_h1 title, subtitles
185
184
  if invoices.empty?
186
- print cyan,"No invoices found.",reset,"\n"
185
+ unless options[:totals_only]
186
+ print cyan,"No invoices found.",reset,"\n"
187
+ end
187
188
  else
188
189
  # current_date = Time.now
189
190
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
@@ -192,82 +193,154 @@ class Morpheus::Cli::InvoicesCommand
192
193
  {"INVOICE ID" => lambda {|it| it['id'] } },
193
194
  {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
194
195
  {"REF ID" => lambda {|it| it['refId'] } },
195
- {"REF NAME" => lambda {|it| it['refName'] } }
196
- ] + (show_projects ? [
197
- {"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
198
- {"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
199
- {"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } }
200
- ] : []) + [
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
+ } },
201
203
  #{"INTERVAL" => lambda {|it| it['interval'] } },
202
204
  {"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
203
- {"ACCOUNT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
204
- {"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
205
- #{"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 } },
206
208
  {"PERIOD" => lambda {|it| format_invoice_period(it) } },
207
209
  {"START" => lambda {|it| format_date(it['startDate']) } },
208
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
209
- {"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]}) } },
210
221
  {"TOTAL" => lambda {|it|
211
-
212
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['totalCost'].to_f > 0
213
- format_money(it['totalCost']) + " (Projected)"
214
- else
215
- format_money(it['totalCost'])
216
- end
222
+ format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
217
223
  } }
218
224
  ]
219
225
 
220
- columns += [
221
- {"COMPUTE" => lambda {|it| format_money(it['computeCost']) } },
222
- # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
223
- {"STORAGE" => lambda {|it| format_money(it['storageCost']) } },
224
- {"NETWORK" => lambda {|it| format_money(it['networkCost']) } },
225
- {"OTHER" => lambda {|it| format_money(it['extraCost']) } },
226
- ]
227
226
  if options[:show_prices]
228
227
  columns += [
229
- {"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice']) } },
230
- # {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice']) } },
231
- {"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice']) } },
232
- {"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice']) } },
233
- {"OTHER PRICE" => lambda {|it| format_money(it['extraPrice']) } },
234
- {"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]}) } },
235
234
  {"TOTAL PRICE" => lambda {|it|
236
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['totalPrice'].to_f > 0
237
- format_money(it['totalPrice']) + " (Projected)"
238
- else
239
- format_money(it['totalPrice'])
240
- end
235
+ format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
241
236
  } }
242
237
  ]
243
238
  end
244
239
  if options[:show_estimates]
245
240
  columns += [
246
- {"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]}) } },
247
247
  {"TOTAL EST." => lambda {|it|
248
- if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['estimatedTotalCost'].to_f > 0
249
- format_money(it['estimatedTotalCost']) + " (Projected)"
250
- else
251
- format_money(it['estimatedTotalCost'])
252
- end
248
+ format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
253
249
  } },
254
- {"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost']) } },
255
- # {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost']) } },
256
- {"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost']) } },
257
- {"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost']) } },
258
- {"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost']) } },
259
250
  ]
260
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
+ ]
261
268
  if options[:show_raw_data]
262
269
  columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
263
270
  end
264
- print as_pretty_table(invoices, columns, options)
265
- 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
275
+
276
+ if options[:show_invoice_totals]
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
+
297
+ if invoice_totals
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
+ },
324
+ }
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)
336
+ else
337
+ print "\n"
338
+ print yellow, "No invoice totals data", reset, "\n"
339
+ end
340
+ end
266
341
  end
267
342
  print reset,"\n"
268
- rescue RestClient::Exception => e
269
- print_rest_exception(e, options)
270
- return 1
343
+ return 0, nil
271
344
  end
272
345
  end
273
346
 
@@ -275,21 +348,32 @@ class Morpheus::Cli::InvoicesCommand
275
348
  options = {}
276
349
  optparse = Morpheus::Cli::OptionParser.new do |opts|
277
350
  opts.banner = subcommand_usage("[id]")
278
- opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
351
+ opts.on('-a', '--all', "Display all details, costs and prices." ) do
279
352
  options[:show_estimates] = true
280
353
  # options[:show_costs] = true
281
354
  options[:show_prices] = true
282
- options[:show_raw_data] = true
355
+ # options[:show_raw_data] = true
356
+ options[:max_line_items] = 10000
357
+ end
358
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
359
+ options[:show_prices] = true
283
360
  end
284
- opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Memory, Storage, etc." ) do
361
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Other" ) do
285
362
  options[:show_estimates] = true
286
363
  end
287
364
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
288
365
  options[:show_raw_data] = true
289
366
  end
367
+ opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
368
+ options[:show_raw_data] = true
369
+ options[:pretty_json] = true
370
+ end
290
371
  opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
291
372
  options[:hide_line_items] = true
292
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
293
377
  build_standard_get_options(opts, options)
294
378
  opts.footer = "Get details about a specific invoice."
295
379
  opts.footer = <<-EOT
@@ -332,19 +416,21 @@ EOT
332
416
  "Ref ID" => lambda {|it| it['refId'] },
333
417
  "Ref Name" => lambda {|it| it['refName'] },
334
418
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
335
- "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
336
- "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
337
- "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
338
419
  "Power State" => lambda {|it| format_server_power_state(it) },
339
- "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
420
+ "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
340
421
  "Active" => lambda {|it| format_boolean(it['active']) },
341
- "Period" => lambda {|it| format_invoice_period(it) },
342
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) },
343
425
  #"Interval" => lambda {|it| it['interval'] },
344
426
  "Start" => lambda {|it| format_date(it['startDate']) },
345
- "End" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' },
346
- "Ref Start" => lambda {|it| format_local_dt(it['refStart']) },
347
- "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']) : '' },
348
434
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
349
435
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
350
436
  }
@@ -364,27 +450,27 @@ EOT
364
450
  =begin
365
451
  print_h2 "Costs"
366
452
  cost_columns = {
367
- "Compute" => lambda {|it| format_money(it['computeCost']) },
368
- "Memory" => lambda {|it| format_money(it['memoryCost']) },
369
- "Storage" => lambda {|it| format_money(it['storageCost']) },
370
- "Network" => lambda {|it| format_money(it['networkCost']) },
371
- "License" => lambda {|it| format_money(it['licenseCost']) },
372
- "Other" => lambda {|it| format_money(it['extraCost']) },
373
- "Running" => lambda {|it| format_money(it['runningCost']) },
374
- "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]}) },
375
461
  }
376
462
  print as_pretty_table([invoice], cost_columns, options)
377
463
 
378
464
  print_h2 "Prices"
379
465
  price_columns = {
380
- "Compute" => lambda {|it| format_money(it['computePrice']) },
381
- "Memory" => lambda {|it| format_money(it['memoryPrice']) },
382
- "Storage" => lambda {|it| format_money(it['storagePrice']) },
383
- "Network" => lambda {|it| format_money(it['networkPrice']) },
384
- "License" => lambda {|it| format_money(it['licensePrice']) },
385
- "Other" => lambda {|it| format_money(it['extraPrice']) },
386
- "Running" => lambda {|it| format_money(it['runningPrice']) },
387
- "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]}) },
388
474
  }
389
475
  print as_pretty_table([invoice], price_columns, options)
390
476
  =end
@@ -392,33 +478,76 @@ EOT
392
478
  # current_date = Time.now
393
479
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
394
480
 
395
- print "\n"
396
- # 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
+
397
526
  cost_rows = [
398
- {label: 'Price'.upcase, compute: invoice['computePrice'], memory: invoice['memoryPrice'], storage: invoice['storagePrice'], network: invoice['networkPrice'], license: invoice['licensePrice'], extra: invoice['extraPrice'], running: invoice['runningPrice'], total: invoice['totalPrice']},
399
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']},
400
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
401
534
  if options[:show_estimates]
402
535
  cost_rows += [
403
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']},
404
- {label: 'Estimated Price'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
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']},
405
538
  ]
406
539
  end
407
540
  cost_columns = {
408
541
  "" => lambda {|it| it[:label] },
409
- "Compute".upcase => lambda {|it| format_money(it[:compute]) },
410
- "Memory".upcase => lambda {|it| format_money(it[:memory]) },
411
- "Storage".upcase => lambda {|it| format_money(it[:storage]) },
412
- "Network".upcase => lambda {|it| format_money(it[:network]) },
413
- "License".upcase => lambda {|it| format_money(it[:license]) },
414
- "Other".upcase => lambda {|it| format_money(it[:extra]) },
415
- "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]}) },
416
549
  "Total".upcase => lambda {|it|
417
- if invoice['runningMultiplier'] && invoice['runningMultiplier'].to_i != 1 && it[:total].to_f.to_f > 0
418
- format_money(it[:total]) + " (Projected)"
419
- else
420
- format_money(it[:total])
421
- end
550
+ format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
422
551
  },
423
552
  }
424
553
  # remove columns that rarely have data...
@@ -435,40 +564,7 @@ EOT
435
564
 
436
565
  if options[:show_raw_data]
437
566
  print_h2 "Raw Data"
438
- puts invoice['rawData']
439
- end
440
-
441
- # Line Items
442
- line_items = invoice['lineItems']
443
- if line_items && line_items.size > 0 && options[:hide_line_items] != true
444
-
445
- line_items_columns = [
446
- {"INVOICE ID" => lambda {|it| it['invoiceId'] } },
447
- {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
448
- {"REF ID" => lambda {|it| it['refId'] } },
449
- {"REF NAME" => lambda {|it| it['refName'] } },
450
- #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
451
- {"START" => lambda {|it| format_date(it['startDate']) } },
452
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
453
- {"USAGE TYPE" => lambda {|it| it['usageType'] } },
454
- {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
455
- {"USAGE" => lambda {|it| it['itemUsage'] } },
456
- {"RATE" => lambda {|it| it['itemRate'] } },
457
- {"COST" => lambda {|it| format_money(it['itemCost']) } },
458
- {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
459
- {"TAX" => lambda {|it| format_money(it['itemTax']) } },
460
- # {"TERM" => lambda {|it| it['itemTerm'] } },
461
- "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
462
- "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
463
- ]
464
-
465
- if options[:show_raw_data]
466
- columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
467
- end
468
-
469
- print_h2 "Line Items"
470
- print as_pretty_table(line_items, line_items_columns, options)
471
- print_results_pagination({total: line_items.size, size: line_items.size})
567
+ puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
472
568
  end
473
569
 
474
570
  print reset,"\n"
@@ -562,37 +658,33 @@ EOT
562
658
  ref_ids = []
563
659
  optparse = Morpheus::Cli::OptionParser.new do |opts|
564
660
  opts.banner = subcommand_usage()
565
- opts.on('-a', '--all', "Display all costs, prices and raw data" ) do
661
+ opts.on('-a', '--all', "Display all details, costs and prices." ) do
566
662
  options[:show_actual_costs] = true
567
663
  options[:show_costs] = true
568
664
  options[:show_prices] = true
569
- options[:show_raw_data] = true
665
+ # options[:show_raw_data] = true
570
666
  end
571
- # 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
572
668
  # options[:show_actual_costs] = true
573
669
  # end
574
- # 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
575
671
  # options[:show_costs] = true
576
672
  # end
577
- # opts.on('--prices', '--prices', "Display prices: Total, Compute, Memory, Storage, etc." ) do
578
- # options[:show_prices] = true
579
- # end
673
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
674
+ options[:show_prices] = true
675
+ end
676
+ opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
677
+ params['invoiceId'] ||= []
678
+ params['invoiceId'] << val
679
+ end
680
+ opts.on('--external-id ID', String, "Filter by External ID") do |val|
681
+ params['externalId'] ||= []
682
+ params['externalId'] << val
683
+ end
580
684
  opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
581
- if val.to_s.downcase == 'cloud' || val.to_s.downcase == 'zone'
582
- params['refType'] = 'ComputeZone'
583
- elsif val.to_s.downcase == 'instance'
584
- params['refType'] = 'Instance'
585
- elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
586
- params['refType'] = 'ComputeServer'
587
- elsif val.to_s.downcase == 'cluster'
588
- params['refType'] = 'ComputeServerGroup'
589
- elsif val.to_s.downcase == 'group'
590
- params['refType'] = 'ComputeSite'
591
- elsif val.to_s.downcase == 'user'
592
- params['refType'] = 'User'
593
- else
594
- params['refType'] = val
595
- end
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) }
596
688
  end
597
689
  opts.on('--id ID', String, "Filter by Ref ID") do |val|
598
690
  ref_ids << val
@@ -654,6 +746,18 @@ EOT
654
746
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
655
747
  options[:show_raw_data] = true
656
748
  end
749
+ opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
750
+ params['includeTotals'] = true
751
+ options[:show_invoice_totals] = true
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
657
761
  build_standard_list_options(opts, options)
658
762
  opts.footer = "List invoice line items."
659
763
  end
@@ -722,21 +826,28 @@ EOT
722
826
  # current_date = Time.now
723
827
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
724
828
  columns = [
829
+ {"ID" => lambda {|it| it['id'] } },
725
830
  {"INVOICE ID" => lambda {|it| it['invoiceId'] } },
726
831
  {"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
727
832
  {"REF ID" => lambda {|it| it['refId'] } },
728
833
  {"REF NAME" => lambda {|it| it['refName'] } },
729
834
  #{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
730
835
  {"START" => lambda {|it| format_date(it['startDate']) } },
731
- {"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
836
+ {"END" => lambda {|it| format_date(it['endDate']) } },
732
837
  {"USAGE TYPE" => lambda {|it| it['usageType'] } },
733
838
  {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
734
839
  {"USAGE" => lambda {|it| it['itemUsage'] } },
735
840
  {"RATE" => lambda {|it| it['itemRate'] } },
736
- {"COST" => lambda {|it| format_money(it['itemCost']) } },
737
- {"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
738
- {"TAX" => lambda {|it| format_money(it['itemTax']) } },
739
- # {"TERM" => lambda {|it| it['itemTerm'] } },
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'] } },
740
851
  "CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
741
852
  "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
742
853
  ]
@@ -744,8 +855,40 @@ EOT
744
855
  if options[:show_raw_data]
745
856
  columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
746
857
  end
747
- print as_pretty_table(line_items, columns, options)
748
- print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
858
+ # if options[:show_invoice_totals]
859
+ # line_item_totals = json_response['lineItemTotals']
860
+ # if line_item_totals
861
+ # totals_row = line_item_totals.clone
862
+ # totals_row['id'] = 'TOTAL:'
863
+ # #totals_row['usageCategory'] = 'TOTAL:'
864
+ # line_items = line_items + [totals_row]
865
+ # end
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
891
+
749
892
  end
750
893
  print reset,"\n"
751
894
  end
@@ -763,6 +906,13 @@ EOT
763
906
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
764
907
  options[:show_raw_data] = true
765
908
  end
909
+ opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
910
+ options[:show_raw_data] = true
911
+ options[:pretty_json] = true
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
766
916
  build_standard_get_options(opts, options)
767
917
  opts.footer = "Get details about a specific invoice line item."
768
918
  opts.footer = <<-EOT
@@ -775,7 +925,7 @@ EOT
775
925
  connect(options)
776
926
  id_list = parse_id_list(args)
777
927
  return run_command_for_each_arg(id_list) do |arg|
778
- _get(arg, options)
928
+ _get_line_item(arg, options)
779
929
  end
780
930
  end
781
931
 
@@ -806,15 +956,26 @@ EOT
806
956
  "Usage Category" => lambda {|it| it['usageCategory'] },
807
957
  "Item Usage" => lambda {|it| it['itemUsage'] },
808
958
  "Item Rate" => lambda {|it| it['itemRate'] },
809
- "Item Cost" => lambda {|it| format_money(it['itemCost']) },
810
- "Item Price" => lambda {|it| format_money(it['itemrPrice']) },
811
- "Item Tax" => lambda {|it| format_money(it['itemTax']) },
812
- "Item Term" => lambda {|it| it['itemTerm'] },
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]}) },
813
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'] },
814
969
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
815
970
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
816
971
  }
817
972
  print_description_list(description_cols, line_item, options)
973
+
974
+ if options[:show_raw_data]
975
+ print_h2 "Raw Data"
976
+ puts as_json(line_item['rawData'], {pretty_json:false}.merge(options))
977
+ end
978
+
818
979
  print reset,"\n"
819
980
  end
820
981
  return 0, nil
@@ -873,6 +1034,25 @@ EOT
873
1034
  end
874
1035
  end
875
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
+
876
1056
  # convert "202003" to "March 2020"
877
1057
  def format_invoice_period(it)
878
1058
  interval = it['interval']
@@ -919,6 +1099,11 @@ EOT
919
1099
  end
920
1100
  end
921
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
+
922
1107
  def format_server_power_state(server, return_color=cyan)
923
1108
  out = ""
924
1109
  if server['powerState'] == 'on'