morpheus-cli 4.2.12 → 4.2.17
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 +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'
|