aspera-cli 4.25.3 → 4.26.0.pre

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 (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +39 -6
  4. data/CONTRIBUTING.md +119 -111
  5. data/README.md +9 -7
  6. data/lib/aspera/agent/direct.rb +10 -8
  7. data/lib/aspera/agent/factory.rb +3 -3
  8. data/lib/aspera/agent/node.rb +1 -1
  9. data/lib/aspera/api/alee.rb +1 -0
  10. data/lib/aspera/api/aoc.rb +13 -12
  11. data/lib/aspera/api/ats.rb +1 -1
  12. data/lib/aspera/api/cos_node.rb +5 -0
  13. data/lib/aspera/api/faspex.rb +15 -2
  14. data/lib/aspera/api/httpgw.rb +2 -0
  15. data/lib/aspera/api/node.rb +82 -29
  16. data/lib/aspera/ascp/installation.rb +9 -10
  17. data/lib/aspera/cli/error.rb +8 -0
  18. data/lib/aspera/cli/formatter.rb +27 -11
  19. data/lib/aspera/cli/info.rb +2 -1
  20. data/lib/aspera/cli/main.rb +30 -12
  21. data/lib/aspera/cli/manager.rb +43 -31
  22. data/lib/aspera/cli/plugins/aoc.rb +7 -5
  23. data/lib/aspera/cli/plugins/base.rb +2 -79
  24. data/lib/aspera/cli/plugins/config.rb +2 -1
  25. data/lib/aspera/cli/plugins/faspex.rb +1 -1
  26. data/lib/aspera/cli/plugins/faspex5.rb +51 -51
  27. data/lib/aspera/cli/plugins/node.rb +9 -14
  28. data/lib/aspera/cli/plugins/shares.rb +4 -2
  29. data/lib/aspera/cli/special_values.rb +1 -0
  30. data/lib/aspera/cli/transfer_agent.rb +3 -0
  31. data/lib/aspera/cli/version.rb +1 -1
  32. data/lib/aspera/cli/wizard.rb +2 -1
  33. data/lib/aspera/dot_container.rb +10 -10
  34. data/lib/aspera/log.rb +1 -1
  35. data/lib/aspera/markdown.rb +1 -1
  36. data/lib/aspera/persistency_folder.rb +1 -1
  37. data/lib/aspera/rest.rb +34 -49
  38. data/lib/aspera/rest_list.rb +116 -0
  39. data/lib/aspera/sync/operations.rb +1 -1
  40. data/lib/aspera/transfer/parameters.rb +8 -8
  41. data/lib/aspera/transfer/spec.rb +1 -0
  42. data/lib/aspera/yaml.rb +1 -1
  43. data.tar.gz.sig +0 -0
  44. metadata +4 -3
  45. metadata.gz.sig +0 -0
@@ -3,6 +3,8 @@
3
3
  require 'aspera/cli/plugins/basic_auth'
4
4
  require 'aspera/cli/plugins/node'
5
5
  require 'aspera/assert'
6
+ require 'aspera/rest_list'
7
+
6
8
  module Aspera
7
9
  module Cli
8
10
  module Plugins
@@ -128,7 +130,7 @@ module Aspera
128
130
  when :admin
129
131
  api_shares_admin = basic_auth_api(ADMIN_API_PATH)
130
132
  admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
131
- lookup_share = ->(field, value){lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
133
+ lookup_share = ->(field, value){RestList.lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
132
134
  case admin_command
133
135
  when :node
134
136
  return entity_execute(api: api_shares_admin, entity: 'data/nodes')
@@ -177,7 +179,7 @@ module Aspera
177
179
  entity_commands = %i[import].freeze
178
180
  end
179
181
  entity_verb = options.get_next_command(entity_commands)
180
- lookup_block = ->(field, value){lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
182
+ lookup_block = ->(field, value){RestList.lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
181
183
  case entity_verb
182
184
  when *ALL_OPS # list, show, delete, create, modify
183
185
  display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
@@ -8,6 +8,7 @@ module Aspera
8
8
  INIT = 'INIT'
9
9
  ALL = 'ALL'
10
10
  DEF = 'DEF'
11
+ EOA = 'END'
11
12
  end
12
13
  end
13
14
  end
@@ -33,6 +33,9 @@ module Aspera
33
33
  :DEFAULT_TRANSFER_NOTIFY_TEMPLATE
34
34
 
35
35
  class << self
36
+ # Analyze transfer session statuses and return a global status
37
+ #
38
+ # @param statuses [Array] list of session status, each status is :success or an error message string
36
39
  # @return [:success] if all sessions statuses returned by "start" are success
37
40
  # @return [Exception] if one sessions statuses returned by "start" is failed
38
41
  def session_status(statuses)
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # For beta add extension : .beta1
6
6
  # For dev version add extension : .pre
7
- VERSION = '4.25.3'
7
+ VERSION = '4.26.0.pre'
8
8
  end
9
9
  end
@@ -62,7 +62,7 @@ module Aspera
62
62
  detection_info = nil
63
63
  begin
64
64
  Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
65
- formatter.long_operation_running("#{plugin_name_sym}\r")
65
+ RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
66
66
  detection_info = plugin_klass.detect(app_url)
67
67
  rescue OpenSSL::SSL::SSLError => e
68
68
  Log.log.warn(e.message)
@@ -78,6 +78,7 @@ module Aspera
78
78
  # If there is a redirect, then the detector can override the url.
79
79
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
80
80
  end
81
+ RestParameters.instance.spinner_cb.call(action: :success)
81
82
  raise "No known application found at #{app_url}" if found_apps.empty?
82
83
  Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
83
84
  return found_apps
@@ -7,13 +7,14 @@ module Aspera
7
7
  class DotContainer
8
8
  class << self
9
9
  # Insert extended value `value` into struct `result` at `path`
10
- # @param path [String] Dotted path in container
11
- # @param value [String] Last value to insert
12
- # @param result [NilClass, Hash, Array] current value
13
- # @return [Hash, Array]
10
+ # @param path [Array<String>] Path in container
11
+ # @param value [Object] Value to insert in deep container
12
+ # @param result [nil, Hash, Array] Current container to use (or nil to create a new one)
13
+ # @return [Hash, Array] Container
14
14
  def dotted_to_container(path, value, result = nil)
15
+ Aspera.assert_array_all(path, String)
15
16
  # Typed keys
16
- keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
17
+ keys = path.map{ |k| int_or_string(k)}
17
18
  # Create, or re-use first level container
18
19
  current = (result ||= new_hash_or_array_from_key(keys.first))
19
20
  # walk the path, and create sub-containers if necessary
@@ -78,7 +79,7 @@ module Aspera
78
79
  to_insert = current.map{ |i| i['name']}
79
80
  # Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
80
81
  elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
81
- add_elements(path, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
82
+ add_elements(path, current.to_h{ |i| [i['name'], i['value']]})
82
83
  else
83
84
  add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
84
85
  end
@@ -86,7 +87,7 @@ module Aspera
86
87
  to_insert = current
87
88
  end
88
89
  end
89
- result[path.map(&:to_s).join(OPTION_DOTTED_SEPARATOR)] = to_insert unless to_insert.nil?
90
+ result[path.join(SEPARATOR)] = to_insert unless to_insert.nil?
90
91
  end
91
92
  result
92
93
  end
@@ -101,8 +102,7 @@ module Aspera
101
102
  nil
102
103
  end
103
104
 
104
- # "."
105
- OPTION_DOTTED_SEPARATOR = '.'
106
- private_constant :OPTION_DOTTED_SEPARATOR
105
+ # Dot-path separator: `.`
106
+ SEPARATOR = '.'
107
107
  end
108
108
  end
data/lib/aspera/log.rb CHANGED
@@ -26,7 +26,7 @@ class Logger
26
26
  # Hash
27
27
  # key [Integer] Log level (e.g. 0 for DEBUG)
28
28
  # value [Symbol] Uppercase log level label (e.g. :DEBUG)
29
- SEVERITY_LABEL = Severity.constants.each_with_object({}){ |name, hash| hash[Severity.const_get(name)] = name}
29
+ SEVERITY_LABEL = Severity.constants.to_h{ |name| [Severity.const_get(name), name]}
30
30
 
31
31
  # Override
32
32
  # @param severity [Integer] Log severity as int
@@ -33,7 +33,7 @@ module Aspera
33
33
 
34
34
  # type: NOTE CAUTION WARNING IMPORTANT TIP INFO
35
35
  def admonition(lines, type: 'INFO')
36
- "> [!{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
36
+ "> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
37
37
  end
38
38
 
39
39
  def code(lines, type: 'shell')
@@ -79,7 +79,7 @@ module Aspera
79
79
  end
80
80
 
81
81
  def current_items(persist_category)
82
- current_files(persist_category).each_with_object({}){ |i, h| h[File.basename(i, FILE_SUFFIX)] = File.read(i)}
82
+ current_files(persist_category).to_h{ |i| [File.basename(i, FILE_SUFFIX), File.read(i)]}
83
83
  end
84
84
 
85
85
  private
data/lib/aspera/rest.rb CHANGED
@@ -33,7 +33,7 @@ module Aspera
33
33
  class RestParameters
34
34
  include Singleton
35
35
 
36
- attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_on_timeout, :retry_on_unavailable, :retry_max, :retry_sleep, :session_cb, :progress_bar
36
+ attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_on_timeout, :retry_on_unavailable, :retry_max, :retry_sleep, :session_cb, :progress_bar, :spinner_cb
37
37
 
38
38
  private
39
39
 
@@ -47,6 +47,7 @@ module Aspera
47
47
  @retry_sleep = 4
48
48
  @session_cb = nil
49
49
  @progress_bar = nil
50
+ @spinner_cb = nil
50
51
  end
51
52
  end
52
53
 
@@ -88,8 +89,9 @@ module Aspera
88
89
  query
89
90
  end
90
91
 
91
- # Build URI from URL and parameters and check it is http or https
92
- # Check iof php style is specified
92
+ # Build URI from URL and parameters and check it is `http` or `https`.
93
+ # Check if php style is specified.
94
+ # `nil` values in query result in key without value, e.g. `?a`, while empty string values result in `?a=`.
93
95
  # @param url [String] The URL without query.
94
96
  # @param query [Hash,Array,String] The query.
95
97
  def build_uri(url, query)
@@ -105,18 +107,19 @@ module Aspera
105
107
  URI.encode_www_form(h_to_query_array(query))
106
108
  when Array
107
109
  Aspera.assert(query.all?{ |i| i.is_a?(Array) && i.length.eql?(2)}){'Query must be array of arrays of 2 elements'}
108
- URI.encode_www_form(query)
110
+ URI.encode_www_form(query) # remove nil values
109
111
  else Aspera.error_unexpected_value(query.class){'query type'}
110
112
  end.gsub('%5B%5D=', '[]=')
111
113
  # [] is allowed in url parameters
112
114
  uri
113
115
  end
114
116
 
117
+ # Support array for query parameter, there is no standard.
118
+ # Either p=1&p=2 (default)
119
+ # or p[]=1&p[]=2 (if `:x_array_php_style` is set to true in query)
115
120
  # @param query [Hash] HTTP query as hash
116
121
  def h_to_query_array(query)
117
122
  Aspera.assert_type(query, Hash)
118
- # Support array for query parameter, there is no standard.
119
- # Either p[]=1&p[]=2, or p=1&p=2
120
123
  suffix = query.delete(:x_array_php_style) ? '[]' : nil
121
124
  query.each_with_object([]) do |(k, v), query_array|
122
125
  case v
@@ -368,8 +371,11 @@ module Aspera
368
371
  end
369
372
  result_http = nil
370
373
  result_data = nil
374
+ # initialize with number of initial retries allowed, nil gives zero
375
+ tries_remain_redirect = @redirect_max
371
376
  # start a block to be able to retry the actual HTTP request in case of OAuth token expiration
372
377
  begin
378
+ Log.log.debug("send request (retries=#{tries_remain_redirect})")
373
379
  # TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
374
380
  # URI.escape()
375
381
  separator = ['', '/'].include?(subpath) ? '' : '/'
@@ -403,16 +409,13 @@ module Aspera
403
409
  Log.dump(:req_body, req.body, level: :trace1)
404
410
  # we try the call, and will retry on some error types
405
411
  error_tries ||= 1 + RestParameters.instance.retry_max
406
- # initialize with number of initial retries allowed, nil gives zero
407
- tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
408
- Log.log.debug("send request (retries=#{tries_remain_redirect})")
409
412
  result_mime = nil
410
413
  file_saved = false
411
414
  # make http request (pipelined)
412
415
  http_session.request(req) do |response|
413
416
  result_http = response
414
417
  result_mime = self.class.parse_header(result_http['Content-Type'] || Mime::TEXT)[:type]
415
- Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime}, mime2= #{response['Content-Type']}"}
418
+ Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime}, content-type=#{response['Content-Type']}"}
416
419
  # JSON data needs to be parsed, in case it contains an error code
417
420
  if !save_to_file.nil? &&
418
421
  result_http.code.to_s.start_with?('2') &&
@@ -453,12 +456,10 @@ module Aspera
453
456
  # TODO : related to mime type encoding ?
454
457
  # result_http.body.force_encoding('UTF-8') if result_http.body.is_a?(String)
455
458
  # Log.log.debug{"result: body=#{result_http.body}"}
456
- if Mime.json?(result_mime)
457
- result_data = JSON.parse(result_http.body) rescue result_http.body
458
- Log.dump(:result_data, result_data)
459
- else # Mime::TEXT
460
- result_data = result_http.body
461
- end
459
+ result_data = result_http.body
460
+ Log.dump(:result_data_raw, result_data, level: :trace1)
461
+ result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
462
+ Log.dump(:result_data, result_data)
462
463
  RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
463
464
  unless file_saved || save_to_file.nil?
464
465
  FileUtils.mkdir_p(File.dirname(save_to_file))
@@ -536,55 +537,39 @@ module Aspera
536
537
 
537
538
  # Create: `POST`
538
539
  def create(subpath, params, **kwargs)
539
- return call(operation: 'POST', subpath: subpath, headers: {'Accept' => Mime::JSON}, body: params, content_type: Mime::JSON, **kwargs)
540
+ kwargs[:headers] ||= {}
541
+ kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
542
+ kwargs[:content_type] = Mime::JSON unless kwargs.key?(:content_type)
543
+ return call(operation: 'POST', subpath: subpath, body: params, **kwargs)
540
544
  end
541
545
 
542
546
  # Read: `GET`
543
547
  def read(subpath, query = nil, **kwargs)
544
- return call(operation: 'GET', subpath: subpath, headers: {'Accept' => Mime::JSON}, query: query, **kwargs)
548
+ kwargs[:headers] ||= {}
549
+ kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
550
+ return call(operation: 'GET', subpath: subpath, query: query, **kwargs)
545
551
  end
546
552
 
547
553
  # Update: `PUT`
548
554
  def update(subpath, params, **kwargs)
549
- return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => Mime::JSON}, body: params, content_type: Mime::JSON, **kwargs)
555
+ kwargs[:headers] ||= {}
556
+ kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
557
+ kwargs[:content_type] = Mime::JSON unless kwargs.key?(:content_type)
558
+ return call(operation: 'PUT', subpath: subpath, body: params, **kwargs)
550
559
  end
551
560
 
552
561
  # Delete: `DELETE`
553
562
  def delete(subpath, params = nil, **kwargs)
554
- return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => Mime::JSON}, query: params, **kwargs)
563
+ kwargs[:headers] ||= {}
564
+ kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
565
+ return call(operation: 'DELETE', subpath: subpath, query: params, **kwargs)
555
566
  end
556
567
 
557
568
  # Cancel: `CANCEL`
558
569
  def cancel(subpath, **kwargs)
559
- return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => Mime::JSON}, **kwargs)
560
- end
561
-
562
- # Query entity by general search (read with parameter `q`)
563
- # TODO: not generic enough ? move somewhere ? inheritance ?
564
- # @param subpath [String] Path of entity in API
565
- # @param search_name [String] Name of searched entity
566
- # @param query [Hash] Additional search query parameters
567
- # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
568
- def lookup_by_name(subpath, search_name, query: nil)
569
- query = {} if query.nil?
570
- # returns entities matching the query (it matches against several fields in case insensitive way)
571
- matching_items = read(subpath, query.merge({'q' => search_name}))
572
- # API style: {totalcount:, ...} cspell: disable-line
573
- matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
574
- Aspera.assert_type(matching_items, Array)
575
- case matching_items.length
576
- when 1 then return matching_items.first
577
- when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
578
- else
579
- # multiple case insensitive partial matches, try case insensitive full match
580
- # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
581
- name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
582
- case name_matches.length
583
- when 1 then return name_matches.first
584
- when 0 then raise %Q(#{subpath}: Multiple case insensitive partial match for: "#{search_name}": #{matching_items.map{ |i| i['name']}} but no case insensitive full match. Please be more specific or give exact name.)
585
- else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
586
- end
587
- end
570
+ kwargs[:headers] ||= {}
571
+ kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
572
+ return call(operation: 'CANCEL', subpath: subpath, **kwargs)
588
573
  end
589
574
 
590
575
  UNAVAILABLE_CODES = ['503']
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ # List and lookup methods for Rest
5
+ # include in classes inheriting Rest
6
+ module RestList
7
+ # Query entity by general search (read with parameter `q`)
8
+ #
9
+ # @param subpath [String] Path of entity in API
10
+ # @param search_name [String] Name of searched entity
11
+ # @param query [Hash] Optional additional search query parameters
12
+ # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
13
+ def lookup_by_name(subpath, search_name, query: nil)
14
+ query = {} if query.nil?
15
+ # returns entities matching the query (it matches against several fields in case insensitive way)
16
+ matching_items = read(subpath, query.merge({'q' => search_name}))
17
+ # API style: {totalcount:, ...} cspell: disable-line
18
+ matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
19
+ Aspera.assert_type(matching_items, Array)
20
+ case matching_items.length
21
+ when 1 then return matching_items.first
22
+ when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
23
+ else
24
+ # multiple case insensitive partial matches, try case insensitive full match
25
+ # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
26
+ name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
27
+ case name_matches.length
28
+ when 1 then return name_matches.first
29
+ when 0 then raise %Q(#{subpath}: Multiple case insensitive partial match for: "#{search_name}": #{matching_items.map{ |i| i['name']}} but no case insensitive full match. Please be more specific or give exact name.)
30
+ else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
31
+ end
32
+ end
33
+ end
34
+
35
+ # Get a (full or partial) list of all entities of a given type with query: offset/limit
36
+ # @param entity [String,Symbol] API endpoint of entity to list
37
+ # @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
38
+ # @param query [Hash,nil] Additional query parameters
39
+ # @return [Array<(Array<Hash>, Integer)>] items, total_count
40
+ def list_entities_limit_offset_total_count(
41
+ entity:,
42
+ items_key: nil,
43
+ query: nil
44
+ )
45
+ entity = entity.to_s if entity.is_a?(Symbol)
46
+ items_key = entity.split('/').last if items_key.nil?
47
+ query = {} if query.nil?
48
+ Aspera.assert_type(entity, String)
49
+ Aspera.assert_type(items_key, String)
50
+ Aspera.assert_type(query, Hash)
51
+ Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
52
+ result = []
53
+ offset = 0
54
+ max_items = query.delete(MAX_ITEMS)
55
+ remain_pages = query.delete(MAX_PAGES)
56
+ # Merge default parameters, by default 100 per page
57
+ query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
58
+ total_count = nil
59
+ loop do
60
+ query['offset'] = offset
61
+ page_result = read(entity, query)
62
+ Aspera.assert_type(page_result[items_key], Array)
63
+ result.concat(page_result[items_key])
64
+ # Reach the limit set by user ?
65
+ if !max_items.nil? && (result.length >= max_items)
66
+ result = result.slice(0, max_items)
67
+ break
68
+ end
69
+ total_count ||= page_result['total_count']
70
+ break if result.length >= total_count
71
+ remain_pages -= 1 unless remain_pages.nil?
72
+ break if remain_pages == 0
73
+ offset += page_result[items_key].length
74
+ RestParameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
75
+ end
76
+ RestParameters.instance.spinner_cb.call(action: :success)
77
+ return result, total_count
78
+ end
79
+
80
+ # Lookup an entity id from its name.
81
+ # Uses query `q` if `query` is `:default` and `field` is `name`.
82
+ # @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
83
+ # @param value [String] Value to lookup
84
+ # @param field [String] Field to match, by default it is `'name'`
85
+ # @param items_key [String] Key in the result to get the list of items (override entity)
86
+ # @param query [Hash] Additional query parameters (Default: `:default`)
87
+ def lookup_entity_by_field(entity:, value:, field: 'name', items_key: nil, query: :default)
88
+ if query.eql?(:default)
89
+ Aspera.assert(field.eql?('name')){'Default query is on name only'}
90
+ query = {'q'=> value}
91
+ end
92
+ lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(entity: entity, items_key: items_key, query: query).first}
93
+ end
94
+
95
+ # Lookup entity by field and value.
96
+ # Extracts a single result from the list returned by the block.
97
+ #
98
+ # @param entity [String] Type of entity to lookup (path, and by default it is also the field name in result)
99
+ # @param value [String] Value to match against the field.
100
+ # @param field [String] Field to match in the hashes (defaults to 'name').
101
+ # @yield [] A mandatory block that returns an Array of Hashes.
102
+ # @return [Hash] The unique matching object.
103
+ # @raise [Cli::BadIdentifier] If 0 or >1 matches are found.
104
+ def lookup_entity_generic(entity:, value:, field: 'name')
105
+ Aspera.assert(block_given?)
106
+ found = yield
107
+ Aspera.assert_array_all(found, Hash)
108
+ found = found.select{ |i| i[field].eql?(value)}
109
+ return found.first if found.length.eql?(1)
110
+ raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
111
+ end
112
+ PER_PAGE_DEFAULT = 1000
113
+ private_constant :PER_PAGE_DEFAULT
114
+ module_function :lookup_entity_generic
115
+ end
116
+ end
@@ -342,7 +342,7 @@ module Aspera
342
342
  CONF_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'conf')
343
343
  CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
344
344
  ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
345
- PRIVATE_FOLDER = '.private-asp'
345
+ PRIVATE_FOLDER = "#{Environment.instance.os.eql?(Environment::OS_WINDOWS) ? '~' : '.'}private-asp"
346
346
  ASYNC_DB = 'snap.db'
347
347
  PARAM_KEYS = %w[local sessions].freeze
348
348
 
@@ -38,19 +38,19 @@ module Aspera
38
38
  @file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
39
39
  end
40
40
 
41
- # File list is provided directly with ascp arguments
42
- # @columns ascp_args [Array,NilClass] ascp arguments
41
+ # File list is provided directly with `ascp` arguments
42
+ # @columns ascp_args [Array,NilClass] `ascp` arguments
43
43
  def ascp_args_file_list?(ascp_args)
44
44
  ascp_args&.any?{ |i| FILE_LIST_OPTIONS.include?(i)}
45
45
  end
46
46
  end
47
47
 
48
- # @param job_spec [Hash] Transfer spec
49
- # @param ascp_args [Array] Other ascp args
50
- # @param quiet [Boolean] Remove ascp output
51
- # @param trusted_certs [Array] Trusted certificates
52
- # @param client_ssh_key [:rsa,:dsa] :rsa or :dsa
53
- # @param check_ignore_cb [Proc] Callback
48
+ # @param job_spec [Hash] Transfer specification
49
+ # @param ascp_args [Array] Other `ascp` args
50
+ # @param quiet [Boolean] Remove `ascp` progress bar if `true`
51
+ # @param client_ssh_key [:rsa,:dsa] Type of Aspera Client SSH key to use.
52
+ # @param trusted_certs [Array<String>] List of path to trusted certificates stores when using WSS
53
+ # @param check_ignore_cb [Proc] Callback to check if WSS connection shall ignore certificate validity
54
54
  def initialize(
55
55
  job_spec,
56
56
  ascp_args: nil,
@@ -25,6 +25,7 @@ module Aspera
25
25
  TRANSPORT_FIELDS = (%w[remote_host] + AK_TSPEC_BASE.keys + WSS_FIELDS).freeze
26
26
  # reserved tag for Aspera
27
27
  TAG_RESERVED = 'aspera'
28
+ SPECIFIC = %w{token paths direction source_root destination_root}.freeze
28
29
  class << self
29
30
  # wrong def in transferd
30
31
  POLICY_FIX = {
data/lib/aspera/yaml.rb CHANGED
@@ -25,7 +25,7 @@ module Aspera
25
25
  counts.each do |key_str, count|
26
26
  next if count <= 1
27
27
  path = (parent_path + [key_str]).join('.')
28
- occurrences = key_nodes[key_str].map{ |kn| kn.start_line ? kn.start_line + 1 : 'unknown'}.map(&:to_s).join(', ')
28
+ occurrences = key_nodes[key_str].map{ |kn| kn.start_line ? kn.start_line + 1 : 'unknown'}.join(', ')
29
29
  duplicate_keys << "#{path}: #{occurrences}"
30
30
  end
31
31
  else
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aspera-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.25.3
4
+ version: 4.26.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Laurent Martin
@@ -424,6 +424,7 @@ files:
424
424
  - lib/aspera/rest_call_error.rb
425
425
  - lib/aspera/rest_error_analyzer.rb
426
426
  - lib/aspera/rest_errors_aspera.rb
427
+ - lib/aspera/rest_list.rb
427
428
  - lib/aspera/secret_hider.rb
428
429
  - lib/aspera/ssh.rb
429
430
  - lib/aspera/ssl.rb
@@ -456,7 +457,7 @@ metadata:
456
457
  source_code_uri: https://github.com/IBM/aspera-cli/tree/main/lib/aspera
457
458
  changelog_uri: https://github.com/IBM/aspera-cli/blob/main/CHANGELOG.md
458
459
  rubygems_uri: https://rubygems.org/gems/aspera-cli
459
- documentation_uri: https://www.rubydoc.info/gems/aspera-cli
460
+ documentation_uri: https://ibm.biz/ascli-doc
460
461
  rdoc_options: []
461
462
  require_paths:
462
463
  - lib
@@ -472,7 +473,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
472
473
  version: '0'
473
474
  requirements:
474
475
  - Read the manual for any requirement
475
- rubygems_version: 4.0.3
476
+ rubygems_version: 4.0.6
476
477
  specification_version: 4
477
478
  summary: 'Execute actions using command line on IBM Aspera Server products: Aspera
478
479
  on Cloud, Faspex, Shares, Node, Console, Orchestrator, High Speed Transfer Server'
metadata.gz.sig CHANGED
Binary file