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.
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]}) } },