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
@@ -32,7 +32,7 @@ module Aspera
32
32
  tools_to_check.delete(:unoconv) if skip_types.include?(:office)
33
33
  # Check for binaries
34
34
  tools_to_check.each do |command_sym|
35
- external_command(command_sym, ['-h'], check_code: false)
35
+ external_command(command_sym, ['-h'])
36
36
  rescue Errno::ENOENT => e
37
37
  raise "missing #{command_sym} binary: #{e}"
38
38
  rescue
@@ -43,19 +43,9 @@ module Aspera
43
43
  # execute external command
44
44
  # one could use "system", but we would need to redirect stdout/err
45
45
  # @return true if su
46
- def external_command(command_sym, command_args, check_code: true)
46
+ def external_command(command_sym, command_args)
47
47
  Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
48
- # build command line, and quote special characters
49
- command_line = command_args.clone.unshift(command_sym).map{|i| shell_quote(i.to_s)}.join(' ')
50
- Log.log.debug{"cmd=#{command_line}".blue}
51
- stdout, stderr, status = Open3.capture3(command_line)
52
- if check_code && !status.success?
53
- Log.log.error{"status: #{status}"}
54
- Log.log.error{"stdout: #{stdout}"}
55
- Log.log.error{"stderr: #{stderr}"}
56
- raise "#{command_sym} error #{status}"
57
- end
58
- return {status: status, stdout: stdout}
48
+ return Environment.secure_capture(command_sym.to_s, *command_args)
59
49
  end
60
50
 
61
51
  def ffmpeg(a)
@@ -73,12 +63,11 @@ module Aspera
73
63
 
74
64
  # @return Float in seconds
75
65
  def video_get_duration(input_file)
76
- result = external_command(:ffprobe, [
66
+ return external_command(:ffprobe, [
77
67
  '-loglevel', 'error',
78
68
  '-show_entries', 'format=duration',
79
69
  '-print_format', 'default=noprint_wrappers=1:nokey=1', # cspell:disable-line
80
- input_file])
81
- return result[:stdout].to_f
70
+ input_file]).to_f
82
71
  end
83
72
 
84
73
  def ffmpeg_fmt(temp_folder)
data/lib/aspera/rest.rb CHANGED
@@ -11,6 +11,8 @@ require 'net/https'
11
11
  require 'json'
12
12
  require 'base64'
13
13
  require 'cgi'
14
+ require 'singleton'
15
+ require 'securerandom'
14
16
 
15
17
  # Cancel method for HTTP
16
18
  class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
@@ -20,22 +22,32 @@ class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModul
20
22
  end
21
23
 
22
24
  module Aspera
25
+ # Global settings
26
+ # @param user_agent [String] HTTP request header: 'User-Agent'
27
+ # @param download_partial_suffix [String] suffix for partial download
28
+ # @param session_cb [lambda] lambda called on new HTTP session. Takes the Net::HTTP as arg. Used to change parameters on creation.
29
+ # @param progress_bar [Object] progress bar object
30
+ class RestParameters
31
+ include Singleton
32
+
33
+ attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_sleep, :session_cb, :progress_bar
34
+
35
+ private
36
+
37
+ def initialize
38
+ @user_agent = 'RubyAsperaRest'
39
+ @download_partial_suffix = '.http_partial'
40
+ @retry_on_error = 0
41
+ @retry_sleep = nil
42
+ @session_cb = nil
43
+ @progress_bar = nil
44
+ end
45
+ end
46
+
23
47
  # a simple class to make HTTP calls, equivalent to rest-client
24
48
  # rest call errors are raised as exception RestCallError
25
49
  # and error are analyzed in RestErrorAnalyzer
26
50
  class Rest
27
- # Global settings also valid for any subclass
28
- # @param user_agent [String] HTTP request header: 'User-Agent'
29
- # @param download_partial_suffix [String] suffix for partial download
30
- # @param session_cb [lambda] lambda called on new HTTP session. Takes the Net::HTTP as arg. Used to change parameters on creation.
31
- # @param progress_bar [Object] progress bar object
32
- @@global = { # rubocop:disable Style/ClassVars
33
- user_agent: 'RubyAsperaRest',
34
- download_partial_suffix: '.http_partial',
35
- session_cb: nil,
36
- progress_bar: nil
37
- }
38
-
39
51
  # flag for array parameters prefixed with []
40
52
  ARRAY_PARAMS = '[]'
41
53
 
@@ -61,37 +73,45 @@ module Aspera
61
73
  return values.first.eql?(ARRAY_PARAMS)
62
74
  end
63
75
 
64
- # Build URI from URL and parameters and check it is http or https, encode array [] parameters
65
- def build_uri(url, query_hash=nil)
76
+ # Build URI from URL and parameters and check it is http or https
77
+ # encode array [] parameters
78
+ # @param query [Hash,Array]
79
+ def build_uri(url, query=nil)
66
80
  uri = URI.parse(url)
67
81
  Aspera.assert(%w[http https].include?(uri.scheme)){"REST endpoint shall be http/s not #{uri.scheme}"}
68
- return uri if query_hash.nil?
69
- Log.log.debug{Log.dump('query', query_hash)}
70
- Aspera.assert_type(query_hash, Hash)
71
- return uri if query_hash.empty?
72
- query = []
73
- query_hash.each do |k, v|
74
- case v
75
- when Array
76
- # support array for query parameter, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
77
- suffix = array_params?(v) ? v.shift : ''
78
- v.each do |e|
79
- query.push(["#{k}#{suffix}", e])
82
+ return uri if query.nil? || query.respond_to?(:empty?) && query.empty?
83
+ Log.log.debug{Log.dump('query', query)}
84
+ query_array = []
85
+ case query
86
+ when Hash
87
+ query.each do |k, v|
88
+ case v
89
+ when Array
90
+ # support array for query parameter, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
91
+ suffix = array_params?(v) ? v.shift : ''
92
+ v.each do |e|
93
+ query_array.push(["#{k}#{suffix}", e])
94
+ end
95
+ else
96
+ query_array.push([k, v])
80
97
  end
81
- else
82
- query.push([k, v])
83
98
  end
99
+ when Array
100
+ Aspera.assert(query.all?{|i| i.is_a?(Array) && i.length.eql?(2)}) {'Query must be array of arrays or 2 elements'}
101
+ query_array = query
102
+ else
103
+ raise "Query must be Hash or Array, not #{query.class}"
84
104
  end
85
105
  # [] is allowed in url parameters
86
- uri.query = URI.encode_www_form(query).gsub('%5B%5D=', '[]=')
106
+ uri.query = URI.encode_www_form(query_array).gsub('%5B%5D=', '[]=')
87
107
  return uri
88
108
  end
89
109
 
90
- # decode query string as hash
110
+ # Decode query string as Hash
91
111
  # Does not support arrays in query string, no standard, e.g. PHP's way is p[]=1&p[]=2
92
- # @param query [String] query string
112
+ # @param query [String] query string as in URI.query
93
113
  # @return [Hash] decoded query
94
- def decode_query(query)
114
+ def query_to_h(query)
95
115
  URI.decode_www_form(query).each_with_object({}) do |pair, h|
96
116
  key = pair.first
97
117
  raise "Array not supported in query string: #{key}" if key.include?('[]') || h.key?(key)
@@ -108,7 +128,7 @@ module Aspera
108
128
  http_session = Net::HTTP.new(uri.host, uri.port)
109
129
  http_session.use_ssl = uri.scheme.eql?('https')
110
130
  # set http options in callback, such as timeout and cert. verification
111
- @@global[:session_cb]&.call(http_session)
131
+ RestParameters.instance.session_cb&.call(http_session)
112
132
  # manually start session for keep alive (if supported by server, else, session is closed every time)
113
133
  http_session.start
114
134
  return http_session
@@ -142,19 +162,6 @@ module Aspera
142
162
  return result
143
163
  end
144
164
 
145
- # set global parameters
146
- def set_parameters(**options)
147
- options.each do |key, value|
148
- Aspera.assert(@@global.key?(key)){"Unknown Rest option #{key}"}
149
- @@global[key] = value
150
- end
151
- end
152
-
153
- # @return [String] HTTP agent name
154
- def user_agent
155
- return @@global[:user_agent]
156
- end
157
-
158
165
  def parse_header(header)
159
166
  type, *params = header.split(/;\s*/)
160
167
  parameters = params.map do |param|
@@ -179,16 +186,17 @@ module Aspera
179
186
 
180
187
  public
181
188
 
182
- attr_reader :auth_params
183
189
  attr_reader :base_url
190
+ attr_reader :auth_params
184
191
 
192
+ # @return creation parameters
185
193
  def params
186
194
  return {
187
- base_url: @base_url,
188
- auth: @auth_params,
189
- not_auth_codes: @not_auth_codes,
190
- redirect_max: @redirect_max,
191
- headers: @headers
195
+ base_url: @base_url, # String
196
+ auth: @auth_params, # Hash
197
+ not_auth_codes: @not_auth_codes, # Array
198
+ redirect_max: @redirect_max, # Integer
199
+ headers: @headers # Hash
192
200
  }
193
201
  end
194
202
 
@@ -226,9 +234,10 @@ module Aspera
226
234
  # OAuth object (created on demand)
227
235
  @oauth = nil
228
236
  @redirect_max = redirect_max
237
+ Aspera.assert_type(@redirect_max, Integer)
229
238
  @headers = headers.nil? ? {} : headers
230
239
  Aspera.assert_type(@headers, Hash)
231
- @headers['User-Agent'] ||= @@global[:user_agent]
240
+ @headers['User-Agent'] ||= RestParameters.instance.user_agent
232
241
  end
233
242
 
234
243
  # @return the OAuth object (create, or cached if already created)
@@ -263,6 +272,8 @@ module Aspera
263
272
  )
264
273
  subpath = subpath.to_s if subpath.is_a?(Symbol)
265
274
  subpath = '' if subpath.nil?
275
+ Log.log.debug{"#{operation} [#{subpath}]".red.bold.bg_green}
276
+ Log.log.debug{Log.dump(:body, body)}
266
277
  Aspera.assert_type(subpath, String)
267
278
  if headers.nil?
268
279
  headers = @headers.clone
@@ -272,7 +283,6 @@ module Aspera
272
283
  headers.merge!(h)
273
284
  end
274
285
  Aspera.assert_type(headers, Hash)
275
- Log.log.debug{"#{operation} [#{subpath}]".red.bold.bg_green}
276
286
  case @auth_params[:type]
277
287
  when :none
278
288
  # no auth
@@ -322,11 +332,13 @@ module Aspera
322
332
  end
323
333
  # :type = :basic
324
334
  req.basic_auth(@auth_params[:username], @auth_params[:password]) if @auth_params[:type].eql?(:basic)
325
- Log.log.debug{Log.dump(:req_body, req.body)}
335
+ Log.log.trace1{Log.dump(:req_body, req.body)}
326
336
  # we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
327
337
  oauth_tries ||= 2
338
+ timeout_tries ||= 5
339
+ general_tries ||= 1 + RestParameters.instance.retry_on_error
328
340
  # initialize with number of initial retries allowed, nil gives zero
329
- tries_remain_redirect = @redirect_max.to_i if tries_remain_redirect.nil?
341
+ tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
330
342
  Log.log.debug("send request (retries=#{tries_remain_redirect})")
331
343
  result_mime = nil
332
344
  file_saved = false
@@ -350,38 +362,44 @@ module Aspera
350
362
  end
351
363
  end
352
364
  # download with temp filename
353
- target_file_tmp = "#{target_file}#{@@global[:download_partial_suffix]}"
365
+ target_file_tmp = "#{target_file}#{RestParameters.instance.download_partial_suffix}"
354
366
  Log.log.debug{"saving to: #{target_file}"}
355
367
  written_size = 0
356
- @@global[:progress_bar]&.event(session_id: 1, type: :session_start)
357
- @@global[:progress_bar]&.event(session_id: 1, type: :session_size, info: total_size)
368
+ session_id = SecureRandom.uuid.freeze
369
+ RestParameters.instance.progress_bar&.event(:session_start, session_id: session_id)
370
+ RestParameters.instance.progress_bar&.event(:session_size, session_id: session_id, info: total_size)
358
371
  File.open(target_file_tmp, 'wb') do |file|
359
372
  result[:http].read_body do |fragment|
360
373
  file.write(fragment)
361
374
  written_size += fragment.length
362
- @@global[:progress_bar]&.event(session_id: 1, type: :transfer, info: written_size)
375
+ RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size)
363
376
  end
364
377
  end
365
- @@global[:progress_bar]&.event(session_id: 1, type: :end)
378
+ RestParameters.instance.progress_bar&.event(:end, session_id: session_id)
366
379
  # rename at the end
367
380
  File.rename(target_file_tmp, target_file)
368
381
  file_saved = true
369
382
  end
370
383
  end
384
+ Log.log.debug{"result: code=#{result[:http].code} mime=#{result_mime}"}
371
385
  # sometimes there is a UTF8 char (e.g. (c) ), TODO : related to mime type encoding ?
372
386
  # result[:http].body.force_encoding('UTF-8') if result[:http].body.is_a?(String)
373
387
  # Log.log.debug{"result: body=#{result[:http].body}"}
374
- result[:data] = case result_mime
388
+ case result_mime
375
389
  when *JSON_DECODE
376
- JSON.parse(result[:http].body) rescue result[:http].body
390
+ result[:data] = JSON.parse(result[:http].body) rescue result[:http].body
391
+ Log.log.debug{Log.dump('result_data', result[:data])}
377
392
  else # when 'text/plain'
378
- result[:http].body
393
+ result[:data] = result[:http].body
379
394
  end
380
- Log.log.debug{"result: code=#{result[:http].code} mime=#{result_mime}"}
381
- Log.log.debug{Log.dump('data', result[:data])}
382
395
  RestErrorAnalyzer.instance.raise_on_error(req, result)
383
- File.write(save_to_file, result[:http].body) unless file_saved || save_to_file.nil?
396
+ File.write(save_to_file, result[:http].body, binmode: true) unless file_saved || save_to_file.nil?
384
397
  rescue RestCallError => e
398
+ do_retry = false
399
+ # AoC have some timeout , like Connect to platform.bss.asperasoft.com:443 ...
400
+ do_retry = true if e.response.body.include?('failed: connect timed out') && (timeout_tries -= 1).positive?
401
+ # possibility to retry anything if it fails
402
+ do_retry = true if (general_tries -= 1).positive?
385
403
  # not authorized: oauth token expired
386
404
  if @not_auth_codes.include?(result[:http].code.to_s) && @auth_params[:type].eql?(:oauth2)
387
405
  begin
@@ -394,7 +412,11 @@ module Aspera
394
412
  req['Authorization'] = oauth.token(refresh: true)
395
413
  end
396
414
  Log.log.debug{"using new token=#{headers['Authorization']}"}
397
- retry if (oauth_tries -= 1).nonzero?
415
+ do_retry = true if (oauth_tries -= 1).positive?
416
+ end
417
+ if do_retry
418
+ sleep(RestParameters.instance.retry_sleep) unless RestParameters.instance.retry_sleep.nil?
419
+ retry
398
420
  end
399
421
  # redirect ? (any code beginning with 3)
400
422
  if e.response.is_a?(Net::HTTPRedirection) && tries_remain_redirect.positive?
@@ -420,27 +442,28 @@ module Aspera
420
442
  end
421
443
 
422
444
  #
423
- # CRUD methods here
445
+ # CRUD simplified methods here
446
+ # If specific elements are needed, then use the full `call` method
424
447
  #
425
448
 
426
449
  def create(subpath, params)
427
- return call(operation: 'POST', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)
450
+ return call(operation: 'POST', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)[:data]
428
451
  end
429
452
 
430
453
  def read(subpath, query=nil)
431
- return call(operation: 'GET', subpath: subpath, headers: {'Accept' => 'application/json'}, query: query)
454
+ return call(operation: 'GET', subpath: subpath, headers: {'Accept' => 'application/json'}, query: query)[:data]
432
455
  end
433
456
 
434
457
  def update(subpath, params)
435
- return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)
458
+ return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, body: params, body_type: :json)[:data]
436
459
  end
437
460
 
438
461
  def delete(subpath, params=nil)
439
- return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}, query: params)
462
+ return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}, query: params)[:data]
440
463
  end
441
464
 
442
465
  def cancel(subpath)
443
- return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => 'application/json'})
466
+ return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => 'application/json'})[:data]
444
467
  end
445
468
 
446
469
  # Query entity by general search (read with parameter `q`)
@@ -449,9 +472,10 @@ module Aspera
449
472
  # @param search_name name of searched entity
450
473
  # @param query additional search query parameters
451
474
  # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
452
- def lookup_by_name(subpath, search_name, query={})
475
+ def lookup_by_name(subpath, search_name, query: nil)
476
+ query = {} if query.nil?
453
477
  # returns entities matching the query (it matches against several fields in case insensitive way)
454
- matching_items = read(subpath, query.merge({'q' => search_name}))[:data]
478
+ matching_items = read(subpath, query.merge({'q' => search_name}))
455
479
  # API style: {totalcount:, ...} cspell: disable-line
456
480
  matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
457
481
  Aspera.assert_type(matching_items, Array)
@@ -16,17 +16,18 @@ module Aspera
16
16
  KEY_SECRETS = %w[password secret passphrase _key apikey crn token].freeze
17
17
  HTTP_SECRETS = %w[Authorization].freeze
18
18
  ALL_SECRETS = [ASCP_ENV_SECRETS, KEY_SECRETS, HTTP_SECRETS].flatten.freeze
19
+ ALL_SECRETS2 = [KEY_SECRETS, HTTP_SECRETS].flatten.freeze
19
20
  KEY_FALSE_POSITIVES = [/^access_key$/, /^fallback_private_key$/].freeze
20
21
  # regex that define named captures :begin and :end
21
22
  REGEX_LOG_REPLACES = [
22
23
  # CLI manager get/set options
23
24
  /(?<begin>[sg]et (?:#{KEY_SECRETS.join('|')})=).*(?<end>)/,
24
25
  # env var ascp exec
25
- /(?<begin> (?:#{ASCP_ENV_SECRETS.join('|')})=)(\\.|[^ ])*(?<end> )/,
26
+ /(?<begin> (?:#{ASCP_ENV_SECRETS.join('|')})=)[^ ]+(?<end> )/,
26
27
  # rendered JSON or Ruby
27
28
  /(?<begin>(?:(?<quote>["'])|:)[^"':=]*(?:#{ALL_SECRETS.join('|')})[^"':=]*\k<quote>?(?:=>|:) *")[^"]+(?<end>")/,
28
29
  # logged data
29
- /(?<begin>(?:#{ALL_SECRETS.join('|')})[ =:]+).*(?<end>$)/,
30
+ /(?<begin>(?:#{ALL_SECRETS2.join('|')})[ =:]+).*(?<end>$)/,
30
31
  # private key values
31
32
  /(?<begin>--+BEGIN [^-]+ KEY--+)[[:ascii:]]+?(?<end>--+?END [^-]+ KEY--+)/,
32
33
  # cred in http dump
data/lib/aspera/ssh.rb CHANGED
@@ -14,7 +14,7 @@ if ENV.fetch('ASCLI_ENABLE_ED25519', 'false').eql?('false')
14
14
  $VERBOSE = old_verbose
15
15
  end
16
16
 
17
- if RUBY_ENGINE == 'jruby' && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')
17
+ if defined?(JRUBY_VERSION) && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')
18
18
  Net::SSH::Transport::Algorithms::ALGORITHMS.each_value { |a| a.reject! { |a| a =~ /^ecd(sa|h)-sha2/ } }
19
19
  Net::SSH::KnownHosts::SUPPORTED_TYPE.reject! { |t| t =~ /^ecd(sa|h)-sha2/ }
20
20
  end
@@ -8,19 +8,21 @@ module Aspera
8
8
  PREFIX = 'faux:///'
9
9
  # size suffix
10
10
  SUFFIX = %w[k m g t p e]
11
+ private_constant :PREFIX, :SUFFIX
11
12
  class << self
12
- def open(name)
13
+ # @return nil if not a faux: scheme, else a FauxFile instance
14
+ def create(name)
13
15
  return nil unless name.start_with?(PREFIX)
14
- parts = name[PREFIX.length..-1].split('?')
15
- raise 'Format: #{PREFIX}<file path>?<size>' unless parts.length.eql?(2)
16
- raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = parts[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
16
+ name_params = name[PREFIX.length..-1].split('?', 2)
17
+ raise 'Format: #{PREFIX}<file path>?<size>' unless name_params.length.eql?(2)
18
+ raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = name_params[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
17
19
  size = m[1].to_i
18
20
  suffix = m[2]
19
21
  SUFFIX.each do |s|
20
22
  size *= 1024
21
23
  break if s.eql?(suffix)
22
24
  end
23
- return FauxFile.new(parts[0], size)
25
+ return FauxFile.new(name_params[0], size)
24
26
  end
25
27
  end
26
28
  attr_reader :path, :size
@@ -21,17 +21,29 @@ module Aspera
21
21
  class Parameters
22
22
  # Agents shown in manual for parameters (sub list)
23
23
  SUPPORTED_AGENTS = %i[direct node connect trsdk httpgw].freeze
24
+ FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
24
25
  # Short names of columns in manual
25
26
  SUPPORTED_AGENTS_SHORT = SUPPORTED_AGENTS.map{|agent_sym|agent_sym.to_s[0].to_sym}
26
- FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
27
+ HTTP_FALLBACK_ACTIVATION_VALUES = ['1', 1, true, 'force'].freeze
27
28
 
28
29
  private_constant :SUPPORTED_AGENTS, :FILE_LIST_OPTIONS
29
30
 
30
31
  class << self
32
+ # temp file list files are created here
33
+ def file_list_folder=(value)
34
+ @file_list_folder = value
35
+ return if @file_list_folder.nil?
36
+
37
+ FileUtils.mkdir_p(@file_list_folder)
38
+ TempFileManager.instance.cleanup_expired(@file_list_folder)
39
+ end
40
+
31
41
  # Temp folder for file lists, must contain only file lists
32
42
  # because of garbage collection takes any file there
33
- # this could be refined, as , for instance, on macos, temp folder is already user specific
34
- @file_list_folder = TempFileManager.instance.new_file_path_global('asession_filelists') # cspell:disable-line
43
+ # this could be refined, as, for example, on macos, temp folder is already user specific
44
+ def file_list_folder
45
+ @file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
46
+ end
35
47
 
36
48
  # @param formatter [Cli::Formatter] formatter to use
37
49
  # @return a table suitable to display in manual
@@ -66,9 +78,7 @@ module Aspera
66
78
  else
67
79
  param[:d].eql?(tick_yes) ? '' : 'n/a'
68
80
  end
69
- if options.key?(:enum)
70
- param[:description] += "\nAllowed values: #{options[:enum].join(', ')}"
71
- end
81
+ param[:description] += "\nAllowed values: #{options[:enum].join(', ')}" if options.key?(:enum)
72
82
  # replace "solidus" HTML entity with its text value
73
83
  param[:description] = param[:description].gsub('&sol;', '\\')
74
84
  result.push(param)
@@ -90,37 +100,30 @@ module Aspera
90
100
  def ascp_args_file_list?(ascp_args)
91
101
  ascp_args&.any?{|i|FILE_LIST_OPTIONS.include?(i)}
92
102
  end
93
-
94
- # temp file list files are created here
95
- def file_list_folder=(value)
96
- @file_list_folder = value
97
- return if @file_list_folder.nil?
98
- FileUtils.mkdir_p(@file_list_folder)
99
- TempFileManager.instance.cleanup_expired(@file_list_folder)
100
- end
101
-
102
- # static methods
103
- attr_reader :file_list_folder
104
103
  end
105
104
 
106
105
  # @param options [Hash] key: :wss: bool, :ascp_args: array of strings
107
106
  def initialize(
108
107
  job_spec,
109
- ascp_args:,
110
- wss:,
111
- quiet:,
112
- trusted_certs:,
113
- check_ignore_cb:
108
+ ascp_args: nil,
109
+ wss: true,
110
+ quiet: true,
111
+ trusted_certs: nil,
112
+ client_ssh_key: nil,
113
+ check_ignore_cb: nil
114
114
  )
115
115
  @job_spec = job_spec
116
- @ascp_args = ascp_args
116
+ @ascp_args = ascp_args.nil? ? [] : ascp_args
117
117
  @wss = wss
118
118
  @quiet = quiet
119
- @trusted_certs = trusted_certs
119
+ @trusted_certs = trusted_certs.nil? ? [] : trusted_certs
120
+ @client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
120
121
  @check_ignore_cb = check_ignore_cb
121
- Aspera.assert_type(job_spec, Hash)
122
+ Aspera.assert_type(@job_spec, Hash)
122
123
  Aspera.assert_type(@ascp_args, Array){'ascp_args'}
123
- Aspera.assert(@ascp_args.all?(String)){'ascp arguments must be Strings'}
124
+ Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
125
+ Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
126
+ Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
124
127
  @builder = CommandLineBuilder.new(@job_spec, Spec::DESCRIPTION)
125
128
  end
126
129
 
@@ -162,6 +165,7 @@ module Aspera
162
165
  @builder.add_command_line_options(["#{file_list_option}=#{file_list_file}"]) unless file_list_option.nil?
163
166
  end
164
167
 
168
+ # @return the list of certificates to use when token/ssh or wss are used
165
169
  def remote_certificates
166
170
  certificates_to_use = []
167
171
  # use web socket secure for session ?
@@ -174,7 +178,7 @@ module Aspera
174
178
  @job_spec.delete('fasp_port')
175
179
  @job_spec.delete('sshfp')
176
180
  # set location for CA bundle to be the one of Ruby, see env var SSL_CERT_FILE / SSL_CERT_DIR
177
- certificates_to_use.concat(@trusted_certs) if @trusted_certs.is_a?(Array)
181
+ certificates_to_use.concat(@trusted_certs)
178
182
  # ignore cert for wss ?
179
183
  if @check_ignore_cb&.call(@job_spec['remote_host'], @job_spec['wss_port'])
180
184
  wss_cert_file = TempFileManager.instance.new_file_path_global('wss_cert')
@@ -191,7 +195,7 @@ module Aspera
191
195
  # add SSH bypass keys when authentication is token and no auth is provided
192
196
  if @job_spec.key?('token') && !@job_spec.key?('remote_password')
193
197
  # @job_spec['remote_password'] = Ascp::Installation.instance.ssh_cert_uuid # not used: no passphrase
194
- certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths)
198
+ certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(@client_ssh_key))
195
199
  end
196
200
  end
197
201
  return certificates_to_use
@@ -200,9 +204,9 @@ module Aspera
200
204
  # translate transfer spec to env vars and command line arguments for ascp
201
205
  def ascp_args
202
206
  env_args = {
203
- args: [],
204
- env: {},
205
- ascp_version: :ascp
207
+ args: [],
208
+ env: {},
209
+ name: :ascp
206
210
  }
207
211
 
208
212
  # special cases
@@ -213,7 +217,7 @@ module Aspera
213
217
 
214
218
  # add ssh or wss certificates
215
219
  # (reverse, to keep order, as we unshift)
216
- remote_certificates.reverse_each do |cert|
220
+ remote_certificates&.reverse_each do |cert|
217
221
  env_args[:args].unshift('-i', cert)
218
222
  end
219
223
 
@@ -223,9 +227,9 @@ module Aspera
223
227
  base64_destination = false
224
228
  # symbol must be index of Ascp::Installation.paths
225
229
  if @builder.read_param('use_ascp4')
226
- env_args[:ascp_version] = :ascp4
230
+ env_args[:name] = :ascp4
227
231
  else
228
- env_args[:ascp_version] = :ascp
232
+ env_args[:name] = :ascp
229
233
  base64_destination = true
230
234
  end
231
235
  # destination will be base64 encoded, put this before source path arguments
@@ -243,10 +247,12 @@ module Aspera
243
247
  @builder.add_env_args(env_args)
244
248
  env_args[:args].unshift('-q') if @quiet
245
249
  # add fallback cert and key as arguments if needed
246
- if ['1', 1, true, 'force'].include?(@job_spec['http_fallback'])
250
+ if HTTP_FALLBACK_ACTIVATION_VALUES.include?(@job_spec['http_fallback'])
247
251
  env_args[:args].unshift('-Y', Ascp::Installation.instance.path(:fallback_private_key))
248
252
  env_args[:args].unshift('-I', Ascp::Installation.instance.path(:fallback_certificate))
249
253
  end
254
+ # disable redis in client
255
+ env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true'
250
256
  Log.log.debug{"ascp args: #{env_args}"}
251
257
  return env_args
252
258
  end
@@ -10,36 +10,30 @@ module Aspera
10
10
  class Spec
11
11
  # default transfer username for access key based transfers
12
12
  ACCESS_KEY_TRANSFER_USER = 'xfer'
13
+ # default ports for SSH and UDP
13
14
  SSH_PORT = 33_001
14
15
  UDP_PORT = 33_001
16
+ # base transfer spec for access keys
15
17
  AK_TSPEC_BASE = {
16
18
  'remote_user' => ACCESS_KEY_TRANSFER_USER,
17
19
  'ssh_port' => SSH_PORT,
18
20
  'fasp_port' => UDP_PORT
19
21
  }.freeze
20
- # fields for transport
21
- TRANSPORT_FIELDS = %w[remote_host remote_user ssh_port fasp_port wss_enabled wss_port].freeze
22
+ # fields for WSS
23
+ WSS_FIELDS = %w[wss_enabled wss_port].freeze
24
+ # all fields for transport
25
+ TRANSPORT_FIELDS = %w[remote_host remote_user ssh_port fasp_port].concat(WSS_FIELDS).freeze
22
26
  # reserved tag for Aspera
23
27
  TAG_RESERVED = 'aspera'
24
28
  class << self
25
- def action_to_direction(tspec, command)
26
- Aspera.assert_type(tspec, Hash){'transfer spec'}
27
- tspec['direction'] = case command.to_sym
28
- when :upload then DIRECTION_SEND
29
- when :download then DIRECTION_RECEIVE
30
- else Aspera.error_unexpected_value(command.to_sym)
31
- end
32
- return tspec
29
+ # translate upload/download to send/receive
30
+ def transfer_type_to_direction(transfer_type)
31
+ XFER_TYPE_TO_DIR.fetch(transfer_type)
33
32
  end
34
33
 
35
- def action(tspec)
36
- Aspera.assert_type(tspec, Hash){'transfer spec'}
37
- Aspera.assert_values(tspec['direction'], [DIRECTION_SEND, DIRECTION_RECEIVE]){'direction'}
38
- case tspec['direction']
39
- when DIRECTION_SEND then :upload
40
- when DIRECTION_RECEIVE then :download
41
- else Aspera.error_unexpected_value(tspec['direction'])
42
- end
34
+ # translate send/receive to upload/download
35
+ def direction_to_transfer_type(direction)
36
+ XFER_DIR_TO_TYPE.fetch(direction)
43
37
  end
44
38
  end
45
39
  DESCRIPTION = CommandLineBuilder.normalize_description(YAML.load_file("#{__FILE__[0..-3]}yaml"))
@@ -51,6 +45,10 @@ module Aspera
51
45
  const_set("#{name.to_s.upcase}_#{enum.upcase.gsub(/[^A-Z0-9]/, '_')}", enum.freeze)
52
46
  end
53
47
  end
48
+ # DIRECTION_* are read from yaml
49
+ XFER_TYPE_TO_DIR = {upload: DIRECTION_SEND, download: DIRECTION_RECEIVE}.freeze
50
+ XFER_DIR_TO_TYPE = XFER_TYPE_TO_DIR.invert.freeze
51
+ private_constant :XFER_TYPE_TO_DIR, :XFER_DIR_TO_TYPE
54
52
  end
55
53
  end
56
54
  end