Class: Aspera::Cli::Plugins::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/cli/plugins/base.rb

Overview

Base class for command plugins

Direct Known Subclasses

Ats, BasicAuth, Config, Cos, Httpgw

Constant Summary collapse

GLOBAL_OPS =

Operations without id (create list)

i[create list].freeze
INSTANCE_OPS =

Operations with id (modify delete show)

i[modify delete show].freeze
ALL_OPS =

All standard operations (create list modify delete show)

(GLOBAL_OPS + INSTANCE_OPS).freeze
MAX_ITEMS =

Special query parameter: max: max number of items for list command

'max'
MAX_PAGES =

Special query parameter: pmax: max number of pages for list command

'pmax'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context:) ⇒ Base



39
40
41
42
43
44
45
# File 'lib/aspera/cli/plugins/base.rb', line 39

def initialize(context:)
  # Check presence in descendant of mandatory method and constant
  Aspera.assert(respond_to?(:execute_action), type: InternalError){"Missing method 'execute_action' in #{self.class}"}
  Aspera.assert(self.class.const_defined?(:ACTIONS), type: InternalError){"Missing constant 'ACTIONS' in #{self.class}"}
  @context = context
  add_manual_header if @context.man_header
end

Instance Attribute Details

#contextObject (readonly)

Global objects



48
49
50
# File 'lib/aspera/cli/plugins/base.rb', line 48

def context
  @context
end

Class Method Details

.declare_options(options) ⇒ Object



23
24
25
26
27
# File 'lib/aspera/cli/plugins/base.rb', line 23

def declare_options(options)
  options.declare(:query, 'Additional filter for for some commands (list/delete)', allowed: [Hash, Array, NilClass])
  options.declare(:bulk, 'Bulk operation (only some)', allowed: Allowed::TYPES_BOOLEAN, default: false)
  options.declare(:bfail, 'Bulk operation error handling', allowed: Allowed::TYPES_BOOLEAN, default: true)
end

.percent_selector(identifier) ⇒ Hash, NilClass



30
31
32
33
34
35
36
# File 'lib/aspera/cli/plugins/base.rb', line 30

def percent_selector(identifier)
  Aspera.assert_type(identifier, String)
  if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD))
    return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")}
  end
  return
end

Instance Method Details

#add_manual_header(has_options = true) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/aspera/cli/plugins/base.rb', line 61

def add_manual_header(has_options = true)
  # Manual header for all plugins
  options.parser.separator('')
  options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
  options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
  options.parser.separator('OPTIONS:') if has_options
end

#configConfig



55
# File 'lib/aspera/cli/plugins/base.rb', line 55

def config; @context.config; end

#do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default) ⇒ Object

For create and delete operations: execute one action or multiple if bulk is yes



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/aspera/cli/plugins/base.rb', line 91

def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default)
  Aspera.assert(block_given?){'missing block'}
  is_bulk = options.get_option(:bulk)
  case values
  when :identifier
    values = instance_identifier(description: descr)
  when Class
    values = value_create_modify(command: command, description: descr, type: values, bulk: is_bulk)
  end
  # If not bulk, there is a single value
  params = is_bulk ? values : [values]
  Log.log.warn('Empty list given for bulk operation') if params.empty?
  Log.dump(:bulk_operation, params)
  result_list = []
  params.each do |param|
    # Init for delete
    result = {id_result => param}
    begin
      # Execute custom code
      res = yield(param)
      # If block returns a hash, let's use this (create)
      result = res if res.is_a?(Hash)
      # TODO: remove when faspio gw api fixes this
      result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
      # Create -> created
      result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
    rescue StandardError => e
      raise e if options.get_option(:bfail)
      result['status'] = e.to_s
    end
    result_list.push(result)
  end
  display_fields = [id_result, 'status']
  if is_bulk
    return Main.result_object_list(result_list, fields: display_fields)
  else
    display_fields = fields unless fields.eql?(:default)
    return Main.result_single_object(result_list.first, fields: display_fields)
  end
end

#entity_execute(api:, entity:, command: nil, display_fields: nil, items_key: nil, delete_style: nil, id_as_arg: false, is_singleton: false, list_query: nil, tclo: false, &block) ⇒ Hash

Operations: Create, Delete, Show, List, Modify



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/aspera/cli/plugins/base.rb', line 144

def entity_execute(
  api:,
  entity:,
  command: nil,
  display_fields: nil,
  items_key: nil,
  delete_style: nil,
  id_as_arg: false,
  is_singleton: false,
  list_query: nil,
  tclo: false,
  &block
)
  command = options.get_next_command(ALL_OPS) if command.nil?
  if is_singleton
    one_res_path = entity
  elsif INSTANCE_OPS.include?(command)
    one_res_id = instance_identifier(&block)
    one_res_path = "#{entity}/#{one_res_id}"
    one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
  end

  case command
  when :create
    raise BadArgument, 'cannot create singleton' if is_singleton
    return do_bulk_operation(command: command, descr: 'data', fields: display_fields) do |params|
      api.create(entity, params)
    end
  when :delete
    raise BadArgument, 'cannot delete singleton' if is_singleton
    if !delete_style.nil?
      one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
      Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
      api.delete(
        entity,
        nil,
        content_type: Mime::JSON,
        body:         {delete_style => one_res_id}
      )
      return Main.result_status('deleted')
    end
    return do_bulk_operation(command: command, values: one_res_id) do |one_id|
      api.delete("#{entity}/#{one_id}", query_read_delete)
      {'id' => one_id}
    end
  when :show
    return Main.result_single_object(api.read(one_res_path), fields: display_fields)
  when :list
    if tclo
      data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
      return Main.result_object_list(data, total: total, fields: display_fields)
    end
    data, http = api.read(entity, query_read_delete, ret: :both)
    return Main.result_empty if http.code == '204'
    # TODO: not generic : which application is this for ?
    if http['Content-Type'].start_with?('application/vnd.api+json')
      Log.log.debug('is vnd.api')
      data = data[entity]
    end
    data = data[items_key] if items_key
    case data
    when Hash
      return Main.result_single_object(data, fields: display_fields)
    when Array
      return Main.result_object_list(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash)
      return Main.result_value_list(data)
    else
      Aspera.error_unexpected_value(data.class.name){'list type'}
    end
  when :modify
    parameters = value_create_modify(command: command)
    api.update(one_res_path, parameters)
    return Main.result_status('modified')
  else
    Aspera.error_unexpected_value(command){'command'}
  end
end

#formatterFormatter



57
# File 'lib/aspera/cli/plugins/base.rb', line 57

def formatter; @context.formatter; end

#instance_identifier(description: 'identifier') ⇒ String, Array

Resource identifier as positional parameter



74
75
76
77
78
79
80
81
82
# File 'lib/aspera/cli/plugins/base.rb', line 74

def instance_identifier(description: 'identifier')
  res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
  # Can be an Array
  if res_id.is_a?(String) && (m = Base.percent_selector(res_id))
    Aspera.assert(block_given?, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
    res_id = yield(m[:field], m[:value])
  end
  return res_id
end

#list_entities_limit_offset_total_count(api:, entity:, items_key: nil, query: nil) ⇒ Array<(Array<Hash>, Integer)>

Get a (full or partial) list of all entities of a given type with query: offset/limit



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/aspera/cli/plugins/base.rb', line 268

def list_entities_limit_offset_total_count(
  api:,
  entity:,
  items_key: nil,
  query: nil
)
  entity = entity.to_s if entity.is_a?(Symbol)
  items_key = entity.split('/').last if items_key.nil?
  query = {} if query.nil?
  Aspera.assert_type(entity, String)
  Aspera.assert_type(items_key, String)
  Aspera.assert_type(query, Hash)
  Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
  result = []
  offset = 0
  max_items = query.delete(MAX_ITEMS)
  remain_pages = query.delete(MAX_PAGES)
  # Merge default parameters, by default 100 per page
  query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
  total_count = nil
  loop do
    query['offset'] = offset
    page_result = api.read(entity, query)
    Aspera.assert_type(page_result[items_key], Array)
    result.concat(page_result[items_key])
    # Reach the limit set by user ?
    if !max_items.nil? && (result.length >= max_items)
      result = result.slice(0, max_items)
      break
    end
    total_count ||= page_result['total_count']
    break if result.length >= total_count
    remain_pages -= 1 unless remain_pages.nil?
    break if remain_pages == 0
    offset += page_result[items_key].length
    formatter.long_operation_running
  end
  formatter.long_operation_terminated
  return result, total_count
end

#lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default) ⇒ Object

Lookup an entity id from its name. Uses query q if query is :default and field is name.



316
317
318
319
320
321
322
# File 'lib/aspera/cli/plugins/base.rb', line 316

def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
  if query.eql?(:default)
    Aspera.assert(field.eql?('name')){'Default query is on name only'}
    query = {'q'=> value}
  end
  lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first}
end

#lookup_entity_generic(entity:, value:, field: 'name', &block) ⇒ Object

Lookup entity by field and value. Extract single result from list of result returned by block.

Raises:



329
330
331
332
333
334
335
336
# File 'lib/aspera/cli/plugins/base.rb', line 329

def lookup_entity_generic(entity:, value:, field: 'name', &block)
  Aspera.assert(block_given?)
  found = yield
  Aspera.assert_array_all(found, Hash)
  found = found.select{ |i| i[field].eql?(value)}
  return found.first if found.length.eql?(1)
  raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
end

#optionsManager



51
# File 'lib/aspera/cli/plugins/base.rb', line 51

def options; @context.options; end

#persistencyPersistencyFolder



59
# File 'lib/aspera/cli/plugins/base.rb', line 59

def persistency; @context.persistency; end

#query_read_delete(default: nil) ⇒ Object

Query parameters in URL suitable for REST: list/GET and delete/DELETE



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/aspera/cli/plugins/base.rb', line 223

def query_read_delete(default: nil)
  # Dup default, as it could be frozen
  query = options.get_option(:query) || default.dup
  Log.log.debug{"query_read_delete=#{query}".bg_red}
  begin
    # Check it is suitable
    URI.encode_www_form(query) unless query.nil?
  rescue StandardError => e
    raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
  end
  return query
end

#transferTransferAgent



53
# File 'lib/aspera/cli/plugins/base.rb', line 53

def transfer; @context.transfer; end

#value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil) ⇒ Object

Retrieves an extended value from command line, used for creation or modification of entities



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/aspera/cli/plugins/base.rb', line 241

def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
  value = options.get_next_argument(
    "parameters for #{command}#{" (#{description})" unless description.nil?}", mandatory: default.nil?,
    validation: bulk ? Array : type
  )
  value = default if value.nil?
  unless type.nil?
    type = [type] unless type.is_a?(Array)
    Aspera.assert_array_all(type, Class){'check types'}
    if bulk
      Aspera.assert_type(value, Array, type: Cli::BadArgument)
      value.each do |v|
        Aspera.assert_values(v.class, type, type: Cli::BadArgument)
      end
    else
      Aspera.assert_values(value.class, type, type: Cli::BadArgument)
    end
  end
  return value
end