aspera-cli 4.24.1 → 4.25.0.pre
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +1064 -745
- data/CONTRIBUTING.md +43 -100
- data/README.md +1281 -720
- data/bin/ascli +20 -1
- data/bin/asession +23 -27
- data/lib/aspera/agent/base.rb +10 -21
- data/lib/aspera/agent/connect.rb +2 -3
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +49 -32
- data/lib/aspera/agent/factory.rb +31 -0
- data/lib/aspera/api/aoc.rb +134 -76
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +213 -0
- data/lib/aspera/api/node.rb +107 -94
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +73 -58
- data/lib/aspera/ascp/management.rb +119 -23
- data/lib/aspera/assert.rb +39 -11
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +91 -67
- data/lib/aspera/cli/formatter.rb +62 -27
- data/lib/aspera/cli/hints.rb +8 -0
- data/lib/aspera/cli/info.rb +4 -4
- data/lib/aspera/cli/main.rb +76 -84
- data/lib/aspera/cli/manager.rb +352 -248
- data/lib/aspera/cli/plugins/alee.rb +5 -4
- data/lib/aspera/cli/plugins/aoc.rb +175 -195
- data/lib/aspera/cli/plugins/ats.rb +4 -4
- data/lib/aspera/cli/plugins/base.rb +343 -0
- data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
- data/lib/aspera/cli/plugins/config.rb +283 -269
- data/lib/aspera/cli/plugins/console.rb +27 -22
- data/lib/aspera/cli/plugins/cos.rb +3 -3
- data/lib/aspera/cli/plugins/factory.rb +78 -0
- data/lib/aspera/cli/plugins/faspex.rb +49 -46
- data/lib/aspera/cli/plugins/faspex5.rb +113 -225
- data/lib/aspera/cli/plugins/faspio.rb +19 -18
- data/lib/aspera/cli/plugins/httpgw.rb +14 -13
- data/lib/aspera/cli/plugins/node.rb +162 -149
- data/lib/aspera/cli/plugins/oauth.rb +48 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
- data/lib/aspera/cli/plugins/preview.rb +30 -50
- data/lib/aspera/cli/plugins/server.rb +21 -21
- data/lib/aspera/cli/plugins/shares.rb +45 -47
- data/lib/aspera/cli/sync_actions.rb +50 -39
- data/lib/aspera/cli/transfer_agent.rb +35 -49
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +3 -3
- data/lib/aspera/cli/wizard.rb +70 -55
- data/lib/aspera/colors.rb +6 -0
- data/lib/aspera/command_line_builder.rb +59 -61
- data/lib/aspera/command_line_converter.rb +2 -1
- data/lib/aspera/coverage.rb +2 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +51 -41
- data/lib/aspera/faspex_gw.rb +7 -5
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +37 -9
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +7 -6
- data/lib/aspera/oauth/base.rb +25 -28
- data/lib/aspera/oauth/factory.rb +9 -9
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/products/connect.rb +7 -6
- data/lib/aspera/products/desktop.rb +1 -4
- data/lib/aspera/products/other.rb +9 -1
- data/lib/aspera/products/transferd.rb +0 -1
- data/lib/aspera/rest.rb +168 -113
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +7 -4
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/args.schema.yaml +46 -3
- data/lib/aspera/sync/conf.schema.yaml +307 -123
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +135 -79
- data/lib/aspera/temp_file_manager.rb +17 -5
- data/lib/aspera/transfer/error.rb +16 -7
- data/lib/aspera/transfer/parameters.rb +35 -22
- data/lib/aspera/transfer/resumer.rb +74 -0
- data/lib/aspera/transfer/spec.rb +5 -5
- data/lib/aspera/transfer/spec.schema.yaml +170 -59
- data/lib/aspera/transfer/spec_doc.rb +49 -43
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +6 -6
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +26 -11
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
- data/lib/aspera/cli/plugin.rb +0 -333
- data/lib/aspera/cli/plugin_factory.rb +0 -81
- data/lib/aspera/resumer.rb +0 -77
- data/lib/aspera/transfer/error_info.rb +0 -91
data/lib/aspera/api/node.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'aspera/cli/error'
|
|
4
3
|
require 'aspera/transfer/spec'
|
|
5
4
|
require 'aspera/rest'
|
|
6
5
|
require 'aspera/oauth'
|
|
@@ -17,14 +16,18 @@ module Aspera
|
|
|
17
16
|
module Api
|
|
18
17
|
# Provides additional functions using node API with gen4 extensions (access keys)
|
|
19
18
|
class Node < Aspera::Rest
|
|
19
|
+
# Separator between node.AK and user:all
|
|
20
20
|
SCOPE_SEPARATOR = ':'
|
|
21
21
|
SCOPE_NODE_PREFIX = 'node.'
|
|
22
|
+
# Accepted types in `file_matcher`
|
|
22
23
|
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
|
24
|
+
# Delimiter in decoded node token
|
|
23
25
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
|
26
|
+
# Default validity when generating a bearer token "manually"
|
|
24
27
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
|
25
|
-
#
|
|
28
|
+
# Fields in @app_info
|
|
26
29
|
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
|
27
|
-
#
|
|
30
|
+
# Methods of @app_info[:api]
|
|
28
31
|
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
|
29
32
|
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
|
|
30
33
|
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
|
|
@@ -32,30 +35,40 @@ module Aspera
|
|
|
32
35
|
|
|
33
36
|
# Node API permissions
|
|
34
37
|
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
|
38
|
+
# Special HTTP Headers
|
|
35
39
|
HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
|
|
36
40
|
HEADER_X_TOTAL_COUNT = 'X-Total-Count'
|
|
37
41
|
HEADER_X_CACHE_CONTROL = 'X-Aspera-Cache-Control'
|
|
38
42
|
HEADER_X_NEXT_ITER_TOKEN = 'X-Aspera-Next-Iteration-Token'
|
|
43
|
+
# Node sub-scopes
|
|
39
44
|
SCOPE_USER = 'user:all'
|
|
40
45
|
SCOPE_ADMIN = 'admin:all'
|
|
41
46
|
# / in cloud
|
|
42
47
|
PATH_SEPARATOR = '/'
|
|
43
48
|
|
|
44
|
-
#
|
|
49
|
+
# Register node special token decoder
|
|
45
50
|
OAuth::Factory.instance.register_decoder(lambda{ |token| Node.decode_bearer_token(token)})
|
|
46
51
|
|
|
47
|
-
#
|
|
52
|
+
# Class instance variable, access with accessors on class
|
|
48
53
|
@use_standard_ports = true
|
|
49
54
|
@use_node_cache = true
|
|
50
55
|
|
|
51
56
|
class << self
|
|
52
|
-
#
|
|
57
|
+
# Set to false to read transfer parameters from download_setup
|
|
53
58
|
attr_accessor :use_standard_ports
|
|
54
|
-
#
|
|
59
|
+
# Set to false to bypass cache in redis
|
|
55
60
|
attr_accessor :use_node_cache
|
|
56
61
|
attr_reader :use_dynamic_key
|
|
57
62
|
|
|
58
|
-
#
|
|
63
|
+
# Adds cache control header, as globally specified to read request
|
|
64
|
+
# Use like this: read(...,**cache_control)
|
|
65
|
+
def cache_control
|
|
66
|
+
headers = {'Accept' => Rest::MIME_JSON}
|
|
67
|
+
headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless use_node_cache
|
|
68
|
+
{headers: headers}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Set private key to be used
|
|
59
72
|
# @param pem_content [String] PEM encoded private key
|
|
60
73
|
def use_dynamic_key=(pem_content)
|
|
61
74
|
Aspera.assert_type(pem_content, String)
|
|
@@ -67,7 +80,7 @@ module Aspera
|
|
|
67
80
|
def add_public_key(h)
|
|
68
81
|
if @dynamic_key
|
|
69
82
|
ssh_key = Net::SSH::Buffer.from(:key, @dynamic_key)
|
|
70
|
-
#
|
|
83
|
+
# Get pub key in OpenSSH public key format (authorized_keys)
|
|
71
84
|
h['public_keys'] = [
|
|
72
85
|
ssh_key.read_string,
|
|
73
86
|
Base64.strict_encode64(ssh_key.to_s)
|
|
@@ -93,14 +106,16 @@ module Aspera
|
|
|
93
106
|
when String
|
|
94
107
|
return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
|
95
108
|
when NilClass then return ->(_){true}
|
|
96
|
-
else Aspera.error_unexpected_value(match_expression.class.name, type:
|
|
109
|
+
else Aspera.error_unexpected_value(match_expression.class.name, type: ParameterError)
|
|
97
110
|
end
|
|
98
111
|
end
|
|
99
112
|
|
|
113
|
+
# @return [Proc] lambda from provided CLI options
|
|
100
114
|
def file_matcher_from_argument(options)
|
|
101
115
|
return file_matcher(options.get_next_argument('filter', validation: MATCH_TYPES, mandatory: false))
|
|
102
116
|
end
|
|
103
117
|
|
|
118
|
+
# Split path into folder + filename
|
|
104
119
|
# @return [Array] containing folder + inside folder/file
|
|
105
120
|
def split_folder(path)
|
|
106
121
|
folder = path.split(PATH_SEPARATOR)
|
|
@@ -108,11 +123,14 @@ module Aspera
|
|
|
108
123
|
[folder.join(PATH_SEPARATOR), inside]
|
|
109
124
|
end
|
|
110
125
|
|
|
111
|
-
#
|
|
126
|
+
# Node API scopes
|
|
127
|
+
# @return [String] node scope
|
|
112
128
|
def token_scope(access_key, scope)
|
|
113
129
|
return [SCOPE_NODE_PREFIX, access_key, SCOPE_SEPARATOR, scope].join('')
|
|
114
130
|
end
|
|
115
131
|
|
|
132
|
+
# Decode node scope into access key and scope
|
|
133
|
+
# @return [Hash]
|
|
116
134
|
def decode_scope(scope)
|
|
117
135
|
items = scope.split(SCOPE_SEPARATOR, 2)
|
|
118
136
|
Aspera.assert(items.length.eql?(2)){"invalid scope: #{scope}"}
|
|
@@ -130,7 +148,7 @@ module Aspera
|
|
|
130
148
|
Aspera.assert_type(payload['user_id'], String)
|
|
131
149
|
Aspera.assert(!payload['user_id'].empty?)
|
|
132
150
|
Aspera.assert_type(private_key, OpenSSL::PKey::RSA)
|
|
133
|
-
#
|
|
151
|
+
# Manage convenience parameters
|
|
134
152
|
expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
|
|
135
153
|
payload.delete('_validity')
|
|
136
154
|
scope = payload['_scope'] || SCOPE_USER
|
|
@@ -152,8 +170,9 @@ module Aspera
|
|
|
152
170
|
return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
|
|
153
171
|
end
|
|
154
172
|
|
|
173
|
+
# @return [Hash] Headers to call node API with access key and auth
|
|
155
174
|
def bearer_headers(bearer_auth, access_key: nil)
|
|
156
|
-
#
|
|
175
|
+
# If username is not provided, use the access key from the token
|
|
157
176
|
if access_key.nil?
|
|
158
177
|
access_key = Node.decode_scope(Node.decode_bearer_token(OAuth::Factory.bearer_token(bearer_auth))['scope'])[:access_key]
|
|
159
178
|
Aspera.assert(!access_key.nil?)
|
|
@@ -167,17 +186,17 @@ module Aspera
|
|
|
167
186
|
|
|
168
187
|
attr_reader :app_info
|
|
169
188
|
|
|
170
|
-
# @param app_info [Hash,NilClass]
|
|
189
|
+
# @param app_info [Hash,NilClass] App information, typically AoC
|
|
171
190
|
# @param add_tspec [Hash,NilClass] Additional transfer spec
|
|
172
191
|
# @param base_url [String] Rest parameters
|
|
173
192
|
# @param auth [String,NilClass] Rest parameters
|
|
174
193
|
# @param headers [String,NilClass] Rest parameters
|
|
175
194
|
def initialize(app_info: nil, add_tspec: nil, **rest_args)
|
|
176
|
-
#
|
|
195
|
+
# Init Rest
|
|
177
196
|
super(**rest_args)
|
|
178
197
|
@dynamic_key = nil
|
|
179
198
|
@app_info = app_info
|
|
180
|
-
#
|
|
199
|
+
# This is added to transfer spec, for instance to add tags (COS)
|
|
181
200
|
@add_tspec = add_tspec
|
|
182
201
|
@std_t_spec_cache = nil
|
|
183
202
|
if !@app_info.nil?
|
|
@@ -190,25 +209,15 @@ module Aspera
|
|
|
190
209
|
end
|
|
191
210
|
end
|
|
192
211
|
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
|
|
197
|
-
return call(
|
|
198
|
-
operation: 'GET',
|
|
199
|
-
subpath: subpath,
|
|
200
|
-
headers: headers,
|
|
201
|
-
query: query
|
|
202
|
-
)[:data]
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# update transfer spec with special additional tags
|
|
212
|
+
# Update transfer spec with special additional tags
|
|
213
|
+
# @param tspec [Hash] Transfer spec to be modified
|
|
214
|
+
# @return [Hash] initial modified tspec
|
|
206
215
|
def add_tspec_info(tspec)
|
|
207
216
|
tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
|
|
208
217
|
return tspec
|
|
209
218
|
end
|
|
210
219
|
|
|
211
|
-
# @returns [Node] a Node or nil
|
|
220
|
+
# @returns [Node] a Node Api object or nil if no App defined
|
|
212
221
|
def node_id_to_node(node_id)
|
|
213
222
|
if !@app_info.nil?
|
|
214
223
|
return self if node_id.eql?(@app_info[:node_info]['id'])
|
|
@@ -226,7 +235,7 @@ module Aspera
|
|
|
226
235
|
# @param entry [Hash] entry in folder
|
|
227
236
|
# @return [Boolean] true if target information is available
|
|
228
237
|
def entry_has_link_information(entry)
|
|
229
|
-
#
|
|
238
|
+
# If target information is missing in folder, try to get it on entry
|
|
230
239
|
if entry['target_node_id'].nil? || entry['target_id'].nil?
|
|
231
240
|
link_entry = read("files/#{entry['id']}")
|
|
232
241
|
entry['target_node_id'] = link_entry['target_node_id']
|
|
@@ -238,27 +247,28 @@ module Aspera
|
|
|
238
247
|
end
|
|
239
248
|
|
|
240
249
|
# Recursively browse in a folder (with non-recursive method)
|
|
241
|
-
#
|
|
242
|
-
#
|
|
250
|
+
# Entries of folders are processed if the processing method returns true
|
|
251
|
+
# Links are processed on the respective node
|
|
243
252
|
# @param method_sym [Symbol] processing method, arguments: entry, path, state
|
|
244
253
|
# @param state [Object] state object sent to processing method
|
|
245
254
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
|
246
255
|
# @param top_file_path [String] path of top folder (default = /)
|
|
247
|
-
|
|
256
|
+
# @para query [Hash, nil] optional query for `read`
|
|
257
|
+
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
|
|
248
258
|
Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
|
|
249
259
|
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
|
250
|
-
#
|
|
260
|
+
# Start at top folder
|
|
251
261
|
folders_to_explore = [{id: top_file_id, path: top_file_path}]
|
|
252
262
|
Log.dump(:folders_to_explore, folders_to_explore)
|
|
253
263
|
until folders_to_explore.empty?
|
|
254
|
-
#
|
|
264
|
+
# Consume first in job list
|
|
255
265
|
current_item = folders_to_explore.shift
|
|
256
266
|
Log.log.debug{"Exploring #{current_item[:path]}".bg_green}
|
|
257
|
-
#
|
|
267
|
+
# Get folder content
|
|
258
268
|
folder_contents =
|
|
259
269
|
begin
|
|
260
270
|
# TODO: use header
|
|
261
|
-
|
|
271
|
+
read("files/#{current_item[:id]}/files", query, **self.class.cache_control)
|
|
262
272
|
rescue StandardError => e
|
|
263
273
|
Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
|
|
264
274
|
[]
|
|
@@ -271,9 +281,9 @@ module Aspera
|
|
|
271
281
|
end
|
|
272
282
|
current_path = File.join(current_item[:path], entry['name'])
|
|
273
283
|
Log.log.debug{"process_folder_tree: checking #{current_path}"}
|
|
274
|
-
#
|
|
284
|
+
# Call block, continue only if method returns true
|
|
275
285
|
next unless send(method_sym, entry, current_path, state)
|
|
276
|
-
#
|
|
286
|
+
# Entry type is file, folder or link
|
|
277
287
|
case entry['type']
|
|
278
288
|
when 'folder'
|
|
279
289
|
folders_to_explore.push({id: entry['id'], path: current_path})
|
|
@@ -303,9 +313,9 @@ module Aspera
|
|
|
303
313
|
process_last_link ||= path.end_with?(PATH_SEPARATOR)
|
|
304
314
|
path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
|
|
305
315
|
return {api: self, file_id: top_file_id} if path_elements.empty?
|
|
306
|
-
resolve_state = {path: path_elements, result: nil, process_last_link: process_last_link}
|
|
316
|
+
resolve_state = {path: path_elements, consumed: [], result: nil, process_last_link: process_last_link}
|
|
307
317
|
process_folder_tree(method_sym: :process_api_fid, state: resolve_state, top_file_id: top_file_id)
|
|
308
|
-
raise "
|
|
318
|
+
raise ParameterError, "Entry not found: #{resolve_state[:path].first} in /#{resolve_state[:consumed].join(PATH_SEPARATOR)}" if resolve_state[:result].nil?
|
|
309
319
|
Log.log.debug{"resolve_api_fid: #{path} -> #{resolve_state[:result][:api].base_url} #{resolve_state[:result][:file_id]}"}
|
|
310
320
|
return resolve_state[:result]
|
|
311
321
|
end
|
|
@@ -338,14 +348,14 @@ module Aspera
|
|
|
338
348
|
source_paths =
|
|
339
349
|
case file_info['type']
|
|
340
350
|
when 'file'
|
|
341
|
-
#
|
|
351
|
+
# If the single source is a file, we need to split into folder path and filename
|
|
342
352
|
src_dir_elements = source_folder.split(Api::Node::PATH_SEPARATOR)
|
|
343
353
|
filename = src_dir_elements.pop
|
|
344
354
|
apifid = resolve_api_fid(top_file_id, src_dir_elements.join(Api::Node::PATH_SEPARATOR), true)
|
|
345
|
-
#
|
|
355
|
+
# Filename is the last one, source folder is what remains
|
|
346
356
|
[{'source' => filename}]
|
|
347
357
|
when 'link', 'folder'
|
|
348
|
-
#
|
|
358
|
+
# Single source is 'folder' or 'link'
|
|
349
359
|
# TODO: add this ? , 'destination'=>file_info['name']
|
|
350
360
|
[{'source' => '.'}]
|
|
351
361
|
else Aspera.error_unexpected_value(file_info['type']){'source type'}
|
|
@@ -354,6 +364,9 @@ module Aspera
|
|
|
354
364
|
[apifid, source_paths]
|
|
355
365
|
end
|
|
356
366
|
|
|
367
|
+
# Recursively find files matching lambda
|
|
368
|
+
# @param top_file_id [String] Search root
|
|
369
|
+
# @param test_lambda [Proc] Test function
|
|
357
370
|
def find_files(top_file_id, test_lambda)
|
|
358
371
|
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
|
359
372
|
find_state = {found: [], test_lambda: test_lambda}
|
|
@@ -361,36 +374,37 @@ module Aspera
|
|
|
361
374
|
return find_state[:found]
|
|
362
375
|
end
|
|
363
376
|
|
|
364
|
-
|
|
377
|
+
# Recursively list all files and folders
|
|
378
|
+
def list_files(top_file_id, query: nil)
|
|
365
379
|
find_state = {found: []}
|
|
366
|
-
process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
|
|
380
|
+
process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id, query: query)
|
|
367
381
|
return find_state[:found]
|
|
368
382
|
end
|
|
369
383
|
|
|
384
|
+
# Generate a refreshed auth token
|
|
370
385
|
def refreshed_transfer_token
|
|
371
386
|
return oauth.authorization(refresh: true)
|
|
372
387
|
end
|
|
373
388
|
|
|
374
|
-
#
|
|
389
|
+
# Get a base download transfer spec (gen3)
|
|
390
|
+
# @return [Hash] Base transfer spec
|
|
391
|
+
def base_spec
|
|
392
|
+
create(
|
|
393
|
+
'files/download_setup',
|
|
394
|
+
{transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
|
|
395
|
+
)['transfer_specs'].first['transfer_spec']
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Get generic part of transfer spec with transport parameters only
|
|
399
|
+
# @return [Hash] Base transfer spec
|
|
375
400
|
def transport_params
|
|
376
|
-
|
|
377
|
-
# retrieve values from API (and keep a copy/cache)
|
|
378
|
-
full_spec = create(
|
|
379
|
-
'files/download_setup',
|
|
380
|
-
{transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
|
|
381
|
-
)['transfer_specs'].first['transfer_spec']
|
|
382
|
-
# set available fields
|
|
383
|
-
@std_t_spec_cache = Transfer::Spec::TRANSPORT_FIELDS.each_with_object({}) do |i, h|
|
|
384
|
-
h[i] = full_spec[i] if full_spec.key?(i)
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
return @std_t_spec_cache
|
|
401
|
+
@std_t_spec_cache ||= base_spec.slice(*Transfer::Spec::TRANSPORT_FIELDS).freeze
|
|
388
402
|
end
|
|
389
403
|
|
|
390
404
|
# Create transfer spec for gen4
|
|
391
|
-
# @param file_id
|
|
392
|
-
# @param direction
|
|
393
|
-
# @param ts_merge
|
|
405
|
+
# @param file_id [String] Destination or source folder (id)
|
|
406
|
+
# @param direction [Symbol] One of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
|
|
407
|
+
# @param ts_merge [Hash,nil] Additional transfer spec to merge
|
|
394
408
|
def transfer_spec_gen4(file_id, direction, ts_merge = nil)
|
|
395
409
|
ak_name = nil
|
|
396
410
|
ak_token = nil
|
|
@@ -402,7 +416,7 @@ module Aspera
|
|
|
402
416
|
when :oauth2
|
|
403
417
|
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
|
404
418
|
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
|
|
405
|
-
#
|
|
419
|
+
# Get bearer token, possibly use cache
|
|
406
420
|
ak_token = oauth.authorization
|
|
407
421
|
when :none
|
|
408
422
|
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
|
@@ -417,92 +431,91 @@ module Aspera
|
|
|
417
431
|
'node' => {
|
|
418
432
|
'access_key' => ak_name,
|
|
419
433
|
'file_id' => file_id
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
423
437
|
}
|
|
424
|
-
#
|
|
438
|
+
# Add specials tags (cos)
|
|
425
439
|
add_tspec_info(transfer_spec)
|
|
426
440
|
transfer_spec.deep_merge!(ts_merge) unless ts_merge.nil?
|
|
427
|
-
#
|
|
428
|
-
app_info[:api].add_ts_tags(transfer_spec: transfer_spec, app_info: app_info) unless app_info.nil?
|
|
429
|
-
#
|
|
441
|
+
# Add application specific tags (AoC)
|
|
442
|
+
@app_info[:api].add_ts_tags(transfer_spec: transfer_spec, app_info: @app_info) unless @app_info.nil?
|
|
443
|
+
# Add remote host info
|
|
430
444
|
if self.class.use_standard_ports
|
|
431
|
-
#
|
|
445
|
+
# Get default TCP/UDP ports and transfer user
|
|
432
446
|
transfer_spec.merge!(Transfer::Spec::AK_TSPEC_BASE)
|
|
433
|
-
#
|
|
447
|
+
# By default: same address as node API
|
|
434
448
|
transfer_spec['remote_host'] = URI.parse(base_url).host
|
|
435
449
|
# AoC allows specification of other url
|
|
436
450
|
transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url'] if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
|
|
437
451
|
info = read('info')
|
|
438
|
-
#
|
|
452
|
+
# Get the transfer user from info on access key
|
|
439
453
|
transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
|
|
440
|
-
#
|
|
454
|
+
# Get settings from name.value array to hash key.value
|
|
441
455
|
settings = info['settings']&.each_with_object({}){ |i, h| h[i['name']] = i['value']}
|
|
442
|
-
#
|
|
456
|
+
# Check WSS ports
|
|
443
457
|
Transfer::Spec::WSS_FIELDS.each do |i|
|
|
444
458
|
transfer_spec[i] = settings[i] if settings.key?(i)
|
|
445
459
|
end if settings.is_a?(Hash)
|
|
446
460
|
else
|
|
447
461
|
transfer_spec.merge!(transport_params)
|
|
448
462
|
end
|
|
449
|
-
|
|
450
|
-
unless transfer_spec['remote_user'].eql?(Transfer::Spec::ACCESS_KEY_TRANSFER_USER)
|
|
463
|
+
Aspera.assert_values(transfer_spec['remote_user'], Transfer::Spec::ACCESS_KEY_TRANSFER_USER, type: :warn){'transfer user'}
|
|
451
464
|
return transfer_spec
|
|
452
465
|
end
|
|
453
466
|
|
|
454
467
|
private
|
|
455
468
|
|
|
456
|
-
#
|
|
469
|
+
# Method called in loop for each entry for `resolve_api_fid`
|
|
470
|
+
# @return `true` to continue digging, `false` to stop processing: set state[:result] if found
|
|
457
471
|
def process_api_fid(entry, path, state)
|
|
458
|
-
#
|
|
472
|
+
# Stop digging here if not in right path
|
|
459
473
|
return false unless entry['name'].eql?(state[:path].first)
|
|
460
|
-
#
|
|
461
|
-
state[:path].shift
|
|
474
|
+
# Ok it matches, so we remove the match, and continue digging
|
|
475
|
+
state[:consumed].push(state[:path].shift)
|
|
462
476
|
path_fully_consumed = state[:path].empty?
|
|
463
477
|
case entry['type']
|
|
464
478
|
when 'file'
|
|
465
|
-
#
|
|
479
|
+
# File must be terminal
|
|
466
480
|
raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless path_fully_consumed
|
|
467
|
-
#
|
|
468
|
-
Log.log.debug{"
|
|
481
|
+
# It's terminal, we found it
|
|
482
|
+
Log.log.debug{"process_api_fid: found #{path} -> #{entry['id']}"}
|
|
469
483
|
state[:result] = {api: self, file_id: entry['id']}
|
|
470
484
|
return false
|
|
471
485
|
when 'folder'
|
|
472
486
|
if path_fully_consumed
|
|
473
|
-
#
|
|
487
|
+
# We found it
|
|
474
488
|
state[:result] = {api: self, file_id: entry['id']}
|
|
475
489
|
return false
|
|
476
490
|
end
|
|
477
491
|
when 'link'
|
|
478
492
|
if path_fully_consumed
|
|
479
493
|
if state[:process_last_link]
|
|
480
|
-
#
|
|
494
|
+
# We found it
|
|
481
495
|
other_node = nil
|
|
482
496
|
other_node = node_id_to_node(entry['target_node_id']) if entry_has_link_information(entry)
|
|
483
497
|
raise Error, 'Cannot resolve link' if other_node.nil?
|
|
484
498
|
state[:result] = {api: other_node, file_id: entry['target_id']}
|
|
485
499
|
else
|
|
486
|
-
#
|
|
500
|
+
# We found it but we do not process the link
|
|
487
501
|
state[:result] = {api: self, file_id: entry['id']}
|
|
488
502
|
end
|
|
489
503
|
return false
|
|
490
504
|
end
|
|
491
|
-
else
|
|
492
|
-
Log.log.warn{"Unknown element type: #{entry['type']}"}
|
|
505
|
+
else Aspera.error_unexpected_value(entry['type'], type: :warn){'folder entry type'}
|
|
493
506
|
end
|
|
494
|
-
#
|
|
507
|
+
# Continue to dig folder
|
|
495
508
|
return true
|
|
496
509
|
end
|
|
497
510
|
|
|
498
|
-
#
|
|
511
|
+
# Method called in loop for each entry for `find_files`
|
|
499
512
|
def process_find_files(entry, path, state)
|
|
500
513
|
state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
|
|
501
|
-
#
|
|
514
|
+
# Test all files deeply
|
|
502
515
|
return true
|
|
503
516
|
end
|
|
504
517
|
|
|
505
|
-
#
|
|
518
|
+
# Method called in loop for each entry for `list_files`
|
|
506
519
|
def process_list_files(entry, path, state)
|
|
507
520
|
state[:found].push(entry.merge({'path' => path}))
|
|
508
521
|
return false
|
data/lib/aspera/ascmd.rb
CHANGED
|
@@ -94,8 +94,7 @@ module Aspera
|
|
|
94
94
|
arguments = [] if arguments.nil?
|
|
95
95
|
Log.log.debug{"execute_single:#{action_sym}:#{arguments}"}
|
|
96
96
|
Aspera.assert_type(action_sym, Symbol)
|
|
97
|
-
Aspera.
|
|
98
|
-
Aspera.assert(arguments.all?(String), 'arguments must be strings')
|
|
97
|
+
Aspera.assert_array_all(arguments, String){'arguments'}
|
|
99
98
|
remote_cmd = 'ascmd'
|
|
100
99
|
# lines of commands (String's)
|
|
101
100
|
command_lines = []
|