aspera-cli 4.16.0 → 4.17.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 (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