aspera-cli 4.25.4 → 4.25.6

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 (44) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +33 -11
  4. data/CONTRIBUTING.md +41 -23
  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 +10 -6
  11. data/lib/aspera/api/ats.rb +1 -1
  12. data/lib/aspera/api/cos_node.rb +1 -0
  13. data/lib/aspera/api/faspex.rb +17 -19
  14. data/lib/aspera/api/httpgw.rb +19 -3
  15. data/lib/aspera/api/node.rb +56 -2
  16. data/lib/aspera/ascp/installation.rb +32 -34
  17. data/lib/aspera/cli/error.rb +8 -0
  18. data/lib/aspera/cli/info.rb +2 -1
  19. data/lib/aspera/cli/main.rb +30 -12
  20. data/lib/aspera/cli/manager.rb +31 -28
  21. data/lib/aspera/cli/plugins/aoc.rb +2 -2
  22. data/lib/aspera/cli/plugins/base.rb +1 -88
  23. data/lib/aspera/cli/plugins/faspex.rb +6 -6
  24. data/lib/aspera/cli/plugins/faspex5.rb +41 -53
  25. data/lib/aspera/cli/plugins/node.rb +26 -68
  26. data/lib/aspera/cli/plugins/server.rb +1 -1
  27. data/lib/aspera/cli/plugins/shares.rb +4 -2
  28. data/lib/aspera/cli/transfer_agent.rb +3 -0
  29. data/lib/aspera/cli/version.rb +1 -1
  30. data/lib/aspera/dot_container.rb +10 -10
  31. data/lib/aspera/environment.rb +29 -19
  32. data/lib/aspera/keychain/macos_security.rb +1 -1
  33. data/lib/aspera/log.rb +1 -1
  34. data/lib/aspera/markdown.rb +1 -1
  35. data/lib/aspera/persistency_folder.rb +1 -1
  36. data/lib/aspera/preview/utils.rb +2 -2
  37. data/lib/aspera/rest.rb +39 -36
  38. data/lib/aspera/rest_list.rb +121 -0
  39. data/lib/aspera/sync/operations.rb +2 -2
  40. data/lib/aspera/transfer/parameters.rb +8 -8
  41. data/lib/aspera/yaml.rb +1 -1
  42. data.tar.gz.sig +0 -0
  43. metadata +4 -3
  44. metadata.gz.sig +0 -0
@@ -84,22 +84,31 @@ module Aspera
84
84
  "'#{str.gsub("'", %q('\'\''))}'"
85
85
  end
86
86
 
87
- # Execute a process securely without shell.
87
+ # Executes a command without invoking a shell.
88
88
  #
89
- # Execution `mode` can be:
90
- # - :execute -> Kernel.system, return nil
91
- # - :background -> Process.spawn, return pid
92
- # - :capture -> Open3.capture3, return stdout
89
+ # The command is provided as an array to avoid shell interpolation and
90
+ # ensure safer execution.
93
91
  #
94
- # @param cmd [Array<String>] Executable and arguments (mapped "to_s")
95
- # @param mode [:execute,:background,:capture] Execution mode
96
- # @param kwargs [Hash] Additional arguments to underlying method
97
- # @option kwargs [Hash] :env Environment variables
98
- # @option kwargs [Boolean] :exception for :capture mode, raise error if process fails
99
- # @option kwargs [Boolean] :close_others for :background mode
100
- # @return [nil] for :execute mode
101
- # @return [Integer] pid for :background mode
102
- # @return [String] stdout for :capture mode
92
+ # @param cmd [Array<#to_s>] The executable and its arguments.
93
+ # @param mode [:execute, :background, :capture] The execution strategy:
94
+ # - `:execute` Uses {Kernel.system}. Returns `true`, `false`, or `nil`.
95
+ # - `:background` Uses {Process.spawn}. Returns the spawned process PID.
96
+ # - `:capture` Uses {Open3.capture3}. Returns captured output and status.
97
+ #
98
+ # @param kwargs [Hash] Additional options forwarded to the underlying call.
99
+ #
100
+ # @option kwargs [Hash{String => String}] :env Environment variables to set for the process.
101
+ # @option kwargs [Boolean] :exception (false) When `true` in `:capture` mode,
102
+ # raises an error if the command exits with a non-zero status.
103
+ # @option kwargs [Boolean] :close_others (true) When `true` in `:background` mode,
104
+ # closes all other file descriptors in the child process.
105
+ #
106
+ # @return [Boolean, nil] For `:execute` mode (`true`, `false`, or `nil`).
107
+ # @return [Integer] For `:background` mode (process ID).
108
+ # @return [Array(String, String, Process::Status)] For `:capture` mode
109
+ # (`stdout`, `stderr`, `status`).
110
+ #
111
+ # @raise [RuntimeError] If `:exception` is `true` and the process fails in `:capture` mode.
103
112
  def secure_execute(*cmd, mode: :execute, **kwargs)
104
113
  cmd = cmd.map(&:to_s)
105
114
  Aspera.assert(cmd.size.positive?, type: ArgumentError){'executable must be present'}
@@ -128,11 +137,12 @@ module Aspera
128
137
  # https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
129
138
  argv = [kwargs.delete(:env)].compact + cmd
130
139
  exception = kwargs.delete(:exception){true}
131
- stdout, stderr, status = Open3.capture3(*argv, **kwargs)
132
- Log.log.debug{"status=#{status}, stderr=#{stderr}"}
133
- Log.log.trace1{"stdout=#{stdout}"}
134
- raise "Process failed: #{status.exitstatus} (#{stderr})" if exception && !status.success?
135
- stdout
140
+ result = Open3.capture3(*argv, **kwargs)
141
+ Log.dump(:stdout, result[0], level: :trace1)
142
+ Log.dump(:stderr, result[1], level: :trace1)
143
+ Log.dump(:status, result[2])
144
+ raise "Process failed: #{result[2].exitstatus} (#{result[1]})" if exception && !result[2].success?
145
+ result
136
146
  else Aspera.error_unreachable_line
137
147
  end
138
148
  end
@@ -57,7 +57,7 @@ module Aspera
57
57
  command_args.push(v.shellescape) unless v.empty?
58
58
  end
59
59
  command_args.push(last_opt) unless last_opt.nil?
60
- return Environment.secure_execute(*command_args, mode: :capture)
60
+ Environment.secure_execute(*command_args, mode: :capture).first
61
61
  end
62
62
 
63
63
  def key_chains(output)
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
@@ -51,11 +51,11 @@ module Aspera
51
51
  Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
52
52
  end
53
53
 
54
- # Execute external command and capture output
54
+ # Execute external command and get stdout
55
55
  # @return [String]
56
56
  def external_capture(command_sym, command_args)
57
57
  Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
58
- return Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture)
58
+ Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture).first
59
59
  end
60
60
 
61
61
  def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
data/lib/aspera/rest.rb CHANGED
@@ -72,11 +72,6 @@ module Aspera
72
72
  # rest call errors are raised as exception RestCallError
73
73
  # and error are analyzed in RestErrorAnalyzer
74
74
  class Rest
75
- # Special query parameter: max number of items for list command
76
- MAX_ITEMS = 'max'
77
- # Special query parameter: max number of pages for list command
78
- MAX_PAGES = 'pmax'
79
-
80
75
  class << self
81
76
  # @return [String] Basic auth token
82
77
  def basic_authorization(user, pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"; end
@@ -155,6 +150,37 @@ module Aspera
155
150
  end
156
151
  end
157
152
 
153
+ # Parse Link header according to RFC 8288 to extract a specific relation
154
+ # @param link_header [String, nil] The Link header value
155
+ # @param rel [String] The relation to look for (default: 'next')
156
+ # @return [String, nil] The URL of the link with the specified relation, or nil
157
+ def parse_link_header(link_header, rel: 'next')
158
+ return if link_header.nil? || link_header.empty?
159
+ # RFC 8288: Link header format is: <URI>; param1=value1; param2=value2, <URI2>; ...
160
+ # We look for the link with the specified rel
161
+ link_header.split(',').each do |link_part|
162
+ link_part = link_part.strip
163
+ # Extract URL between < and >
164
+ url_match = link_part.match(/<([^>]+)>/)
165
+ next unless url_match
166
+ url = url_match[1]
167
+ # Extract parameters after the URL
168
+ params_str = link_part[url_match.end(0)..-1]
169
+ # Check if this link has the specified rel (with or without quotes, case insensitive)
170
+ next unless /;\s*rel\s*=\s*"?#{Regexp.escape(rel)}"?/i.match?(params_str)
171
+ return url
172
+ end
173
+ # Fallback: if no rel found and looking for 'next', try the first link (backward compatibility)
174
+ if rel.eql?('next')
175
+ first_link = link_header.split(',').first&.strip
176
+ if first_link
177
+ url_match = first_link.match(/<([^>]+)>/)
178
+ return url_match[1] if url_match
179
+ end
180
+ end
181
+ nil
182
+ end
183
+
158
184
  # Start a HTTP/S session, also used for web sockets
159
185
  # @param base_url [String] Base url of HTTP/S session
160
186
  # @return [Net::HTTP] A started HTTP session
@@ -174,6 +200,8 @@ module Aspera
174
200
  # get Net::HTTP underlying socket i/o
175
201
  # little hack, handy because HTTP debug, proxy, etc... will be available
176
202
  # used implement web sockets after `start_http_session`
203
+ # @param http_session [Net::HTTP] the session object
204
+ # @return [Net::BufferedIO] The underlying socket i/o
177
205
  def io_http_session(http_session)
178
206
  Aspera.assert_type(http_session, Net::HTTP)
179
207
  # Net::BufferedIO in net/protocol.rb
@@ -371,8 +399,11 @@ module Aspera
371
399
  end
372
400
  result_http = nil
373
401
  result_data = nil
402
+ # initialize with number of initial retries allowed, nil gives zero
403
+ tries_remain_redirect = @redirect_max
374
404
  # start a block to be able to retry the actual HTTP request in case of OAuth token expiration
375
405
  begin
406
+ Log.log.debug("send request (retries=#{tries_remain_redirect})")
376
407
  # TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
377
408
  # URI.escape()
378
409
  separator = ['', '/'].include?(subpath) ? '' : '/'
@@ -406,9 +437,6 @@ module Aspera
406
437
  Log.dump(:req_body, req.body, level: :trace1)
407
438
  # we try the call, and will retry on some error types
408
439
  error_tries ||= 1 + RestParameters.instance.retry_max
409
- # initialize with number of initial retries allowed, nil gives zero
410
- tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
411
- Log.log.debug("send request (retries=#{tries_remain_redirect})")
412
440
  result_mime = nil
413
441
  file_saved = false
414
442
  # make http request (pipelined)
@@ -458,6 +486,9 @@ module Aspera
458
486
  # Log.log.debug{"result: body=#{result_http.body}"}
459
487
  result_data = result_http.body
460
488
  Log.dump(:result_data_raw, result_data, level: :trace1)
489
+ # TODO: Remove next 2 lines when bug in async node api is fixed. (Aspera/core/issues/4490)
490
+ node_api_bug = result_data&.index('}HTTP/1.1 400 Bad Request') if result_data.is_a?(String)
491
+ result_data = result_data[0..node_api_bug] if node_api_bug
461
492
  result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
462
493
  Log.dump(:result_data, result_data)
463
494
  RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
@@ -572,34 +603,6 @@ module Aspera
572
603
  return call(operation: 'CANCEL', subpath: subpath, **kwargs)
573
604
  end
574
605
 
575
- # Query entity by general search (read with parameter `q`)
576
- # TODO: not generic enough ? move somewhere ? inheritance ?
577
- # @param subpath [String] Path of entity in API
578
- # @param search_name [String] Name of searched entity
579
- # @param query [Hash] Additional search query parameters
580
- # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
581
- def lookup_by_name(subpath, search_name, query: nil)
582
- query = {} if query.nil?
583
- # returns entities matching the query (it matches against several fields in case insensitive way)
584
- matching_items = read(subpath, query.merge({'q' => search_name}))
585
- # API style: {totalcount:, ...} cspell: disable-line
586
- matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
587
- Aspera.assert_type(matching_items, Array)
588
- case matching_items.length
589
- when 1 then return matching_items.first
590
- when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
591
- else
592
- # multiple case insensitive partial matches, try case insensitive full match
593
- # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
594
- name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
595
- case name_matches.length
596
- when 1 then return name_matches.first
597
- 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.)
598
- else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
599
- end
600
- end
601
- end
602
-
603
606
  UNAVAILABLE_CODES = ['503']
604
607
 
605
608
  private_constant :UNAVAILABLE_CODES
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ # List and lookup methods for Rest
5
+ # To be included in classes inheriting Rest that require those methods.
6
+ module RestList
7
+ # `max`: special query parameter: max number of items for list command
8
+ MAX_ITEMS = 'max'
9
+ # `pmax`: special query parameter: max number of pages for list command
10
+ MAX_PAGES = 'pmax'
11
+
12
+ # Query entity by general search (read with parameter `q`)
13
+ #
14
+ # @param subpath [String] Path of entity in API
15
+ # @param search_name [String] Name of searched entity
16
+ # @param query [Hash] Optional additional search query parameters
17
+ # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
18
+ def lookup_by_name(subpath, search_name, query: nil)
19
+ query = {} if query.nil?
20
+ # returns entities matching the query (it matches against several fields in case insensitive way)
21
+ matching_items = read(subpath, query.merge({'q' => search_name}))
22
+ # API style: {totalcount:, ...} cspell: disable-line
23
+ matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
24
+ Aspera.assert_type(matching_items, Array)
25
+ case matching_items.length
26
+ when 1 then return matching_items.first
27
+ when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
28
+ else
29
+ # multiple case insensitive partial matches, try case insensitive full match
30
+ # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
31
+ name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
32
+ case name_matches.length
33
+ when 1 then return name_matches.first
34
+ 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.)
35
+ else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
36
+ end
37
+ end
38
+ end
39
+
40
+ # Get a (full or partial) list of all entities of a given type with query: offset/limit
41
+ # @param entity [String,Symbol] API endpoint of entity to list
42
+ # @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
43
+ # @param query [Hash,nil] Additional query parameters
44
+ # @return [Array<(Array<Hash>, Integer)>] items, total_count
45
+ def list_entities_limit_offset_total_count(
46
+ entity:,
47
+ items_key: nil,
48
+ query: nil
49
+ )
50
+ entity = entity.to_s if entity.is_a?(Symbol)
51
+ items_key = entity.split('/').last if items_key.nil?
52
+ query = {} if query.nil?
53
+ Aspera.assert_type(entity, String)
54
+ Aspera.assert_type(items_key, String)
55
+ Aspera.assert_type(query, Hash)
56
+ Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
57
+ result = []
58
+ offset = 0
59
+ max_items = query.delete(MAX_ITEMS)
60
+ remain_pages = query.delete(MAX_PAGES)
61
+ # Merge default parameters, by default 100 per page
62
+ query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
63
+ total_count = nil
64
+ loop do
65
+ query['offset'] = offset
66
+ page_result = read(entity, query)
67
+ Aspera.assert_type(page_result[items_key], Array)
68
+ result.concat(page_result[items_key])
69
+ # Reach the limit set by user ?
70
+ if !max_items.nil? && (result.length >= max_items)
71
+ result = result.slice(0, max_items)
72
+ break
73
+ end
74
+ total_count ||= page_result['total_count']
75
+ break if result.length >= total_count
76
+ remain_pages -= 1 unless remain_pages.nil?
77
+ break if remain_pages == 0
78
+ offset += page_result[items_key].length
79
+ RestParameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
80
+ end
81
+ RestParameters.instance.spinner_cb.call(action: :success)
82
+ return result, total_count
83
+ end
84
+
85
+ # Lookup an entity id from its name.
86
+ # Uses query `q` if `query` is `:default` and `field` is `name`.
87
+ # @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
88
+ # @param value [String] Value to lookup
89
+ # @param field [String] Field to match, by default it is `'name'`
90
+ # @param items_key [String] Key in the result to get the list of items (override entity)
91
+ # @param query [Hash] Additional query parameters (Default: `:default`)
92
+ def lookup_entity_by_field(entity:, value:, field: 'name', items_key: nil, query: :default)
93
+ if query.eql?(:default)
94
+ Aspera.assert(field.eql?('name')){'Default query is on name only'}
95
+ query = {'q'=> value}
96
+ end
97
+ lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(entity: entity, items_key: items_key, query: query).first}
98
+ end
99
+
100
+ # Lookup entity by field and value.
101
+ # Extracts a single result from the list returned by the block.
102
+ #
103
+ # @param entity [String] Type of entity to lookup (path, and by default it is also the field name in result)
104
+ # @param value [String] Value to match against the field.
105
+ # @param field [String] Field to match in the hashes (defaults to 'name').
106
+ # @yield [] A mandatory block that returns an Array of Hashes.
107
+ # @return [Hash] The unique matching object.
108
+ # @raise [Cli::BadIdentifier] If 0 or >1 matches are found.
109
+ def lookup_entity_generic(entity:, value:, field: 'name')
110
+ Aspera.assert(block_given?)
111
+ found = yield
112
+ Aspera.assert_array_all(found, Hash)
113
+ found = found.select{ |i| i[field].eql?(value)}
114
+ return found.first if found.length.eql?(1)
115
+ raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
116
+ end
117
+ PER_PAGE_DEFAULT = 1000
118
+ private_constant :PER_PAGE_DEFAULT
119
+ module_function :lookup_entity_generic
120
+ end
121
+ end
@@ -197,7 +197,7 @@ module Aspera
197
197
  raise Error, 'Missing either local_db_dir or local_dir'
198
198
  end
199
199
  end
200
- stdout = Environment.secure_execute(*arguments, mode: :capture)
200
+ stdout = Environment.secure_execute(*arguments, mode: :capture).first
201
201
  return parse_status(stdout)
202
202
  end
203
203
 
@@ -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,
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.4
4
+ version: 4.25.6
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