morpheus-cli 4.2.11 → 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: 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'