morpheus-cli 4.2.12 → 4.2.17
Sign up to get free protection for your applications and to get access to all the features.
- 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 +43 -54
- 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 +3 -1
- data/lib/morpheus/cli/access_token_command.rb +27 -10
- data/lib/morpheus/cli/apps.rb +23 -16
- data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
- data/lib/morpheus/cli/backups_command.rb +271 -0
- data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +176 -45
- data/lib/morpheus/cli/cli_registry.rb +10 -1
- data/lib/morpheus/cli/clusters.rb +2 -19
- 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/containers_command.rb +2 -1
- data/lib/morpheus/cli/credentials.rb +14 -10
- 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/health_command.rb +4 -3
- data/lib/morpheus/cli/hosts.rb +2 -1
- data/lib/morpheus/cli/image_builder_command.rb +2 -2
- data/lib/morpheus/cli/instances.rb +138 -18
- data/lib/morpheus/cli/invoices_command.rb +338 -223
- 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/logs_command.rb +3 -2
- 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/logs_helper.rb +18 -9
- 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/remote.rb +8 -10
- 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/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 +1 -1
- 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)
|
@@ -457,7 +457,7 @@ class Morpheus::Cli::HealthCommand
|
|
457
457
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
458
458
|
opts.banner = subcommand_usage()
|
459
459
|
opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
|
460
|
-
params['level'] = params['level'] ? [params['level'], val].flatten : val
|
460
|
+
params['level'] = params['level'] ? [params['level'], val].flatten : [val]
|
461
461
|
end
|
462
462
|
opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start timestamp. Default is 30 days ago.") do |val|
|
463
463
|
start_date = parse_time(val) #.utc.iso8601
|
@@ -465,7 +465,7 @@ class Morpheus::Cli::HealthCommand
|
|
465
465
|
opts.on('--end TIMESTAMP','--end TIMESTAMP', "End timestamp. Default is now.") do |val|
|
466
466
|
end_date = parse_time(val) #.utc.iso8601
|
467
467
|
end
|
468
|
-
opts.on('
|
468
|
+
opts.on('-t', '--table', "Format output as a table.") do
|
469
469
|
options[:table] = true
|
470
470
|
end
|
471
471
|
opts.on('-a', '--all', "Display all details: entire message." ) do
|
@@ -484,6 +484,7 @@ class Morpheus::Cli::HealthCommand
|
|
484
484
|
# params['endDate'] = end_date.utc.iso8601 if end_date
|
485
485
|
params['startMs'] = (start_date.to_i * 1000) if start_date
|
486
486
|
params['endMs'] = (end_date.to_i * 1000) if end_date
|
487
|
+
params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
|
487
488
|
params.merge!(parse_list_options(options))
|
488
489
|
@health_interface.setopts(options)
|
489
490
|
if options[:dry_run]
|
@@ -497,7 +498,7 @@ class Morpheus::Cli::HealthCommand
|
|
497
498
|
title = "Morpheus Health Logs"
|
498
499
|
subtitles = []
|
499
500
|
if params['level']
|
500
|
-
subtitles << "Level: #{params['level']}"
|
501
|
+
subtitles << "Level: #{[params['level']].flatten.join(',')}"
|
501
502
|
end
|
502
503
|
if start_date
|
503
504
|
subtitles << "Start: #{start_date}"
|
data/lib/morpheus/cli/hosts.rb
CHANGED
@@ -582,7 +582,7 @@ class Morpheus::Cli::Hosts
|
|
582
582
|
options[:end] = parse_time(val) #.utc.iso8601
|
583
583
|
end
|
584
584
|
opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
|
585
|
-
params['level'] = params['level'] ? [params['level'], val].flatten : val
|
585
|
+
params['level'] = params['level'] ? [params['level'], val].flatten : [val]
|
586
586
|
end
|
587
587
|
opts.on('--table', '--table', "Format ouput as a table.") do
|
588
588
|
options[:table] = true
|
@@ -600,6 +600,7 @@ class Morpheus::Cli::Hosts
|
|
600
600
|
connect(options)
|
601
601
|
begin
|
602
602
|
server = find_host_by_name_or_id(args[0])
|
603
|
+
params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
|
603
604
|
params.merge!(parse_list_options(options))
|
604
605
|
params['query'] = params.delete('phrase') if params['phrase']
|
605
606
|
params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
|
@@ -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)
|
@@ -1009,7 +1043,7 @@ class Morpheus::Cli::Instances
|
|
1009
1043
|
# options[:interval] = parse_time(val).utc.iso8601
|
1010
1044
|
# end
|
1011
1045
|
opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
|
1012
|
-
params['level'] = params['level'] ? [params['level'], val].flatten : val
|
1046
|
+
params['level'] = params['level'] ? [params['level'], val].flatten : [val]
|
1013
1047
|
end
|
1014
1048
|
opts.on('--table', '--table', "Format ouput as a table.") do
|
1015
1049
|
options[:table] = true
|
@@ -1036,6 +1070,7 @@ class Morpheus::Cli::Instances
|
|
1036
1070
|
return 1
|
1037
1071
|
end
|
1038
1072
|
end
|
1073
|
+
params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
|
1039
1074
|
params.merge!(parse_list_options(options))
|
1040
1075
|
params['query'] = params.delete('phrase') if params['phrase']
|
1041
1076
|
params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
|
@@ -1217,7 +1252,6 @@ class Morpheus::Cli::Instances
|
|
1217
1252
|
"Environment" => 'instanceContext',
|
1218
1253
|
"Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
|
1219
1254
|
"Metadata" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
1220
|
-
"Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
|
1221
1255
|
"Owner" => lambda {|it|
|
1222
1256
|
if it['owner']
|
1223
1257
|
(it['owner']['username'] || it['owner']['id'])
|
@@ -1227,13 +1261,20 @@ class Morpheus::Cli::Instances
|
|
1227
1261
|
},
|
1228
1262
|
#"Tenant" => lambda {|it| it['tenant'] ? it['tenant']['name'] : '' },
|
1229
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']) : '' },
|
1230
1269
|
"Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
|
1231
1270
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
1232
1271
|
"Status" => lambda {|it| format_instance_status(it) }
|
1233
1272
|
}
|
1234
|
-
|
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?
|
1235
1276
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
1236
|
-
|
1277
|
+
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
1237
1278
|
print_description_list(description_cols, instance)
|
1238
1279
|
|
1239
1280
|
if instance['statusMessage']
|
@@ -3605,6 +3646,60 @@ class Morpheus::Cli::Instances
|
|
3605
3646
|
end
|
3606
3647
|
end
|
3607
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
|
+
|
3608
3703
|
private
|
3609
3704
|
|
3610
3705
|
def find_zone_by_name_or_id(group_id, val)
|
@@ -3901,4 +3996,29 @@ private
|
|
3901
3996
|
]
|
3902
3997
|
end
|
3903
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
|
+
|
3904
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
47
|
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
|
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)
|
@@ -185,7 +193,9 @@ class Morpheus::Cli::InvoicesCommand
|
|
185
193
|
subtitles += parse_list_subtitles(options)
|
186
194
|
print_h1 title, subtitles
|
187
195
|
if invoices.empty?
|
188
|
-
|
196
|
+
unless options[:totals_only]
|
197
|
+
print cyan,"No invoices found.",reset,"\n"
|
198
|
+
end
|
189
199
|
else
|
190
200
|
# current_date = Time.now
|
191
201
|
# current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
|
@@ -194,105 +204,137 @@ class Morpheus::Cli::InvoicesCommand
|
|
194
204
|
{"INVOICE ID" => lambda {|it| it['id'] } },
|
195
205
|
{"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
196
206
|
{"REF ID" => lambda {|it| it['refId'] } },
|
197
|
-
{"REF NAME" => lambda {|it|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
207
|
+
{"REF NAME" => lambda {|it|
|
208
|
+
if options[:show_all]
|
209
|
+
it['refName']
|
210
|
+
else
|
211
|
+
truncate_string_right(it['refName'], 100)
|
212
|
+
end
|
213
|
+
} },
|
203
214
|
#{"INTERVAL" => lambda {|it| it['interval'] } },
|
204
215
|
{"CLOUD" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' } },
|
205
|
-
{"
|
206
|
-
|
207
|
-
#{"
|
216
|
+
#{"TENANT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
|
217
|
+
|
218
|
+
#{"COST TYPE" => lambda {|it| it['costType'].to_s.capitalize } },
|
208
219
|
{"PERIOD" => lambda {|it| format_invoice_period(it) } },
|
209
220
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
210
|
-
{"END" => lambda {|it|
|
211
|
-
|
221
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
222
|
+
] + (options[:show_all] ? [
|
223
|
+
{"REF START" => lambda {|it| format_dt(it['refStart']) } },
|
224
|
+
{"REF END" => lambda {|it| format_dt(it['refEnd']) } },
|
225
|
+
] : []) + [
|
226
|
+
{"COMPUTE" => lambda {|it| format_money(it['computeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
227
|
+
# {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
|
228
|
+
{"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
229
|
+
{"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
230
|
+
{"EXTRA" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
231
|
+
{"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
212
232
|
{"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
|
233
|
+
format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
219
234
|
} }
|
220
235
|
]
|
221
236
|
|
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
237
|
if options[:show_prices]
|
230
238
|
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']) } },
|
239
|
+
{"COMPUTE PRICE" => lambda {|it| format_money(it['computePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
240
|
+
# {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
241
|
+
{"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
242
|
+
{"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
243
|
+
{"EXTRA PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
244
|
+
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
237
245
|
{"TOTAL PRICE" => lambda {|it|
|
238
|
-
|
239
|
-
format_money(it['totalPrice']) + " (Projected)"
|
240
|
-
else
|
241
|
-
format_money(it['totalPrice'])
|
242
|
-
end
|
246
|
+
format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
243
247
|
} }
|
244
248
|
]
|
245
249
|
end
|
246
250
|
if options[:show_estimates]
|
247
251
|
columns += [
|
248
|
-
{"
|
252
|
+
{"COMPUTE EST." => lambda {|it| format_money(it['estimatedComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
253
|
+
# {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
254
|
+
{"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
255
|
+
{"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
256
|
+
{"EXTRA EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
257
|
+
{"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
249
258
|
{"TOTAL EST." => lambda {|it|
|
250
|
-
|
251
|
-
format_money(it['estimatedTotalCost']) + " (Projected)"
|
252
|
-
else
|
253
|
-
format_money(it['estimatedTotalCost'])
|
254
|
-
end
|
259
|
+
format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
|
255
260
|
} },
|
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
261
|
]
|
262
262
|
end
|
263
|
+
columns += [
|
264
|
+
{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
|
265
|
+
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
266
|
+
{"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
|
267
|
+
{"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
|
268
|
+
]
|
269
|
+
if show_projects
|
270
|
+
columns += [
|
271
|
+
{"PROJECT ID" => lambda {|it| it['project'] ? it['project']['id'] : '' } },
|
272
|
+
{"PROJECT NAME" => lambda {|it| it['project'] ? it['project']['name'] : '' } },
|
273
|
+
{"PROJECT TAGS" => lambda {|it| it['project'] ? truncate_string(format_metadata(it['project']['tags']), 50) : '' } },
|
274
|
+
]
|
275
|
+
end
|
276
|
+
columns += [
|
277
|
+
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
278
|
+
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
279
|
+
]
|
263
280
|
if options[:show_raw_data]
|
264
281
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
265
282
|
end
|
266
|
-
|
267
|
-
|
283
|
+
unless options[:totals_only]
|
284
|
+
print as_pretty_table(invoices, columns, options)
|
285
|
+
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
286
|
+
end
|
268
287
|
|
269
288
|
if options[:show_invoice_totals]
|
270
289
|
invoice_totals = json_response['invoiceTotals']
|
290
|
+
print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
|
291
|
+
|
271
292
|
if invoice_totals
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
293
|
+
cost_rows = [
|
294
|
+
{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']},
|
295
|
+
]
|
296
|
+
if options[:show_prices]
|
297
|
+
cost_rows += [
|
298
|
+
{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']},
|
299
|
+
]
|
300
|
+
end
|
301
|
+
if options[:show_estimates]
|
302
|
+
cost_rows += [
|
303
|
+
{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']},
|
304
|
+
{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']},
|
305
|
+
]
|
306
|
+
end
|
307
|
+
cost_columns = {
|
308
|
+
"" => lambda {|it| it[:label] },
|
309
|
+
"Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
|
310
|
+
"Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
|
311
|
+
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
312
|
+
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
313
|
+
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
314
|
+
"Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
315
|
+
"MTD".upcase => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
316
|
+
"Total".upcase => lambda {|it|
|
317
|
+
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
318
|
+
},
|
287
319
|
}
|
288
|
-
|
320
|
+
# remove columns that rarely have data...
|
321
|
+
if cost_rows.sum { |it| it[:memory].to_f } == 0
|
322
|
+
cost_columns.delete("Memory".upcase)
|
323
|
+
end
|
324
|
+
if cost_rows.sum { |it| it[:license].to_f } == 0
|
325
|
+
cost_columns.delete("License".upcase)
|
326
|
+
end
|
327
|
+
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
328
|
+
cost_columns.delete("Extra".upcase)
|
329
|
+
end
|
330
|
+
print as_pretty_table(cost_rows, cost_columns, options)
|
289
331
|
else
|
290
332
|
print "\n"
|
291
333
|
print yellow, "No invoice totals data", reset, "\n"
|
292
334
|
end
|
293
335
|
end
|
336
|
+
print reset,"\n"
|
294
337
|
end
|
295
|
-
print reset,"\n"
|
296
338
|
return 0, nil
|
297
339
|
end
|
298
340
|
end
|
@@ -301,14 +343,17 @@ class Morpheus::Cli::InvoicesCommand
|
|
301
343
|
options = {}
|
302
344
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
303
345
|
opts.banner = subcommand_usage("[id]")
|
304
|
-
opts.on('-a', '--all', "Display all
|
346
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
305
347
|
options[:show_estimates] = true
|
306
348
|
# options[:show_costs] = true
|
307
349
|
options[:show_prices] = true
|
308
|
-
options[:show_raw_data] = true
|
350
|
+
# options[:show_raw_data] = true
|
309
351
|
options[:max_line_items] = 10000
|
310
352
|
end
|
311
|
-
opts.on('--
|
353
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
354
|
+
options[:show_prices] = true
|
355
|
+
end
|
356
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
312
357
|
options[:show_estimates] = true
|
313
358
|
end
|
314
359
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
@@ -318,12 +363,12 @@ class Morpheus::Cli::InvoicesCommand
|
|
318
363
|
options[:show_raw_data] = true
|
319
364
|
options[:pretty_json] = true
|
320
365
|
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
366
|
opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
|
325
367
|
options[:hide_line_items] = true
|
326
368
|
end
|
369
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
370
|
+
options[:sigdig] = val.to_i
|
371
|
+
end
|
327
372
|
build_standard_get_options(opts, options)
|
328
373
|
opts.footer = "Get details about a specific invoice."
|
329
374
|
opts.footer = <<-EOT
|
@@ -365,20 +410,24 @@ EOT
|
|
365
410
|
"Type" => lambda {|it| format_invoice_ref_type(it) },
|
366
411
|
"Ref ID" => lambda {|it| it['refId'] },
|
367
412
|
"Ref Name" => lambda {|it| it['refName'] },
|
413
|
+
"Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
|
368
414
|
"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
415
|
"Power State" => lambda {|it| format_server_power_state(it) },
|
373
|
-
"
|
416
|
+
"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
374
417
|
"Active" => lambda {|it| format_boolean(it['active']) },
|
375
|
-
"Period" => lambda {|it| format_invoice_period(it) },
|
376
418
|
"Estimate" => lambda {|it| format_boolean(it['estimate']) },
|
419
|
+
#"Cost Type" => lambda {|it| it['costType'].to_s.capitalize },
|
420
|
+
"Period" => lambda {|it| format_invoice_period(it) },
|
377
421
|
#"Interval" => lambda {|it| it['interval'] },
|
378
422
|
"Start" => lambda {|it| format_date(it['startDate']) },
|
379
|
-
"End" => lambda {|it|
|
380
|
-
"Ref Start" => lambda {|it|
|
381
|
-
"Ref End" => lambda {|it|
|
423
|
+
"End" => lambda {|it| format_date(it['endDate']) },
|
424
|
+
"Ref Start" => lambda {|it| format_dt(it['refStart']) },
|
425
|
+
"Ref End" => lambda {|it| format_dt(it['refEnd']) },
|
426
|
+
"Items" => lambda {|it| it['lineItems'].size rescue '' },
|
427
|
+
"Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
428
|
+
"Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
|
429
|
+
"Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
|
430
|
+
"Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
|
382
431
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
383
432
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
384
433
|
}
|
@@ -391,6 +440,9 @@ EOT
|
|
391
440
|
description_cols.delete("Project Name")
|
392
441
|
description_cols.delete("Project Tags")
|
393
442
|
end
|
443
|
+
if invoice['metadata'].nil? || invoice['metadata'].empty?
|
444
|
+
description_cols.delete("Tags")
|
445
|
+
end
|
394
446
|
if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
|
395
447
|
description_cols.delete("Power State")
|
396
448
|
end
|
@@ -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
|
-
"
|
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
|
+
"Extra" => 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
|
-
"
|
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
|
+
"Extra" => 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,77 @@ 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
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
499
|
+
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
500
|
+
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
501
|
+
#{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
502
|
+
# {"TERM" => lambda {|it| it['itemTerm'] } },
|
503
|
+
{"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
|
504
|
+
{"ITEM NAME" => lambda {|it| it['itemName'] } },
|
505
|
+
{"ITEM TYPE" => lambda {|it| it['itemType'] } },
|
506
|
+
{"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
|
507
|
+
{"PRODUCT CODE" => lambda {|it| it['productCode'] } },
|
508
|
+
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
509
|
+
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
510
|
+
]
|
511
|
+
if options[:show_raw_data]
|
512
|
+
line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
513
|
+
end
|
514
|
+
print_h2 "Line Items"
|
515
|
+
#max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
|
516
|
+
paged_line_items = line_items #.first(max_line_items)
|
517
|
+
print as_pretty_table(paged_line_items, line_items_columns, options)
|
518
|
+
print_results_pagination({total: line_items.size, size: paged_line_items.size}, {:label => "line item", :n_label => "line items"})
|
519
|
+
end
|
520
|
+
|
521
|
+
# cost_types = ["Costs"]
|
522
|
+
# cost_types << "Prices" if options[:show_prices]
|
523
|
+
# cost_types << "Estimates" if options[:show_estimates]
|
524
|
+
# print_h2 cost_types.size == 1 ? "Totals" : "Total #{anded_list(cost_types)}"
|
525
|
+
print_h2 "Invoice Totals"
|
526
|
+
|
431
527
|
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
528
|
{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
529
|
]
|
530
|
+
if options[:show_prices]
|
531
|
+
cost_rows += [
|
532
|
+
{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']},
|
533
|
+
]
|
534
|
+
end
|
435
535
|
if options[:show_estimates]
|
436
536
|
cost_rows += [
|
437
537
|
{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['
|
538
|
+
{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
539
|
]
|
440
540
|
end
|
441
541
|
cost_columns = {
|
442
542
|
"" => 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]) },
|
543
|
+
"Compute".upcase => lambda {|it| format_money(it[:compute], 'usd', {sigdig:options[:sigdig]}) },
|
544
|
+
"Memory".upcase => lambda {|it| format_money(it[:memory], 'usd', {sigdig:options[:sigdig]}) },
|
545
|
+
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
546
|
+
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
547
|
+
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
548
|
+
"Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
549
|
+
"MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
450
550
|
"Total".upcase => lambda {|it|
|
451
|
-
|
452
|
-
format_money(it[:total]) + " (Projected)"
|
453
|
-
else
|
454
|
-
format_money(it[:total])
|
455
|
-
end
|
551
|
+
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
456
552
|
},
|
457
553
|
}
|
458
554
|
# remove columns that rarely have data...
|
@@ -463,7 +559,7 @@ EOT
|
|
463
559
|
cost_columns.delete("License".upcase)
|
464
560
|
end
|
465
561
|
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
466
|
-
cost_columns.delete("
|
562
|
+
cost_columns.delete("Extra".upcase)
|
467
563
|
end
|
468
564
|
print as_pretty_table(cost_rows, cost_columns, options)
|
469
565
|
|
@@ -471,41 +567,6 @@ EOT
|
|
471
567
|
print_h2 "Raw Data"
|
472
568
|
puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
|
473
569
|
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
570
|
|
510
571
|
print reset,"\n"
|
511
572
|
return 0
|
@@ -596,43 +657,36 @@ EOT
|
|
596
657
|
options = {}
|
597
658
|
params = {}
|
598
659
|
ref_ids = []
|
660
|
+
query_tags = {}
|
599
661
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
600
662
|
opts.banner = subcommand_usage()
|
601
|
-
opts.on('-a', '--all', "Display all
|
663
|
+
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
602
664
|
options[:show_actual_costs] = true
|
603
665
|
options[:show_costs] = true
|
604
666
|
options[:show_prices] = true
|
605
|
-
options[:show_raw_data] = true
|
667
|
+
# options[:show_raw_data] = true
|
606
668
|
end
|
607
|
-
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute,
|
669
|
+
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
|
608
670
|
# options[:show_actual_costs] = true
|
609
671
|
# end
|
610
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute,
|
672
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
|
611
673
|
# options[:show_costs] = true
|
612
674
|
# end
|
613
|
-
|
614
|
-
|
615
|
-
|
675
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
676
|
+
options[:show_prices] = true
|
677
|
+
end
|
616
678
|
opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
|
617
679
|
params['invoiceId'] ||= []
|
618
680
|
params['invoiceId'] << val
|
619
681
|
end
|
682
|
+
opts.on('--external-id ID', String, "Filter by External ID") do |val|
|
683
|
+
params['externalId'] ||= []
|
684
|
+
params['externalId'] << val
|
685
|
+
end
|
620
686
|
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
|
687
|
+
params['refType'] ||= []
|
688
|
+
values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
|
689
|
+
values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
|
636
690
|
end
|
637
691
|
opts.on('--id ID', String, "Filter by Ref ID") do |val|
|
638
692
|
ref_ids << val
|
@@ -691,6 +745,11 @@ EOT
|
|
691
745
|
opts.on('--tenant ID', String, "View invoice line items for a tenant. Default is your own account.") do |val|
|
692
746
|
params['accountId'] = val
|
693
747
|
end
|
748
|
+
# opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
|
749
|
+
# k,v = val.split("=")
|
750
|
+
# query_tags[k] ||= []
|
751
|
+
# query_tags[k] << v
|
752
|
+
# end
|
694
753
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
695
754
|
options[:show_raw_data] = true
|
696
755
|
end
|
@@ -698,6 +757,14 @@ EOT
|
|
698
757
|
params['includeTotals'] = true
|
699
758
|
options[:show_invoice_totals] = true
|
700
759
|
end
|
760
|
+
opts.on('--totals-only', "View totals only") do |val|
|
761
|
+
params['includeTotals'] = true
|
762
|
+
options[:show_invoice_totals] = true
|
763
|
+
options[:totals_only] = true
|
764
|
+
end
|
765
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
766
|
+
options[:sigdig] = val.to_i
|
767
|
+
end
|
701
768
|
build_standard_list_options(opts, options)
|
702
769
|
opts.footer = "List invoice line items."
|
703
770
|
end
|
@@ -742,6 +809,11 @@ EOT
|
|
742
809
|
end
|
743
810
|
params['rawData'] = true if options[:show_raw_data]
|
744
811
|
params['refId'] = ref_ids unless ref_ids.empty?
|
812
|
+
if query_tags && !query_tags.empty?
|
813
|
+
query_tags.each do |k,v|
|
814
|
+
params['tags.' + k] = v
|
815
|
+
end
|
816
|
+
end
|
745
817
|
@invoice_line_items_interface.setopts(options)
|
746
818
|
if options[:dry_run]
|
747
819
|
print_dry_run @invoice_line_items_interface.dry.list(params)
|
@@ -773,15 +845,22 @@ EOT
|
|
773
845
|
{"REF NAME" => lambda {|it| it['refName'] } },
|
774
846
|
#{"REF CATEGORY" => lambda {|it| it['refCategory'] } },
|
775
847
|
{"START" => lambda {|it| format_date(it['startDate']) } },
|
776
|
-
{"END" => lambda {|it|
|
848
|
+
{"END" => lambda {|it| format_date(it['endDate']) } },
|
777
849
|
{"USAGE TYPE" => lambda {|it| it['usageType'] } },
|
778
850
|
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
779
851
|
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
780
852
|
{"RATE" => lambda {|it| it['itemRate'] } },
|
781
|
-
{"
|
782
|
-
{"
|
783
|
-
|
784
|
-
|
853
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
854
|
+
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
855
|
+
] + (options[:show_prices] ? [
|
856
|
+
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
857
|
+
{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
858
|
+
] : []) + [
|
859
|
+
{"ITEM ID" => lambda {|it| truncate_string_right(it['itemId'], 65) } },
|
860
|
+
{"ITEM NAME" => lambda {|it| it['itemName'] } },
|
861
|
+
{"ITEM TYPE" => lambda {|it| it['itemType'] } },
|
862
|
+
{"ITEM DESCRIPTION" => lambda {|it| it['itemDescription'] } },
|
863
|
+
{"PRODUCT CODE" => lambda {|it| it['productCode'] } },
|
785
864
|
"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) },
|
786
865
|
"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
787
866
|
]
|
@@ -789,35 +868,39 @@ EOT
|
|
789
868
|
if options[:show_raw_data]
|
790
869
|
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
791
870
|
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
871
|
# if options[:show_invoice_totals]
|
805
872
|
# line_item_totals = json_response['lineItemTotals']
|
806
873
|
# 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"
|
874
|
+
# totals_row = line_item_totals.clone
|
875
|
+
# totals_row['id'] = 'TOTAL:'
|
876
|
+
# #totals_row['usageCategory'] = 'TOTAL:'
|
877
|
+
# line_items = line_items + [totals_row]
|
819
878
|
# end
|
820
879
|
# end
|
880
|
+
unless options[:totals_only]
|
881
|
+
print as_pretty_table(line_items, columns, options)
|
882
|
+
print_results_pagination(json_response, {:label => "line item", :n_label => "line items"})
|
883
|
+
end
|
884
|
+
|
885
|
+
if options[:show_invoice_totals]
|
886
|
+
line_item_totals = json_response['lineItemTotals']
|
887
|
+
if line_item_totals
|
888
|
+
print_h2 "Line Item Totals" unless options[:totals_only]
|
889
|
+
invoice_totals_columns = [
|
890
|
+
{"Items" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
|
891
|
+
#{"Usage" => lambda {|it| it['itemUsage'] } },
|
892
|
+
{"Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
893
|
+
] + (options[:show_prices] ? [
|
894
|
+
{"Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
895
|
+
#{"Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
896
|
+
|
897
|
+
] : [])
|
898
|
+
print_description_list(invoice_totals_columns, line_item_totals)
|
899
|
+
else
|
900
|
+
print "\n"
|
901
|
+
print yellow, "No line item totals data", reset, "\n"
|
902
|
+
end
|
903
|
+
end
|
821
904
|
|
822
905
|
end
|
823
906
|
print reset,"\n"
|
@@ -840,6 +923,9 @@ EOT
|
|
840
923
|
options[:show_raw_data] = true
|
841
924
|
options[:pretty_json] = true
|
842
925
|
end
|
926
|
+
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
927
|
+
options[:sigdig] = val.to_i
|
928
|
+
end
|
843
929
|
build_standard_get_options(opts, options)
|
844
930
|
opts.footer = "Get details about a specific invoice line item."
|
845
931
|
opts.footer = <<-EOT
|
@@ -883,11 +969,16 @@ EOT
|
|
883
969
|
"Usage Category" => lambda {|it| it['usageCategory'] },
|
884
970
|
"Item Usage" => lambda {|it| it['itemUsage'] },
|
885
971
|
"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'] },
|
972
|
+
"Item Cost" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) },
|
973
|
+
"Item Price" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
974
|
+
#"Item Tax" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) },
|
890
975
|
#"Tax Type" => lambda {|it| it['taxType'] },
|
976
|
+
"Item Term" => lambda {|it| it['itemTerm'] },
|
977
|
+
"Item ID" => lambda {|it| it['itemId'] },
|
978
|
+
"Item Name" => lambda {|it| it['itemName'] },
|
979
|
+
"Item Type" => lambda {|it| it['itemType'] },
|
980
|
+
"Item Description" => lambda {|it| it['itemDescription'] },
|
981
|
+
"Product Code" => lambda {|it| it['productCode'] },
|
891
982
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
892
983
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
893
984
|
}
|
@@ -956,6 +1047,25 @@ EOT
|
|
956
1047
|
end
|
957
1048
|
end
|
958
1049
|
|
1050
|
+
def parse_invoice_ref_type(ref_type)
|
1051
|
+
val = ref_type.to_s.downcase
|
1052
|
+
if val == 'cloud' || val == 'zone'
|
1053
|
+
'ComputeZone'
|
1054
|
+
elsif val == 'instance'
|
1055
|
+
'Instance'
|
1056
|
+
elsif val == 'server' || val == 'host'
|
1057
|
+
'ComputeServer'
|
1058
|
+
elsif val == 'cluster'
|
1059
|
+
'ComputeServerGroup'
|
1060
|
+
elsif val == 'group' || val == 'site'
|
1061
|
+
'ComputeSite'
|
1062
|
+
elsif val == 'user'
|
1063
|
+
'User'
|
1064
|
+
else
|
1065
|
+
ref_type
|
1066
|
+
end
|
1067
|
+
end
|
1068
|
+
|
959
1069
|
# convert "202003" to "March 2020"
|
960
1070
|
def format_invoice_period(it)
|
961
1071
|
interval = it['interval']
|
@@ -1002,6 +1112,11 @@ EOT
|
|
1002
1112
|
end
|
1003
1113
|
end
|
1004
1114
|
|
1115
|
+
def get_current_period()
|
1116
|
+
now = Time.now.utc
|
1117
|
+
now.year.to_s + now.month.to_s.rjust(2,'0')
|
1118
|
+
end
|
1119
|
+
|
1005
1120
|
def format_server_power_state(server, return_color=cyan)
|
1006
1121
|
out = ""
|
1007
1122
|
if server['powerState'] == 'on'
|