morpheus-cli 4.2.16 → 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 +21 -15
- data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
- data/lib/morpheus/cli/backups_command.rb +271 -0
- data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +92 -41
- data/lib/morpheus/cli/clusters.rb +0 -18
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +7 -7
- data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
- data/lib/morpheus/cli/credentials.rb +13 -9
- data/lib/morpheus/cli/deploy.rb +374 -0
- data/lib/morpheus/cli/deployments.rb +521 -197
- data/lib/morpheus/cli/deploys.rb +271 -126
- data/lib/morpheus/cli/doc.rb +182 -0
- data/lib/morpheus/cli/error_handler.rb +23 -8
- data/lib/morpheus/cli/errors.rb +3 -2
- data/lib/morpheus/cli/image_builder_command.rb +2 -2
- data/lib/morpheus/cli/instances.rb +136 -17
- data/lib/morpheus/cli/invoices_command.rb +51 -38
- data/lib/morpheus/cli/library_layouts_command.rb +1 -1
- data/lib/morpheus/cli/login.rb +9 -3
- data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
- data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
- data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
- data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +110 -74
- 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 +1 -1
- data/lib/morpheus/cli/remote.rb +3 -2
- data/lib/morpheus/cli/roles.rb +49 -92
- data/lib/morpheus/cli/security_groups.rb +7 -1
- data/lib/morpheus/cli/service_plans_command.rb +10 -10
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +7 -6
- data/lib/morpheus/cli/subnets_command.rb +1 -1
- data/lib/morpheus/cli/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/terminal.rb +1 -0
- metadata +12 -3
- data/lib/morpheus/cli/auth_command.rb +0 -105
@@ -31,18 +31,31 @@ class Morpheus::Cli::ErrorHandler
|
|
31
31
|
# raise err
|
32
32
|
# @stderr.puts "#{red}#{err.message}#{reset}"
|
33
33
|
puts_angry_error err.message
|
34
|
-
@stderr.puts
|
34
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
|
35
|
+
@stderr.puts "Try --help for more usage information"
|
35
36
|
do_print_stacktrace = false
|
36
37
|
# exit_code = 127
|
37
|
-
|
38
|
+
when Morpheus::Cli::CommandArgumentsError
|
39
|
+
puts_angry_error err.message
|
40
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
|
41
|
+
@stderr.puts "Try --help for more usage information"
|
42
|
+
do_print_stacktrace = false
|
43
|
+
if err.exit_code
|
44
|
+
exit_code = err.exit_code
|
45
|
+
end
|
46
|
+
|
38
47
|
when Morpheus::Cli::CommandError
|
39
|
-
#
|
40
|
-
#
|
48
|
+
# this should probably always print the whole thing as red, but just does the first line for now.
|
49
|
+
# until verify_args! replaces raise_command_error where the full parser help is in the error message..
|
41
50
|
message_lines = err.message.split(/\r?\n/)
|
42
51
|
first_line = message_lines.shift
|
43
52
|
puts_angry_error first_line
|
44
|
-
|
45
|
-
|
53
|
+
if !message_lines.empty?
|
54
|
+
@stderr.puts message_lines.join("\n") unless message_lines.empty?
|
55
|
+
else
|
56
|
+
@stderr.puts err.optparse.banner if err.optparse && err.optparse.banner && message_lines.empty?
|
57
|
+
@stderr.puts "Try --help for more usage information"
|
58
|
+
end
|
46
59
|
do_print_stacktrace = false
|
47
60
|
if err.exit_code
|
48
61
|
exit_code = err.exit_code
|
@@ -82,7 +95,7 @@ class Morpheus::Cli::ErrorHandler
|
|
82
95
|
@stderr.puts err.to_s
|
83
96
|
end
|
84
97
|
else
|
85
|
-
@stderr.puts "Use --debug for more information."
|
98
|
+
@stderr.puts "Use -V or --debug for more verbose debugging information."
|
86
99
|
end
|
87
100
|
end
|
88
101
|
|
@@ -127,6 +140,8 @@ class Morpheus::Cli::ErrorHandler
|
|
127
140
|
begin
|
128
141
|
print_rest_errors(JSON.parse(err.response.to_s), options)
|
129
142
|
rescue TypeError, JSON::ParserError => ex
|
143
|
+
# not json, just 404
|
144
|
+
@stderr.print red, "Error Communicating with the remote appliance. #{e}", reset, "\n"
|
130
145
|
end
|
131
146
|
else
|
132
147
|
@stderr.print red, "Error Communicating with the remote appliance. #{e}", reset, "\n"
|
@@ -145,7 +160,7 @@ class Morpheus::Cli::ErrorHandler
|
|
145
160
|
@stderr.print reset
|
146
161
|
end
|
147
162
|
else
|
148
|
-
@stderr.puts "Use --debug for more information."
|
163
|
+
@stderr.puts "Use -V or --debug for more verbose debugging information."
|
149
164
|
end
|
150
165
|
end
|
151
166
|
else
|
data/lib/morpheus/cli/errors.rb
CHANGED
@@ -3,7 +3,7 @@ module Morpheus::Cli
|
|
3
3
|
# A standard error to raise in your CliCommand classes.
|
4
4
|
class CommandError < StandardError
|
5
5
|
|
6
|
-
attr_reader :args, :exit_code
|
6
|
+
attr_reader :args, :optparse, :exit_code
|
7
7
|
|
8
8
|
def initialize(msg, args=[], optparse=nil, exit_code=nil)
|
9
9
|
@args = args
|
@@ -11,6 +11,7 @@ module Morpheus::Cli
|
|
11
11
|
@exit_code = exit_code # || 1
|
12
12
|
super(msg)
|
13
13
|
end
|
14
|
+
|
14
15
|
end
|
15
16
|
|
16
17
|
# An error indicating the command was not recoginzed
|
@@ -24,7 +25,7 @@ module Morpheus::Cli
|
|
24
25
|
|
25
26
|
# An error for wrong number of arguments
|
26
27
|
# could use ::OptionParser::MissingArgument, ::OptionParser::NeedlessArgument
|
27
|
-
# maybe return an error code
|
28
|
+
# maybe return an error code other than 1?
|
28
29
|
class CommandArgumentsError < CommandError
|
29
30
|
|
30
31
|
def initialize(msg, args=[], optparse=nil, exit_code=nil)
|
@@ -518,7 +518,7 @@ class Morpheus::Cli::ImageBuilderCommand
|
|
518
518
|
opts.on( '-K', '--keep-virtual-images', "Preserve associated virtual images" ) do
|
519
519
|
query_params['keepVirtualImages'] = 'on'
|
520
520
|
end
|
521
|
-
build_common_options(opts, options, [:
|
521
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
522
522
|
end
|
523
523
|
optparse.parse!(args)
|
524
524
|
|
@@ -565,7 +565,7 @@ class Morpheus::Cli::ImageBuilderCommand
|
|
565
565
|
query_params = {}
|
566
566
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
567
567
|
opts.banner = subcommand_usage("[image-build]")
|
568
|
-
build_common_options(opts, options, [:
|
568
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
569
569
|
end
|
570
570
|
optparse.parse!(args)
|
571
571
|
|
@@ -9,14 +9,22 @@ class Morpheus::Cli::Instances
|
|
9
9
|
include Morpheus::Cli::AccountsHelper # needed? replace with OptionSourceHelper
|
10
10
|
include Morpheus::Cli::OptionSourceHelper
|
11
11
|
include Morpheus::Cli::ProvisioningHelper
|
12
|
+
include Morpheus::Cli::DeploymentsHelper
|
12
13
|
include Morpheus::Cli::ProcessesHelper
|
13
14
|
include Morpheus::Cli::LogsHelper
|
14
15
|
|
15
16
|
set_command_name :instances
|
16
17
|
set_command_description "View and manage instances."
|
17
|
-
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :logs,
|
18
|
-
|
19
|
-
|
18
|
+
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :logs,
|
19
|
+
:history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details},
|
20
|
+
:stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :stop_service, :start_service, :restart_service,
|
21
|
+
:backup, :backups, :resize, :clone, :envs, :setenv, :delenv,
|
22
|
+
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot,
|
23
|
+
:console, :status_check, {:containers => :list_containers},
|
24
|
+
:scaling, {:'scaling-update' => :scaling_update},
|
25
|
+
:wiki, :update_wiki,
|
26
|
+
{:exec => :execution_request},
|
27
|
+
:deploys
|
20
28
|
#register_subcommands :firewall_disable, :firewall_enable
|
21
29
|
# register_subcommands {:'lb-update' => :load_balancer_update}
|
22
30
|
alias_subcommand :details, :get
|
@@ -43,6 +51,8 @@ class Morpheus::Cli::Instances
|
|
43
51
|
@options_interface = @api_client.options
|
44
52
|
@active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
|
45
53
|
@execution_request_interface = @api_client.execution_request
|
54
|
+
@deploy_interface = @api_client.deploy
|
55
|
+
@deployments_interface = @api_client.deployments
|
46
56
|
end
|
47
57
|
|
48
58
|
def handle(args)
|
@@ -546,18 +556,12 @@ class Morpheus::Cli::Instances
|
|
546
556
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
547
557
|
end
|
548
558
|
optparse.parse!(args)
|
549
|
-
|
550
|
-
puts optparse
|
551
|
-
exit 1
|
552
|
-
end
|
559
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
553
560
|
connect(options)
|
554
561
|
|
555
562
|
begin
|
556
563
|
instance = find_instance_by_name_or_id(args[0])
|
557
564
|
return 1 if instance.nil?
|
558
|
-
new_group = nil
|
559
|
-
|
560
|
-
|
561
565
|
if options[:payload]
|
562
566
|
payload = options[:payload]
|
563
567
|
end
|
@@ -579,10 +583,40 @@ class Morpheus::Cli::Instances
|
|
579
583
|
params['ownerId'] = owner_id
|
580
584
|
#payload['createdById'] = options[:owner].to_i # pre 4.2.1 api
|
581
585
|
end
|
582
|
-
if
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
+
if options[:group]
|
587
|
+
group = find_group_by_name_or_id_for_provisioning(options[:group])
|
588
|
+
if group.nil?
|
589
|
+
return 1, "group not found"
|
590
|
+
end
|
591
|
+
payload['instance']['site'] = {'id' => group['id']}
|
592
|
+
end
|
593
|
+
# metadata tags
|
594
|
+
# if options[:options]['metadata'].is_a?(Array) && !options[:metadata]
|
595
|
+
# options[:metadata] = options[:options]['metadata']
|
596
|
+
# end
|
597
|
+
if options[:metadata]
|
598
|
+
metadata = []
|
599
|
+
if options[:metadata] == "[]" || options[:metadata] == "null"
|
600
|
+
payload['instance']['metadata'] = []
|
601
|
+
elsif options[:metadata].is_a?(Array)
|
602
|
+
payload['instance']['metadata'] = options[:metadata]
|
603
|
+
else
|
604
|
+
# parse string into format name:value, name:value
|
605
|
+
# merge IDs from current metadata
|
606
|
+
# todo: should allow quoted semicolons..
|
607
|
+
metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
|
608
|
+
metadata_list = metadata_list.collect do |it|
|
609
|
+
metadata_pair = it.split(":")
|
610
|
+
row = {}
|
611
|
+
row['name'] = metadata_pair[0].to_s.strip
|
612
|
+
row['value'] = metadata_pair[1].to_s.strip
|
613
|
+
row
|
614
|
+
end
|
615
|
+
payload['instance']['metadata'] = metadata_list
|
616
|
+
end
|
617
|
+
end
|
618
|
+
if payload['instance'].empty? && params.empty? && options[:owner].nil?
|
619
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
586
620
|
end
|
587
621
|
if !params.empty?
|
588
622
|
payload['instance'].deep_merge!(params)
|
@@ -1218,7 +1252,6 @@ class Morpheus::Cli::Instances
|
|
1218
1252
|
"Environment" => 'instanceContext',
|
1219
1253
|
"Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
|
1220
1254
|
"Metadata" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
1221
|
-
"Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
|
1222
1255
|
"Owner" => lambda {|it|
|
1223
1256
|
if it['owner']
|
1224
1257
|
(it['owner']['username'] || it['owner']['id'])
|
@@ -1228,13 +1261,20 @@ class Morpheus::Cli::Instances
|
|
1228
1261
|
},
|
1229
1262
|
#"Tenant" => lambda {|it| it['tenant'] ? it['tenant']['name'] : '' },
|
1230
1263
|
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
1264
|
+
# "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
1265
|
+
"Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
|
1266
|
+
"Last Deployment" => lambda {|it| (it['lastDeploy'] ? "#{it['lastDeploy']['deployment']['name']} #{it['lastDeploy']['deploymentVersion']['userVersion']} at #{format_local_dt it['lastDeploy']['deployDate']}" : nil) rescue "" },
|
1267
|
+
"Expire Date" => lambda {|it| it['expireDate'] ? format_local_dt(it['expireDate']) : '' },
|
1268
|
+
"Shutdown Date" => lambda {|it| it['shutdownDate'] ? format_local_dt(it['shutdownDate']) : '' },
|
1231
1269
|
"Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
|
1232
1270
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
1233
1271
|
"Status" => lambda {|it| format_instance_status(it) }
|
1234
1272
|
}
|
1235
|
-
|
1273
|
+
description_cols.delete("Power Schedule") if instance['powerSchedule'].nil?
|
1274
|
+
description_cols.delete("Expire Date") if instance['expireDate'].nil?
|
1275
|
+
description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
|
1236
1276
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
1237
|
-
|
1277
|
+
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
1238
1278
|
print_description_list(description_cols, instance)
|
1239
1279
|
|
1240
1280
|
if instance['statusMessage']
|
@@ -3606,6 +3646,60 @@ class Morpheus::Cli::Instances
|
|
3606
3646
|
end
|
3607
3647
|
end
|
3608
3648
|
|
3649
|
+
def deploys(args)
|
3650
|
+
params = {}
|
3651
|
+
options = {}
|
3652
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3653
|
+
opts.banner = subcommand_usage("[instance] [search]")
|
3654
|
+
build_standard_list_options(opts, options)
|
3655
|
+
opts.footer = <<-EOT
|
3656
|
+
List deployments for an instance.
|
3657
|
+
[instance] is required. This is the name or id of an instance
|
3658
|
+
[search] is optional. Filters on deployment version identifier
|
3659
|
+
EOT
|
3660
|
+
end
|
3661
|
+
optparse.parse!(args)
|
3662
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
3663
|
+
connect(options)
|
3664
|
+
if args.count > 1
|
3665
|
+
options[:phrase] = args.join(" ")
|
3666
|
+
end
|
3667
|
+
params.merge!(parse_list_options(options))
|
3668
|
+
instance = find_instance_by_name_or_id(args[0])
|
3669
|
+
return 1 if instance.nil?
|
3670
|
+
# @deploy_interface.setopts(options)
|
3671
|
+
# if options[:dry_run]
|
3672
|
+
# print_dry_run @deploy_interface.dry.list(instance['id'], params)
|
3673
|
+
# return
|
3674
|
+
# end
|
3675
|
+
# json_response = @deploy_interface.list(instance['id'], params)
|
3676
|
+
|
3677
|
+
@instances_interface.setopts(options)
|
3678
|
+
if options[:dry_run]
|
3679
|
+
print_dry_run @instances_interface.dry.deploys(instance['id'], params)
|
3680
|
+
return
|
3681
|
+
end
|
3682
|
+
json_response = @instances_interface.deploys(instance['id'], params)
|
3683
|
+
|
3684
|
+
app_deploys = json_response['appDeploys']
|
3685
|
+
render_response(json_response, options, 'appDeploys') do
|
3686
|
+
print_h1 "Instance Deploys", ["#{instance['name']}"] + parse_list_subtitles(options), options
|
3687
|
+
if app_deploys.empty?
|
3688
|
+
print cyan,"No deployments found.",reset,"\n"
|
3689
|
+
else
|
3690
|
+
print as_pretty_table(app_deploys, app_deploy_column_definitions.upcase_keys!, options)
|
3691
|
+
if json_response['meta']
|
3692
|
+
print_results_pagination(json_response)
|
3693
|
+
else
|
3694
|
+
print_results_pagination({size:app_deploys.size,total:app_deploys.size.to_i})
|
3695
|
+
end
|
3696
|
+
|
3697
|
+
end
|
3698
|
+
print reset,"\n"
|
3699
|
+
end
|
3700
|
+
return 0
|
3701
|
+
end
|
3702
|
+
|
3609
3703
|
private
|
3610
3704
|
|
3611
3705
|
def find_zone_by_name_or_id(group_id, val)
|
@@ -3902,4 +3996,29 @@ private
|
|
3902
3996
|
]
|
3903
3997
|
end
|
3904
3998
|
|
3999
|
+
def app_deploy_column_definitions
|
4000
|
+
{
|
4001
|
+
"ID" => 'id',
|
4002
|
+
"Deployment" => lambda {|it| it['deployment']['name'] rescue '' },
|
4003
|
+
"Version" => lambda {|it| (it['deploymentVersion']['userVersion'] || it['deploymentVersion']['version']) rescue '' },
|
4004
|
+
"Deploy Date" => lambda {|it| format_local_dt(it['deployDate']) },
|
4005
|
+
"Status" => lambda {|it| format_app_deploy_status(it['status']) },
|
4006
|
+
}
|
4007
|
+
end
|
4008
|
+
|
4009
|
+
def format_app_deploy_status(status, return_color=cyan)
|
4010
|
+
out = ""
|
4011
|
+
s = status.to_s.downcase
|
4012
|
+
if s == 'deployed'
|
4013
|
+
out << "#{green}#{s.upcase}#{return_color}"
|
4014
|
+
elsif s == 'open' || s == 'archived' || s == 'committed'
|
4015
|
+
out << "#{cyan}#{s.upcase}#{return_color}"
|
4016
|
+
elsif s == 'failed'
|
4017
|
+
out << "#{red}#{s.upcase}#{return_color}"
|
4018
|
+
else
|
4019
|
+
out << "#{yellow}#{s.upcase}#{return_color}"
|
4020
|
+
end
|
4021
|
+
out
|
4022
|
+
end
|
4023
|
+
|
3905
4024
|
end
|
@@ -25,6 +25,7 @@ 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
31
|
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
@@ -34,13 +35,13 @@ class Morpheus::Cli::InvoicesCommand
|
|
34
35
|
options[:show_prices] = true
|
35
36
|
# options[:show_raw_data] = true
|
36
37
|
end
|
37
|
-
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network,
|
38
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
38
39
|
options[:show_estimates] = true
|
39
40
|
end
|
40
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network,
|
41
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
|
41
42
|
# options[:show_costs] = true
|
42
43
|
# end
|
43
|
-
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network,
|
44
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
44
45
|
options[:show_prices] = true
|
45
46
|
end
|
46
47
|
opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
|
@@ -105,6 +106,11 @@ class Morpheus::Cli::InvoicesCommand
|
|
105
106
|
opts.on('--tenant ID', String, "View invoices for a tenant. Default is your own account.") do |val|
|
106
107
|
params['accountId'] = val
|
107
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
|
108
114
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
109
115
|
options[:show_raw_data] = true
|
110
116
|
end
|
@@ -163,6 +169,11 @@ class Morpheus::Cli::InvoicesCommand
|
|
163
169
|
end
|
164
170
|
params['rawData'] = true if options[:show_raw_data]
|
165
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
|
166
177
|
@invoices_interface.setopts(options)
|
167
178
|
if options[:dry_run]
|
168
179
|
print_dry_run @invoices_interface.dry.list(params)
|
@@ -216,7 +227,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
216
227
|
# {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
|
217
228
|
{"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
218
229
|
{"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
219
|
-
{"
|
230
|
+
{"EXTRA" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
220
231
|
{"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
221
232
|
{"TOTAL" => lambda {|it|
|
222
233
|
format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
@@ -229,7 +240,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
229
240
|
# {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
230
241
|
{"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
231
242
|
{"NETWORK PRICE" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
232
|
-
{"
|
243
|
+
{"EXTRA PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
233
244
|
{"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
234
245
|
{"TOTAL PRICE" => lambda {|it|
|
235
246
|
format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
|
@@ -242,7 +253,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
242
253
|
# {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
243
254
|
{"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
244
255
|
{"NETWORK EST." => lambda {|it| format_money(it['estimatedNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
245
|
-
{"
|
256
|
+
{"EXTRA EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
246
257
|
{"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
247
258
|
{"TOTAL EST." => lambda {|it|
|
248
259
|
format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
|
@@ -253,6 +264,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
253
264
|
{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
|
254
265
|
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
255
266
|
{"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
|
267
|
+
{"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
|
256
268
|
]
|
257
269
|
if show_projects
|
258
270
|
columns += [
|
@@ -273,34 +285,17 @@ class Morpheus::Cli::InvoicesCommand
|
|
273
285
|
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
274
286
|
end
|
275
287
|
|
276
|
-
if options[:show_invoice_totals]
|
277
|
-
invoice_totals = json_response['invoiceTotals']
|
278
|
-
print_h2 "Line Item Totals" unless options[:totals_only]
|
279
|
-
invoice_totals_columns = [
|
280
|
-
{"Invoices" => lambda {|it| format_number(json_response['meta']['total']) rescue '' } },
|
281
|
-
{"Compute" => lambda {|it| format_money(it['actualComputeCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
282
|
-
{"Storage" => lambda {|it| format_money(it['actualStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
283
|
-
{"Network" => lambda {|it| format_money(it['actualNetworkCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
284
|
-
{"Extra" => lambda {|it| format_money(it['actualExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
285
|
-
] + (options[:show_prices] ? [
|
286
|
-
{"Compute Price" => lambda {|it| format_money(it['actualComputePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
287
|
-
{"Storage Price" => lambda {|it| format_money(it['actualStoragePrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
288
|
-
{"Network Price" => lambda {|it| format_money(it['actualNetworkPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
289
|
-
{"Extra Price" => lambda {|it| format_money(it['actualExtraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
290
|
-
] : [])
|
291
|
-
print_description_list(invoice_totals_columns, line_item_totals)
|
292
|
-
end
|
293
288
|
if options[:show_invoice_totals]
|
294
289
|
invoice_totals = json_response['invoiceTotals']
|
295
290
|
print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
|
296
291
|
|
297
292
|
if invoice_totals
|
298
293
|
cost_rows = [
|
299
|
-
{label: 'Cost', compute: invoice_totals['actualComputeCost'], memory: invoice_totals['actualMemoryCost'], storage: invoice_totals['actualStorageCost'], network: invoice_totals['actualNetworkCost'], license: invoice_totals['actualLicenseCost'], extra: invoice_totals['actualExtraCost'], running: invoice_totals['actualRunningCost'], total: invoice_totals['actualTotalCost']},
|
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']},
|
300
295
|
]
|
301
296
|
if options[:show_prices]
|
302
297
|
cost_rows += [
|
303
|
-
{label: 'Price', compute: invoice_totals['actualComputePrice'], memory: invoice_totals['actualMemoryPrice'], storage: invoice_totals['actualStoragePrice'], network: invoice_totals['actualNetworkPrice'], license: invoice_totals['actualLicensePrice'], extra: invoice_totals['actualExtraPrice'], running: invoice_totals['actualRunningPrice'], total: invoice_totals['actualTotalPrice']},
|
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']},
|
304
299
|
]
|
305
300
|
end
|
306
301
|
if options[:show_estimates]
|
@@ -316,8 +311,8 @@ class Morpheus::Cli::InvoicesCommand
|
|
316
311
|
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
317
312
|
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
318
313
|
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
319
|
-
"
|
320
|
-
"MTD" => lambda {|it| format_money(it[:running], '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]}) },
|
321
316
|
"Total".upcase => lambda {|it|
|
322
317
|
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
323
318
|
},
|
@@ -330,7 +325,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
330
325
|
cost_columns.delete("License".upcase)
|
331
326
|
end
|
332
327
|
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
333
|
-
cost_columns.delete("
|
328
|
+
cost_columns.delete("Extra".upcase)
|
334
329
|
end
|
335
330
|
print as_pretty_table(cost_rows, cost_columns, options)
|
336
331
|
else
|
@@ -338,8 +333,8 @@ class Morpheus::Cli::InvoicesCommand
|
|
338
333
|
print yellow, "No invoice totals data", reset, "\n"
|
339
334
|
end
|
340
335
|
end
|
336
|
+
print reset,"\n"
|
341
337
|
end
|
342
|
-
print reset,"\n"
|
343
338
|
return 0, nil
|
344
339
|
end
|
345
340
|
end
|
@@ -355,10 +350,10 @@ class Morpheus::Cli::InvoicesCommand
|
|
355
350
|
# options[:show_raw_data] = true
|
356
351
|
options[:max_line_items] = 10000
|
357
352
|
end
|
358
|
-
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network,
|
353
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
359
354
|
options[:show_prices] = true
|
360
355
|
end
|
361
|
-
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network,
|
356
|
+
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
362
357
|
options[:show_estimates] = true
|
363
358
|
end
|
364
359
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
@@ -415,6 +410,7 @@ EOT
|
|
415
410
|
"Type" => lambda {|it| format_invoice_ref_type(it) },
|
416
411
|
"Ref ID" => lambda {|it| it['refId'] },
|
417
412
|
"Ref Name" => lambda {|it| it['refName'] },
|
413
|
+
"Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
|
418
414
|
"Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
|
419
415
|
"Power State" => lambda {|it| format_server_power_state(it) },
|
420
416
|
"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
@@ -428,6 +424,7 @@ EOT
|
|
428
424
|
"Ref Start" => lambda {|it| format_dt(it['refStart']) },
|
429
425
|
"Ref End" => lambda {|it| format_dt(it['refEnd']) },
|
430
426
|
"Items" => lambda {|it| it['lineItems'].size rescue '' },
|
427
|
+
"Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
431
428
|
"Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
|
432
429
|
"Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
|
433
430
|
"Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
|
@@ -443,6 +440,9 @@ EOT
|
|
443
440
|
description_cols.delete("Project Name")
|
444
441
|
description_cols.delete("Project Tags")
|
445
442
|
end
|
443
|
+
if invoice['metadata'].nil? || invoice['metadata'].empty?
|
444
|
+
description_cols.delete("Tags")
|
445
|
+
end
|
446
446
|
if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
|
447
447
|
description_cols.delete("Power State")
|
448
448
|
end
|
@@ -455,7 +455,7 @@ EOT
|
|
455
455
|
"Storage" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) },
|
456
456
|
"Network" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) },
|
457
457
|
"License" => lambda {|it| format_money(it['licenseCost'], 'usd', {sigdig:options[:sigdig]}) },
|
458
|
-
"
|
458
|
+
"Extra" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
|
459
459
|
"Running" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) },
|
460
460
|
"Total Cost" => lambda {|it| format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) },
|
461
461
|
}
|
@@ -468,7 +468,7 @@ EOT
|
|
468
468
|
"Storage" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
469
469
|
"Network" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
470
470
|
"License" => lambda {|it| format_money(it['licensePrice'], 'usd', {sigdig:options[:sigdig]}) },
|
471
|
-
"
|
471
|
+
"Extra" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
472
472
|
"Running" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
473
473
|
"Total Price" => lambda {|it| format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) },
|
474
474
|
}
|
@@ -495,6 +495,7 @@ EOT
|
|
495
495
|
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
496
496
|
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
497
497
|
{"RATE" => lambda {|it| it['itemRate'] } },
|
498
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
498
499
|
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
499
500
|
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|
500
501
|
#{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
|
@@ -544,7 +545,7 @@ EOT
|
|
544
545
|
"Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
|
545
546
|
"Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
|
546
547
|
"License".upcase => lambda {|it| format_money(it[:license], 'usd', {sigdig:options[:sigdig]}) },
|
547
|
-
"
|
548
|
+
"Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
|
548
549
|
"MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
|
549
550
|
"Total".upcase => lambda {|it|
|
550
551
|
format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
|
@@ -558,7 +559,7 @@ EOT
|
|
558
559
|
cost_columns.delete("License".upcase)
|
559
560
|
end
|
560
561
|
if cost_rows.sum { |it| it[:extra].to_f } == 0
|
561
|
-
cost_columns.delete("
|
562
|
+
cost_columns.delete("Extra".upcase)
|
562
563
|
end
|
563
564
|
print as_pretty_table(cost_rows, cost_columns, options)
|
564
565
|
|
@@ -656,6 +657,7 @@ EOT
|
|
656
657
|
options = {}
|
657
658
|
params = {}
|
658
659
|
ref_ids = []
|
660
|
+
query_tags = {}
|
659
661
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
660
662
|
opts.banner = subcommand_usage()
|
661
663
|
opts.on('-a', '--all', "Display all details, costs and prices." ) do
|
@@ -664,13 +666,13 @@ EOT
|
|
664
666
|
options[:show_prices] = true
|
665
667
|
# options[:show_raw_data] = true
|
666
668
|
end
|
667
|
-
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network,
|
669
|
+
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
|
668
670
|
# options[:show_actual_costs] = true
|
669
671
|
# end
|
670
|
-
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network,
|
672
|
+
# opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
|
671
673
|
# options[:show_costs] = true
|
672
674
|
# end
|
673
|
-
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network,
|
675
|
+
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
674
676
|
options[:show_prices] = true
|
675
677
|
end
|
676
678
|
opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
|
@@ -743,6 +745,11 @@ EOT
|
|
743
745
|
opts.on('--tenant ID', String, "View invoice line items for a tenant. Default is your own account.") do |val|
|
744
746
|
params['accountId'] = val
|
745
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
|
746
753
|
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
747
754
|
options[:show_raw_data] = true
|
748
755
|
end
|
@@ -802,6 +809,11 @@ EOT
|
|
802
809
|
end
|
803
810
|
params['rawData'] = true if options[:show_raw_data]
|
804
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
|
805
817
|
@invoice_line_items_interface.setopts(options)
|
806
818
|
if options[:dry_run]
|
807
819
|
print_dry_run @invoice_line_items_interface.dry.list(params)
|
@@ -838,6 +850,7 @@ EOT
|
|
838
850
|
{"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
|
839
851
|
{"USAGE" => lambda {|it| it['itemUsage'] } },
|
840
852
|
{"RATE" => lambda {|it| it['itemRate'] } },
|
853
|
+
{"UNIT" => lambda {|it| it['rateUnit'] } },
|
841
854
|
{"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
|
842
855
|
] + (options[:show_prices] ? [
|
843
856
|
{"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
|