morpheus-cli 4.2.16 → 4.2.21

Sign up to get free protection for your applications and to get access to all the features.
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]}) } },