aspera-cli 4.20.0 → 4.21.2
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 +41 -3
- data/CONTRIBUTING.md +69 -142
- data/README.md +687 -461
- data/bin/ascli +5 -14
- data/bin/asession +3 -5
- data/examples/get_proto_file.rb +4 -3
- data/examples/proxy.pac +20 -20
- data/lib/aspera/agent/base.rb +2 -0
- data/lib/aspera/agent/connect.rb +20 -2
- data/lib/aspera/agent/{alpha.rb → desktop.rb} +12 -18
- data/lib/aspera/agent/direct.rb +30 -31
- data/lib/aspera/agent/node.rb +1 -11
- data/lib/aspera/agent/{trsdk.rb → transferd.rb} +37 -51
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +13 -8
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/node.rb +49 -32
- data/lib/aspera/ascp/installation.rb +98 -77
- data/lib/aspera/ascp/management.rb +27 -6
- data/lib/aspera/cli/extended_value.rb +9 -3
- data/lib/aspera/cli/formatter.rb +155 -154
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/main.rb +12 -0
- data/lib/aspera/cli/manager.rb +4 -4
- data/lib/aspera/cli/plugin.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +134 -73
- data/lib/aspera/cli/plugins/config.rb +114 -83
- data/lib/aspera/cli/plugins/cos.rb +1 -0
- data/lib/aspera/cli/plugins/faspex.rb +4 -2
- data/lib/aspera/cli/plugins/faspex5.rb +29 -14
- data/lib/aspera/cli/plugins/node.rb +51 -41
- data/lib/aspera/cli/transfer_progress.rb +2 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +1 -1
- data/lib/aspera/coverage.rb +5 -3
- data/lib/aspera/environment.rb +59 -16
- data/lib/aspera/faspex_postproc.rb +3 -5
- data/lib/aspera/hash_ext.rb +2 -12
- data/lib/aspera/node_simulator.rb +230 -112
- data/lib/aspera/oauth/base.rb +40 -48
- data/lib/aspera/oauth/factory.rb +41 -2
- data/lib/aspera/oauth/jwt.rb +4 -1
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/persistency_folder.rb +20 -2
- data/lib/aspera/preview/generator.rb +13 -10
- data/lib/aspera/preview/options.rb +2 -2
- data/lib/aspera/preview/terminal.rb +1 -1
- data/lib/aspera/preview/utils.rb +11 -6
- data/lib/aspera/products/connect.rb +82 -0
- data/lib/aspera/products/desktop.rb +30 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/transferd.rb +61 -0
- data/lib/aspera/rest.rb +22 -17
- data/lib/aspera/secret_hider.rb +9 -2
- data/lib/aspera/ssh.rb +31 -24
- data/lib/aspera/temp_file_manager.rb +5 -4
- data/lib/aspera/transfer/parameters.rb +2 -1
- data/lib/aspera/transfer/spec.yaml +22 -20
- data/lib/aspera/transfer/sync.rb +1 -5
- data/lib/aspera/transfer/uri.rb +2 -2
- data/lib/aspera/uri_reader.rb +18 -1
- data/lib/transferd_pb.rb +86 -0
- data/lib/transferd_services_pb.rb +84 -0
- data.tar.gz.sig +0 -0
- metadata +13 -166
- metadata.gz.sig +0 -0
- data/examples/build_exec +0 -74
- data/examples/build_exec_rubyc +0 -40
- data/lib/aspera/ascp/products.rb +0 -168
- data/lib/transfer_pb.rb +0 -84
- data/lib/transfer_services_pb.rb +0 -82
data/lib/aspera/api/aoc.rb
CHANGED
@@ -63,13 +63,14 @@ module Aspera
|
|
63
63
|
return client_key, DataRepository.instance.item(client_key)
|
64
64
|
end
|
65
65
|
|
66
|
-
# base API url depends on domain, which could be "qa.xxx"
|
67
|
-
def api_base_url(
|
68
|
-
return "https
|
66
|
+
# base API url depends on domain, which could be "qa.xxx" or self-managed domain
|
67
|
+
def api_base_url(api_domain: SAAS_DOMAIN_PROD)
|
68
|
+
return "https://api.#{api_domain}"
|
69
69
|
end
|
70
70
|
|
71
|
-
# split host of
|
71
|
+
# split host of URL into organization and domain
|
72
72
|
def split_org_domain(uri)
|
73
|
+
Aspera.assert_type(uri, URI)
|
73
74
|
raise "No host found in URL.Please check URL format: https://myorg.#{SAAS_DOMAIN_PROD}" if uri.host.nil?
|
74
75
|
parts = uri.host.split('.', 2)
|
75
76
|
Aspera.assert(parts.length == 2){"expecting a public FQDN for #{PRODUCT_NAME}"}
|
@@ -77,6 +78,10 @@ module Aspera
|
|
77
78
|
return %i{organization domain}.zip(parts).to_h
|
78
79
|
end
|
79
80
|
|
81
|
+
def saas_url?(url)
|
82
|
+
url.include?(SAAS_DOMAIN_PROD)
|
83
|
+
end
|
84
|
+
|
80
85
|
# @param url [String] URL of AoC public link
|
81
86
|
# @return [Hash] information about public link, or nil if not a public link
|
82
87
|
def link_info(url)
|
@@ -187,6 +192,7 @@ module Aspera
|
|
187
192
|
}
|
188
193
|
# add jwt payload for global client id
|
189
194
|
auth_params[:payload][:org] = url_info[:organization] if GLOBAL_CLIENT_APPS.include?(auth_params[:client_id])
|
195
|
+
auth_params[:cache_ids] = [url_info[:organization]]
|
190
196
|
when :url_json
|
191
197
|
auth_params[:url] = {grant_type: 'url_token'} # URL arguments
|
192
198
|
auth_params[:json] = {url_token: url_info[:token]} # JSON body
|
@@ -442,13 +448,12 @@ module Aspera
|
|
442
448
|
case pkg_data['metadata']
|
443
449
|
when Array, NilClass # no action
|
444
450
|
when Hash
|
445
|
-
api_meta = []
|
446
|
-
|
447
|
-
api_meta.push({
|
451
|
+
api_meta = pkg_data['metadata'].map do |k, v|
|
452
|
+
{
|
448
453
|
# 'input_type' => 'single-dropdown',
|
449
454
|
'name' => k,
|
450
455
|
'values' => v.is_a?(Array) ? v : [v]
|
451
|
-
}
|
456
|
+
}
|
452
457
|
end
|
453
458
|
pkg_data['metadata'] = api_meta
|
454
459
|
else Aspera.error_unexpected_value(pkg_meta.class)
|
data/lib/aspera/api/cos_node.rb
CHANGED
@@ -87,7 +87,7 @@ module Aspera
|
|
87
87
|
receiver_client_ids: 'aspera_ats'
|
88
88
|
)
|
89
89
|
# get delegated token to be placed in rest call header and in transfer tags
|
90
|
-
@storage_credentials['token'][TOKEN_FIELD] =
|
90
|
+
@storage_credentials['token'][TOKEN_FIELD] = delegated_oauth.token
|
91
91
|
@headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
|
92
92
|
end
|
93
93
|
end
|
data/lib/aspera/api/node.rb
CHANGED
@@ -16,8 +16,6 @@ module Aspera
|
|
16
16
|
class Node < Aspera::Rest
|
17
17
|
SCOPE_SEPARATOR = ':'
|
18
18
|
SCOPE_NODE_PREFIX = 'node.'
|
19
|
-
# prefix for ruby code for filter (deprecated)
|
20
|
-
MATCH_EXEC_PREFIX = 'exec:'
|
21
19
|
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
22
20
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
23
21
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
@@ -25,7 +23,7 @@ module Aspera
|
|
25
23
|
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
26
24
|
# methods of @app_info[:api]
|
27
25
|
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
28
|
-
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :
|
26
|
+
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
|
29
27
|
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
|
30
28
|
:REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
31
29
|
|
@@ -47,27 +45,20 @@ module Aspera
|
|
47
45
|
@use_node_cache = true
|
48
46
|
|
49
47
|
class << self
|
48
|
+
# set to false to read transfer parameters from download_setup
|
50
49
|
attr_accessor :use_standard_ports
|
50
|
+
# set to false to bypass cache in redis
|
51
51
|
attr_accessor :use_node_cache
|
52
52
|
|
53
|
-
def cache_control_headers
|
54
|
-
h = {'Accept' => 'application/json'}
|
55
|
-
h[HEADER_X_CACHE_CONTROL] = 'no-cache' unless use_node_cache
|
56
|
-
h
|
57
|
-
end
|
58
|
-
|
59
53
|
# For access keys: provide expression to match entry in folder
|
54
|
+
# @param match_expression one of supported types
|
55
|
+
# @return lambda function
|
60
56
|
def file_matcher(match_expression)
|
61
57
|
case match_expression
|
62
58
|
when Proc then return match_expression
|
63
59
|
when Regexp then return ->(f){f['name'].match?(match_expression)}
|
64
60
|
when String
|
65
|
-
|
66
|
-
code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
|
67
|
-
Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
|
68
|
-
return Environment.secure_eval(code, __FILE__, __LINE__)
|
69
|
-
end
|
70
|
-
return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
61
|
+
return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
71
62
|
when NilClass then return ->(_){true}
|
72
63
|
else Aspera.error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
|
73
64
|
end
|
@@ -158,7 +149,13 @@ module Aspera
|
|
158
149
|
|
159
150
|
# Call node API, possibly adding cache control header, as globally specified
|
160
151
|
def read_with_cache(subpath, query=nil)
|
161
|
-
|
152
|
+
headers = {'Accept' => 'application/json'}
|
153
|
+
headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
|
154
|
+
return call(
|
155
|
+
operation: 'GET',
|
156
|
+
subpath: subpath,
|
157
|
+
headers: headers,
|
158
|
+
query: query)[:data]
|
162
159
|
end
|
163
160
|
|
164
161
|
# update transfer spec with special additional tags
|
@@ -198,11 +195,11 @@ module Aspera
|
|
198
195
|
# Recursively browse in a folder (with non-recursive method)
|
199
196
|
# sub folders are processed if the processing method returns true
|
200
197
|
# links are processed on the respective node
|
198
|
+
# @param method_sym [Symbol] processing method, arguments: entry, path, state
|
201
199
|
# @param state [Object] state object sent to processing method
|
202
200
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
203
201
|
# @param top_file_path [String] path of top folder (default = /)
|
204
|
-
|
205
|
-
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
|
202
|
+
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
|
206
203
|
Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
|
207
204
|
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
208
205
|
# start at top folder
|
@@ -215,7 +212,8 @@ module Aspera
|
|
215
212
|
# get folder content
|
216
213
|
folder_contents =
|
217
214
|
begin
|
218
|
-
|
215
|
+
# TODO: use header
|
216
|
+
read_with_cache("files/#{current_item[:id]}/files")
|
219
217
|
rescue StandardError => e
|
220
218
|
Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
|
221
219
|
[]
|
@@ -228,21 +226,21 @@ module Aspera
|
|
228
226
|
end
|
229
227
|
next
|
230
228
|
end
|
231
|
-
|
232
|
-
Log.log.debug{"process_folder_tree: checking #{
|
229
|
+
current_path = File.join(current_item[:path], entry['name'])
|
230
|
+
Log.log.debug{"process_folder_tree: checking #{current_path}"}
|
233
231
|
# call block, continue only if method returns true
|
234
|
-
next unless send(method_sym, entry,
|
232
|
+
next unless send(method_sym, entry, current_path, state)
|
235
233
|
# entry type is file, folder or link
|
236
234
|
case entry['type']
|
237
235
|
when 'folder'
|
238
|
-
folders_to_explore.push({id: entry['id'], path:
|
236
|
+
folders_to_explore.push({id: entry['id'], path: current_path})
|
239
237
|
when 'link'
|
240
238
|
if entry_has_link_information(entry)
|
241
239
|
node_id_to_node(entry['target_node_id'])&.process_folder_tree(
|
242
240
|
method_sym: method_sym,
|
243
241
|
state: state,
|
244
242
|
top_file_id: entry['target_id'],
|
245
|
-
top_file_path:
|
243
|
+
top_file_path: current_path)
|
246
244
|
end
|
247
245
|
end
|
248
246
|
end
|
@@ -268,15 +266,21 @@ module Aspera
|
|
268
266
|
return resolve_state[:result]
|
269
267
|
end
|
270
268
|
|
271
|
-
def find_files(top_file_id,
|
269
|
+
def find_files(top_file_id, test_lambda)
|
272
270
|
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
273
|
-
find_state = {found: [],
|
271
|
+
find_state = {found: [], test_lambda: test_lambda}
|
274
272
|
process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
|
275
273
|
return find_state[:found]
|
276
274
|
end
|
277
275
|
|
276
|
+
def list_files(top_file_id)
|
277
|
+
find_state = {found: []}
|
278
|
+
process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
|
279
|
+
return find_state[:found]
|
280
|
+
end
|
281
|
+
|
278
282
|
def refreshed_transfer_token
|
279
|
-
return oauth.
|
283
|
+
return oauth.authorization(refresh: true)
|
280
284
|
end
|
281
285
|
|
282
286
|
# @return part of transfer spec with transport parameters only
|
@@ -296,6 +300,9 @@ module Aspera
|
|
296
300
|
end
|
297
301
|
|
298
302
|
# Create transfer spec for gen4
|
303
|
+
# @param file_id destination or source folder (id)
|
304
|
+
# @param direction one of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
|
305
|
+
# @param ts_merge additional transfer spec to merge
|
299
306
|
def transfer_spec_gen4(file_id, direction, ts_merge=nil)
|
300
307
|
ak_name = nil
|
301
308
|
ak_token = nil
|
@@ -303,12 +310,15 @@ module Aspera
|
|
303
310
|
when :basic
|
304
311
|
ak_name = auth_params[:username]
|
305
312
|
Aspera.assert(auth_params[:password]){'no secret in node object'}
|
306
|
-
ak_token = Rest.
|
313
|
+
ak_token = Rest.basic_authorization(auth_params[:username], auth_params[:password])
|
307
314
|
when :oauth2
|
308
315
|
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
309
|
-
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.
|
316
|
+
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
|
310
317
|
# get bearer token, possibly use cache
|
311
|
-
ak_token = oauth.
|
318
|
+
ak_token = oauth.authorization
|
319
|
+
when :none
|
320
|
+
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
321
|
+
ak_token = params[:headers]['Authorization']
|
312
322
|
else Aspera.error_unexpected_value(auth_params[:type])
|
313
323
|
end
|
314
324
|
transfer_spec = {
|
@@ -357,8 +367,8 @@ module Aspera
|
|
357
367
|
|
358
368
|
private
|
359
369
|
|
370
|
+
# method called in loop for each entry for `resolve_api_fid`
|
360
371
|
def process_api_fid(entry, path, state)
|
361
|
-
# this block is called recursively for each entry in folder
|
362
372
|
# stop digging here if not in right path
|
363
373
|
return false unless entry['name'].eql?(state[:path].first)
|
364
374
|
# ok it matches, so we remove the match, and continue digging
|
@@ -401,11 +411,18 @@ module Aspera
|
|
401
411
|
return true
|
402
412
|
end
|
403
413
|
|
414
|
+
# method called in loop for each entry for `find_files`
|
404
415
|
def process_find_files(entry, path, state)
|
405
|
-
state[:found].push(entry.merge({'path' => path})) if state[:
|
416
|
+
state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
|
406
417
|
# test all files deeply
|
407
418
|
return true
|
408
419
|
end
|
420
|
+
|
421
|
+
# method called in loop for each entry for `list_files`
|
422
|
+
def process_list_files(entry, path, state)
|
423
|
+
state[:found].push(entry.merge({'path' => path}))
|
424
|
+
return false
|
425
|
+
end
|
409
426
|
end
|
410
427
|
end
|
411
428
|
end
|
@@ -3,11 +3,17 @@
|
|
3
3
|
# cspell:ignore protobuf ckpt
|
4
4
|
require 'aspera/environment'
|
5
5
|
require 'aspera/data_repository'
|
6
|
-
require 'aspera/ascp/products'
|
7
6
|
require 'aspera/log'
|
8
7
|
require 'aspera/rest'
|
8
|
+
require 'aspera/uri_reader'
|
9
9
|
require 'aspera/assert'
|
10
10
|
require 'aspera/web_server_simple'
|
11
|
+
require 'aspera/cli/info'
|
12
|
+
require 'aspera/cli/version'
|
13
|
+
require 'aspera/products/desktop'
|
14
|
+
require 'aspera/products/connect'
|
15
|
+
require 'aspera/products/transferd'
|
16
|
+
require 'aspera/products/other'
|
11
17
|
require 'English'
|
12
18
|
require 'singleton'
|
13
19
|
require 'xmlsimple'
|
@@ -20,15 +26,12 @@ module Aspera
|
|
20
26
|
# Singleton that tells where to find ascp and other local resources (keys..) , using the "path(:name)" method.
|
21
27
|
# It is used by object : AgentDirect to find necessary resources
|
22
28
|
# By default it takes the first Aspera product found
|
23
|
-
#
|
29
|
+
# The user can specify ascp location by calling:
|
24
30
|
# Installation.instance.use_ascp_from_product(product_name)
|
25
31
|
# or
|
26
32
|
# Installation.instance.ascp_path=""
|
27
33
|
class Installation
|
28
34
|
include Singleton
|
29
|
-
# protobuf generated files from sdk
|
30
|
-
EXT_RUBY_PROTOBUF = '_pb.rb'
|
31
|
-
RB_SDK_SUBFOLDER = 'lib'
|
32
35
|
DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
|
33
36
|
<?xml version='1.0' encoding='UTF-8'?>
|
34
37
|
<CONF version="2">
|
@@ -44,14 +47,29 @@ module Aspera
|
|
44
47
|
EXE_FILES = %i[ascp ascp4 async].freeze
|
45
48
|
FILES = %i[transferd ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].unshift(*EXE_FILES).freeze
|
46
49
|
TRANSFER_SDK_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
47
|
-
|
48
|
-
|
49
|
-
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_SUBFOLDER, :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL, :FILE_SCHEME_PREFIX
|
50
|
+
# filename for ascp with optional extension (Windows)
|
51
|
+
private_constant :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL
|
50
52
|
# options for SSH client private key
|
51
53
|
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
|
52
54
|
|
55
|
+
# Loads YAML from cloud with locations of SDK archives for all platforms
|
56
|
+
# @return location structure
|
57
|
+
def sdk_locations
|
58
|
+
location_url = @transferd_urls
|
59
|
+
transferd_locations = UriReader.read(location_url)
|
60
|
+
Log.log.debug{"Retrieving SDK locations from #{location_url}"}
|
61
|
+
begin
|
62
|
+
return YAML.load(transferd_locations)
|
63
|
+
rescue Psych::SyntaxError
|
64
|
+
raise "Error when parsing yaml data from: #{location_url}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
53
68
|
# set ascp executable path
|
54
69
|
def ascp_path=(v)
|
70
|
+
Aspera.assert_type(v, String)
|
71
|
+
Aspera.assert(!v.empty?) {'ascp path cannot be empty: check your config file'}
|
72
|
+
Aspera.assert(File.exist?(v)) {"No such file: [#{v}]"}
|
55
73
|
@path_to_ascp = v
|
56
74
|
end
|
57
75
|
|
@@ -59,31 +77,19 @@ module Aspera
|
|
59
77
|
path(:ascp)
|
60
78
|
end
|
61
79
|
|
62
|
-
#
|
80
|
+
# Compatibility
|
63
81
|
def sdk_folder=(v)
|
64
|
-
|
65
|
-
@sdk_dir = v
|
66
|
-
sdk_folder
|
67
|
-
end
|
68
|
-
|
69
|
-
# backward compatibility in sample program
|
70
|
-
alias_method :folder=, :sdk_folder=
|
71
|
-
|
72
|
-
# @return the path to folder where SDK is installed
|
73
|
-
def sdk_folder
|
74
|
-
raise 'SDK path was ot initialized' if @sdk_dir.nil?
|
75
|
-
FileUtils.mkdir_p(@sdk_dir)
|
76
|
-
@sdk_dir
|
82
|
+
Products::Transferd.sdk_directory = v
|
77
83
|
end
|
78
84
|
|
79
85
|
# find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
|
80
|
-
# or select one from
|
86
|
+
# or select one from installed_products()
|
81
87
|
def use_ascp_from_product(product_name)
|
82
88
|
if product_name.eql?(FIRST_FOUND)
|
83
|
-
pl =
|
84
|
-
raise "
|
89
|
+
pl = installed_products.first
|
90
|
+
raise "no Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf ascp install" if pl.nil?
|
85
91
|
else
|
86
|
-
pl =
|
92
|
+
pl = installed_products.find{|i|i[:name].eql?(product_name)}
|
87
93
|
raise "no such product installed: #{product_name}" if pl.nil?
|
88
94
|
end
|
89
95
|
self.ascp_path = pl[:ascp_path]
|
@@ -96,6 +102,8 @@ module Aspera
|
|
96
102
|
m[v.to_s] =
|
97
103
|
begin
|
98
104
|
path(v)
|
105
|
+
rescue Errno::ENOENT => e
|
106
|
+
e.message.gsub(/.*assertion failed: /, '').gsub(/\): .*/, ')')
|
99
107
|
rescue => e
|
100
108
|
e.message
|
101
109
|
end
|
@@ -103,7 +111,7 @@ module Aspera
|
|
103
111
|
end
|
104
112
|
|
105
113
|
def check_or_create_sdk_file(filename, force: false, &block)
|
106
|
-
return Environment.write_file_restricted(File.join(
|
114
|
+
return Environment.write_file_restricted(File.join(Products::Transferd.sdk_directory, filename), force: force, mode: 0o644, &block)
|
107
115
|
end
|
108
116
|
|
109
117
|
# get path of one resource file of currently activated product
|
@@ -118,7 +126,7 @@ module Aspera
|
|
118
126
|
file = @path_to_ascp.gsub('ascp', k.to_s)
|
119
127
|
when :transferd
|
120
128
|
file_is_optional = true
|
121
|
-
file =
|
129
|
+
file = Products::Transferd.transferd_path
|
122
130
|
when :ssh_private_dsa, :ssh_private_rsa
|
123
131
|
# assume last 3 letters are type
|
124
132
|
type = k.to_s[-3..-1].to_sym
|
@@ -128,8 +136,8 @@ module Aspera
|
|
128
136
|
when :aspera_conf
|
129
137
|
file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
|
130
138
|
when :fallback_certificate, :fallback_private_key
|
131
|
-
file_key = File.join(
|
132
|
-
file_cert = File.join(
|
139
|
+
file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
|
140
|
+
file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
|
133
141
|
if !File.exist?(file_key) || !File.exist?(file_cert)
|
134
142
|
require 'openssl'
|
135
143
|
# create new self signed certificate for http fallback
|
@@ -143,7 +151,7 @@ module Aspera
|
|
143
151
|
else Aspera.error_unexpected_value(k)
|
144
152
|
end
|
145
153
|
return nil if file_is_optional && !File.exist?(file)
|
146
|
-
Aspera.assert(File.exist?(file)){"
|
154
|
+
Aspera.assert(File.exist?(file), exception_class: Errno::ENOENT){"#{k} not found (#{file})"}
|
147
155
|
return file
|
148
156
|
end
|
149
157
|
|
@@ -231,18 +239,10 @@ module Aspera
|
|
231
239
|
def ascp_info
|
232
240
|
ascp_data = file_paths
|
233
241
|
ascp_data.merge!(ascp_pvcl_info)
|
234
|
-
ascp_data['sdk_locations'] = TRANSFER_SDK_LOCATION_URL
|
235
242
|
ascp_data.merge!(ascp_ssl_info)
|
236
243
|
return ascp_data
|
237
244
|
end
|
238
245
|
|
239
|
-
# Loads YAML from cloud with locations of SDK archives for all platforms
|
240
|
-
# @return location structure
|
241
|
-
def sdk_locations
|
242
|
-
yaml_text = Aspera::Rest.new(base_url: TRANSFER_SDK_LOCATION_URL, redirect_max: 3).call(operation: 'GET')[:data]
|
243
|
-
YAML.load(yaml_text)
|
244
|
-
end
|
245
|
-
|
246
246
|
# @return the url for download of SDK archive for the given platform and version
|
247
247
|
def sdk_url_for_platform(platform: nil, version: nil)
|
248
248
|
locations = sdk_locations
|
@@ -255,6 +255,7 @@ module Aspera
|
|
255
255
|
return info.first['url']
|
256
256
|
end
|
257
257
|
|
258
|
+
# @param &block called with entry information
|
258
259
|
def extract_archive_files(sdk_archive_path)
|
259
260
|
raise 'missing block' unless block_given?
|
260
261
|
case sdk_archive_path
|
@@ -265,7 +266,7 @@ module Aspera
|
|
265
266
|
Zip::File.open(sdk_archive_path) do |zip_file|
|
266
267
|
zip_file.each do |entry|
|
267
268
|
next if entry.name.end_with?('/')
|
268
|
-
yield(entry.name, entry.get_input_stream)
|
269
|
+
yield(entry.name, entry.get_input_stream, nil)
|
269
270
|
end
|
270
271
|
end
|
271
272
|
# Other Unixes use tar.gz
|
@@ -276,7 +277,7 @@ module Aspera
|
|
276
277
|
Gem::Package::TarReader.new(gzip) do |tar|
|
277
278
|
tar.each do |entry|
|
278
279
|
next if entry.directory?
|
279
|
-
yield(entry.full_name, entry)
|
280
|
+
yield(entry.full_name, entry, entry.symlink? ? entry.header.linkname : nil)
|
280
281
|
end
|
281
282
|
end
|
282
283
|
end
|
@@ -285,54 +286,52 @@ module Aspera
|
|
285
286
|
end
|
286
287
|
end
|
287
288
|
|
288
|
-
#
|
289
|
-
#
|
290
|
-
# @param
|
289
|
+
# Retrieves ascp binary for current system architecture from URL or file
|
290
|
+
# @param url [String] URL to SDK archive, or SpecialValues::DEF
|
291
|
+
# @param folder [String] Destination folder path
|
292
|
+
# @param backup [Bool] If destination folder exists, then rename
|
293
|
+
# @param with_exe [Bool] If false, only retrieves files, but do not generate or restrict access
|
294
|
+
# @param &block [Proc] A lambda that receives a file path from archive and tells detination sub folder(end with /) or file, or nil to not extract
|
291
295
|
# @return ascp version (from execution)
|
292
|
-
def install_sdk(url: nil, folder: nil, backup: true, with_exe: true, &block)
|
293
|
-
url = sdk_url_for_platform if url.nil? || url.eql?('DEF')
|
294
|
-
folder =
|
296
|
+
def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
|
297
|
+
url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
|
298
|
+
folder = Products::Transferd.sdk_directory if folder.nil?
|
295
299
|
subfolder_lambda = block
|
296
300
|
if subfolder_lambda.nil?
|
301
|
+
# default files to extract directly to main folder if in selected source folders
|
297
302
|
subfolder_lambda = ->(name) do
|
298
|
-
|
299
|
-
'/'
|
300
|
-
elsif name.end_with?(EXT_RUBY_PROTOBUF)
|
301
|
-
RB_SDK_SUBFOLDER
|
302
|
-
end
|
303
|
+
Products::Transferd::RUNTIME_FOLDERS.any?{|i|name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
|
303
304
|
end
|
304
305
|
end
|
305
|
-
if url.start_with?('file:')
|
306
|
-
# require specific file scheme: the path part is "relative", or absolute if there are 4 slash
|
307
|
-
raise 'use format: file:///<path>' unless url.start_with?(FILE_SCHEME_PREFIX)
|
308
|
-
sdk_archive_path = url[FILE_SCHEME_PREFIX.length..-1]
|
309
|
-
delete_archive = false
|
310
|
-
else
|
311
|
-
sdk_archive_path = File.join(Dir.tmpdir, File.basename(url))
|
312
|
-
Aspera::Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', save_to_file: sdk_archive_path)
|
313
|
-
delete_archive = true
|
314
|
-
end
|
315
306
|
# rename old install
|
316
307
|
if backup && !Dir.empty?(folder)
|
317
308
|
Log.log.warn('Previous install exists, renaming folder.')
|
318
309
|
File.rename(folder, "#{folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
|
319
310
|
# TODO: delete old archives ?
|
320
311
|
end
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
312
|
+
sdk_archive_path = UriReader.read_as_file(url)
|
313
|
+
extract_archive_files(sdk_archive_path) do |entry_name, entry_stream, link_target|
|
314
|
+
dest_folder = subfolder_lambda.call(entry_name)
|
315
|
+
next if dest_folder.nil?
|
316
|
+
dest_folder = File.join(folder, dest_folder)
|
317
|
+
if dest_folder.end_with?('/')
|
318
|
+
dest_file = File.join(dest_folder, File.basename(entry_name))
|
319
|
+
else
|
320
|
+
dest_file = dest_folder
|
321
|
+
dest_folder = File.dirname(dest_file)
|
322
|
+
end
|
325
323
|
FileUtils.mkdir_p(dest_folder)
|
326
|
-
|
327
|
-
IO.copy_stream(entry_stream, output_stream)
|
324
|
+
if link_target.nil?
|
325
|
+
File.open(dest_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
|
326
|
+
else
|
327
|
+
File.symlink(link_target, dest_file)
|
328
328
|
end
|
329
329
|
end
|
330
|
-
File.unlink(sdk_archive_path) rescue nil if delete_archive # Windows may give error
|
331
330
|
return unless with_exe
|
332
331
|
# ensure license file are generated so that ascp invocation for version works
|
333
332
|
path(:aspera_license)
|
334
333
|
path(:aspera_conf)
|
335
|
-
sdk_ascp_file =
|
334
|
+
sdk_ascp_file = Environment.exe_file('ascp')
|
336
335
|
sdk_ascp_path = File.join(folder, sdk_ascp_file)
|
337
336
|
raise "No #{sdk_ascp_file} found in SDK archive" unless File.exist?(sdk_ascp_path)
|
338
337
|
EXE_FILES.each do |exe_sym|
|
@@ -340,16 +339,18 @@ module Aspera
|
|
340
339
|
Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
|
341
340
|
end
|
342
341
|
sdk_ascp_version = get_ascp_version(sdk_ascp_path)
|
343
|
-
|
344
|
-
Log.log.warn{"No #{
|
345
|
-
Environment.restrict_file_access(
|
346
|
-
transferd_version = get_exe_version(
|
342
|
+
transferd_exe_path = Products::Transferd.transferd_path
|
343
|
+
Log.log.warn{"No #{transferd_exe_path} in SDK archive"} unless File.exist?(transferd_exe_path)
|
344
|
+
Environment.restrict_file_access(transferd_exe_path, mode: 0o755) if File.exist?(transferd_exe_path)
|
345
|
+
transferd_version = get_exe_version(transferd_exe_path, 'version')
|
347
346
|
sdk_name = 'IBM Aspera Transfer SDK'
|
348
347
|
sdk_version = transferd_version || sdk_ascp_version
|
349
|
-
File.write(File.join(folder, Products::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
348
|
+
File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
350
349
|
return sdk_name, sdk_version
|
351
350
|
end
|
352
351
|
|
352
|
+
attr_accessor :transferd_urls
|
353
|
+
|
353
354
|
private
|
354
355
|
|
355
356
|
# policy for product selection
|
@@ -358,10 +359,30 @@ module Aspera
|
|
358
359
|
def initialize
|
359
360
|
@path_to_ascp = nil
|
360
361
|
@sdk_dir = nil
|
362
|
+
@found_products = nil
|
363
|
+
@transferd_urls = TRANSFER_SDK_LOCATION_URL
|
361
364
|
end
|
362
365
|
|
363
|
-
|
364
|
-
|
366
|
+
public
|
367
|
+
|
368
|
+
# @return the list of installed products in format of product_locations_on_current_os
|
369
|
+
def installed_products
|
370
|
+
if @found_products.nil?
|
371
|
+
# :expected M app name is taken from the manifest if present, else defaults to this value
|
372
|
+
# :app_root M main folder for the application
|
373
|
+
# :log_root O location of log files (Linux uses syslog)
|
374
|
+
# :run_root O only for Connect Client, location of http port file
|
375
|
+
# :sub_bin O subfolder with executables, default : bin
|
376
|
+
scan_locations = Products::Transferd.locations.concat(
|
377
|
+
Products::Desktop.locations,
|
378
|
+
Products::Connect.locations,
|
379
|
+
Products::Other::LOCATION_ON_THIS_OS
|
380
|
+
)
|
381
|
+
# .each {|item| item.deep_do {|h, _k, _v, _m|h.freeze}}.freeze
|
382
|
+
# search installed products: with ascp
|
383
|
+
@found_products = Products::Other.find(scan_locations)
|
384
|
+
end
|
385
|
+
return @found_products
|
365
386
|
end
|
366
387
|
end
|
367
388
|
end
|
@@ -198,18 +198,39 @@ module Aspera
|
|
198
198
|
BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
|
199
199
|
MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
|
200
200
|
BOOLEAN_TRUE = 'Yes'
|
201
|
+
|
202
|
+
private_constant :OPERATIONS, :PARAMETERS, :MGT_HEADER, :MGT_FRAME_SEPARATOR, :INTEGER_FIELDS, :BOOLEAN_FIELDS, :BOOLEAN_TRUE
|
201
203
|
# cspell: enable
|
202
204
|
|
203
205
|
class << self
|
204
206
|
# translates mgt port event into (enhanced) typed event
|
205
207
|
def enhanced_event_format(event)
|
206
208
|
return event.keys.each_with_object({}) do |e, h|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
209
|
+
new_name =
|
210
|
+
case e
|
211
|
+
when 'Elapsedusec' then 'elapsed_usec'
|
212
|
+
when 'Bytescont' then 'bytes_cont'
|
213
|
+
else e.capital_to_snake
|
214
|
+
end
|
215
|
+
h[new_name] =
|
216
|
+
if INTEGER_FIELDS.include?(e) then event[e].to_i
|
217
|
+
elsif BOOLEAN_FIELDS.include?(e) then event[e].eql?(BOOLEAN_TRUE)
|
218
|
+
else
|
219
|
+
event[e]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# build command to send on management port
|
225
|
+
# @param data [Hash] {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
226
|
+
def command_to_stream(data)
|
227
|
+
# TODO: translate enhanced to capitalized ?
|
228
|
+
data
|
229
|
+
.keys
|
230
|
+
.map{|k|"#{k.capitalize}: #{data[k]}"}
|
231
|
+
.unshift(MGT_HEADER)
|
232
|
+
.push('', '')
|
233
|
+
.join("\n")
|
213
234
|
end
|
214
235
|
end
|
215
236
|
|