morpheus-cli 4.2.16 → 4.2.21

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 (76) 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 +44 -55
  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 +4 -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/blueprints_command.rb +27 -61
  22. data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
  23. data/lib/morpheus/cli/budgets_command.rb +4 -4
  24. data/lib/morpheus/cli/cli_command.rb +99 -41
  25. data/lib/morpheus/cli/cloud_resource_pools_command.rb +16 -0
  26. data/lib/morpheus/cli/clouds.rb +7 -10
  27. data/lib/morpheus/cli/clusters.rb +0 -18
  28. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +7 -7
  29. data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
  30. data/lib/morpheus/cli/credentials.rb +13 -9
  31. data/lib/morpheus/cli/deploy.rb +374 -0
  32. data/lib/morpheus/cli/deployments.rb +521 -197
  33. data/lib/morpheus/cli/deploys.rb +271 -126
  34. data/lib/morpheus/cli/doc.rb +182 -0
  35. data/lib/morpheus/cli/error_handler.rb +23 -8
  36. data/lib/morpheus/cli/errors.rb +3 -2
  37. data/lib/morpheus/cli/image_builder_command.rb +2 -2
  38. data/lib/morpheus/cli/instances.rb +136 -17
  39. data/lib/morpheus/cli/invoices_command.rb +59 -47
  40. data/lib/morpheus/cli/jobs_command.rb +2 -2
  41. data/lib/morpheus/cli/library_instance_types_command.rb +17 -3
  42. data/lib/morpheus/cli/library_layouts_command.rb +1 -1
  43. data/lib/morpheus/cli/login.rb +9 -3
  44. data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
  45. data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
  46. data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
  47. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  48. data/lib/morpheus/cli/mixins/print_helper.rb +110 -74
  49. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
  50. data/lib/morpheus/cli/mixins/whoami_helper.rb +19 -6
  51. data/lib/morpheus/cli/network_routers_command.rb +1 -1
  52. data/lib/morpheus/cli/option_parser.rb +48 -5
  53. data/lib/morpheus/cli/option_types.rb +1 -1
  54. data/lib/morpheus/cli/projects_command.rb +7 -7
  55. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  56. data/lib/morpheus/cli/remote.rb +3 -2
  57. data/lib/morpheus/cli/roles.rb +49 -92
  58. data/lib/morpheus/cli/security_groups.rb +7 -1
  59. data/lib/morpheus/cli/service_plans_command.rb +10 -10
  60. data/lib/morpheus/cli/setup.rb +1 -1
  61. data/lib/morpheus/cli/shell.rb +7 -6
  62. data/lib/morpheus/cli/subnets_command.rb +1 -1
  63. data/lib/morpheus/cli/tasks.rb +24 -10
  64. data/lib/morpheus/cli/tenants_command.rb +133 -163
  65. data/lib/morpheus/cli/user_groups_command.rb +20 -65
  66. data/lib/morpheus/cli/user_settings_command.rb +115 -13
  67. data/lib/morpheus/cli/user_sources_command.rb +57 -24
  68. data/lib/morpheus/cli/users.rb +210 -186
  69. data/lib/morpheus/cli/version.rb +1 -1
  70. data/lib/morpheus/cli/whitelabel_settings_command.rb +29 -5
  71. data/lib/morpheus/cli/whoami.rb +113 -6
  72. data/lib/morpheus/cli/workflows.rb +11 -8
  73. data/lib/morpheus/ext/hash.rb +21 -0
  74. data/lib/morpheus/terminal.rb +1 -0
  75. metadata +12 -3
  76. 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,16 +35,16 @@ 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
- opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
47
+ opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
47
48
  params['refType'] ||= []
48
49
  values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
49
50
  values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
@@ -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)
@@ -182,9 +193,8 @@ class Morpheus::Cli::InvoicesCommand
182
193
  subtitles += parse_list_subtitles(options)
183
194
  print_h1 title, subtitles
184
195
  if invoices.empty?
185
- unless options[:totals_only]
186
- print cyan,"No invoices found.",reset,"\n"
187
- end
196
+ print cyan,"No invoices found.",reset,"\n"
197
+ print reset,"\n"
188
198
  else
189
199
  # current_date = Time.now
190
200
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
@@ -216,7 +226,7 @@ class Morpheus::Cli::InvoicesCommand
216
226
  # {"MEMORY" => lambda {|it| format_money(it['memoryCost']) } },
217
227
  {"STORAGE" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) } },
218
228
  {"NETWORK" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) } },
219
- {"OTHER" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
229
+ {"EXTRA" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) } },
220
230
  {"MTD" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) } },
221
231
  {"TOTAL" => lambda {|it|
222
232
  format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
@@ -229,7 +239,7 @@ class Morpheus::Cli::InvoicesCommand
229
239
  # {"MEMORY PRICE" => lambda {|it| format_money(it['memoryPrice'], 'usd', {sigdig:options[:sigdig]}) } },
230
240
  {"STORAGE PRICE" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) } },
231
241
  {"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]}) } },
242
+ {"EXTRA PRICE" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) } },
233
243
  {"MTD PRICE" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) } },
234
244
  {"TOTAL PRICE" => lambda {|it|
235
245
  format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) + ((it['totalCost'].to_f > 0 && it['totalCost'] != it['runningCost']) ? " (Projected)" : "")
@@ -242,7 +252,7 @@ class Morpheus::Cli::InvoicesCommand
242
252
  # {"MEMORY EST." => lambda {|it| format_money(it['estimatedMemoryCost'], 'usd', {sigdig:options[:sigdig]}) } },
243
253
  {"STORAGE EST." => lambda {|it| format_money(it['estimatedStorageCost'], 'usd', {sigdig:options[:sigdig]}) } },
244
254
  {"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]}) } },
255
+ {"EXTRA EST." => lambda {|it| format_money(it['estimatedExtraCost'], 'usd', {sigdig:options[:sigdig]}) } },
246
256
  {"MTD EST." => lambda {|it| format_money(it['estimatedRunningCost'], 'usd', {sigdig:options[:sigdig]}) } },
247
257
  {"TOTAL EST." => lambda {|it|
248
258
  format_money(it['estimatedTotalCost'], 'usd', {sigdig:options[:sigdig]}) + ((it['estimatedTotalCost'].to_f > 0 && it['estimatedTotalCost'] != it['estimatedRunningCost']) ? " (Projected)" : "")
@@ -253,6 +263,7 @@ class Morpheus::Cli::InvoicesCommand
253
263
  {"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
254
264
  {"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
255
265
  {"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
266
+ {"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
256
267
  ]
257
268
  if show_projects
258
269
  columns += [
@@ -273,40 +284,23 @@ class Morpheus::Cli::InvoicesCommand
273
284
  print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
274
285
  end
275
286
 
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
287
  if options[:show_invoice_totals]
294
288
  invoice_totals = json_response['invoiceTotals']
295
289
  print_h2 "Invoice Totals (#{format_number(json_response['meta']['total']) rescue ''})"
296
290
 
297
291
  if invoice_totals
298
292
  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']},
293
+ {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
294
  ]
301
295
  if options[:show_prices]
302
296
  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']},
297
+ {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
298
  ]
305
299
  end
306
300
  if options[:show_estimates]
307
301
  cost_rows += [
308
- {label: 'Estimated Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
309
- {label: 'Estimated Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
302
+ {label: 'Metered Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
303
+ {label: 'Metered Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
310
304
  ]
311
305
  end
312
306
  cost_columns = {
@@ -316,8 +310,8 @@ class Morpheus::Cli::InvoicesCommand
316
310
  "Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
317
311
  "Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
318
312
  "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]}) },
313
+ "Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
314
+ "MTD".upcase => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
321
315
  "Total".upcase => lambda {|it|
322
316
  format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
323
317
  },
@@ -330,7 +324,7 @@ class Morpheus::Cli::InvoicesCommand
330
324
  cost_columns.delete("License".upcase)
331
325
  end
332
326
  if cost_rows.sum { |it| it[:extra].to_f } == 0
333
- cost_columns.delete("Other".upcase)
327
+ cost_columns.delete("Extra".upcase)
334
328
  end
335
329
  print as_pretty_table(cost_rows, cost_columns, options)
336
330
  else
@@ -338,8 +332,8 @@ class Morpheus::Cli::InvoicesCommand
338
332
  print yellow, "No invoice totals data", reset, "\n"
339
333
  end
340
334
  end
335
+ print reset,"\n"
341
336
  end
342
- print reset,"\n"
343
337
  return 0, nil
344
338
  end
345
339
  end
@@ -355,10 +349,10 @@ class Morpheus::Cli::InvoicesCommand
355
349
  # options[:show_raw_data] = true
356
350
  options[:max_line_items] = 10000
357
351
  end
358
- opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
352
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
359
353
  options[:show_prices] = true
360
354
  end
361
- opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Other" ) do
355
+ opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
362
356
  options[:show_estimates] = true
363
357
  end
364
358
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
@@ -415,6 +409,7 @@ EOT
415
409
  "Type" => lambda {|it| format_invoice_ref_type(it) },
416
410
  "Ref ID" => lambda {|it| it['refId'] },
417
411
  "Ref Name" => lambda {|it| it['refName'] },
412
+ "Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
418
413
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
419
414
  "Power State" => lambda {|it| format_server_power_state(it) },
420
415
  "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
@@ -428,6 +423,7 @@ EOT
428
423
  "Ref Start" => lambda {|it| format_dt(it['refStart']) },
429
424
  "Ref End" => lambda {|it| format_dt(it['refEnd']) },
430
425
  "Items" => lambda {|it| it['lineItems'].size rescue '' },
426
+ "Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
431
427
  "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
432
428
  "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
433
429
  "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
@@ -443,6 +439,9 @@ EOT
443
439
  description_cols.delete("Project Name")
444
440
  description_cols.delete("Project Tags")
445
441
  end
442
+ if invoice['metadata'].nil? || invoice['metadata'].empty?
443
+ description_cols.delete("Tags")
444
+ end
446
445
  if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
447
446
  description_cols.delete("Power State")
448
447
  end
@@ -455,7 +454,7 @@ EOT
455
454
  "Storage" => lambda {|it| format_money(it['storageCost'], 'usd', {sigdig:options[:sigdig]}) },
456
455
  "Network" => lambda {|it| format_money(it['networkCost'], 'usd', {sigdig:options[:sigdig]}) },
457
456
  "License" => lambda {|it| format_money(it['licenseCost'], 'usd', {sigdig:options[:sigdig]}) },
458
- "Other" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
457
+ "Extra" => lambda {|it| format_money(it['extraCost'], 'usd', {sigdig:options[:sigdig]}) },
459
458
  "Running" => lambda {|it| format_money(it['runningCost'], 'usd', {sigdig:options[:sigdig]}) },
460
459
  "Total Cost" => lambda {|it| format_money(it['totalCost'], 'usd', {sigdig:options[:sigdig]}) },
461
460
  }
@@ -468,7 +467,7 @@ EOT
468
467
  "Storage" => lambda {|it| format_money(it['storagePrice'], 'usd', {sigdig:options[:sigdig]}) },
469
468
  "Network" => lambda {|it| format_money(it['networkPrice'], 'usd', {sigdig:options[:sigdig]}) },
470
469
  "License" => lambda {|it| format_money(it['licensePrice'], 'usd', {sigdig:options[:sigdig]}) },
471
- "Other" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
470
+ "Extra" => lambda {|it| format_money(it['extraPrice'], 'usd', {sigdig:options[:sigdig]}) },
472
471
  "Running" => lambda {|it| format_money(it['runningPrice'], 'usd', {sigdig:options[:sigdig]}) },
473
472
  "Total Price" => lambda {|it| format_money(it['totalPrice'], 'usd', {sigdig:options[:sigdig]}) },
474
473
  }
@@ -495,6 +494,7 @@ EOT
495
494
  {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
496
495
  {"USAGE" => lambda {|it| it['itemUsage'] } },
497
496
  {"RATE" => lambda {|it| it['itemRate'] } },
497
+ {"UNIT" => lambda {|it| it['rateUnit'] } },
498
498
  {"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
499
499
  {"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },
500
500
  #{"TAX" => lambda {|it| format_money(it['itemTax'], 'usd', {sigdig:options[:sigdig]}) } },
@@ -533,8 +533,8 @@ EOT
533
533
  end
534
534
  if options[:show_estimates]
535
535
  cost_rows += [
536
- {label: 'Estimated Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
537
- {label: 'Estimated Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
536
+ {label: 'Metered Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
537
+ {label: 'Metered Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
538
538
  ]
539
539
  end
540
540
  cost_columns = {
@@ -544,7 +544,7 @@ EOT
544
544
  "Storage".upcase => lambda {|it| format_money(it[:storage], 'usd', {sigdig:options[:sigdig]}) },
545
545
  "Network".upcase => lambda {|it| format_money(it[:network], 'usd', {sigdig:options[:sigdig]}) },
546
546
  "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]}) },
547
+ "Extra".upcase => lambda {|it| format_money(it[:extra], 'usd', {sigdig:options[:sigdig]}) },
548
548
  "MTD" => lambda {|it| format_money(it[:running], 'usd', {sigdig:options[:sigdig]}) },
549
549
  "Total".upcase => lambda {|it|
550
550
  format_money(it[:total], 'usd', {sigdig:options[:sigdig]}) + ((it[:total].to_f > 0 && it[:total] != it[:running]) ? " (Projected)" : "")
@@ -558,7 +558,7 @@ EOT
558
558
  cost_columns.delete("License".upcase)
559
559
  end
560
560
  if cost_rows.sum { |it| it[:extra].to_f } == 0
561
- cost_columns.delete("Other".upcase)
561
+ cost_columns.delete("Extra".upcase)
562
562
  end
563
563
  print as_pretty_table(cost_rows, cost_columns, options)
564
564
 
@@ -656,6 +656,7 @@ EOT
656
656
  options = {}
657
657
  params = {}
658
658
  ref_ids = []
659
+ query_tags = {}
659
660
  optparse = Morpheus::Cli::OptionParser.new do |opts|
660
661
  opts.banner = subcommand_usage()
661
662
  opts.on('-a', '--all', "Display all details, costs and prices." ) do
@@ -664,13 +665,13 @@ EOT
664
665
  options[:show_prices] = true
665
666
  # options[:show_raw_data] = true
666
667
  end
667
- # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Other" ) do
668
+ # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
668
669
  # options[:show_actual_costs] = true
669
670
  # end
670
- # opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Other" ) do
671
+ # opts.on('--costs', '--costs', "Display all costs: Compute, Storage, Network, Extra" ) do
671
672
  # options[:show_costs] = true
672
673
  # end
673
- opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Other" ) do
674
+ opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
674
675
  options[:show_prices] = true
675
676
  end
676
677
  opts.on('--invoice-id ID', String, "Filter by Invoice ID") do |val|
@@ -681,7 +682,7 @@ EOT
681
682
  params['externalId'] ||= []
682
683
  params['externalId'] << val
683
684
  end
684
- opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
685
+ opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
685
686
  params['refType'] ||= []
686
687
  values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
687
688
  values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
@@ -743,6 +744,11 @@ EOT
743
744
  opts.on('--tenant ID', String, "View invoice line items for a tenant. Default is your own account.") do |val|
744
745
  params['accountId'] = val
745
746
  end
747
+ # opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
748
+ # k,v = val.split("=")
749
+ # query_tags[k] ||= []
750
+ # query_tags[k] << v
751
+ # end
746
752
  opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
747
753
  options[:show_raw_data] = true
748
754
  end
@@ -802,6 +808,11 @@ EOT
802
808
  end
803
809
  params['rawData'] = true if options[:show_raw_data]
804
810
  params['refId'] = ref_ids unless ref_ids.empty?
811
+ if query_tags && !query_tags.empty?
812
+ query_tags.each do |k,v|
813
+ params['tags.' + k] = v
814
+ end
815
+ end
805
816
  @invoice_line_items_interface.setopts(options)
806
817
  if options[:dry_run]
807
818
  print_dry_run @invoice_line_items_interface.dry.list(params)
@@ -838,6 +849,7 @@ EOT
838
849
  {"USAGE CATEGORY" => lambda {|it| it['usageCategory'] } },
839
850
  {"USAGE" => lambda {|it| it['itemUsage'] } },
840
851
  {"RATE" => lambda {|it| it['itemRate'] } },
852
+ {"UNIT" => lambda {|it| it['rateUnit'] } },
841
853
  {"COST" => lambda {|it| format_money(it['itemCost'], 'usd', {sigdig:options[:sigdig]}) } },
842
854
  ] + (options[:show_prices] ? [
843
855
  {"PRICE" => lambda {|it| format_money(it['itemPrice'], 'usd', {sigdig:options[:sigdig]}) } },