aspera-cli 4.16.0 → 4.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -19
  4. data/CONTRIBUTING.md +3 -1
  5. data/README.md +965 -793
  6. data/bin/asession +29 -21
  7. data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
  8. data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
  9. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  10. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
  11. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
  12. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
  13. data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
  14. data/lib/aspera/api/aoc.rb +586 -0
  15. data/lib/aspera/api/ats.rb +46 -0
  16. data/lib/aspera/api/cos_node.rb +95 -0
  17. data/lib/aspera/api/node.rb +344 -0
  18. data/lib/aspera/ascmd.rb +46 -10
  19. data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
  20. data/lib/aspera/{fasp → ascp}/management.rb +3 -8
  21. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  22. data/lib/aspera/assert.rb +30 -30
  23. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  24. data/lib/aspera/cli/extended_value.rb +1 -1
  25. data/lib/aspera/cli/formatter.rb +13 -13
  26. data/lib/aspera/cli/hints.rb +5 -5
  27. data/lib/aspera/cli/main.rb +35 -28
  28. data/lib/aspera/cli/manager.rb +25 -24
  29. data/lib/aspera/cli/plugin.rb +22 -15
  30. data/lib/aspera/cli/plugin_factory.rb +61 -0
  31. data/lib/aspera/cli/plugins/alee.rb +7 -7
  32. data/lib/aspera/cli/plugins/aoc.rb +83 -77
  33. data/lib/aspera/cli/plugins/ats.rb +32 -33
  34. data/lib/aspera/cli/plugins/bss.rb +3 -4
  35. data/lib/aspera/cli/plugins/config.rb +169 -186
  36. data/lib/aspera/cli/plugins/console.rb +8 -6
  37. data/lib/aspera/cli/plugins/cos.rb +19 -18
  38. data/lib/aspera/cli/plugins/faspex.rb +61 -54
  39. data/lib/aspera/cli/plugins/faspex5.rb +150 -103
  40. data/lib/aspera/cli/plugins/node.rb +68 -73
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
  42. data/lib/aspera/cli/plugins/preview.rb +31 -31
  43. data/lib/aspera/cli/plugins/server.rb +31 -33
  44. data/lib/aspera/cli/plugins/shares.rb +13 -11
  45. data/lib/aspera/cli/sync_actions.rb +8 -8
  46. data/lib/aspera/cli/transfer_agent.rb +32 -19
  47. data/lib/aspera/cli/transfer_progress.rb +1 -1
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/colors.rb +5 -0
  50. data/lib/aspera/command_line_builder.rb +14 -14
  51. data/lib/aspera/coverage.rb +1 -2
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +2 -3
  54. data/lib/aspera/faspex_gw.rb +5 -6
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/id_generator.rb +2 -2
  57. data/lib/aspera/json_rpc.rb +5 -5
  58. data/lib/aspera/keychain/encrypted_hash.rb +6 -6
  59. data/lib/aspera/keychain/macos_security.rb +27 -22
  60. data/lib/aspera/log.rb +2 -2
  61. data/lib/aspera/nagios.rb +3 -3
  62. data/lib/aspera/node_simulator.rb +5 -6
  63. data/lib/aspera/oauth/base.rb +143 -0
  64. data/lib/aspera/oauth/factory.rb +124 -0
  65. data/lib/aspera/oauth/generic.rb +34 -0
  66. data/lib/aspera/oauth/jwt.rb +51 -0
  67. data/lib/aspera/oauth/url_json.rb +31 -0
  68. data/lib/aspera/oauth/web.rb +50 -0
  69. data/lib/aspera/oauth.rb +5 -331
  70. data/lib/aspera/open_application.rb +7 -7
  71. data/lib/aspera/persistency_action_once.rb +4 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +5 -5
  74. data/lib/aspera/preview/terminal.rb +3 -2
  75. data/lib/aspera/preview/utils.rb +3 -3
  76. data/lib/aspera/proxy_auto_config.rb +4 -4
  77. data/lib/aspera/rest.rb +175 -144
  78. data/lib/aspera/rest_errors_aspera.rb +3 -3
  79. data/lib/aspera/resumer.rb +77 -0
  80. data/lib/aspera/ssh.rb +6 -1
  81. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  82. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  83. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  84. data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
  85. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
  86. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  87. data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
  88. data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
  89. data/lib/aspera/web_server_simple.rb +11 -3
  90. data.tar.gz.sig +0 -0
  91. metadata +36 -63
  92. metadata.gz.sig +0 -0
  93. data/lib/aspera/aoc.rb +0 -601
  94. data/lib/aspera/ats_api.rb +0 -47
  95. data/lib/aspera/cos_node.rb +0 -94
  96. data/lib/aspera/fasp/resume_policy.rb +0 -79
  97. data/lib/aspera/node.rb +0 -339
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aspera/log'
4
- require 'aspera/assert'
5
- require 'aspera/rest'
6
- require 'aspera/oauth'
7
- require 'xmlsimple'
8
-
9
- module Aspera
10
- class CosNode < Aspera::Node
11
- class << self
12
- def parameters_from_svc_credentials(service_credentials, bucket_region)
13
- # check necessary contents
14
- assert_type(service_credentials, Hash){'service_credentials'}
15
- %w[apikey resource_instance_id endpoints].each do |field|
16
- assert(service_credentials.key?(field)){"service_credentials must have a field: #{field}"}
17
- end
18
- Aspera::Log.dump('service_credentials', service_credentials)
19
- # read endpoints from service provided in service credentials
20
- endpoints = Aspera::Rest.new({base_url: service_credentials['endpoints']}).read('')[:data]
21
- Aspera::Log.dump('endpoints', endpoints)
22
- storage_endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
23
- raise "no such region: #{bucket_region}" if storage_endpoint.nil?
24
- return {
25
- instance_id: service_credentials['resource_instance_id'],
26
- service_api_key: service_credentials['apikey'],
27
- storage_endpoint: "https://#{storage_endpoint}"
28
- }
29
- end
30
- end
31
- IBM_CLOUD_TOKEN_URL = 'https://iam.cloud.ibm.com/identity'
32
- TOKEN_FIELD = 'delegated_refresh_token'
33
-
34
- def initialize(bucket_name, storage_endpoint, instance_id, api_key, auth_url= IBM_CLOUD_TOKEN_URL)
35
- @auth_url = auth_url
36
- @api_key = api_key
37
- s3_api = Aspera::Rest.new({
38
- base_url: storage_endpoint,
39
- not_auth_codes: %w[401 403], # error codes when not authorized
40
- headers: {'ibm-service-instance-id' => instance_id},
41
- auth: {
42
- type: :oauth2,
43
- base_url: @auth_url,
44
- grant_method: :generic,
45
- generic: {
46
- grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
47
- response_type: 'cloud_iam',
48
- apikey: @api_key
49
- }}})
50
- # read FASP connection information for bucket
51
- xml_result_text = s3_api.call(
52
- operation: 'GET',
53
- subpath: bucket_name,
54
- headers: {'Accept' => 'application/xml'},
55
- url_params: {'faspConnectionInfo' => nil}
56
- )[:http].body
57
- ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
58
- Aspera::Log.dump('ats_info', ats_info)
59
- @storage_credentials = {
60
- 'type' => 'token',
61
- 'token' => {TOKEN_FIELD => nil}
62
- }
63
- super(
64
- params: {
65
- base_url: ats_info['ATSEndpoint'],
66
- auth: {
67
- type: :basic,
68
- username: ats_info['AccessKey']['Id'],
69
- password: ats_info['AccessKey']['Secret']}},
70
- add_tspec: {'tags'=>{Fasp::TransferSpec::TAG_RESERVED=>{'node'=>{'storage_credentials'=>@storage_credentials}}}})
71
- # update storage_credentials AND Rest params
72
- generate_token
73
- end
74
-
75
- # potentially call this if delegated token is expired
76
- def generate_token
77
- # OAuth API to get delegated token
78
- delegated_oauth = Oauth.new({
79
- type: :oauth2,
80
- base_url: @auth_url,
81
- token_field: TOKEN_FIELD,
82
- grant_method: :generic,
83
- generic: {
84
- grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
85
- response_type: 'delegated_refresh_token',
86
- apikey: @api_key,
87
- receiver_client_ids: 'aspera_ats'
88
- }})
89
- # get delegated token to be placed in rest call header and in transfer tags
90
- @storage_credentials['token'][TOKEN_FIELD] = Oauth.bearer_extract(delegated_oauth.get_authorization)
91
- @params[:headers] = {'X-Aspera-Storage-Credentials' => JSON.generate(@storage_credentials)}
92
- end
93
- end
94
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'singleton'
4
- require 'aspera/log'
5
- require 'aspera/assert'
6
-
7
- module Aspera
8
- module Fasp
9
- # implements a simple resume policy
10
- class ResumePolicy
11
- # list of supported parameters and default values
12
- DEFAULTS = {
13
- iter_max: 7,
14
- sleep_initial: 2,
15
- sleep_factor: 2,
16
- sleep_max: 60
17
- }.freeze
18
-
19
- # @param params see DEFAULTS
20
- def initialize(params=nil)
21
- @parameters = DEFAULTS.dup
22
- if !params.nil?
23
- assert_type(params, Hash)
24
- params.each do |k, v|
25
- assert_values(k, DEFAULTS.keys){'resume parameter'}
26
- assert_type(v, Integer){k}
27
- @parameters[k] = v
28
- end
29
- end
30
- Log.log.debug{"resume params=#{@parameters}"}
31
- end
32
-
33
- # calls block a number of times (resumes) until success or limit reached
34
- # this is re-entrant, one resumer can handle multiple transfers in //
35
- def execute_with_resume
36
- assert(block_given?)
37
- # maximum of retry
38
- remaining_resumes = @parameters[:iter_max]
39
- sleep_seconds = @parameters[:sleep_initial]
40
- Log.log.debug{"retries=#{remaining_resumes}"}
41
- # try to send the file until ascp is successful
42
- loop do
43
- Log.log.debug('transfer starting')
44
- begin
45
- # call provided block
46
- yield
47
- # exit retry loop if success
48
- break
49
- rescue Fasp::Error => e
50
- Log.log.warn{"An error occurred during transfer: #{e.message}"}
51
- # failure in ascp
52
- if e.retryable?
53
- # exit if we exceed the max number of retry
54
- raise Fasp::Error, "Maximum number of retry reached (#{@parameters[:iter_max]})" if remaining_resumes <= 0
55
- else
56
- # give one chance only to non retryable errors
57
- unless remaining_resumes.eql?(@parameters[:iter_max])
58
- Log.log.error('non-retryable error'.red.blink)
59
- raise e
60
- end
61
- end
62
- end
63
-
64
- # take this retry in account
65
- remaining_resumes -= 1
66
- Log.log.warn{"Resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})"}
67
-
68
- # wait a bit before retrying, maybe network condition will be better
69
- sleep(sleep_seconds)
70
-
71
- # increase retry period
72
- sleep_seconds *= @parameters[:sleep_factor]
73
- # cap value
74
- sleep_seconds = @parameters[:sleep_max] if sleep_seconds > @parameters[:sleep_max]
75
- end # loop
76
- end
77
- end
78
- end
79
- end
data/lib/aspera/node.rb DELETED
@@ -1,339 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aspera/cli/error'
4
- require 'aspera/fasp/transfer_spec'
5
- require 'aspera/rest'
6
- require 'aspera/oauth'
7
- require 'aspera/log'
8
- require 'aspera/assert'
9
- require 'aspera/environment'
10
- require 'zlib'
11
- require 'base64'
12
-
13
- module Aspera
14
- # Provides additional functions using node API with gen4 extensions (access keys)
15
- class Node < Aspera::Rest
16
- # permissions
17
- ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
18
- # prefix for ruby code for filter (deprecated)
19
- MATCH_EXEC_PREFIX = 'exec:'
20
- MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
21
- HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
22
- PATH_SEPARATOR = '/'
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
31
-
32
- # register node special token decoder
33
- Oauth.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
34
-
35
- # class instance variable, access with accessors on class
36
- @use_standard_ports = true
37
-
38
- class << self
39
- attr_accessor :use_standard_ports
40
-
41
- # For access keys: provide expression to match entry in folder
42
- def file_matcher(match_expression)
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?)
109
- end
110
- return {
111
- Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => access_key,
112
- 'Authorization' => bearer_auth
113
- }
114
- end
115
- end
116
-
117
- # fields in @app_info
118
- REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
119
- # methods of @app_info[:api]
120
- REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
121
- private_constant :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
122
-
123
- attr_reader :app_info
124
-
125
- # @param params [Hash] Rest parameters
126
- # @param app_info [Hash,NilClass] special processing for AoC
127
- def initialize(params:, app_info: nil, add_tspec: nil)
128
- # init Rest
129
- super(params)
130
- @app_info = app_info
131
- # this is added to transfer spec, for instance to add tags (COS)
132
- @add_tspec = add_tspec
133
- if !@app_info.nil?
134
- REQUIRED_APP_INFO_FIELDS.each do |field|
135
- assert(@app_info.key?(field)){"app_info lacks field #{field}"}
136
- end
137
- REQUIRED_APP_API_METHODS.each do |method|
138
- assert(@app_info[:api].respond_to?(method)){"#{@app_info[:api].class} lacks method #{method}"}
139
- end
140
- end
141
- end
142
-
143
- # update transfer spec with special additional tags
144
- def add_tspec_info(tspec)
145
- tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
146
- return tspec
147
- end
148
-
149
- # @returns [Aspera::Node] a Node or nil
150
- def node_id_to_node(node_id)
151
- if !@app_info.nil?
152
- return self if node_id.eql?(@app_info[:node_info]['id'])
153
- return @app_info[:api].node_api_from(
154
- node_id: node_id,
155
- workspace_id: @app_info[:workspace_id],
156
- workspace_name: @app_info[:workspace_name])
157
- end
158
- Log.log.warn{"cannot resolve link with node id #{node_id}"}
159
- return nil
160
- end
161
-
162
- # Recursively browse in a folder (with non-recursive method)
163
- # sub folders are processed if the processing method returns true
164
- # @param state [Object] state object sent to processing method
165
- # @param top_file_id [String] file id to start at (default = access key root file id)
166
- # @param top_file_path [String] path of top folder (default = /)
167
- # @param block [Proc] processing method, arguments: entry, path, state
168
- def process_folder_tree(state:, top_file_id:, top_file_path: '/', &block)
169
- assert(!top_file_path.nil?){'top_file_path not set'}
170
- assert(block){'Missing block'}
171
- # start at top folder
172
- folders_to_explore = [{id: top_file_id, path: top_file_path}]
173
- Log.log.debug{Log.dump(:folders_to_explore, folders_to_explore)}
174
- until folders_to_explore.empty?
175
- current_item = folders_to_explore.shift
176
- Log.log.debug{"searching #{current_item[:path]}".bg_green}
177
- # get folder content
178
- folder_contents =
179
- begin
180
- read("files/#{current_item[:id]}/files")[:data]
181
- rescue StandardError => e
182
- Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
183
- []
184
- end
185
- Log.log.debug{Log.dump(:folder_contents, folder_contents)}
186
- folder_contents.each do |entry|
187
- relative_path = File.join(current_item[:path], entry['name'])
188
- Log.log.debug{"process_folder_tree checking #{relative_path}"}
189
- # continue only if method returns true
190
- next unless yield(entry, relative_path, state)
191
- # entry type is file, folder or link
192
- case entry['type']
193
- when 'folder'
194
- folders_to_explore.push({id: entry['id'], path: relative_path})
195
- when 'link'
196
- node_id_to_node(entry['target_node_id'])&.process_folder_tree(
197
- state: state,
198
- top_file_id: entry['target_id'],
199
- top_file_path: relative_path,
200
- &block)
201
- end
202
- end
203
- end
204
- end # process_folder_tree
205
-
206
- # Navigate the path from given file id
207
- # @param top_file_id [String] id initial file id
208
- # @param path [String] file path
209
- # @return [Hash] {.api,.file_id}
210
- def resolve_api_fid(top_file_id, path)
211
- assert_type(top_file_id, String)
212
- process_last_link = path.end_with?(PATH_SEPARATOR)
213
- path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
214
- return {api: self, file_id: top_file_id} if path_elements.empty?
215
- resolve_state = {path: path_elements, result: nil}
216
- process_folder_tree(state: resolve_state, top_file_id: top_file_id) do |entry, _path, state|
217
- # this block is called recursively for each entry in folder
218
- # stop digging here if not in right path
219
- next false unless entry['name'].eql?(state[:path].first)
220
- # ok it matches, so we remove the match
221
- state[:path].shift
222
- case entry['type']
223
- when 'file'
224
- # file must be terminal
225
- raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless state[:path].empty?
226
- # it's terminal, we found it
227
- state[:result] = {api: self, file_id: entry['id']}
228
- next false
229
- when 'folder'
230
- if state[:path].empty?
231
- # we found it
232
- state[:result] = {api: self, file_id: entry['id']}
233
- next false
234
- end
235
- when 'link'
236
- if state[:path].empty?
237
- if process_last_link
238
- # we found it
239
- other_node = node_id_to_node(entry['target_node_id'])
240
- raise 'cannot resolve link' if other_node.nil?
241
- state[:result] = {api: other_node, file_id: entry['target_id']}
242
- else
243
- # we found it but we do not process the link
244
- state[:result] = {api: self, file_id: entry['id']}
245
- end
246
- next false
247
- end
248
- else
249
- Log.log.warn{"Unknown element type: #{entry['type']}"}
250
- end
251
- # continue to dig folder
252
- next true
253
- end
254
- raise "entry not found: #{resolve_state[:path]}" if resolve_state[:result].nil?
255
- return resolve_state[:result]
256
- end
257
-
258
- def find_files(top_file_id, test_block)
259
- Log.log.debug{"find_files: file id=#{top_file_id}"}
260
- find_state = {found: [], test_block: test_block}
261
- process_folder_tree(state: find_state, top_file_id: top_file_id) do |entry, path, state|
262
- state[:found].push(entry.merge({'path' => path})) if state[:test_block].call(entry)
263
- # test all files deeply
264
- true
265
- end
266
- return find_state[:found]
267
- end
268
-
269
- def refreshed_transfer_token
270
- return oauth_token(force_refresh: true)
271
- end
272
-
273
- # Create transfer spec for gen4
274
- def transfer_spec_gen4(file_id, direction, ts_merge=nil)
275
- ak_name = nil
276
- ak_token = nil
277
- case params[:auth][:type]
278
- when :basic
279
- ak_name = params[:auth][:username]
280
- assert(params[:auth][:password]){'no secret in node object'}
281
- ak_token = Rest.basic_token(params[:auth][:username], params[:auth][:password])
282
- when :oauth2
283
- ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
284
- # TODO: token_generation_lambda = lambda{|do_refresh|oauth_token(force_refresh: do_refresh)}
285
- # get bearer token, possibly use cache
286
- ak_token = oauth_token(force_refresh: false)
287
- else error_unexpected_value(params[:auth][:type])
288
- end
289
- transfer_spec = {
290
- 'direction' => direction,
291
- 'token' => ak_token,
292
- 'tags' => {
293
- Fasp::TransferSpec::TAG_RESERVED => {
294
- 'node' => {
295
- 'access_key' => ak_name,
296
- 'file_id' => file_id
297
- } # node
298
- } # aspera
299
- } # tags
300
- }
301
- # add specials tags (cos)
302
- add_tspec_info(transfer_spec)
303
- transfer_spec.deep_merge!(ts_merge) unless ts_merge.nil?
304
- # add application specific tags (AoC)
305
- app_info[:api].add_ts_tags(transfer_spec: transfer_spec, app_info: app_info) unless app_info.nil?
306
- # add remote host info
307
- if self.class.use_standard_ports
308
- # get default TCP/UDP ports and transfer user
309
- transfer_spec.merge!(Fasp::TransferSpec::AK_TSPEC_BASE)
310
- # by default: same address as node API
311
- transfer_spec['remote_host'] = URI.parse(params[:base_url]).host
312
- # AoC allows specification of other url
313
- if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
314
- transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url']
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)
325
- else
326
- # retrieve values from API (and keep a copy/cache)
327
- @std_t_spec_cache ||= create(
328
- 'files/download_setup',
329
- {transfer_requests: [{ transfer_request: {paths: [{'source' => '/'}] } }] }
330
- )[:data]['transfer_specs'].first['transfer_spec']
331
- # copy some parts
332
- TS_FIELDS_TO_COPY.each {|i| transfer_spec[i] = @std_t_spec_cache[i] if @std_t_spec_cache.key?(i)}
333
- end
334
- Log.log.warn{"Expected transfer user: #{Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER}, but have #{transfer_spec['remote_user']}"} \
335
- unless transfer_spec['remote_user'].eql?(Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER)
336
- return transfer_spec
337
- end
338
- end
339
- end