aspera-cli 4.18.1 → 4.20.0

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 (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +33 -0
  4. data/CONTRIBUTING.md +17 -12
  5. data/README.md +396 -185
  6. data/bin/asession +26 -19
  7. data/examples/build_exec +74 -0
  8. data/examples/{rubyc → build_exec_rubyc} +18 -2
  9. data/examples/get_proto_file.rb +7 -0
  10. data/lib/aspera/agent/alpha.rb +8 -8
  11. data/lib/aspera/agent/base.rb +4 -18
  12. data/lib/aspera/agent/connect.rb +14 -13
  13. data/lib/aspera/agent/direct.rb +123 -120
  14. data/lib/aspera/agent/httpgw.rb +2 -3
  15. data/lib/aspera/agent/node.rb +10 -10
  16. data/lib/aspera/agent/trsdk.rb +17 -20
  17. data/lib/aspera/api/alee.rb +15 -0
  18. data/lib/aspera/api/aoc.rb +128 -99
  19. data/lib/aspera/api/ats.rb +1 -1
  20. data/lib/aspera/api/cos_node.rb +1 -1
  21. data/lib/aspera/api/httpgw.rb +104 -64
  22. data/lib/aspera/api/node.rb +33 -12
  23. data/lib/aspera/ascmd.rb +56 -48
  24. data/lib/aspera/ascp/installation.rb +142 -70
  25. data/lib/aspera/ascp/management.rb +7 -3
  26. data/lib/aspera/ascp/products.rb +13 -7
  27. data/lib/aspera/assert.rb +10 -5
  28. data/lib/aspera/cli/formatter.rb +42 -26
  29. data/lib/aspera/cli/hints.rb +2 -1
  30. data/lib/aspera/cli/info.rb +12 -10
  31. data/lib/aspera/cli/main.rb +16 -13
  32. data/lib/aspera/cli/manager.rb +15 -10
  33. data/lib/aspera/cli/plugin.rb +17 -31
  34. data/lib/aspera/cli/plugin_factory.rb +10 -1
  35. data/lib/aspera/cli/plugins/alee.rb +3 -3
  36. data/lib/aspera/cli/plugins/aoc.rb +222 -194
  37. data/lib/aspera/cli/plugins/ats.rb +16 -14
  38. data/lib/aspera/cli/plugins/config.rb +66 -53
  39. data/lib/aspera/cli/plugins/console.rb +3 -3
  40. data/lib/aspera/cli/plugins/faspex.rb +11 -21
  41. data/lib/aspera/cli/plugins/faspex5.rb +44 -42
  42. data/lib/aspera/cli/plugins/faspio.rb +2 -2
  43. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  44. data/lib/aspera/cli/plugins/node.rb +155 -96
  45. data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
  46. data/lib/aspera/cli/plugins/preview.rb +8 -9
  47. data/lib/aspera/cli/plugins/server.rb +6 -10
  48. data/lib/aspera/cli/plugins/shares.rb +13 -9
  49. data/lib/aspera/cli/sync_actions.rb +72 -31
  50. data/lib/aspera/cli/transfer_agent.rb +13 -14
  51. data/lib/aspera/cli/transfer_progress.rb +36 -18
  52. data/lib/aspera/cli/version.rb +1 -1
  53. data/lib/aspera/command_line_builder.rb +3 -4
  54. data/lib/aspera/coverage.rb +13 -1
  55. data/lib/aspera/environment.rb +59 -10
  56. data/lib/aspera/faspex_gw.rb +3 -3
  57. data/lib/aspera/json_rpc.rb +1 -1
  58. data/lib/aspera/keychain/encrypted_hash.rb +2 -0
  59. data/lib/aspera/keychain/macos_security.rb +7 -12
  60. data/lib/aspera/log.rb +4 -4
  61. data/lib/aspera/node_simulator.rb +1 -1
  62. data/lib/aspera/oauth/base.rb +39 -45
  63. data/lib/aspera/oauth/factory.rb +11 -4
  64. data/lib/aspera/oauth/generic.rb +4 -8
  65. data/lib/aspera/oauth/jwt.rb +4 -4
  66. data/lib/aspera/oauth/url_json.rb +3 -2
  67. data/lib/aspera/oauth/web.rb +10 -6
  68. data/lib/aspera/persistency_action_once.rb +16 -8
  69. data/lib/aspera/preview/utils.rb +5 -16
  70. data/lib/aspera/rest.rb +100 -76
  71. data/lib/aspera/secret_hider.rb +3 -2
  72. data/lib/aspera/ssh.rb +1 -1
  73. data/lib/aspera/transfer/faux_file.rb +7 -5
  74. data/lib/aspera/transfer/parameters.rb +41 -35
  75. data/lib/aspera/transfer/spec.rb +16 -18
  76. data/lib/aspera/transfer/sync.rb +51 -50
  77. data/lib/aspera/transfer/uri.rb +1 -1
  78. data/lib/aspera/uri_reader.rb +1 -1
  79. data/lib/aspera/web_auth.rb +166 -18
  80. data/lib/aspera/web_server_simple.rb +27 -15
  81. data/lib/transfer_pb.rb +84 -0
  82. data/lib/transfer_services_pb.rb +82 -0
  83. data.tar.gz.sig +0 -0
  84. metadata +25 -6
  85. metadata.gz.sig +0 -0
@@ -94,7 +94,7 @@ module Aspera
94
94
  # create formatter, in case there is an exception, it is used to display.
95
95
  @plug_init[:formatter] = Formatter.new
96
96
  # create command line manager with arguments
97
- @plug_init[:options] = Manager.new(PROGRAM_NAME, @argv)
97
+ @plug_init[:options] = Manager.new(Info::CMD_NAME, @argv)
98
98
  # formatter adds options
99
99
  @plug_init[:formatter].declare_options(options)
100
100
  ExtendedValue.instance.default_decoder = options.get_option(:struct_parser)
@@ -102,16 +102,18 @@ module Aspera
102
102
  current_prog_name = File.basename($PROGRAM_NAME)
103
103
  formatter.display_message(
104
104
  :error,
105
- "#{Formatter::WARNING_FLASH} Please use '#{PROGRAM_NAME}' instead of '#{current_prog_name}'") unless current_prog_name.eql?(PROGRAM_NAME)
105
+ "#{Formatter::WARNING_FLASH} Please use '#{Info::CMD_NAME}' instead of '#{current_prog_name}'") unless current_prog_name.eql?(Info::CMD_NAME)
106
106
  # declare and parse global options
107
107
  declare_global_options
108
108
  # the Config plugin adds the @preset parser, so declare before TransferAgent which may use it
109
- @plug_init[:config] = Plugins::Config.new(**@plug_init, gem: GEM_NAME, name: PROGRAM_NAME, help: DOC_URL, version: Cli::VERSION)
109
+ @plug_init[:config] = Plugins::Config.new(**@plug_init, man_header: false)
110
110
  @plug_init[:persistency] = @plug_init[:config].persistency
111
111
  # data persistency
112
112
  Aspera.assert(@plug_init[:persistency]){'missing persistency object'}
113
113
  # the TransferAgent plugin may use the @preset parser
114
114
  @plug_init[:config].transfer = @plug_init[:transfer] = TransferAgent.new(options, config)
115
+ # add commands for config plugin after all options have been added
116
+ @plug_init[:config].add_manual_header(false)
115
117
  nil_keys = @plug_init.select{|_, value|value.nil?}.keys
116
118
  Aspera.assert(nil_keys.empty?){"nil : #{nil_keys}"}
117
119
  Log.log.debug('plugin env created'.red)
@@ -123,23 +125,23 @@ module Aspera
123
125
  t = ' ' * 8
124
126
  return <<~END_OF_BANNER
125
127
  NAME
126
- #{t}#{PROGRAM_NAME} -- a command line tool for Aspera Applications (v#{Cli::VERSION})
128
+ #{t}#{Info::CMD_NAME} -- a command line tool for Aspera Applications (v#{Cli::VERSION})
127
129
 
128
130
  SYNOPSIS
129
- #{t}#{PROGRAM_NAME} COMMANDS [OPTIONS] [ARGS]
131
+ #{t}#{Info::CMD_NAME} COMMANDS [OPTIONS] [ARGS]
130
132
 
131
133
  DESCRIPTION
132
134
  #{t}Use Aspera application to perform operations on command line.
133
- #{t}Documentation and examples: #{GEM_URL}
134
- #{t}execute: #{PROGRAM_NAME} conf doc
135
- #{t}or visit: #{DOC_URL}
136
- #{t}source repo: #{SRC_URL}
135
+ #{t}Documentation and examples: #{Info::GEM_URL}
136
+ #{t}execute: #{Info::CMD_NAME} conf doc
137
+ #{t}or visit: #{Info::DOC_URL}
138
+ #{t}source repo: #{Info::SRC_URL}
137
139
 
138
140
  ENVIRONMENT VARIABLES
139
141
  #{t}Any option can be set as an environment variable, refer to the manual
140
142
 
141
143
  COMMANDS
142
- #{t}To list first level commands, execute: #{PROGRAM_NAME}
144
+ #{t}To list first level commands, execute: #{Info::CMD_NAME}
143
145
  #{t}Note that commands can be written shortened (provided it is unique).
144
146
 
145
147
  OPTIONS
@@ -199,16 +201,17 @@ module Aspera
199
201
 
200
202
  def exit_with_usage(include_all_plugins)
201
203
  Log.log.debug{"exit_with_usage(#{include_all_plugins})".bg_red}
202
- # display main plugin options
204
+ # display main plugin options (+config)
203
205
  formatter.display_message(:error, options.parser)
204
206
  if include_all_plugins
205
207
  # list plugins that have a "require" field, i.e. all but main plugin
206
208
  PluginFactory.instance.plugin_list.each do |plugin_name_sym|
209
+ # config was already included in the global options
207
210
  next if plugin_name_sym.eql?(COMMAND_CONFIG)
208
211
  # override main option parser with a brand new, to avoid having global options
209
212
  plugin_env = @plug_init.clone
210
213
  plugin_env[:only_manual] = true # force declaration of all options
211
- plugin_env[:options] = Manager.new(PROGRAM_NAME)
214
+ plugin_env[:options] = Manager.new(Info::CMD_NAME)
212
215
  plugin_env[:options].parser.banner = '' # remove default banner
213
216
  get_plugin_instance_with_options(plugin_name_sym, plugin_env)
214
217
  # display generated help for plugin options
@@ -223,7 +226,7 @@ module Aspera
223
226
  # early debug for parser
224
227
  # Note: does not accept shortcuts
225
228
  def early_debug_setup
226
- Log.instance.program_name = PROGRAM_NAME
229
+ Log.instance.program_name = Info::CMD_NAME
227
230
  @argv.each do |arg|
228
231
  case arg
229
232
  when '--' then break
@@ -20,7 +20,7 @@ module Aspera
20
20
  @method = method_name
21
21
  @option_name = option_name
22
22
  @has_writer = @object.respond_to?(writer_method)
23
- Log.log.debug{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
23
+ Log.log.trace1{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
24
24
  Aspera.assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
25
25
  end
26
26
 
@@ -328,7 +328,7 @@ module Aspera
328
328
  if opt[:read_write].eql?(:accessor)
329
329
  Aspera.assert_type(handler, Hash)
330
330
  Aspera.assert(handler.keys.sort.eql?(%i[m o]))
331
- Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
331
+ Log.log.trace1{"set attr obj: #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
332
332
  opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
333
333
  end
334
334
  set_option(option_symbol, default, where: 'default') unless default.nil?
@@ -372,7 +372,7 @@ module Aspera
372
372
  @parser.on(*on_args, &block)
373
373
  else Aspera.error_unexpected_value(values)
374
374
  end
375
- Log.log.debug{"on_args=#{on_args}"}
375
+ Log.log.trace1{"on_args=#{on_args}"}
376
376
  end
377
377
 
378
378
  # Adds each of the keys of specified hash as an option
@@ -386,6 +386,11 @@ module Aspera
386
386
  end
387
387
  end
388
388
 
389
+ # allows a plugin to add an argument as next argument to process
390
+ def unshift_next_argument(argument)
391
+ @unprocessed_cmd_line_arguments.unshift(argument)
392
+ end
393
+
389
394
  # check if there were unprocessed values to generate error
390
395
  def command_or_arg_empty?
391
396
  return @unprocessed_cmd_line_arguments.empty?
@@ -435,7 +440,7 @@ module Aspera
435
440
 
436
441
  # removes already known options from the list
437
442
  def parse_options!
438
- Log.log.debug('parse_options!'.red)
443
+ Log.log.trace1('parse_options!'.red)
439
444
  # first conf file, then env var
440
445
  consume_option_pairs(@option_pairs_batch, 'set')
441
446
  consume_option_pairs(@option_pairs_env, 'env')
@@ -443,16 +448,16 @@ module Aspera
443
448
  unknown_options = []
444
449
  begin
445
450
  # remove known options one by one, exception if unknown
446
- Log.log.debug('before parse'.red)
451
+ Log.log.trace1('before parse'.red)
447
452
  @parser.parse!(@unprocessed_cmd_line_options)
448
- Log.log.debug('After parse'.red)
453
+ Log.log.trace1('After parse'.red)
449
454
  rescue OptionParser::InvalidOption => e
450
- Log.log.debug{"InvalidOption #{e}".red}
455
+ Log.log.trace1{"InvalidOption #{e}".red}
451
456
  # save for later processing
452
457
  unknown_options.push(e.args.first)
453
458
  retry
454
459
  end
455
- Log.log.debug{"remains: #{unknown_options}"}
460
+ Log.log.trace1{"remains: #{unknown_options}"}
456
461
  # set unprocessed options for next time
457
462
  @unprocessed_cmd_line_options = unknown_options
458
463
  end
@@ -538,7 +543,7 @@ module Aspera
538
543
  # @param unprocessed_options [Array] list of options to apply (key_sym,value)
539
544
  # @param where [String] where the options come from
540
545
  def consume_option_pairs(unprocessed_options, where)
541
- Log.log.debug{"consume_option_pairs: #{where}"}
546
+ Log.log.trace1{"consume_option_pairs: #{where}"}
542
547
  options_to_set = {}
543
548
  unprocessed_options.each do |k, v|
544
549
  if @declared_options.key?(k)
@@ -548,7 +553,7 @@ module Aspera
548
553
  end
549
554
  options_to_set[k] = v
550
555
  else
551
- Log.log.debug{"unprocessed: #{k}: #{v}"}
556
+ Log.log.trace1{"unprocessed: #{k}: #{v}"}
552
557
  end
553
558
  end
554
559
  options_to_set.each do |k, v|
@@ -5,7 +5,7 @@ require 'aspera/assert'
5
5
 
6
6
  module Aspera
7
7
  module Cli
8
- # base class for plugins modules
8
+ # Base class for plugins
9
9
  class Plugin
10
10
  # operations without id
11
11
  GLOBAL_OPS = %i[create list].freeze
@@ -24,10 +24,7 @@ module Aspera
24
24
 
25
25
  class << self
26
26
  def declare_generic_options(options)
27
- options.declare(:query, 'Additional filter for for some commands (list/delete)', types: Hash)
28
- options.declare(
29
- :value, 'Value for create, update, list filter', types: Hash,
30
- deprecation: '(4.14) Use positional value for create/modify or option: query for list/delete')
27
+ options.declare(:query, 'Additional filter for for some commands (list/delete)', types: [Hash, Array])
31
28
  options.declare(:property, 'Name of property to set (modify operation)')
32
29
  options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
33
30
  options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
@@ -36,25 +33,29 @@ module Aspera
36
33
 
37
34
  attr_accessor(*INIT_PARAMS)
38
35
 
39
- def initialize(options:, transfer:, config:, formatter:, persistency:, only_manual:)
36
+ def initialize(options:, transfer:, config:, formatter:, persistency:, only_manual:, man_header: true)
37
+ # check presence in descendant of mandatory method and constant
38
+ Aspera.assert(respond_to?(:execute_action)){"Missing method 'execute_action' in #{self.class}"}
39
+ Aspera.assert(self.class.constants.include?(:ACTIONS)){"Missing constant 'ACTIONS' in #{self.class}"}
40
40
  @options = options
41
41
  @transfer = transfer
42
42
  @config = config
43
43
  @formatter = formatter
44
44
  @persistency = persistency
45
45
  @only_manual = only_manual
46
- # check presence in descendant of mandatory method and constant
47
- Aspera.assert(respond_to?(:execute_action)){"Missing method 'execute_action' in #{self.class}"}
48
- Aspera.assert(self.class.constants.include?(:ACTIONS)){'ACTIONS shall be redefined by subclass'}
46
+ add_manual_header if man_header
47
+ end
48
+
49
+ def add_manual_header(has_options = true)
49
50
  # manual header for all plugins
50
51
  options.parser.separator('')
51
52
  options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
52
53
  options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
53
- options.parser.separator('OPTIONS:')
54
+ options.parser.separator('OPTIONS:') if has_options
54
55
  end
55
56
 
57
+ # @return a hash of instance variables
56
58
  def init_params
57
- # return a hash of instance variables
58
59
  INIT_PARAMS.map{|p| [p, instance_variable_get("@#{p}".to_sym)]}.to_h
59
60
  end
60
61
 
@@ -156,7 +157,7 @@ module Aspera
156
157
  when :create
157
158
  raise 'cannot create singleton' if is_singleton
158
159
  return do_bulk_operation(command: command, descr: 'data', fields: display_fields) do |params|
159
- rest_api.create(res_class_path, params)[:data]
160
+ rest_api.create(res_class_path, params)
160
161
  end
161
162
  when :delete
162
163
  raise 'cannot delete singleton' if is_singleton
@@ -171,9 +172,9 @@ module Aspera
171
172
  {'id' => one_id}
172
173
  end
173
174
  when :show
174
- return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
175
+ return {type: :single_object, data: rest_api.read(one_res_path), fields: display_fields}
175
176
  when :list
176
- resp = rest_api.read(res_class_path, query_read_delete)
177
+ resp = rest_api.call(operation: 'GET', subpath: res_class_path, headers: {'Accept' => 'application/json'}, query: query_read_delete)
177
178
  return Main.result_empty if resp[:http].code == '204'
178
179
  data = resp[:data]
179
180
  # TODO: not generic : which application is this for ?
@@ -214,7 +215,7 @@ module Aspera
214
215
  return entity_command(command, rest_api, res_class_path, **opts, &block)
215
216
  end
216
217
 
217
- # query parameters in URL suitable for REST list/GET and delete/DELETE
218
+ # query parameters in URL suitable for REST: list/GET and delete/DELETE
218
219
  def query_read_delete(default: nil)
219
220
  query = options.get_option(:query)
220
221
  # dup default, as it could be frozen
@@ -229,30 +230,15 @@ module Aspera
229
230
  return query
230
231
  end
231
232
 
232
- # TODO: when deprecation of `value` is completed: remove this method, replace with options.get_option(:query)
233
- # deprecation: 4.14
234
- def query_option(mandatory: false, default: nil)
235
- option = :value
236
- value = options.get_option(option, mandatory: false)
237
- if value.nil?
238
- option = :query
239
- value = options.get_option(option, mandatory: mandatory, default: default)
240
- end
241
- return value
242
- end
243
-
244
233
  # Retrieves an extended value from command line, used for creation or modification of entities
245
234
  # @param command [Symbol] command name for error message
246
235
  # @param type [Class] expected type of value, either a Class, an Array of Class
247
236
  # @param bulk [Boolean] if true, value must be an Array of <type>
248
237
  # @param default [Object] default value if not provided
249
- # TODO: when deprecation of `value` is completed: remove line with :value
250
238
  def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
251
- value = options.get_option(:value)
252
- Log.log.warn("option `value` is deprecated. Use positional parameter for #{command}") unless value.nil?
253
239
  value = options.get_next_argument(
254
240
  "parameters for #{command}#{description.nil? ? '' : " (#{description})"}", mandatory: default.nil?,
255
- validation: bulk ? Array : type) if value.nil?
241
+ validation: bulk ? Array : type)
256
242
  value = default if value.nil?
257
243
  unless type.nil?
258
244
  type = [type] unless type.is_a?(Array)
@@ -3,7 +3,7 @@
3
3
  require 'singleton'
4
4
  module Aspera
5
5
  module Cli
6
- # option is retrieved from another object using accessor
6
+ # Instantiate plugin from well-known locations
7
7
  class PluginFactory
8
8
  include Singleton
9
9
 
@@ -19,14 +19,17 @@ module Aspera
19
19
  @plugins = {}
20
20
  end
21
21
 
22
+ # @return list of registered plugins
22
23
  def plugin_list
23
24
  @plugins.keys
24
25
  end
25
26
 
27
+ # @return path to source file of plugin
26
28
  def plugin_source(plugin_name_sym)
27
29
  @plugins[plugin_name_sym][:source]
28
30
  end
29
31
 
32
+ # add a folder to the list of folders to look for plugins
30
33
  def add_lookup_folder(folder)
31
34
  @lookup_folders.unshift(folder)
32
35
  end
@@ -43,6 +46,7 @@ module Aspera
43
46
  end
44
47
  end
45
48
 
49
+ # @return Class object for plugin
46
50
  def plugin_class(plugin_name_sym)
47
51
  raise "ERROR: plugin not found: #{plugin_name_sym}" unless @plugins.key?(plugin_name_sym)
48
52
  require @plugins[plugin_name_sym][:require_stanza]
@@ -50,6 +54,9 @@ module Aspera
50
54
  return Object.const_get("#{Module.nesting[1]}::#{PLUGINS_MODULE}::#{plugin_name_sym.to_s.capitalize}")
51
55
  end
52
56
 
57
+ # Create specified plugin
58
+ # @param plugin_name_sym [Symbol] name of plugin
59
+ # @param args [Hash] arguments to pass to plugin constructor
53
60
  def create(plugin_name_sym, **args)
54
61
  # TODO: check that ancestor is Plugin?
55
62
  plugin_class(plugin_name_sym).new(**args)
@@ -57,6 +64,8 @@ module Aspera
57
64
 
58
65
  private
59
66
 
67
+ # add plugin information to list
68
+ # @param path [String] path to plugin source file
60
69
  def add_plugin_info(path)
61
70
  raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
62
71
  plugin_symbol = File.basename(path, RUBY_FILE_EXT).to_sym
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/api/aoc'
3
+ require 'aspera/api/alee'
4
4
  require 'aspera/nagios'
5
5
 
6
6
  module Aspera
@@ -16,7 +16,7 @@ module Aspera
16
16
  nagios = Nagios.new
17
17
  begin
18
18
  api = Api::Alee.new(nil, nil, version: 'ping')
19
- result = api.call(operation: 'GET', subpath: '')
19
+ result = api.call(operation: 'GET')
20
20
  raise "unexpected response: #{result[:http].body}" unless result[:http].body.eql?('pong')
21
21
  nagios.add_ok('api', 'answered ok')
22
22
  rescue StandardError => e
@@ -27,7 +27,7 @@ module Aspera
27
27
  entitlement_id = options.get_option(:username, mandatory: true)
28
28
  customer_id = options.get_option(:password, mandatory: true)
29
29
  api_metering = Api::Alee.new(entitlement_id, customer_id)
30
- return {type: :single_object, data: api_metering.read('entitlement')[:data]}
30
+ return {type: :single_object, data: api_metering.read('entitlement')}
31
31
  end
32
32
  end
33
33
  end