morpheus-cli 4.2.16 → 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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/README.md +8 -6
  4. data/lib/morpheus/api/api_client.rb +32 -14
  5. data/lib/morpheus/api/auth_interface.rb +4 -2
  6. data/lib/morpheus/api/backup_jobs_interface.rb +9 -0
  7. data/lib/morpheus/api/backups_interface.rb +16 -0
  8. data/lib/morpheus/api/deploy_interface.rb +25 -56
  9. data/lib/morpheus/api/deployments_interface.rb +43 -54
  10. data/lib/morpheus/api/doc_interface.rb +57 -0
  11. data/lib/morpheus/api/instances_interface.rb +5 -0
  12. data/lib/morpheus/api/rest_interface.rb +40 -0
  13. data/lib/morpheus/api/user_sources_interface.rb +0 -15
  14. data/lib/morpheus/api/users_interface.rb +2 -3
  15. data/lib/morpheus/benchmarking.rb +2 -2
  16. data/lib/morpheus/cli.rb +3 -1
  17. data/lib/morpheus/cli/access_token_command.rb +27 -10
  18. data/lib/morpheus/cli/apps.rb +21 -15
  19. data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
  20. data/lib/morpheus/cli/backups_command.rb +271 -0
  21. data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
  22. data/lib/morpheus/cli/cli_command.rb +92 -41
  23. data/lib/morpheus/cli/clusters.rb +0 -18
  24. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +7 -7
  25. data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
  26. data/lib/morpheus/cli/credentials.rb +13 -9
  27. data/lib/morpheus/cli/deploy.rb +374 -0
  28. data/lib/morpheus/cli/deployments.rb +521 -197
  29. data/lib/morpheus/cli/deploys.rb +271 -126
  30. data/lib/morpheus/cli/doc.rb +182 -0
  31. data/lib/morpheus/cli/error_handler.rb +23 -8
  32. data/lib/morpheus/cli/errors.rb +3 -2
  33. data/lib/morpheus/cli/image_builder_command.rb +2 -2
  34. data/lib/morpheus/cli/instances.rb +136 -17
  35. data/lib/morpheus/cli/invoices_command.rb +51 -38
  36. data/lib/morpheus/cli/library_layouts_command.rb +1 -1
  37. data/lib/morpheus/cli/login.rb +9 -3
  38. data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
  39. data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
  40. data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
  41. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  42. data/lib/morpheus/cli/mixins/print_helper.rb +110 -74
  43. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
  44. data/lib/morpheus/cli/mixins/whoami_helper.rb +19 -6
  45. data/lib/morpheus/cli/network_routers_command.rb +1 -1
  46. data/lib/morpheus/cli/option_parser.rb +48 -5
  47. data/lib/morpheus/cli/option_types.rb +1 -1
  48. data/lib/morpheus/cli/remote.rb +3 -2
  49. data/lib/morpheus/cli/roles.rb +49 -92
  50. data/lib/morpheus/cli/security_groups.rb +7 -1
  51. data/lib/morpheus/cli/service_plans_command.rb +10 -10
  52. data/lib/morpheus/cli/setup.rb +1 -1
  53. data/lib/morpheus/cli/shell.rb +7 -6
  54. data/lib/morpheus/cli/subnets_command.rb +1 -1
  55. data/lib/morpheus/cli/tenants_command.rb +133 -163
  56. data/lib/morpheus/cli/user_groups_command.rb +20 -65
  57. data/lib/morpheus/cli/user_settings_command.rb +115 -13
  58. data/lib/morpheus/cli/user_sources_command.rb +57 -24
  59. data/lib/morpheus/cli/users.rb +210 -186
  60. data/lib/morpheus/cli/version.rb +1 -1
  61. data/lib/morpheus/cli/whitelabel_settings_command.rb +29 -5
  62. data/lib/morpheus/cli/whoami.rb +113 -6
  63. data/lib/morpheus/cli/workflows.rb +1 -1
  64. data/lib/morpheus/ext/hash.rb +21 -0
  65. data/lib/morpheus/terminal.rb +1 -0
  66. metadata +12 -3
  67. 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 "Use -h to get help with this command."
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
- # when Morpheus::Cli::CommandArgumentsError
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
- # @stderr.puts "#{red}#{err.message}#{reset}"
40
- # this should probably print the whole thing as red, but just does the first line for now.
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
- @stderr.puts message_lines.join("\n") unless message_lines.empty?
45
- @stderr.puts "Use -h to get help with this command."
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
@@ -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 niftier than 1?
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, [:account, :auto_confirm, :json, :dry_run, :remote])
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, [:account, :auto_confirm, :json, :dry_run, :remote])
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, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}, :stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :backup, :backups, :stop_service, :start_service, :restart_service, :resize, :clone, :envs, :setenv, :delenv, :security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
18
- register_subcommands :wiki, :update_wiki
19
- register_subcommands :exec => :execution_request
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
- if args.count < 1
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 params.empty? && options[:owner].nil?
583
- print_red_alert "Specify at least one option to update"
584
- puts optparse
585
- exit 1
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, Other" ) do
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, Other" ) do
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, Other" ) do
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
- {"OTHER" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
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
- {"OTHER PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
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
- {"OTHER EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
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
- "Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
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("Other".upcase)
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, Other" ) do
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, Other" ) do
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
- "Other" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
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
- "Other" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
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
- "Other".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
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("Other".upcase)
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, Other" ) do
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, Other" ) do
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, Other" ) do
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]}) } },