morpheus-cli 4.2.14 → 4.2.19
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/README.md +8 -6
- data/lib/morpheus/api/api_client.rb +32 -14
- data/lib/morpheus/api/auth_interface.rb +4 -2
- data/lib/morpheus/api/backup_jobs_interface.rb +9 -0
- data/lib/morpheus/api/backups_interface.rb +16 -0
- data/lib/morpheus/api/deploy_interface.rb +25 -56
- data/lib/morpheus/api/deployments_interface.rb +44 -55
- data/lib/morpheus/api/doc_interface.rb +57 -0
- data/lib/morpheus/api/instances_interface.rb +5 -0
- data/lib/morpheus/api/rest_interface.rb +40 -0
- data/lib/morpheus/api/user_sources_interface.rb +0 -15
- data/lib/morpheus/api/users_interface.rb +2 -3
- data/lib/morpheus/benchmarking.rb +2 -2
- data/lib/morpheus/cli.rb +4 -1
- data/lib/morpheus/cli/access_token_command.rb +27 -10
- data/lib/morpheus/cli/apps.rb +21 -15
- data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
- data/lib/morpheus/cli/backups_command.rb +271 -0
- data/lib/morpheus/cli/blueprints_command.rb +27 -61
- data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +183 -45
- data/lib/morpheus/cli/cli_registry.rb +3 -0
- data/lib/morpheus/cli/clouds.rb +7 -10
- data/lib/morpheus/cli/clusters.rb +0 -18
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +23 -20
- data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
- data/lib/morpheus/cli/credentials.rb +13 -9
- data/lib/morpheus/cli/deploy.rb +374 -0
- data/lib/morpheus/cli/deployments.rb +521 -197
- data/lib/morpheus/cli/deploys.rb +271 -126
- data/lib/morpheus/cli/doc.rb +182 -0
- data/lib/morpheus/cli/error_handler.rb +23 -8
- data/lib/morpheus/cli/errors.rb +3 -2
- data/lib/morpheus/cli/image_builder_command.rb +2 -2
- data/lib/morpheus/cli/instances.rb +136 -17
- data/lib/morpheus/cli/invoices_command.rb +339 -225
- data/lib/morpheus/cli/jobs_command.rb +2 -2
- data/lib/morpheus/cli/library_layouts_command.rb +1 -1
- 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/login.rb +9 -3
- data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
- data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
- data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
- data/lib/morpheus/cli/mixins/library_helper.rb +32 -0
- data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +149 -84
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
- data/lib/morpheus/cli/mixins/whoami_helper.rb +19 -6
- data/lib/morpheus/cli/network_routers_command.rb +1 -1
- data/lib/morpheus/cli/option_parser.rb +48 -5
- data/lib/morpheus/cli/option_types.rb +46 -10
- data/lib/morpheus/cli/price_sets_command.rb +1 -1
- data/lib/morpheus/cli/projects_command.rb +7 -7
- data/lib/morpheus/cli/remote.rb +3 -2
- data/lib/morpheus/cli/roles.rb +49 -92
- data/lib/morpheus/cli/security_groups.rb +7 -1
- data/lib/morpheus/cli/service_plans_command.rb +10 -10
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +7 -6
- data/lib/morpheus/cli/subnets_command.rb +1 -1
- data/lib/morpheus/cli/tasks.rb +24 -10
- data/lib/morpheus/cli/tenants_command.rb +133 -163
- data/lib/morpheus/cli/user_groups_command.rb +20 -65
- data/lib/morpheus/cli/user_settings_command.rb +115 -13
- data/lib/morpheus/cli/user_sources_command.rb +57 -24
- data/lib/morpheus/cli/users.rb +210 -186
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whitelabel_settings_command.rb +29 -5
- data/lib/morpheus/cli/whoami.rb +113 -6
- data/lib/morpheus/cli/workflows.rb +11 -8
- data/lib/morpheus/ext/hash.rb +21 -0
- data/lib/morpheus/formatters.rb +7 -19
- data/lib/morpheus/terminal.rb +1 -0
- metadata +12 -3
- data/lib/morpheus/cli/auth_command.rb +0 -105
@@ -31,18 +31,31 @@ class Morpheus::Cli::ErrorHandler
|
|
31
31
|
# raise err
|
32
32
|
# @stderr.puts "#{red}#{err.message}#{reset}"
|
33
33
|
puts_angry_error err.message
|
34
|
-
@stderr.puts
|
34
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
|
35
|
+
@stderr.puts "Try --help for more usage information"
|
35
36
|
do_print_stacktrace = false
|
36
37
|
# exit_code = 127
|
37
|
-
|
38
|
+
when Morpheus::Cli::CommandArgumentsError
|
39
|
+
puts_angry_error err.message
|
40
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
|
41
|
+
@stderr.puts "Try --help for more usage information"
|
42
|
+
do_print_stacktrace = false
|
43
|
+
if err.exit_code
|
44
|
+
exit_code = err.exit_code
|
45
|
+
end
|
46
|
+
|
38
47
|
when Morpheus::Cli::CommandError
|
39
|
-
#
|
40
|
-
#
|
48
|
+
# this should probably always print the whole thing as red, but just does the first line for now.
|
49
|
+
# until verify_args! replaces raise_command_error where the full parser help is in the error message..
|
41
50
|
message_lines = err.message.split(/\r?\n/)
|
42
51
|
first_line = message_lines.shift
|
43
52
|
puts_angry_error first_line
|
44
|
-
|
45
|
-
|
53
|
+
if !message_lines.empty?
|
54
|
+
@stderr.puts message_lines.join("\n") unless message_lines.empty?
|
55
|
+
else
|
56
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner && message_lines.empty?
|
57
|
+
@stderr.puts "Try --help for more usage information"
|
58
|
+
end
|
46
59
|
do_print_stacktrace = false
|
47
60
|
if err.exit_code
|
48
61
|
exit_code = err.exit_code
|
@@ -82,7 +95,7 @@ class Morpheus::Cli::ErrorHandler
|
|
82
95
|
@stderr.puts err.to_s
|
83
96
|
end
|
84
97
|
else
|
85
|
-
@stderr.puts "Use --debug for more information."
|
98
|
+
@stderr.puts "Use -V or --debug for more verbose debugging information."
|
86
99
|
end
|
87
100
|
end
|
88
101
|
|
@@ -127,6 +140,8 @@ class Morpheus::Cli::ErrorHandler
|
|
127
140
|
begin
|
128
141
|
print_rest_errors(JSON.parse(err.response.to_s), options)
|
129
142
|
rescue TypeError, JSON::ParserError => ex
|
143
|
+
# not json, just 404
|
144
|
+
@stderr.print red, "Error Communicating with the remote appliance. #{e}", reset, "\n"
|
130
145
|
end
|
131
146
|
else
|
132
147
|
@stderr.print red, "Error Communicating with the remote appliance. #{e}", reset, "\n"
|
@@ -145,7 +160,7 @@ class Morpheus::Cli::ErrorHandler
|
|
145
160
|
@stderr.print reset
|
146
161
|
end
|
147
162
|
else
|
148
|
-
@stderr.puts "Use --debug for more information."
|
163
|
+
@stderr.puts "Use -V or --debug for more verbose debugging information."
|
149
164
|
end
|
150
165
|
end
|
151
166
|
else
|
data/lib/morpheus/cli/errors.rb
CHANGED
@@ -3,7 +3,7 @@ module Morpheus::Cli
|
|
3
3
|
# A standard error to raise in your CliCommand classes.
|
4
4
|
class CommandError < StandardError
|
5
5
|
|
6
|
-
attr_reader :args, :exit_code
|
6
|
+
attr_reader :args, :optparse, :exit_code
|
7
7
|
|
8
8
|
def initialize(msg, args=[], optparse=nil, exit_code=nil)
|
9
9
|
@args = args
|
@@ -11,6 +11,7 @@ module Morpheus::Cli
|
|
11
11
|
@exit_code = exit_code # || 1
|
12
12
|
super(msg)
|
13
13
|
end
|
14
|
+
|
14
15
|
end
|
15
16
|
|
16
17
|
# An error indicating the command was not recoginzed
|
@@ -24,7 +25,7 @@ module Morpheus::Cli
|
|
24
25
|
|
25
26
|
# An error for wrong number of arguments
|
26
27
|
# could use ::OptionParser::MissingArgument, ::OptionParser::NeedlessArgument
|
27
|
-
# maybe return an error code
|
28
|
+
# maybe return an error code other than 1?
|
28
29
|
class CommandArgumentsError < CommandError
|
29
30
|
|
30
31
|
def initialize(msg, args=[], optparse=nil, exit_code=nil)
|
@@ -518,7 +518,7 @@ class Morpheus::Cli::ImageBuilderCommand
|
|
518
518
|
opts.on( '-K', '--keep-virtual-images', "Preserve associated virtual images" ) do
|
519
519
|
query_params['keepVirtualImages'] = 'on'
|
520
520
|
end
|
521
|
-
build_common_options(opts, options, [:
|
521
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
522
522
|
end
|
523
523
|
optparse.parse!(args)
|
524
524
|
|
@@ -565,7 +565,7 @@ class Morpheus::Cli::ImageBuilderCommand
|
|
565
565
|
query_params = {}
|
566
566
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
567
567
|
opts.banner = subcommand_usage("[image-build]")
|
568
|
-
build_common_options(opts, options, [:
|
568
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
569
569
|
end
|
570
570
|
optparse.parse!(args)
|
571
571
|
|
@@ -9,14 +9,22 @@ class Morpheus::Cli::Instances
|
|
9
9
|
include Morpheus::Cli::AccountsHelper # needed? replace with OptionSourceHelper
|
10
10
|
include Morpheus::Cli::OptionSourceHelper
|
11
11
|
include Morpheus::Cli::ProvisioningHelper
|
12
|
+
include Morpheus::Cli::DeploymentsHelper
|
12
13
|
include Morpheus::Cli::ProcessesHelper
|
13
14
|
include Morpheus::Cli::LogsHelper
|
14
15
|
|
15
16
|
set_command_name :instances
|
16
17
|
set_command_description "View and manage instances."
|
17
|
-
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :logs,
|
18
|
-
|
19
|
-
|
18
|
+
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :logs,
|
19
|
+
:history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details},
|
20
|
+
:stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :stop_service, :start_service, :restart_service,
|
21
|
+
:backup, :backups, :resize, :clone, :envs, :setenv, :delenv,
|
22
|
+
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot,
|
23
|
+
:console, :status_check, {:containers => :list_containers},
|
24
|
+
:scaling, {:'scaling-update' => :scaling_update},
|
25
|
+
:wiki, :update_wiki,
|
26
|
+
{:exec => :execution_request},
|
27
|
+
:deploys
|
20
28
|
#register_subcommands :firewall_disable, :firewall_enable
|
21
29
|
# register_subcommands {:'lb-update' => :load_balancer_update}
|
22
30
|
alias_subcommand :details, :get
|
@@ -43,6 +51,8 @@ class Morpheus::Cli::Instances
|
|
43
51
|
@options_interface = @api_client.options
|
44
52
|
@active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
|
45
53
|
@execution_request_interface = @api_client.execution_request
|
54
|
+
@deploy_interface = @api_client.deploy
|
55
|
+
@deployments_interface = @api_client.deployments
|
46
56
|
end
|
47
57
|
|
48
58
|
def handle(args)
|
@@ -546,18 +556,12 @@ class Morpheus::Cli::Instances
|
|
546
556
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
547
557
|
end
|
548
558
|
optparse.parse!(args)
|
549
|
-
|
550
|
-
puts optparse
|
551
|
-
exit 1
|
552
|
-
end
|
559
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
553
560
|
connect(options)
|
554
561
|
|
555
562
|
begin
|
556
563
|
instance = find_instance_by_name_or_id(args[0])
|
557
564
|
return 1 if instance.nil?
|
558
|
-
new_group = nil
|
559
|
-
|
560
|
-
|
561
565
|
if options[:payload]
|
562
566
|
payload = options[:payload]
|
563
567
|
end
|
@@ -579,10 +583,40 @@ class Morpheus::Cli::Instances
|
|
579
583
|
params['ownerId'] = owner_id
|
580
584
|
#payload['createdById'] = options[:owner].to_i # pre 4.2.1 api
|
581
585
|
end
|
582
|
-
if
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
+
if options[:group]
|
587
|
+
group = find_group_by_name_or_id_for_provisioning(options[:group])
|
588
|
+
if group.nil?
|
589
|
+
return 1, "group not found"
|
590
|
+
end
|
591
|
+
payload['instance']['site'] = {'id' => group['id']}
|
592
|
+
end
|
593
|
+
# metadata tags
|
594
|
+
# if options[:options]['metadata'].is_a?(Array) && !options[:metadata]
|
595
|
+
# options[:metadata] = options[:options]['metadata']
|
596
|
+
# end
|
597
|
+
if options[:metadata]
|
598
|
+
metadata = []
|
599
|
+
if options[:metadata] == "[]" || options[:metadata] == "null"
|
600
|
+
payload['instance']['metadata'] = []
|
601
|
+
elsif options[:metadata].is_a?(Array)
|
602
|
+
payload['instance']['metadata'] = options[:metadata]
|
603
|
+
else
|
604
|
+
# parse string into format name:value, name:value
|
605
|
+
# merge IDs from current metadata
|
606
|
+
# todo: should allow quoted semicolons..
|
607
|
+
metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
|
608
|
+
metadata_list = metadata_list.collect do |it|
|
609
|
+
metadata_pair = it.split(":")
|
610
|
+
row = {}
|
611
|
+
row['name'] = metadata_pair[0].to_s.strip
|
612
|
+
row['value'] = metadata_pair[1].to_s.strip
|
613
|
+
row
|
614
|
+
end
|
615
|
+
payload['instance']['metadata'] = metadata_list
|
616
|
+
end
|
617
|
+
end
|
618
|
+
if payload['instance'].empty? && params.empty? && options[:owner].nil?
|
619
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
586
620
|
end
|
587
621
|
if !params.empty?
|
588
622
|
payload['instance'].deep_merge!(params)
|
@@ -1218,7 +1252,6 @@ class Morpheus::Cli::Instances
|
|
1218
1252
|
"Environment" => 'instanceContext',
|
1219
1253
|
"Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
|
1220
1254
|
"Metadata" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
1221
|
-
"Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
|
1222
1255
|
"Owner" => lambda {|it|
|
1223
1256
|
if it['owner']
|
1224
1257
|
(it['owner']['username'] || it['owner']['id'])
|
@@ -1228,13 +1261,20 @@ class Morpheus::Cli::Instances
|
|
1228
1261
|
},
|
1229
1262
|
#"Tenant" => lambda {|it| it['tenant'] ? it['tenant']['name'] : '' },
|
1230
1263
|
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
1264
|
+
# "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
1265
|
+
"Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
|
1266
|
+
"Last Deployment" => lambda {|it| (it['lastDeploy'] ? "#{it['lastDeploy']['deployment']['name']} #{it['lastDeploy']['deploymentVersion']['userVersion']} at #{format_local_dt it['lastDeploy']['deployDate']}" : nil) rescue "" },
|
1267
|
+
"Expire Date" => lambda {|it| it['expireDate'] ? format_local_dt(it['expireDate']) : '' },
|
1268
|
+
"Shutdown Date" => lambda {|it| it['shutdownDate'] ? format_local_dt(it['shutdownDate']) : '' },
|
1231
1269
|
"Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
|
1232
1270
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
1233
1271
|
"Status" => lambda {|it| format_instance_status(it) }
|
1234
1272
|
}
|
1235
|
-
|
1273
|
+
description_cols.delete("Power Schedule") if instance['powerSchedule'].nil?
|
1274
|
+
description_cols.delete("Expire Date") if instance['expireDate'].nil?
|
1275
|
+
description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
|
1236
1276
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
1237
|
-
|
1277
|
+
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
1238
1278
|
print_description_list(description_cols, instance)
|
1239
1279
|
|
1240
1280
|
if instance['statusMessage']
|
@@ -3606,6 +3646,60 @@ class Morpheus::Cli::Instances
|
|
3606
3646
|
end
|
3607
3647
|
end
|
3608
3648
|
|
3649
|
+
def deploys(args)
|
3650
|
+
params = {}
|
3651
|
+
options = {}
|
3652
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3653
|
+
opts.banner = subcommand_usage("[instance] [search]")
|
3654
|
+
build_standard_list_options(opts, options)
|
3655
|
+
opts.footer = <<-EOT
|
3656
|
+
List deployments for an instance.
|
3657
|
+
[instance] is required. This is the name or id of an instance
|
3658
|
+
[search] is optional. Filters on deployment version identifier
|
3659
|
+
EOT
|
3660
|
+
end
|
3661
|
+
optparse.parse!(args)
|
3662
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
3663
|
+
connect(options)
|
3664
|
+
if args.count > 1
|
3665
|
+
options[:phrase] = args.join(" ")
|
3666
|
+
end
|
3667
|
+
params.merge!(parse_list_options(options))
|
3668
|
+
instance = find_instance_by_name_or_id(args[0])
|
3669
|
+
return 1 if instance.nil?
|
3670
|
+
# @deploy_interface.setopts(options)
|
3671
|
+
# if options[:dry_run]
|
3672
|
+
# print_dry_run @deploy_interface.dry.list(instance['id'], params)
|
3673
|
+
# return
|
3674
|
+
# end
|
3675
|
+
# json_response = @deploy_interface.list(instance['id'], params)
|
3676
|
+
|
3677
|
+
@instances_interface.setopts(options)
|
3678
|
+
if options[:dry_run]
|
3679
|
+
print_dry_run @instances_interface.dry.deploys(instance['id'], params)
|
3680
|
+
return
|
3681
|
+
end
|
3682
|
+
json_response = @instances_interface.deploys(instance['id'], params)
|
3683
|
+
|
3684
|
+
app_deploys = json_response['appDeploys']
|
3685
|
+
render_response(json_response, options, 'appDeploys') do
|
3686
|
+
print_h1 "Instance Deploys", ["#{instance['name']}"] + parse_list_subtitles(options), options
|
3687
|
+
if app_deploys.empty?
|
3688
|
+
print cyan,"No deployments found.",reset,"\n"
|
3689
|
+
else
|
3690
|
+
print as_pretty_table(app_deploys, app_deploy_column_definitions.upcase_keys!, options)
|
3691
|
+
if json_response['meta']
|
3692
|
+
print_results_pagination(json_response)
|
3693
|
+
else
|
3694
|
+
print_results_pagination({size:app_deploys.size,total:app_deploys.size.to_i})
|
3695
|
+
end
|
3696
|
+
|
3697
|
+
end
|
3698
|
+
print reset,"\n"
|
3699
|
+
end
|
3700
|
+
return 0
|
3701
|
+
end
|
3702
|
+
|
3609
3703
|
private
|
3610
3704
|
|
3611
3705
|
def find_zone_by_name_or_id(group_id, val)
|
@@ -3902,4 +3996,29 @@ private
|
|
3902
3996
|
]
|
3903
3997
|
end
|
3904
3998
|
|
3999
|
+
def app_deploy_column_definitions
|
4000
|
+
{
|
4001
|
+
"ID" => 'id',
|
4002
|
+
"Deployment" => lambda {|it| it['deployment']['name'] rescue '' },
|
4003
|
+
"Version" => lambda {|it| (it['deploymentVersion']['userVersion'] || it['deploymentVersion']['version']) rescue '' },
|
4004
|
+
"Deploy Date" => lambda {|it| format_local_dt(it['deployDate']) },
|
4005
|
+
"Status" => lambda {|it| format_app_deploy_status(it['status']) },
|
4006
|
+
}
|
4007
|
+
end
|
4008
|
+
|
4009
|
+
def format_app_deploy_status(status, return_color=cyan)
|
4010
|
+
out = ""
|
4011
|
+
s = status.to_s.downcase
|
4012
|
+
if s == 'deployed'
|
4013
|
+
out << "#{green}#{s.upcase}#{return_color}"
|
4014
|
+
elsif s == 'open' || s == 'archived' || s == 'committed'
|
4015
|
+
out << "#{cyan}#{s.upcase}#{return_color}"
|
4016
|
+
elsif s == 'failed'
|
4017
|
+
out << "#{red}#{s.upcase}#{return_color}"
|
4018
|
+
else
|
4019
|
+
out << "#{yellow}#{s.upcase}#{return_color}"
|
4020
|
+
end
|
4021
|
+
out
|
4022
|
+
end
|
4023
|
+
|
3905
4024
|
end
|
@@ -25,39 +25,29 @@ class Morpheus::Cli::InvoicesCommand
|
|
25
25
|
options = {}
|
26
26
|
params = {}
|
27
27
|
ref_ids = []
|
28
|
+
query_tags = {}
|
28
29
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
29
30
|
opts.banner = subcommand_usage()
|
30
|
-
opts.on('-a', '--all', "Display all
|
31
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
32
|
+
options[:show_all] = true
|
31
33
|
options[:show_estimates] = true
|
32
34
|
# options[:show_costs] = true
|
33
35
|
options[:show_prices] = true
|
34
|
-
options[:show_raw_data] = true
|
36
|
+
# options[:show_raw_data] = true
|
35
37
|
end
|
36
|
-
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute,
|
38
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
37
39
|
options[:show_estimates] = true
|
38
40
|
end
|
39
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute,
|
41
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
|
40
42
|
# options[:show_costs] = true
|
41
43
|
# end
|
42
|
-
opts.on('--prices', '--prices', "Display prices: Total, Compute,
|
44
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
43
45
|
options[:show_prices] = true
|
44
46
|
end
|
45
|
-
opts.on('--type TYPE',
|
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
|
+
opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
|
48
|
+
params['refType'] ||= []
|
49
|
+
values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
|
50
|
+
values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
|
61
51
|
end
|
62
52
|
opts.on('--id ID', String, "Filter by Ref ID") do |val|
|
63
53
|
ref_ids << val
|
@@ -116,6 +106,11 @@ class Morpheus::Cli::InvoicesCommand
|
|
116
106
|
opts.on('--tenant ID', String, "View invoices for a tenant. Default is your own account.") do |val|
|
117
107
|
params['accountId'] = val
|
118
108
|
end
|
109
|
+
opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
|
110
|
+
k,v = val.split("=")
|
111
|
+
query_tags[k] ||= []
|
112
|
+
query_tags[k] << v
|
113
|
+
end
|
119
114
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
120
115
|
options[:show_raw_data] = true
|
121
116
|
end
|
@@ -123,6 +118,14 @@ class Morpheus::Cli::InvoicesCommand
|
|
123
118
|
params['includeTotals'] = true
|
124
119
|
options[:show_invoice_totals] = true
|
125
120
|
end
|
121
|
+
opts.on('--totals-only', "View totals only") do |val|
|
122
|
+
params['includeTotals'] = true
|
123
|
+
options[:show_invoice_totals] = true
|
124
|
+
options[:totals_only] = true
|
125
|
+
end
|
126
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
127
|
+
options[:sigdig] = val.to_i
|
128
|
+
end
|
126
129
|
build_standard_list_options(opts, options)
|
127
130
|
opts.footer = "List invoices."
|
128
131
|
end
|
@@ -166,6 +169,11 @@ class Morpheus::Cli::InvoicesCommand
|
|
166
169
|
end
|
167
170
|
params['rawData'] = true if options[:show_raw_data]
|
168
171
|
params['refId'] = ref_ids unless ref_ids.empty?
|
172
|
+
if query_tags && !query_tags.empty?
|
173
|
+
query_tags.each do |k,v|
|
174
|
+
params['tags.' + k] = v
|
175
|
+
end
|
176
|
+
end
|
169
177
|
@invoices_interface.setopts(options)
|
170
178
|
if options[:dry_run]
|
171
179
|
print_dry_run @invoices_interface.dry.list(params)
|
@@ -186,6 +194,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
186
194
|
print_h1 title, subtitles
|
187
195
|
if invoices.empty?
|
188
196
|
print cyan,"No invoices found.",reset,"\n"
|
197
|
+
print reset,"\n"
|
189
198
|
else
|
190
199
|
# current_date = Time.now
|
191
200
|
# current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
|
@@ -194,105 +203,137 @@ class Morpheus::Cli::InvoicesCommand
|
|
194
203
|
{"INVOICE ID" => lambda {|it| it['id'] } },
|
195
204
|
{"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
196
205
|
{"REF ID" => lambda {|it| it['refId'] } },
|
197
|
-
{"REF NAME" => lambda {|it|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
206
|
+
{"REF NAME" => lambda {|it|
|
207
|
+
if options[:show_all]
|
208
|
+
it['refName']
|
209
|
+
else
|
210
|
+
truncate_string_right(it['refName'], 100)
|
211
|
+
end
|
212
|
+
} },
|
203
213
|
#{"INTERVAL" => lambda {|it| it['interval'] } },
|
204
214
|
{"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
|
205
|
-
{"
|
206
|
-
|
207
|
-
#{"
|
215
|
+
#{"TENANT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
|
216
|
+
|
217
|
+
#{"COST TYPE" => lambda {|it| it['costType'].to_s.capitalize } },
|
208
218
|
{"PERIOD" => lambda {|it| format_invoice_period(it) } },
|
209
219
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
210
|
-
{"END" => lambda {|it|
|
211
|
-
|
220
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
221
|
+
] + (options[:show_all] ? [
|
222
|
+
{"REF START" => lambda {|it| format_dt(it['refStart']) } },
|
223
|
+
{"REF END" => lambda {|it| format_dt(it['refEnd']) } },
|
224
|
+
] : []) + [
|
225
|
+
{"COMPUTE" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
226
|
+
# {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
|
227
|
+
{"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
228
|
+
{"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
229
|
+
{"EXTRA" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
230
|
+
{"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
212
231
|
{"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
|
232
|
+
format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
219
233
|
} }
|
220
234
|
]
|
221
235
|
|
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
236
|
if options[:show_prices]
|
230
237
|
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
|
-
{"
|
236
|
-
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice']) } },
|
238
|
+
{"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
239
|
+
# {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
240
|
+
{"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
241
|
+
{"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
242
|
+
{"EXTRA PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
243
|
+
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
237
244
|
{"TOTAL PRICE" => lambda {|it|
|
238
|
-
|
239
|
-
format_money(it['totalPrice']) + " (Projected)"
|
240
|
-
else
|
241
|
-
format_money(it['totalPrice'])
|
242
|
-
end
|
245
|
+
format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
243
246
|
} }
|
244
247
|
]
|
245
248
|
end
|
246
249
|
if options[:show_estimates]
|
247
250
|
columns += [
|
248
|
-
{"
|
251
|
+
{"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
252
|
+
# {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
253
|
+
{"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
254
|
+
{"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
255
|
+
{"EXTRA EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
256
|
+
{"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
249
257
|
{"TOTAL EST." => lambda {|it|
|
250
|
-
|
251
|
-
format_money(it['estimatedTotalCost']) + " (Projected)"
|
252
|
-
else
|
253
|
-
format_money(it['estimatedTotalCost'])
|
254
|
-
end
|
258
|
+
format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
|
255
259
|
} },
|
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
260
|
]
|
262
261
|
end
|
262
|
+
columns += [
|
263
|
+
{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
|
264
|
+
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
265
|
+
{"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
|
266
|
+
{"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
|
267
|
+
]
|
268
|
+
if show_projects
|
269
|
+
columns += [
|
270
|
+
{"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
|
271
|
+
{"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
|
272
|
+
{"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } },
|
273
|
+
]
|
274
|
+
end
|
275
|
+
columns += [
|
276
|
+
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
277
|
+
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
278
|
+
]
|
263
279
|
if options[:show_raw_data]
|
264
280
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
265
281
|
end
|
266
|
-
|
267
|
-
|
282
|
+
unless options[:totals_only]
|
283
|
+
print as_pretty_table(invoices, columns, options)
|
284
|
+
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
285
|
+
end
|
268
286
|
|
269
287
|
if options[:show_invoice_totals]
|
270
288
|
invoice_totals = json_response['invoiceTotals']
|
289
|
+
print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
|
290
|
+
|
271
291
|
if invoice_totals
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
292
|
+
cost_rows = [
|
293
|
+
{label: 'Cost'.upcase, 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']},
|
294
|
+
]
|
295
|
+
if options[:show_prices]
|
296
|
+
cost_rows += [
|
297
|
+
{label: 'Price'.upcase, 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']},
|
298
|
+
]
|
299
|
+
end
|
300
|
+
if options[:show_estimates]
|
301
|
+
cost_rows += [
|
302
|
+
{label: 'Metered 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']},
|
303
|
+
{label: 'Metered 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']},
|
304
|
+
]
|
305
|
+
end
|
306
|
+
cost_columns = {
|
307
|
+
"" => lambda {|it| it[:label] },
|
308
|
+
"Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
|
309
|
+
"Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
|
310
|
+
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
311
|
+
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
312
|
+
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
313
|
+
"Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
314
|
+
"MTD".upcase => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
315
|
+
"Total".upcase => lambda {|it|
|
316
|
+
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
317
|
+
},
|
287
318
|
}
|
288
|
-
|
319
|
+
# remove columns that rarely have data...
|
320
|
+
if cost_rows.sum { |it| it[:memory].to_f } == 0
|
321
|
+
cost_columns.delete("Memory".upcase)
|
322
|
+
end
|
323
|
+
if cost_rows.sum { |it| it[:license].to_f } == 0
|
324
|
+
cost_columns.delete("License".upcase)
|
325
|
+
end
|
326
|
+
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
327
|
+
cost_columns.delete("Extra".upcase)
|
328
|
+
end
|
329
|
+
print as_pretty_table(cost_rows, cost_columns, options)
|
289
330
|
else
|
290
331
|
print "\n"
|
291
332
|
print yellow, "No invoice totals data", reset, "\n"
|
292
333
|
end
|
293
334
|
end
|
335
|
+
print reset,"\n"
|
294
336
|
end
|
295
|
-
print reset,"\n"
|
296
337
|
return 0, nil
|
297
338
|
end
|
298
339
|
end
|
@@ -301,14 +342,17 @@ class Morpheus::Cli::InvoicesCommand
|
|
301
342
|
options = {}
|
302
343
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
303
344
|
opts.banner = subcommand_usage("[id]")
|
304
|
-
opts.on('-a', '--all', "Display all
|
345
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
305
346
|
options[:show_estimates] = true
|
306
347
|
# options[:show_costs] = true
|
307
348
|
options[:show_prices] = true
|
308
|
-
options[:show_raw_data] = true
|
349
|
+
# options[:show_raw_data] = true
|
309
350
|
options[:max_line_items] = 10000
|
310
351
|
end
|
311
|
-
opts.on('--
|
352
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
353
|
+
options[:show_prices] = true
|
354
|
+
end
|
355
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
312
356
|
options[:show_estimates] = true
|
313
357
|
end
|
314
358
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
@@ -318,12 +362,12 @@ class Morpheus::Cli::InvoicesCommand
|
|
318
362
|
options[:show_raw_data] = true
|
319
363
|
options[:pretty_json] = true
|
320
364
|
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
365
|
opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
|
325
366
|
options[:hide_line_items] = true
|
326
367
|
end
|
368
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
369
|
+
options[:sigdig] = val.to_i
|
370
|
+
end
|
327
371
|
build_standard_get_options(opts, options)
|
328
372
|
opts.footer = "Get details about a specific invoice."
|
329
373
|
opts.footer = <<-EOT
|
@@ -365,20 +409,24 @@ EOT
|
|
365
409
|
"Type" => lambda {|it| format_invoice_ref_type(it) },
|
366
410
|
"Ref ID" => lambda {|it| it['refId'] },
|
367
411
|
"Ref Name" => lambda {|it| it['refName'] },
|
412
|
+
"Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
|
368
413
|
"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
414
|
"Power State" => lambda {|it| format_server_power_state(it) },
|
373
|
-
"
|
415
|
+
"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
374
416
|
"Active" => lambda {|it| format_boolean(it['active']) },
|
375
|
-
"Period" => lambda {|it| format_invoice_period(it) },
|
376
417
|
"Estimate" => lambda {|it| format_boolean(it['estimate']) },
|
418
|
+
#"Cost Type" => lambda {|it| it['costType'].to_s.capitalize },
|
419
|
+
"Period" => lambda {|it| format_invoice_period(it) },
|
377
420
|
#"Interval" => lambda {|it| it['interval'] },
|
378
421
|
"Start" => lambda {|it| format_date(it['startDate']) },
|
379
|
-
"End" => lambda {|it|
|
380
|
-
"Ref Start" => lambda {|it|
|
381
|
-
"Ref End" => lambda {|it|
|
422
|
+
"End" => lambda {|it| format_date(it['endDate']) },
|
423
|
+
"Ref Start" => lambda {|it| format_dt(it['refStart']) },
|
424
|
+
"Ref End" => lambda {|it| format_dt(it['refEnd']) },
|
425
|
+
"Items" => lambda {|it| it['lineItems'].size rescue '' },
|
426
|
+
"Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
427
|
+
"Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
|
428
|
+
"Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
|
429
|
+
"Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
|
382
430
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
383
431
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
384
432
|
}
|
@@ -391,6 +439,9 @@ EOT
|
|
391
439
|
description_cols.delete("Project Name")
|
392
440
|
description_cols.delete("Project Tags")
|
393
441
|
end
|
442
|
+
if invoice['metadata'].nil? || invoice['metadata'].empty?
|
443
|
+
description_cols.delete("Tags")
|
444
|
+
end
|
394
445
|
if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
|
395
446
|
description_cols.delete("Power State")
|
396
447
|
end
|
@@ -398,27 +449,27 @@ EOT
|
|
398
449
|
=begin
|
399
450
|
print_h2 "Costs"
|
400
451
|
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
|
-
"
|
407
|
-
"Running" => lambda {|it| format_money(it['runningCost']) },
|
408
|
-
"Total Cost" => lambda {|it| format_money(it['totalCost']) },
|
452
|
+
"Compute" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) },
|
453
|
+
"Memory" => lambda {|it| format_money(it['memoryCost'], 'usd', {sigdig:options[:sigdig]}) },
|
454
|
+
"Storage" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) },
|
455
|
+
"Network" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) },
|
456
|
+
"License" => lambda {|it| format_money(it['licenseCost'], 'usd', {sigdig:options[:sigdig]}) },
|
457
|
+
"Extra" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
|
458
|
+
"Running" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) },
|
459
|
+
"Total Cost" => lambda {|it| format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) },
|
409
460
|
}
|
410
461
|
print as_pretty_table([invoice], cost_columns, options)
|
411
462
|
|
412
463
|
print_h2 "Prices"
|
413
464
|
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
|
-
"
|
420
|
-
"Running" => lambda {|it| format_money(it['runningPrice']) },
|
421
|
-
"Total Price" => lambda {|it| format_money(it['totalPrice']) },
|
465
|
+
"Compute" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
466
|
+
"Memory" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
467
|
+
"Storage" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
468
|
+
"Network" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
469
|
+
"License" => lambda {|it| format_money(it['licensePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
470
|
+
"Extra" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
471
|
+
"Running" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
472
|
+
"Total Price" => lambda {|it| format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
422
473
|
}
|
423
474
|
print as_pretty_table([invoice], price_columns, options)
|
424
475
|
=end
|
@@ -426,33 +477,77 @@ EOT
|
|
426
477
|
# current_date = Time.now
|
427
478
|
# current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
|
428
479
|
|
429
|
-
|
430
|
-
|
480
|
+
|
481
|
+
|
482
|
+
# Line Items
|
483
|
+
line_items = invoice['lineItems']
|
484
|
+
if line_items && line_items.size > 0 && options[:hide_line_items] != true
|
485
|
+
line_items_columns = [
|
486
|
+
{"ID" => lambda {|it| it['id'] } },
|
487
|
+
#{"REF TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
488
|
+
#{"REF ID" => lambda {|it| it['refId'] } },
|
489
|
+
#{"REF NAME" => lambda {|it| it['refName'] } },
|
490
|
+
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
491
|
+
{"START" => lambda {|it| format_dt(it['startDate']) } },
|
492
|
+
{"END" => lambda {|it| format_dt(it['endDate']) } },
|
493
|
+
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
494
|
+
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
495
|
+
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
496
|
+
{"RATE" => lambda {|it| it['itemRate'] } },
|
497
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
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
|
-
{label: '
|
438
|
-
{label: '
|
536
|
+
{label: 'Metered 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']},
|
537
|
+
{label: 'Metered 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
|
-
"
|
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
|
+
"Extra".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...
|
@@ -463,7 +558,7 @@ EOT
|
|
463
558
|
cost_columns.delete("License".upcase)
|
464
559
|
end
|
465
560
|
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
466
|
-
cost_columns.delete("
|
561
|
+
cost_columns.delete("Extra".upcase)
|
467
562
|
end
|
468
563
|
print as_pretty_table(cost_rows, cost_columns, options)
|
469
564
|
|
@@ -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
|
@@ -596,43 +656,36 @@ EOT
|
|
596
656
|
options = {}
|
597
657
|
params = {}
|
598
658
|
ref_ids = []
|
659
|
+
query_tags = {}
|
599
660
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
600
661
|
opts.banner = subcommand_usage()
|
601
|
-
opts.on('-a', '--all', "Display all
|
662
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
602
663
|
options[:show_actual_costs] = true
|
603
664
|
options[:show_costs] = true
|
604
665
|
options[:show_prices] = true
|
605
|
-
options[:show_raw_data] = true
|
666
|
+
# options[:show_raw_data] = true
|
606
667
|
end
|
607
|
-
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute,
|
668
|
+
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
|
608
669
|
# options[:show_actual_costs] = true
|
609
670
|
# end
|
610
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute,
|
671
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
|
611
672
|
# options[:show_costs] = true
|
612
673
|
# end
|
613
|
-
|
614
|
-
|
615
|
-
|
674
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
675
|
+
options[:show_prices] = true
|
676
|
+
end
|
616
677
|
opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
|
617
678
|
params['invoiceId'] ||= []
|
618
679
|
params['invoiceId'] << val
|
619
680
|
end
|
620
|
-
opts.on('--
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
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
|
681
|
+
opts.on('--external-id ID', String, "Filter by External ID") do |val|
|
682
|
+
params['externalId'] ||= []
|
683
|
+
params['externalId'] << val
|
684
|
+
end
|
685
|
+
opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
|
686
|
+
params['refType'] ||= []
|
687
|
+
values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
|
688
|
+
values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
|
636
689
|
end
|
637
690
|
opts.on('--id ID', String, "Filter by Ref ID") do |val|
|
638
691
|
ref_ids << val
|
@@ -691,6 +744,11 @@ EOT
|
|
691
744
|
opts.on('--tenant ID', String, "View invoice line items for a tenant. Default is your own account.") do |val|
|
692
745
|
params['accountId'] = val
|
693
746
|
end
|
747
|
+
# opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
|
748
|
+
# k,v = val.split("=")
|
749
|
+
# query_tags[k] ||= []
|
750
|
+
# query_tags[k] << v
|
751
|
+
# end
|
694
752
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
695
753
|
options[:show_raw_data] = true
|
696
754
|
end
|
@@ -698,6 +756,14 @@ EOT
|
|
698
756
|
params['includeTotals'] = true
|
699
757
|
options[:show_invoice_totals] = true
|
700
758
|
end
|
759
|
+
opts.on('--totals-only', "View totals only") do |val|
|
760
|
+
params['includeTotals'] = true
|
761
|
+
options[:show_invoice_totals] = true
|
762
|
+
options[:totals_only] = true
|
763
|
+
end
|
764
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
765
|
+
options[:sigdig] = val.to_i
|
766
|
+
end
|
701
767
|
build_standard_list_options(opts, options)
|
702
768
|
opts.footer = "List invoice line items."
|
703
769
|
end
|
@@ -742,6 +808,11 @@ EOT
|
|
742
808
|
end
|
743
809
|
params['rawData'] = true if options[:show_raw_data]
|
744
810
|
params['refId'] = ref_ids unless ref_ids.empty?
|
811
|
+
if query_tags && !query_tags.empty?
|
812
|
+
query_tags.each do |k,v|
|
813
|
+
params['tags.' + k] = v
|
814
|
+
end
|
815
|
+
end
|
745
816
|
@invoice_line_items_interface.setopts(options)
|
746
817
|
if options[:dry_run]
|
747
818
|
print_dry_run @invoice_line_items_interface.dry.list(params)
|
@@ -773,15 +844,22 @@ EOT
|
|
773
844
|
{"REF NAME" => lambda {|it| it['refName'] } },
|
774
845
|
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
775
846
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
776
|
-
{"END" => lambda {|it|
|
847
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
777
848
|
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
778
849
|
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
779
850
|
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
780
851
|
{"RATE" => lambda {|it| it['itemRate'] } },
|
781
|
-
{"
|
782
|
-
{"
|
783
|
-
|
784
|
-
|
852
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
853
|
+
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
854
|
+
] + (options[:show_prices] ? [
|
855
|
+
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
856
|
+
{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
857
|
+
] : []) + [
|
858
|
+
{"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
|
859
|
+
{"ITEM NAME" => lambda {|it| it['itemName'] } },
|
860
|
+
{"ITEM TYPE" => lambda {|it| it['itemType'] } },
|
861
|
+
{"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
|
862
|
+
{"PRODUCT CODE" => lambda {|it| it['productCode'] } },
|
785
863
|
"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
|
786
864
|
"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
787
865
|
]
|
@@ -789,35 +867,39 @@ EOT
|
|
789
867
|
if options[:show_raw_data]
|
790
868
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
791
869
|
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
870
|
# if options[:show_invoice_totals]
|
805
871
|
# line_item_totals = json_response['lineItemTotals']
|
806
872
|
# 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"
|
873
|
+
# totals_row = line_item_totals.clone
|
874
|
+
# totals_row['id'] = 'TOTAL:'
|
875
|
+
# #totals_row['usageCategory'] = 'TOTAL:'
|
876
|
+
# line_items = line_items + [totals_row]
|
819
877
|
# end
|
820
878
|
# end
|
879
|
+
unless options[:totals_only]
|
880
|
+
print as_pretty_table(line_items, columns, options)
|
881
|
+
print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
|
882
|
+
end
|
883
|
+
|
884
|
+
if options[:show_invoice_totals]
|
885
|
+
line_item_totals = json_response['lineItemTotals']
|
886
|
+
if line_item_totals
|
887
|
+
print_h2 "Line Item Totals" unless options[:totals_only]
|
888
|
+
invoice_totals_columns = [
|
889
|
+
{"Items" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
|
890
|
+
#{"Usage" => lambda {|it| it['itemUsage'] } },
|
891
|
+
{"Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
892
|
+
] + (options[:show_prices] ? [
|
893
|
+
{"Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
894
|
+
#{"Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
895
|
+
|
896
|
+
] : [])
|
897
|
+
print_description_list(invoice_totals_columns, line_item_totals)
|
898
|
+
else
|
899
|
+
print "\n"
|
900
|
+
print yellow, "No line item totals data", reset, "\n"
|
901
|
+
end
|
902
|
+
end
|
821
903
|
|
822
904
|
end
|
823
905
|
print reset,"\n"
|
@@ -840,6 +922,9 @@ EOT
|
|
840
922
|
options[:show_raw_data] = true
|
841
923
|
options[:pretty_json] = true
|
842
924
|
end
|
925
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
926
|
+
options[:sigdig] = val.to_i
|
927
|
+
end
|
843
928
|
build_standard_get_options(opts, options)
|
844
929
|
opts.footer = "Get details about a specific invoice line item."
|
845
930
|
opts.footer = <<-EOT
|
@@ -883,11 +968,16 @@ EOT
|
|
883
968
|
"Usage Category" => lambda {|it| it['usageCategory'] },
|
884
969
|
"Item Usage" => lambda {|it| it['itemUsage'] },
|
885
970
|
"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'] },
|
971
|
+
"Item Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) },
|
972
|
+
"Item Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
973
|
+
#"Item Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) },
|
890
974
|
#"Tax Type" => lambda {|it| it['taxType'] },
|
975
|
+
"Item Term" => lambda {|it| it['itemTerm'] },
|
976
|
+
"Item ID" => lambda {|it| it['itemId'] },
|
977
|
+
"Item Name" => lambda {|it| it['itemName'] },
|
978
|
+
"Item Type" => lambda {|it| it['itemType'] },
|
979
|
+
"Item Description" => lambda {|it| it['itemDescription'] },
|
980
|
+
"Product Code" => lambda {|it| it['productCode'] },
|
891
981
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
892
982
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
893
983
|
}
|
@@ -956,6 +1046,25 @@ EOT
|
|
956
1046
|
end
|
957
1047
|
end
|
958
1048
|
|
1049
|
+
def parse_invoice_ref_type(ref_type)
|
1050
|
+
val = ref_type.to_s.downcase
|
1051
|
+
if val == 'cloud' || val == 'zone'
|
1052
|
+
'ComputeZone'
|
1053
|
+
elsif val == 'instance'
|
1054
|
+
'Instance'
|
1055
|
+
elsif val == 'server' || val == 'host'
|
1056
|
+
'ComputeServer'
|
1057
|
+
elsif val == 'cluster'
|
1058
|
+
'ComputeServerGroup'
|
1059
|
+
elsif val == 'group' || val == 'site'
|
1060
|
+
'ComputeSite'
|
1061
|
+
elsif val == 'user'
|
1062
|
+
'User'
|
1063
|
+
else
|
1064
|
+
ref_type
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
|
959
1068
|
# convert "202003" to "March 2020"
|
960
1069
|
def format_invoice_period(it)
|
961
1070
|
interval = it['interval']
|
@@ -1002,6 +1111,11 @@ EOT
|
|
1002
1111
|
end
|
1003
1112
|
end
|
1004
1113
|
|
1114
|
+
def get_current_period()
|
1115
|
+
now = Time.now.utc
|
1116
|
+
now.year.to_s + now.month.to_s.rjust(2,'0')
|
1117
|
+
end
|
1118
|
+
|
1005
1119
|
def format_server_power_state(server, return_color=cyan)
|
1006
1120
|
out = ""
|
1007
1121
|
if server['powerState'] == 'on'
|