morpheus-cli 4.2.15 → 4.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/cli/cli_command.rb +84 -4
- data/lib/morpheus/cli/cli_registry.rb +3 -0
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +16 -13
- data/lib/morpheus/cli/invoices_command.rb +323 -221
- data/lib/morpheus/cli/library_option_lists_command.rb +61 -125
- data/lib/morpheus/cli/library_option_types_command.rb +14 -9
- data/lib/morpheus/cli/mixins/library_helper.rb +32 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +39 -10
- data/lib/morpheus/cli/option_types.rb +15 -4
- data/lib/morpheus/cli/price_sets_command.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
@@ -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
|
@@ -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
|
@@ -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
|
@@ -123,6 +112,14 @@ class Morpheus::Cli::InvoicesCommand
|
|
123
112
|
params['includeTotals'] = true
|
124
113
|
options[:show_invoice_totals] = true
|
125
114
|
end
|
115
|
+
opts.on('--totals-only', "View totals only") do |val|
|
116
|
+
params['includeTotals'] = true
|
117
|
+
options[:show_invoice_totals] = true
|
118
|
+
options[:totals_only] = true
|
119
|
+
end
|
120
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
121
|
+
options[:sigdig] = val.to_i
|
122
|
+
end
|
126
123
|
build_standard_list_options(opts, options)
|
127
124
|
opts.footer = "List invoices."
|
128
125
|
end
|
@@ -185,7 +182,9 @@ class Morpheus::Cli::InvoicesCommand
|
|
185
182
|
subtitles += parse_list_subtitles(options)
|
186
183
|
print_h1 title, subtitles
|
187
184
|
if invoices.empty?
|
188
|
-
|
185
|
+
unless options[:totals_only]
|
186
|
+
print cyan,"No invoices found.",reset,"\n"
|
187
|
+
end
|
189
188
|
else
|
190
189
|
# current_date = Time.now
|
191
190
|
# current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
|
@@ -194,98 +193,146 @@ class Morpheus::Cli::InvoicesCommand
|
|
194
193
|
{"INVOICE ID" => lambda {|it| it['id'] } },
|
195
194
|
{"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
196
195
|
{"REF ID" => lambda {|it| it['refId'] } },
|
197
|
-
{"REF NAME" => lambda {|it|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
196
|
+
{"REF NAME" => lambda {|it|
|
197
|
+
if options[:show_all]
|
198
|
+
it['refName']
|
199
|
+
else
|
200
|
+
truncate_string_right(it['refName'], 100)
|
201
|
+
end
|
202
|
+
} },
|
203
203
|
#{"INTERVAL" => lambda {|it| it['interval'] } },
|
204
204
|
{"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
|
205
|
-
{"
|
206
|
-
|
207
|
-
#{"
|
205
|
+
#{"TENANT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
|
206
|
+
|
207
|
+
#{"COST TYPE" => lambda {|it| it['costType'].to_s.capitalize } },
|
208
208
|
{"PERIOD" => lambda {|it| format_invoice_period(it) } },
|
209
209
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
210
|
-
{"END" => lambda {|it|
|
211
|
-
|
210
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
211
|
+
] + (options[:show_all] ? [
|
212
|
+
{"REF START" => lambda {|it| format_dt(it['refStart']) } },
|
213
|
+
{"REF END" => lambda {|it| format_dt(it['refEnd']) } },
|
214
|
+
] : []) + [
|
215
|
+
{"COMPUTE" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
216
|
+
# {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
|
217
|
+
{"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
218
|
+
{"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
219
|
+
{"OTHER" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
220
|
+
{"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
212
221
|
{"TOTAL" => lambda {|it|
|
213
|
-
|
214
|
-
if it['runningMultiplier'] && it['runningMultiplier'].to_i != 1 && it['totalCost'].to_f > 0
|
215
|
-
format_money(it['totalCost']) + " (Projected)"
|
216
|
-
else
|
217
|
-
format_money(it['totalCost'])
|
218
|
-
end
|
222
|
+
format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
219
223
|
} }
|
220
224
|
]
|
221
225
|
|
222
|
-
columns += [
|
223
|
-
{"COMPUTE" => lambda {|it| format_money(it['computeCost']) } },
|
224
|
-
# {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
|
225
|
-
{"STORAGE" => lambda {|it| format_money(it['storageCost']) } },
|
226
|
-
{"NETWORK" => lambda {|it| format_money(it['networkCost']) } },
|
227
|
-
{"OTHER" => lambda {|it| format_money(it['extraCost']) } },
|
228
|
-
]
|
229
226
|
if options[:show_prices]
|
230
227
|
columns += [
|
231
|
-
{"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice']) } },
|
232
|
-
# {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice']) } },
|
233
|
-
{"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice']) } },
|
234
|
-
{"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice']) } },
|
235
|
-
{"OTHER PRICE" => lambda {|it| format_money(it['extraPrice']) } },
|
236
|
-
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice']) } },
|
228
|
+
{"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
229
|
+
# {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
230
|
+
{"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
231
|
+
{"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
232
|
+
{"OTHER PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
233
|
+
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
237
234
|
{"TOTAL PRICE" => lambda {|it|
|
238
|
-
|
239
|
-
format_money(it['totalPrice']) + " (Projected)"
|
240
|
-
else
|
241
|
-
format_money(it['totalPrice'])
|
242
|
-
end
|
235
|
+
format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
243
236
|
} }
|
244
237
|
]
|
245
238
|
end
|
246
239
|
if options[:show_estimates]
|
247
240
|
columns += [
|
248
|
-
{"
|
241
|
+
{"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
242
|
+
# {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
243
|
+
{"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
244
|
+
{"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
245
|
+
{"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
246
|
+
{"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
249
247
|
{"TOTAL EST." => lambda {|it|
|
250
|
-
|
251
|
-
format_money(it['estimatedTotalCost']) + " (Projected)"
|
252
|
-
else
|
253
|
-
format_money(it['estimatedTotalCost'])
|
254
|
-
end
|
248
|
+
format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
|
255
249
|
} },
|
256
|
-
{"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost']) } },
|
257
|
-
# {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost']) } },
|
258
|
-
{"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost']) } },
|
259
|
-
{"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost']) } },
|
260
|
-
{"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost']) } },
|
261
250
|
]
|
262
251
|
end
|
252
|
+
columns += [
|
253
|
+
{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
|
254
|
+
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
255
|
+
{"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
|
256
|
+
]
|
257
|
+
if show_projects
|
258
|
+
columns += [
|
259
|
+
{"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
|
260
|
+
{"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
|
261
|
+
{"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } },
|
262
|
+
]
|
263
|
+
end
|
264
|
+
columns += [
|
265
|
+
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
266
|
+
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
267
|
+
]
|
263
268
|
if options[:show_raw_data]
|
264
269
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
265
270
|
end
|
266
|
-
|
267
|
-
|
271
|
+
unless options[:totals_only]
|
272
|
+
print as_pretty_table(invoices, columns, options)
|
273
|
+
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
274
|
+
end
|
268
275
|
|
269
276
|
if options[:show_invoice_totals]
|
270
277
|
invoice_totals = json_response['invoiceTotals']
|
278
|
+
print_h2 "Line Item Totals" unless options[:totals_only]
|
279
|
+
invoice_totals_columns = [
|
280
|
+
{"Invoices" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
|
281
|
+
{"Compute" => lambda {|it| format_money(it['actualComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
282
|
+
{"Storage" => lambda {|it| format_money(it['actualStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
283
|
+
{"Network" => lambda {|it| format_money(it['actualNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
284
|
+
{"Extra" => lambda {|it| format_money(it['actualExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
285
|
+
] + (options[:show_prices] ? [
|
286
|
+
{"Compute Price" => lambda {|it| format_money(it['actualComputePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
287
|
+
{"Storage Price" => lambda {|it| format_money(it['actualStoragePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
288
|
+
{"Network Price" => lambda {|it| format_money(it['actualNetworkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
289
|
+
{"Extra Price" => lambda {|it| format_money(it['actualExtraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
290
|
+
] : [])
|
291
|
+
print_description_list(invoice_totals_columns, line_item_totals)
|
292
|
+
end
|
293
|
+
if options[:show_invoice_totals]
|
294
|
+
invoice_totals = json_response['invoiceTotals']
|
295
|
+
print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
|
296
|
+
|
271
297
|
if invoice_totals
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
298
|
+
cost_rows = [
|
299
|
+
{label: 'Cost', compute: invoice_totals['actualComputeCost'], memory: invoice_totals['actualMemoryCost'], storage: invoice_totals['actualStorageCost'], network: invoice_totals['actualNetworkCost'], license: invoice_totals['actualLicenseCost'], extra: invoice_totals['actualExtraCost'], running: invoice_totals['actualRunningCost'], total: invoice_totals['actualTotalCost']},
|
300
|
+
]
|
301
|
+
if options[:show_prices]
|
302
|
+
cost_rows += [
|
303
|
+
{label: 'Price', compute: invoice_totals['actualComputePrice'], memory: invoice_totals['actualMemoryPrice'], storage: invoice_totals['actualStoragePrice'], network: invoice_totals['actualNetworkPrice'], license: invoice_totals['actualLicensePrice'], extra: invoice_totals['actualExtraPrice'], running: invoice_totals['actualRunningPrice'], total: invoice_totals['actualTotalPrice']},
|
304
|
+
]
|
305
|
+
end
|
306
|
+
if options[:show_estimates]
|
307
|
+
cost_rows += [
|
308
|
+
{label: 'Estimated Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
|
309
|
+
{label: 'Estimated Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
|
310
|
+
]
|
311
|
+
end
|
312
|
+
cost_columns = {
|
313
|
+
"" => lambda {|it| it[:label] },
|
314
|
+
"Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
|
315
|
+
"Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
|
316
|
+
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
317
|
+
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
318
|
+
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
319
|
+
"Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
320
|
+
"MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
321
|
+
"Total".upcase => lambda {|it|
|
322
|
+
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
323
|
+
},
|
287
324
|
}
|
288
|
-
|
325
|
+
# remove columns that rarely have data...
|
326
|
+
if cost_rows.sum { |it| it[:memory].to_f } == 0
|
327
|
+
cost_columns.delete("Memory".upcase)
|
328
|
+
end
|
329
|
+
if cost_rows.sum { |it| it[:license].to_f } == 0
|
330
|
+
cost_columns.delete("License".upcase)
|
331
|
+
end
|
332
|
+
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
333
|
+
cost_columns.delete("Other".upcase)
|
334
|
+
end
|
335
|
+
print as_pretty_table(cost_rows, cost_columns, options)
|
289
336
|
else
|
290
337
|
print "\n"
|
291
338
|
print yellow, "No invoice totals data", reset, "\n"
|
@@ -301,14 +348,17 @@ class Morpheus::Cli::InvoicesCommand
|
|
301
348
|
options = {}
|
302
349
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
303
350
|
opts.banner = subcommand_usage("[id]")
|
304
|
-
opts.on('-a', '--all', "Display all
|
351
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
305
352
|
options[:show_estimates] = true
|
306
353
|
# options[:show_costs] = true
|
307
354
|
options[:show_prices] = true
|
308
|
-
options[:show_raw_data] = true
|
355
|
+
# options[:show_raw_data] = true
|
309
356
|
options[:max_line_items] = 10000
|
310
357
|
end
|
311
|
-
opts.on('--
|
358
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
|
359
|
+
options[:show_prices] = true
|
360
|
+
end
|
361
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Other" ) do
|
312
362
|
options[:show_estimates] = true
|
313
363
|
end
|
314
364
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
@@ -318,12 +368,12 @@ class Morpheus::Cli::InvoicesCommand
|
|
318
368
|
options[:show_raw_data] = true
|
319
369
|
options[:pretty_json] = true
|
320
370
|
end
|
321
|
-
opts.on('-m', '--max-line-items NUMBER', "Maximum number of line items to display. Default is 5.") do |val|
|
322
|
-
options[:max_line_items] = val.to_i
|
323
|
-
end
|
324
371
|
opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
|
325
372
|
options[:hide_line_items] = true
|
326
373
|
end
|
374
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
375
|
+
options[:sigdig] = val.to_i
|
376
|
+
end
|
327
377
|
build_standard_get_options(opts, options)
|
328
378
|
opts.footer = "Get details about a specific invoice."
|
329
379
|
opts.footer = <<-EOT
|
@@ -366,19 +416,21 @@ EOT
|
|
366
416
|
"Ref ID" => lambda {|it| it['refId'] },
|
367
417
|
"Ref Name" => lambda {|it| it['refName'] },
|
368
418
|
"Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
|
369
|
-
"Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
|
370
|
-
"Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
|
371
|
-
"Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
|
372
419
|
"Power State" => lambda {|it| format_server_power_state(it) },
|
373
|
-
"
|
420
|
+
"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
374
421
|
"Active" => lambda {|it| format_boolean(it['active']) },
|
375
|
-
"Period" => lambda {|it| format_invoice_period(it) },
|
376
422
|
"Estimate" => lambda {|it| format_boolean(it['estimate']) },
|
423
|
+
#"Cost Type" => lambda {|it| it['costType'].to_s.capitalize },
|
424
|
+
"Period" => lambda {|it| format_invoice_period(it) },
|
377
425
|
#"Interval" => lambda {|it| it['interval'] },
|
378
426
|
"Start" => lambda {|it| format_date(it['startDate']) },
|
379
|
-
"End" => lambda {|it|
|
380
|
-
"Ref Start" => lambda {|it|
|
381
|
-
"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']) : '' },
|
382
434
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
383
435
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
384
436
|
}
|
@@ -398,27 +450,27 @@ EOT
|
|
398
450
|
=begin
|
399
451
|
print_h2 "Costs"
|
400
452
|
cost_columns = {
|
401
|
-
"Compute" => lambda {|it| format_money(it['computeCost']) },
|
402
|
-
"Memory" => lambda {|it| format_money(it['memoryCost']) },
|
403
|
-
"Storage" => lambda {|it| format_money(it['storageCost']) },
|
404
|
-
"Network" => lambda {|it| format_money(it['networkCost']) },
|
405
|
-
"License" => lambda {|it| format_money(it['licenseCost']) },
|
406
|
-
"Other" => lambda {|it| format_money(it['extraCost']) },
|
407
|
-
"Running" => lambda {|it| format_money(it['runningCost']) },
|
408
|
-
"Total Cost" => lambda {|it| format_money(it['totalCost']) },
|
453
|
+
"Compute" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) },
|
454
|
+
"Memory" => lambda {|it| format_money(it['memoryCost'], 'usd', {sigdig:options[:sigdig]}) },
|
455
|
+
"Storage" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) },
|
456
|
+
"Network" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) },
|
457
|
+
"License" => lambda {|it| format_money(it['licenseCost'], 'usd', {sigdig:options[:sigdig]}) },
|
458
|
+
"Other" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
|
459
|
+
"Running" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) },
|
460
|
+
"Total Cost" => lambda {|it| format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) },
|
409
461
|
}
|
410
462
|
print as_pretty_table([invoice], cost_columns, options)
|
411
463
|
|
412
464
|
print_h2 "Prices"
|
413
465
|
price_columns = {
|
414
|
-
"Compute" => lambda {|it| format_money(it['computePrice']) },
|
415
|
-
"Memory" => lambda {|it| format_money(it['memoryPrice']) },
|
416
|
-
"Storage" => lambda {|it| format_money(it['storagePrice']) },
|
417
|
-
"Network" => lambda {|it| format_money(it['networkPrice']) },
|
418
|
-
"License" => lambda {|it| format_money(it['licensePrice']) },
|
419
|
-
"Other" => lambda {|it| format_money(it['extraPrice']) },
|
420
|
-
"Running" => lambda {|it| format_money(it['runningPrice']) },
|
421
|
-
"Total Price" => lambda {|it| format_money(it['totalPrice']) },
|
466
|
+
"Compute" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
467
|
+
"Memory" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
468
|
+
"Storage" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
469
|
+
"Network" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
470
|
+
"License" => lambda {|it| format_money(it['licensePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
471
|
+
"Other" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
472
|
+
"Running" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
473
|
+
"Total Price" => lambda {|it| format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
422
474
|
}
|
423
475
|
print as_pretty_table([invoice], price_columns, options)
|
424
476
|
=end
|
@@ -426,33 +478,76 @@ EOT
|
|
426
478
|
# current_date = Time.now
|
427
479
|
# current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
|
428
480
|
|
429
|
-
|
430
|
-
|
481
|
+
|
482
|
+
|
483
|
+
# Line Items
|
484
|
+
line_items = invoice['lineItems']
|
485
|
+
if line_items && line_items.size > 0 && options[:hide_line_items] != true
|
486
|
+
line_items_columns = [
|
487
|
+
{"ID" => lambda {|it| it['id'] } },
|
488
|
+
#{"REF TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
489
|
+
#{"REF ID" => lambda {|it| it['refId'] } },
|
490
|
+
#{"REF NAME" => lambda {|it| it['refName'] } },
|
491
|
+
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
492
|
+
{"START" => lambda {|it| format_dt(it['startDate']) } },
|
493
|
+
{"END" => lambda {|it| format_dt(it['endDate']) } },
|
494
|
+
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
495
|
+
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
496
|
+
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
497
|
+
{"RATE" => lambda {|it| it['itemRate'] } },
|
498
|
+
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
499
|
+
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
500
|
+
#{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
501
|
+
# {"TERM" => lambda {|it| it['itemTerm'] } },
|
502
|
+
{"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
|
503
|
+
{"ITEM NAME" => lambda {|it| it['itemName'] } },
|
504
|
+
{"ITEM TYPE" => lambda {|it| it['itemType'] } },
|
505
|
+
{"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
|
506
|
+
{"PRODUCT CODE" => lambda {|it| it['productCode'] } },
|
507
|
+
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
508
|
+
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
509
|
+
]
|
510
|
+
if options[:show_raw_data]
|
511
|
+
line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
512
|
+
end
|
513
|
+
print_h2 "Line Items"
|
514
|
+
#max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
|
515
|
+
paged_line_items = line_items #.first(max_line_items)
|
516
|
+
print as_pretty_table(paged_line_items, line_items_columns, options)
|
517
|
+
print_results_pagination({total: line_items.size, size: paged_line_items.size}, {:label => "line item", :n_label => "line items"})
|
518
|
+
end
|
519
|
+
|
520
|
+
# cost_types = ["Costs"]
|
521
|
+
# cost_types << "Prices" if options[:show_prices]
|
522
|
+
# cost_types << "Estimates" if options[:show_estimates]
|
523
|
+
# print_h2 cost_types.size == 1 ? "Totals" : "Total #{anded_list(cost_types)}"
|
524
|
+
print_h2 "Invoice Totals"
|
525
|
+
|
431
526
|
cost_rows = [
|
432
|
-
{label: 'Price'.upcase, compute: invoice['computePrice'], memory: invoice['memoryPrice'], storage: invoice['storagePrice'], network: invoice['networkPrice'], license: invoice['licensePrice'], extra: invoice['extraPrice'], running: invoice['runningPrice'], total: invoice['totalPrice']},
|
433
527
|
{label: 'Cost'.upcase, compute: invoice['computeCost'], memory: invoice['memoryCost'], storage: invoice['storageCost'], network: invoice['networkCost'], license: invoice['licenseCost'], extra: invoice['extraCost'], running: invoice['runningCost'], total: invoice['totalCost']},
|
434
528
|
]
|
529
|
+
if options[:show_prices]
|
530
|
+
cost_rows += [
|
531
|
+
{label: 'Price'.upcase, compute: invoice['computePrice'], memory: invoice['memoryPrice'], storage: invoice['storagePrice'], network: invoice['networkPrice'], license: invoice['licensePrice'], extra: invoice['extraPrice'], running: invoice['runningPrice'], total: invoice['totalPrice']},
|
532
|
+
]
|
533
|
+
end
|
435
534
|
if options[:show_estimates]
|
436
535
|
cost_rows += [
|
437
536
|
{label: 'Estimated Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
|
438
|
-
{label: 'Estimated Price'.upcase, compute: invoice['
|
537
|
+
{label: 'Estimated Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
|
439
538
|
]
|
440
539
|
end
|
441
540
|
cost_columns = {
|
442
541
|
"" => lambda {|it| it[:label] },
|
443
|
-
"Compute".upcase => lambda {|it| format_money(it[:compute]) },
|
444
|
-
"Memory".upcase => lambda {|it| format_money(it[:memory]) },
|
445
|
-
"Storage".upcase => lambda {|it| format_money(it[:storage]) },
|
446
|
-
"Network".upcase => lambda {|it| format_money(it[:network]) },
|
447
|
-
"License".upcase => lambda {|it| format_money(it[:license]) },
|
448
|
-
"Other".upcase => lambda {|it| format_money(it[:extra]) },
|
449
|
-
"MTD" => lambda {|it| format_money(it[:running]) },
|
542
|
+
"Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
|
543
|
+
"Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
|
544
|
+
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
545
|
+
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
546
|
+
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
547
|
+
"Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
548
|
+
"MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
450
549
|
"Total".upcase => lambda {|it|
|
451
|
-
|
452
|
-
format_money(it[:total]) + " (Projected)"
|
453
|
-
else
|
454
|
-
format_money(it[:total])
|
455
|
-
end
|
550
|
+
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
456
551
|
},
|
457
552
|
}
|
458
553
|
# remove columns that rarely have data...
|
@@ -471,41 +566,6 @@ EOT
|
|
471
566
|
print_h2 "Raw Data"
|
472
567
|
puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
|
473
568
|
end
|
474
|
-
|
475
|
-
# Line Items
|
476
|
-
line_items = invoice['lineItems']
|
477
|
-
if line_items && line_items.size > 0 && options[:hide_line_items] != true
|
478
|
-
|
479
|
-
line_items_columns = [
|
480
|
-
{"ID" => lambda {|it| it['id'] } },
|
481
|
-
{"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
482
|
-
{"REF ID" => lambda {|it| it['refId'] } },
|
483
|
-
{"REF NAME" => lambda {|it| it['refName'] } },
|
484
|
-
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
485
|
-
{"START" => lambda {|it| format_date(it['startDate']) } },
|
486
|
-
{"END" => lambda {|it| it['endDate'] ? format_date(it['endDate']) : '' } },
|
487
|
-
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
488
|
-
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
489
|
-
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
490
|
-
{"RATE" => lambda {|it| it['itemRate'] } },
|
491
|
-
{"COST" => lambda {|it| format_money(it['itemCost']) } },
|
492
|
-
{"PRICE" => lambda {|it| format_money(it['itemPrice']) } },
|
493
|
-
{"TAX" => lambda {|it| format_money(it['itemTax']) } },
|
494
|
-
# {"TERM" => lambda {|it| it['itemTerm'] } },
|
495
|
-
"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
|
496
|
-
"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
497
|
-
]
|
498
|
-
|
499
|
-
if options[:show_raw_data]
|
500
|
-
line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
501
|
-
end
|
502
|
-
|
503
|
-
print_h2 "Line Items"
|
504
|
-
max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
|
505
|
-
paged_line_items = line_items.first(max_line_items)
|
506
|
-
print as_pretty_table(paged_line_items, line_items_columns, options)
|
507
|
-
print_results_pagination({total: line_items.size, size: paged_line_items.size}, {:label => "line item", :n_label => "line items"})
|
508
|
-
end
|
509
569
|
|
510
570
|
print reset,"\n"
|
511
571
|
return 0
|
@@ -598,41 +658,33 @@ EOT
|
|
598
658
|
ref_ids = []
|
599
659
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
600
660
|
opts.banner = subcommand_usage()
|
601
|
-
opts.on('-a', '--all', "Display all
|
661
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
602
662
|
options[:show_actual_costs] = true
|
603
663
|
options[:show_costs] = true
|
604
664
|
options[:show_prices] = true
|
605
|
-
options[:show_raw_data] = true
|
665
|
+
# options[:show_raw_data] = true
|
606
666
|
end
|
607
|
-
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute,
|
667
|
+
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Other" ) do
|
608
668
|
# options[:show_actual_costs] = true
|
609
669
|
# end
|
610
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute,
|
670
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Other" ) do
|
611
671
|
# options[:show_costs] = true
|
612
672
|
# end
|
613
|
-
|
614
|
-
|
615
|
-
|
673
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
|
674
|
+
options[:show_prices] = true
|
675
|
+
end
|
616
676
|
opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
|
617
677
|
params['invoiceId'] ||= []
|
618
678
|
params['invoiceId'] << val
|
619
679
|
end
|
680
|
+
opts.on('--external-id ID', String, "Filter by External ID") do |val|
|
681
|
+
params['externalId'] ||= []
|
682
|
+
params['externalId'] << val
|
683
|
+
end
|
620
684
|
opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
params['refType'] = 'Instance'
|
625
|
-
elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
|
626
|
-
params['refType'] = 'ComputeServer'
|
627
|
-
elsif val.to_s.downcase == 'cluster'
|
628
|
-
params['refType'] = 'ComputeServerGroup'
|
629
|
-
elsif val.to_s.downcase == 'group'
|
630
|
-
params['refType'] = 'ComputeSite'
|
631
|
-
elsif val.to_s.downcase == 'user'
|
632
|
-
params['refType'] = 'User'
|
633
|
-
else
|
634
|
-
params['refType'] = val
|
635
|
-
end
|
685
|
+
params['refType'] ||= []
|
686
|
+
values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
|
687
|
+
values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
|
636
688
|
end
|
637
689
|
opts.on('--id ID', String, "Filter by Ref ID") do |val|
|
638
690
|
ref_ids << val
|
@@ -698,6 +750,14 @@ EOT
|
|
698
750
|
params['includeTotals'] = true
|
699
751
|
options[:show_invoice_totals] = true
|
700
752
|
end
|
753
|
+
opts.on('--totals-only', "View totals only") do |val|
|
754
|
+
params['includeTotals'] = true
|
755
|
+
options[:show_invoice_totals] = true
|
756
|
+
options[:totals_only] = true
|
757
|
+
end
|
758
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
759
|
+
options[:sigdig] = val.to_i
|
760
|
+
end
|
701
761
|
build_standard_list_options(opts, options)
|
702
762
|
opts.footer = "List invoice line items."
|
703
763
|
end
|
@@ -773,15 +833,21 @@ EOT
|
|
773
833
|
{"REF NAME" => lambda {|it| it['refName'] } },
|
774
834
|
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
775
835
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
776
|
-
{"END" => lambda {|it|
|
836
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
777
837
|
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
778
838
|
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
779
839
|
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
780
840
|
{"RATE" => lambda {|it| it['itemRate'] } },
|
781
|
-
{"COST" => lambda {|it| format_money(it['itemCost']) } },
|
782
|
-
|
783
|
-
{"
|
784
|
-
|
841
|
+
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
842
|
+
] + (options[:show_prices] ? [
|
843
|
+
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
844
|
+
{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
845
|
+
] : []) + [
|
846
|
+
{"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
|
847
|
+
{"ITEM NAME" => lambda {|it| it['itemName'] } },
|
848
|
+
{"ITEM TYPE" => lambda {|it| it['itemType'] } },
|
849
|
+
{"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
|
850
|
+
{"PRODUCT CODE" => lambda {|it| it['productCode'] } },
|
785
851
|
"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
|
786
852
|
"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
787
853
|
]
|
@@ -789,35 +855,39 @@ EOT
|
|
789
855
|
if options[:show_raw_data]
|
790
856
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
791
857
|
end
|
792
|
-
if options[:show_invoice_totals]
|
793
|
-
line_item_totals = json_response['lineItemTotals']
|
794
|
-
if line_item_totals
|
795
|
-
totals_row = line_item_totals.clone
|
796
|
-
totals_row['id'] = 'TOTAL:'
|
797
|
-
#totals_row['usageCategory'] = 'TOTAL:'
|
798
|
-
line_items = line_items + [totals_row]
|
799
|
-
end
|
800
|
-
end
|
801
|
-
print as_pretty_table(line_items, columns, options)
|
802
|
-
print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
|
803
|
-
|
804
858
|
# if options[:show_invoice_totals]
|
805
859
|
# line_item_totals = json_response['lineItemTotals']
|
806
860
|
# if line_item_totals
|
807
|
-
#
|
808
|
-
#
|
809
|
-
#
|
810
|
-
#
|
811
|
-
# "Price" => lambda {|it| format_money(it['itemPrice']) },
|
812
|
-
# "Tax" => lambda {|it| format_money(it['itemTax']) },
|
813
|
-
# "Usage" => lambda {|it| it['itemUsage'] },
|
814
|
-
# }
|
815
|
-
# print_description_list(invoice_totals_columns, line_item_totals)
|
816
|
-
# else
|
817
|
-
# print "\n"
|
818
|
-
# print yellow, "No line item totals data", reset, "\n"
|
861
|
+
# totals_row = line_item_totals.clone
|
862
|
+
# totals_row['id'] = 'TOTAL:'
|
863
|
+
# #totals_row['usageCategory'] = 'TOTAL:'
|
864
|
+
# line_items = line_items + [totals_row]
|
819
865
|
# end
|
820
866
|
# end
|
867
|
+
unless options[:totals_only]
|
868
|
+
print as_pretty_table(line_items, columns, options)
|
869
|
+
print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
|
870
|
+
end
|
871
|
+
|
872
|
+
if options[:show_invoice_totals]
|
873
|
+
line_item_totals = json_response['lineItemTotals']
|
874
|
+
if line_item_totals
|
875
|
+
print_h2 "Line Item Totals" unless options[:totals_only]
|
876
|
+
invoice_totals_columns = [
|
877
|
+
{"Items" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
|
878
|
+
#{"Usage" => lambda {|it| it['itemUsage'] } },
|
879
|
+
{"Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
880
|
+
] + (options[:show_prices] ? [
|
881
|
+
{"Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
882
|
+
#{"Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
883
|
+
|
884
|
+
] : [])
|
885
|
+
print_description_list(invoice_totals_columns, line_item_totals)
|
886
|
+
else
|
887
|
+
print "\n"
|
888
|
+
print yellow, "No line item totals data", reset, "\n"
|
889
|
+
end
|
890
|
+
end
|
821
891
|
|
822
892
|
end
|
823
893
|
print reset,"\n"
|
@@ -840,6 +910,9 @@ EOT
|
|
840
910
|
options[:show_raw_data] = true
|
841
911
|
options[:pretty_json] = true
|
842
912
|
end
|
913
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
914
|
+
options[:sigdig] = val.to_i
|
915
|
+
end
|
843
916
|
build_standard_get_options(opts, options)
|
844
917
|
opts.footer = "Get details about a specific invoice line item."
|
845
918
|
opts.footer = <<-EOT
|
@@ -883,11 +956,16 @@ EOT
|
|
883
956
|
"Usage Category" => lambda {|it| it['usageCategory'] },
|
884
957
|
"Item Usage" => lambda {|it| it['itemUsage'] },
|
885
958
|
"Item Rate" => lambda {|it| it['itemRate'] },
|
886
|
-
"Item Cost" => lambda {|it| format_money(it['itemCost']) },
|
887
|
-
"Item Price" => lambda {|it| format_money(it['
|
888
|
-
"Item Tax" => lambda {|it| format_money(it['itemTax']) },
|
889
|
-
"Item Term" => lambda {|it| it['itemTerm'] },
|
959
|
+
"Item Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) },
|
960
|
+
"Item Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
961
|
+
#"Item Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) },
|
890
962
|
#"Tax Type" => lambda {|it| it['taxType'] },
|
963
|
+
"Item Term" => lambda {|it| it['itemTerm'] },
|
964
|
+
"Item ID" => lambda {|it| it['itemId'] },
|
965
|
+
"Item Name" => lambda {|it| it['itemName'] },
|
966
|
+
"Item Type" => lambda {|it| it['itemType'] },
|
967
|
+
"Item Description" => lambda {|it| it['itemDescription'] },
|
968
|
+
"Product Code" => lambda {|it| it['productCode'] },
|
891
969
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
892
970
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
893
971
|
}
|
@@ -956,6 +1034,25 @@ EOT
|
|
956
1034
|
end
|
957
1035
|
end
|
958
1036
|
|
1037
|
+
def parse_invoice_ref_type(ref_type)
|
1038
|
+
val = ref_type.to_s.downcase
|
1039
|
+
if val == 'cloud' || val == 'zone'
|
1040
|
+
'ComputeZone'
|
1041
|
+
elsif val == 'instance'
|
1042
|
+
'Instance'
|
1043
|
+
elsif val == 'server' || val == 'host'
|
1044
|
+
'ComputeServer'
|
1045
|
+
elsif val == 'cluster'
|
1046
|
+
'ComputeServerGroup'
|
1047
|
+
elsif val == 'group' || val == 'site'
|
1048
|
+
'ComputeSite'
|
1049
|
+
elsif val == 'user'
|
1050
|
+
'User'
|
1051
|
+
else
|
1052
|
+
ref_type
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
959
1056
|
# convert "202003" to "March 2020"
|
960
1057
|
def format_invoice_period(it)
|
961
1058
|
interval = it['interval']
|
@@ -1002,6 +1099,11 @@ EOT
|
|
1002
1099
|
end
|
1003
1100
|
end
|
1004
1101
|
|
1102
|
+
def get_current_period()
|
1103
|
+
now = Time.now.utc
|
1104
|
+
now.year.to_s + now.month.to_s.rjust(2,'0')
|
1105
|
+
end
|
1106
|
+
|
1005
1107
|
def format_server_power_state(server, return_color=cyan)
|
1006
1108
|
out = ""
|
1007
1109
|
if server['powerState'] == 'on'
|