aspera-cli 4.14.0 → 4.16.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 (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -2,6 +2,8 @@
2
2
 
3
3
  # https://github.com/fastlane-community/security
4
4
  require 'aspera/cli/info'
5
+ require 'aspera/log'
6
+ require 'aspera/assert'
5
7
 
6
8
  # enhance the gem to support other key chains
7
9
  module Aspera
@@ -36,7 +38,7 @@ module Aspera
36
38
  url = options&.delete(:url)
37
39
  if !url.nil?
38
40
  uri = URI.parse(url)
39
- raise 'only https' unless uri.scheme.eql?('https')
41
+ assert(uri.scheme.eql?('https')){'only https'}
40
42
  options[:protocol] = 'htps' # cspell: disable-line
41
43
  raise 'host required in URL' if uri.host.nil?
42
44
  options[:server] = uri.host
@@ -45,7 +47,7 @@ module Aspera
45
47
  end
46
48
  cmd = ['security', command]
47
49
  options&.each do |k, v|
48
- raise "unknown option: #{k}" unless supported.key?(k)
50
+ assert(supported.key?(k)){"unknown option: #{k}"}
49
51
  next if v.nil?
50
52
  cmd.push("-#{supported[k]}")
51
53
  cmd.push(v.shellescape) unless v.empty?
@@ -70,7 +72,7 @@ module Aspera
70
72
  end
71
73
 
72
74
  def list(options={})
73
- raise ArgumentError, "Invalid domain #{options[:domain]}, expected one of: #{DOMAINS}" unless options[:domain].nil? || DOMAINS.include?(options[:domain])
75
+ assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
74
76
  key_chains(execute('list-key_chains', options, LIST_OPTIONS))
75
77
  end
76
78
 
@@ -89,11 +91,11 @@ module Aspera
89
91
  end
90
92
 
91
93
  def password(operation, pass_type, options)
92
- raise "wrong operation: #{operation}" unless %i[add find delete].include?(operation)
93
- raise "wrong pass_type: #{pass_type}" unless %i[generic internet].include?(pass_type)
94
- raise 'options shall be Hash' unless options.is_a?(Hash)
94
+ assert_values(operation, %i[add find delete]){'operation'}
95
+ assert_values(pass_type, %i[generic internet]){'pass_type'}
96
+ assert_type(options, Hash)
95
97
  missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - options.keys
96
- raise "missing options: #{missing}" unless missing.empty?
98
+ assert(missing.empty?){"missing options: #{missing}"}
97
99
  options[:getpass] = '' if operation.eql?(:find)
98
100
  output = self.class.execute("#{operation}-#{pass_type}-password", options, ADD_PASS_OPTIONS, @path)
99
101
  raise output.gsub(/^.*: /, '') if output.start_with?('security: ')
@@ -127,18 +129,18 @@ module Aspera
127
129
  end
128
130
 
129
131
  def set(options)
130
- raise 'options shall be Hash' unless options.is_a?(Hash)
132
+ assert_type(options, Hash){'options'}
131
133
  unsupported = options.keys - %i[label username password url description]
132
- raise "unsupported options: #{unsupported}" unless unsupported.empty?
134
+ assert(unsupported.empty?){"unsupported options: #{unsupported}"}
133
135
  @keychain.password(
134
136
  :add, :generic, service: options[:label],
135
137
  account: options[:username] || 'none', password: options[:password], comment: options[:description])
136
138
  end
137
139
 
138
140
  def get(options)
139
- raise 'options shall be Hash' unless options.is_a?(Hash)
141
+ assert_type(options, Hash){'options'}
140
142
  unsupported = options.keys - %i[label]
141
- raise "unsupported options: #{unsupported}" unless unsupported.empty?
143
+ assert(unsupported.empty?){"unsupported options: #{unsupported}"}
142
144
  info = @keychain.password(:find, :generic, label: options[:label])
143
145
  raise 'not found' if info.nil?
144
146
  result = options.clone
@@ -153,9 +155,9 @@ module Aspera
153
155
  end
154
156
 
155
157
  def delete(options)
156
- raise 'options shall be Hash' unless options.is_a?(Hash)
158
+ assert_type(options, Hash){'options'}
157
159
  unsupported = options.keys - %i[label]
158
- raise "unsupported options: #{unsupported}" unless unsupported.empty?
160
+ assert(unsupported.empty?){"unsupported options: #{unsupported}"}
159
161
  raise 'delete not implemented, use macos keychain app'
160
162
  end
161
163
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/log'
4
+
5
+ module Aspera
6
+ # used for logging http
7
+ class LineLogger
8
+ def initialize(level)
9
+ @level = level
10
+ @buffer = []
11
+ end
12
+
13
+ def <<(string)
14
+ return if string.nil? || string.empty?
15
+ if !string.end_with?("\n")
16
+ @buffer.push(string)
17
+ return
18
+ end
19
+ Log.log.send(@level, @buffer.join('') + string.chomp)
20
+ @buffer.clear
21
+ end
22
+ end
23
+ end
data/lib/aspera/log.rb CHANGED
@@ -2,17 +2,55 @@
2
2
 
3
3
  require 'aspera/colors'
4
4
  require 'aspera/secret_hider'
5
+ require 'aspera/environment'
6
+ require 'aspera/assert'
5
7
  require 'logger'
6
8
  require 'pp'
7
9
  require 'json'
8
10
  require 'singleton'
9
11
 
12
+ # extend Ruby logger with trace levels
13
+ class Logger
14
+ TRACE_MAX = 2
15
+ # add custom level to logger severity
16
+ module Severity
17
+ 1.upto(TRACE_MAX).each { |level| const_set("TRACE#{level}", - level)}
18
+ end
19
+ # quick access to label
20
+ SEVERITY_LABEL = Severity.constants.each_with_object({}) { |name, hash| hash[Severity.const_get(name)] = name}
21
+ def format_severity(severity)
22
+ SEVERITY_LABEL[severity] || 'ANY'
23
+ end
24
+
25
+ # define methods for a given level
26
+ def self.make_methods(str_level) # rubocop:disable Style/ClassMethodsDefinitions
27
+ int_level = ::Logger.const_get(str_level.upcase)
28
+ str_level = str_level.downcase
29
+ Kernel.send('lave'.reverse, <<-EOM, nil, __FILE__, __LINE__ + 1)
30
+ def #{str_level}(message = nil, &block)
31
+ add(#{int_level}, message, &block)
32
+ end
33
+
34
+ def #{str_level}?
35
+ level <= #{int_level}
36
+ end
37
+
38
+ def #{str_level}!
39
+ self.level = #{int_level}
40
+ end
41
+ EOM
42
+ end
43
+ # declare methods for all levels
44
+ Logger::Severity.constants.each { |severity| make_methods(severity) }
45
+ end
46
+
10
47
  module Aspera
11
48
  # Singleton object for logging
12
49
  class Log
13
50
  include Singleton
14
51
  # where logs are sent to
15
52
  LOG_TYPES = %i[stderr stdout syslog].freeze
53
+ @@format = :json # rubocop:disable Style/ClassVars
16
54
  # class methods
17
55
  class << self
18
56
  # levels are :debug,:info,:warn,:error,fatal,:unknown
@@ -21,22 +59,20 @@ module Aspera
21
59
  # get the logger object of singleton
22
60
  def log; instance.logger; end
23
61
 
24
- # dump object in debug mode
62
+ # dump object suitable for Log.log.debug
25
63
  # @param name string or symbol
26
64
  # @param format either pp or json format
27
- def dump(name, object, format=:json)
28
- log.debug do
29
- result =
30
- case format
31
- when :json
32
- JSON.pretty_generate(object) rescue PP.pp(object, +'')
33
- when :ruby
34
- PP.pp(object, +'')
35
- else
36
- raise 'wrong parameter, expect pp or json'
37
- end
38
- "#{name.to_s.green} (#{format})=\n#{result}"
39
- end
65
+ def dump(name, object)
66
+ result =
67
+ case @@format
68
+ when :json
69
+ JSON.pretty_generate(object) rescue PP.pp(object, +'')
70
+ when :ruby
71
+ PP.pp(object, +'')
72
+ else
73
+ raise 'wrong parameter, expect ruby or json'
74
+ end
75
+ "#{name.to_s.green} (#{@@format})=\n#{result}"
40
76
  end
41
77
 
42
78
  # Capture the output of $stderr and log it at debug level
@@ -63,26 +99,32 @@ module Aspera
63
99
  Logger::Severity.constants.each do |name|
64
100
  return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
65
101
  end
66
- # should not happen
67
- raise "INTERNAL ERROR: unexpected level #{@logger.level}"
102
+ error_unexpected_value(@logger.level){'log level'}
68
103
  end
69
104
 
70
105
  # change underlying logger, but keep log level
71
106
  def logger_type=(new_log_type)
72
107
  current_severity_integer = @logger.level unless @logger.nil?
73
- current_severity_integer = ENV['AS_LOG_LEVEL'] if current_severity_integer.nil? && ENV.key?('AS_LOG_LEVEL')
108
+ current_severity_integer = ENV.fetch('AS_LOG_LEVEL', nil) if current_severity_integer.nil? && ENV.key?('AS_LOG_LEVEL')
74
109
  current_severity_integer = Logger::Severity::WARN if current_severity_integer.nil?
75
110
  case new_log_type
76
111
  when :stderr
77
- # typed: Logger
78
112
  @logger = Logger.new($stderr)
79
113
  when :stdout
80
114
  @logger = Logger.new($stdout)
81
115
  when :syslog
82
116
  require 'syslog/logger'
117
+ # the syslog class automatically creates methods from the severity names
118
+ # we just need to add the mapping (but syslog lowest is DEBUG)
119
+ 1.upto(Logger::TRACE_MAX).each do |level|
120
+ Syslog::Logger.const_get(:LEVEL_MAP)[Logger.const_get("TRACE#{level}")] = Syslog::LOG_DEBUG
121
+ end
122
+ Logger::Severity.constants.each do |severity|
123
+ Syslog::Logger.make_methods(severity.downcase)
124
+ end
83
125
  @logger = Syslog::Logger.new(@program_name, Syslog::LOG_LOCAL2)
84
126
  else
85
- raise "unknown log type: #{new_log_type.class} #{new_log_type}"
127
+ raise "unknown log type: #{new_log_type}, use one of: #{LOG_TYPES.join(', ')}"
86
128
  end
87
129
  @logger.level = current_severity_integer
88
130
  @logger_type = new_log_type
data/lib/aspera/nagios.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/log'
4
+ require 'aspera/assert'
3
5
  require 'date'
4
6
 
5
7
  module Aspera
@@ -21,8 +23,11 @@ module Aspera
21
23
  class << self
22
24
  # process results of a analysis and display status and exit with code
23
25
  def process(data)
24
- raise 'INTERNAL ERROR, result must be list and not empty' unless data.is_a?(Array) && !data.empty?
25
- %w[status component message].each{|c|raise "INTERNAL ERROR, result must have #{c}" unless data.first.key?(c)}
26
+ assert_type(data, Array)
27
+ assert(!data.empty?){'data is empty'}
28
+ %w[status component message].each do |c|
29
+ assert(data.first.key?(c)){"result must have #{c}"}
30
+ end
26
31
  res_errors = data.reject{|s|s['status'].eql?('ok')}
27
32
  # keep only errors in case of problem, other ok are assumed so
28
33
  data = res_errors unless res_errors.empty?
data/lib/aspera/node.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/cli/error'
3
4
  require 'aspera/fasp/transfer_spec'
4
5
  require 'aspera/rest'
5
6
  require 'aspera/oauth'
6
7
  require 'aspera/log'
8
+ require 'aspera/assert'
7
9
  require 'aspera/environment'
8
10
  require 'zlib'
9
11
  require 'base64'
@@ -13,14 +15,22 @@ module Aspera
13
15
  class Node < Aspera::Rest
14
16
  # permissions
15
17
  ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
16
- # prefix for ruby code for filter
18
+ # prefix for ruby code for filter (deprecated)
17
19
  MATCH_EXEC_PREFIX = 'exec:'
20
+ MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
18
21
  HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
19
22
  PATH_SEPARATOR = '/'
20
23
  TS_FIELDS_TO_COPY = %w[remote_host remote_user ssh_port fasp_port wss_enabled wss_port].freeze
24
+ SCOPE_USER = 'user:all'
25
+ SCOPE_ADMIN = 'admin:all'
26
+ SCOPE_PREFIX = 'node.'
27
+ SCOPE_SEPARATOR = ':'
28
+ SIGNATURE_DELIMITER = '==SIGNATURE=='
29
+ BEARER_TOKEN_VALIDITY_DEFAULT = 86400
30
+ BEARER_TOKEN_SCOPE_DEFAULT = SCOPE_USER
21
31
 
22
32
  # register node special token decoder
23
- Oauth.register_decoder(lambda{|token|JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition('==SIGNATURE==').first)})
33
+ Oauth.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
24
34
 
25
35
  # class instance variable, access with accessors on class
26
36
  @use_standard_ports = true
@@ -28,16 +38,79 @@ module Aspera
28
38
  class << self
29
39
  attr_accessor :use_standard_ports
30
40
 
31
- # for access keys: provide expression to match entry in folder
32
- # if no prefix: regex
33
- # if prefix: ruby code
34
- # if expression is nil, then always match
41
+ # For access keys: provide expression to match entry in folder
35
42
  def file_matcher(match_expression)
36
- match_expression ||= "#{MATCH_EXEC_PREFIX}true"
37
- if match_expression.start_with?(MATCH_EXEC_PREFIX)
38
- return Environment.secure_eval("lambda{|f|#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}")
43
+ case match_expression
44
+ when Proc then return match_expression
45
+ when Regexp then return ->(f){f['name'].match?(match_expression)}
46
+ when String
47
+ if match_expression.start_with?(MATCH_EXEC_PREFIX)
48
+ code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
49
+ Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
50
+ return Environment.secure_eval(code, __FILE__, __LINE__)
51
+ end
52
+ return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
53
+ when NilClass then return ->(_){true}
54
+ else error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
55
+ end
56
+ end
57
+
58
+ def file_matcher_from_argument(options)
59
+ return file_matcher(options.get_next_argument('filter', type: MATCH_TYPES, mandatory: false))
60
+ end
61
+
62
+ # node API scopes
63
+ def token_scope(access_key, scope)
64
+ return [SCOPE_PREFIX, access_key, SCOPE_SEPARATOR, scope].join('')
65
+ end
66
+
67
+ def decode_scope(scope)
68
+ items = scope.split(SCOPE_SEPARATOR, 2)
69
+ assert(items.length.eql?(2)){"invalid scope: #{scope}"}
70
+ assert(items[0].start_with?(SCOPE_PREFIX)){"invalid scope: #{scope}"}
71
+ return {access_key: items[0][SCOPE_PREFIX.length..-1], scope: items[1]}
72
+ end
73
+
74
+ # Create an Aspera Node bearer token
75
+ # @param payload [String] JSON payload to be included in the token
76
+ # @param private_key [OpenSSL::PKey::RSA] Private key to sign the token
77
+ def bearer_token(access_key:, payload:, private_key:)
78
+ assert_type(payload, Hash)
79
+ assert(payload.key?('user_id'))
80
+ assert_type(payload['user_id'], String)
81
+ assert(!payload['user_id'].empty?)
82
+ assert_type(private_key, OpenSSL::PKey::RSA)
83
+ # manage convenience parameters
84
+ expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
85
+ payload.delete('_validity')
86
+ scope = payload['_scope'] || BEARER_TOKEN_SCOPE_DEFAULT
87
+ payload.delete('_scope')
88
+ payload['scope'] ||= token_scope(access_key, scope)
89
+ payload['auth_type'] ||= 'access_key'
90
+ payload['expires_at'] ||= (Time.now + expiration_sec).utc.strftime('%FT%TZ')
91
+ payload_json = JSON.generate(payload)
92
+ return Base64.strict_encode64(Zlib::Deflate.deflate([
93
+ payload_json,
94
+ SIGNATURE_DELIMITER,
95
+ Base64.strict_encode64(private_key.sign(OpenSSL::Digest.new('sha512'), payload_json)).scan(/.{1,60}/).join("\n"),
96
+ ''
97
+ ].join("\n")))
98
+ end
99
+
100
+ def decode_bearer_token(token)
101
+ return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
102
+ end
103
+
104
+ def bearer_headers(bearer_auth, access_key: nil)
105
+ # if username is not provided, use the access key from the token
106
+ if access_key.nil?
107
+ access_key = Aspera::Node.decode_scope(Aspera::Node.decode_bearer_token(Oauth.bearer_extract(bearer_auth))['scope'])[:access_key]
108
+ assert(!access_key.nil?)
39
109
  end
40
- return lambda{|f|f['name'].match(/#{match_expression}/)}
110
+ return {
111
+ Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => access_key,
112
+ 'Authorization' => bearer_auth
113
+ }
41
114
  end
42
115
  end
43
116
 
@@ -52,16 +125,17 @@ module Aspera
52
125
  # @param params [Hash] Rest parameters
53
126
  # @param app_info [Hash,NilClass] special processing for AoC
54
127
  def initialize(params:, app_info: nil, add_tspec: nil)
128
+ # init Rest
55
129
  super(params)
56
130
  @app_info = app_info
57
131
  # this is added to transfer spec, for instance to add tags (COS)
58
132
  @add_tspec = add_tspec
59
133
  if !@app_info.nil?
60
134
  REQUIRED_APP_INFO_FIELDS.each do |field|
61
- raise "INTERNAL ERROR: app_info lacks field #{field}" unless @app_info.key?(field)
135
+ assert(@app_info.key?(field)){"app_info lacks field #{field}"}
62
136
  end
63
137
  REQUIRED_APP_API_METHODS.each do |method|
64
- raise "INTERNAL ERROR: #{@app_info[:api].class} lacks method #{method}" unless @app_info[:api].respond_to?(method)
138
+ assert(@app_info[:api].respond_to?(method)){"#{@app_info[:api].class} lacks method #{method}"}
65
139
  end
66
140
  end
67
141
  end
@@ -90,13 +164,13 @@ module Aspera
90
164
  # @param state [Object] state object sent to processing method
91
165
  # @param top_file_id [String] file id to start at (default = access key root file id)
92
166
  # @param top_file_path [String] path of top folder (default = /)
93
- # @param block [Proc] processing method, args: entry, path, state
167
+ # @param block [Proc] processing method, arguments: entry, path, state
94
168
  def process_folder_tree(state:, top_file_id:, top_file_path: '/', &block)
95
- raise 'INTERNAL ERROR: top_file_path not set' if top_file_path.nil?
96
- raise 'INTERNAL ERROR: Missing block' unless block
169
+ assert(!top_file_path.nil?){'top_file_path not set'}
170
+ assert(block){'Missing block'}
97
171
  # start at top folder
98
172
  folders_to_explore = [{id: top_file_id, path: top_file_path}]
99
- Log.dump(:folders_to_explore, folders_to_explore)
173
+ Log.log.debug{Log.dump(:folders_to_explore, folders_to_explore)}
100
174
  until folders_to_explore.empty?
101
175
  current_item = folders_to_explore.shift
102
176
  Log.log.debug{"searching #{current_item[:path]}".bg_green}
@@ -108,7 +182,7 @@ module Aspera
108
182
  Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
109
183
  []
110
184
  end
111
- Log.dump(:folder_contents, folder_contents)
185
+ Log.log.debug{Log.dump(:folder_contents, folder_contents)}
112
186
  folder_contents.each do |entry|
113
187
  relative_path = File.join(current_item[:path], entry['name'])
114
188
  Log.log.debug{"process_folder_tree checking #{relative_path}"}
@@ -134,7 +208,7 @@ module Aspera
134
208
  # @param path [String] file path
135
209
  # @return [Hash] {.api,.file_id}
136
210
  def resolve_api_fid(top_file_id, path)
137
- raise 'file id shall be String' unless top_file_id.is_a?(String)
211
+ assert_type(top_file_id, String)
138
212
  process_last_link = path.end_with?(PATH_SEPARATOR)
139
213
  path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
140
214
  return {api: self, file_id: top_file_id} if path_elements.empty?
@@ -203,14 +277,14 @@ module Aspera
203
277
  case params[:auth][:type]
204
278
  when :basic
205
279
  ak_name = params[:auth][:username]
206
- raise 'ERROR: no secret in node object' unless params[:auth][:password]
207
- ak_token = Rest.basic_creds(params[:auth][:username], params[:auth][:password])
280
+ assert(params[:auth][:password]){'no secret in node object'}
281
+ ak_token = Rest.basic_token(params[:auth][:username], params[:auth][:password])
208
282
  when :oauth2
209
283
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
210
284
  # TODO: token_generation_lambda = lambda{|do_refresh|oauth_token(force_refresh: do_refresh)}
211
285
  # get bearer token, possibly use cache
212
286
  ak_token = oauth_token(force_refresh: false)
213
- else raise "Unsupported auth method for node gen4: #{params[:auth][:type]}"
287
+ else error_unexpected_value(params[:auth][:type])
214
288
  end
215
289
  transfer_spec = {
216
290
  'direction' => direction,
@@ -235,9 +309,19 @@ module Aspera
235
309
  transfer_spec.merge!(Fasp::TransferSpec::AK_TSPEC_BASE)
236
310
  # by default: same address as node API
237
311
  transfer_spec['remote_host'] = URI.parse(params[:base_url]).host
312
+ # AoC allows specification of other url
238
313
  if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
239
314
  transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url']
240
315
  end
316
+ info = read('info')[:data]
317
+ # get the transfer user from info on access key
318
+ transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
319
+ # get settings from name.value array to hash key.value
320
+ settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
321
+ # check WSS ports
322
+ %w[wss_enabled wss_port].each do |i|
323
+ transfer_spec[i] = settings[i] if settings.key?(i)
324
+ end if settings.is_a?(Hash)
241
325
  else
242
326
  # retrieve values from API (and keep a copy/cache)
243
327
  @std_t_spec_cache ||= create(