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 +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/cli/apps.rb +2 -1
- data/lib/morpheus/cli/cli_command.rb +84 -4
- data/lib/morpheus/cli/cli_registry.rb +10 -1
- data/lib/morpheus/cli/clusters.rb +2 -1
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +16 -13
- data/lib/morpheus/cli/containers_command.rb +2 -1
- data/lib/morpheus/cli/credentials.rb +1 -1
- data/lib/morpheus/cli/health_command.rb +4 -3
- data/lib/morpheus/cli/hosts.rb +2 -1
- data/lib/morpheus/cli/instances.rb +2 -1
- data/lib/morpheus/cli/invoices_command.rb +412 -227
- data/lib/morpheus/cli/library_option_lists_command.rb +61 -125
- data/lib/morpheus/cli/library_option_types_command.rb +32 -37
- data/lib/morpheus/cli/logs_command.rb +3 -2
- data/lib/morpheus/cli/mixins/library_helper.rb +32 -0
- data/lib/morpheus/cli/mixins/logs_helper.rb +18 -9
- data/lib/morpheus/cli/mixins/print_helper.rb +39 -10
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +12 -1
- data/lib/morpheus/cli/option_types.rb +45 -9
- data/lib/morpheus/cli/price_sets_command.rb +1 -1
- data/lib/morpheus/cli/projects_command.rb +51 -121
- data/lib/morpheus/cli/remote.rb +5 -8
- data/lib/morpheus/cli/users.rb +1 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +7 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3481d47679761310fbffd07ad968588ace4b92d9f428f68983f90a3ef4e13799
|
4
|
+
data.tar.gz: 5c900c15f68c14d5ab10d89b148e2bee5216653c123004b528f3d8bf1c1740cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 384148f22d822c664456d5de65813d8ff6e4ea07b9a26a68ab8803131aa43247e093f4cae9304cb591d58f6f075bebccdbbdd2ca614f615b33ebc46b242a8cf2
|
7
|
+
data.tar.gz: 8ae2af56264a5bb3163ae5b8bb04747f58ef4e96f3ecdfed5b3a66e14ebce5147ef73623624fc22ab16536d4834a21b7de8a7b57952b86a9ad128abb712cf37f
|
data/Dockerfile
CHANGED
data/lib/morpheus/cli/apps.rb
CHANGED
@@ -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)' : ''}
|
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 {|
|
849
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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('
|
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}"
|
data/lib/morpheus/cli/hosts.rb
CHANGED
@@ -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
|
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,
|
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,
|
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,
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
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|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
{"
|
204
|
-
|
205
|
-
#{"
|
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|
|
209
|
-
|
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
|
-
|
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
|
-
{"
|
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
|
-
|
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
|
-
|
265
|
-
|
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
|
-
|
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
|
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,
|
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
|
-
"
|
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|
|
346
|
-
"Ref Start" => lambda {|it|
|
347
|
-
"Ref End" => lambda {|it|
|
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
|
-
|
396
|
-
|
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['
|
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
|
-
|
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
|
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,
|
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,
|
670
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Other" ) do
|
575
671
|
# options[:show_costs] = true
|
576
672
|
# end
|
577
|
-
|
578
|
-
|
579
|
-
|
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
|
-
|
582
|
-
|
583
|
-
|
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|
|
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
|
-
|
738
|
-
{"
|
739
|
-
|
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
|
-
|
748
|
-
|
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
|
-
|
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['
|
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'
|