aspera-cli 4.14.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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(