aspera-cli 4.20.0 → 4.21.1
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 +29 -3
- data/CONTRIBUTING.md +2 -0
- data/README.md +571 -375
- data/bin/asession +2 -2
- data/examples/get_proto_file.rb +1 -1
- data/lib/aspera/agent/alpha.rb +10 -16
- data/lib/aspera/agent/connect.rb +20 -2
- data/lib/aspera/agent/direct.rb +21 -30
- data/lib/aspera/agent/node.rb +1 -11
- data/lib/aspera/agent/{trsdk.rb → transferd.rb} +13 -34
- data/lib/aspera/api/aoc.rb +13 -8
- data/lib/aspera/api/node.rb +45 -28
- data/lib/aspera/ascp/installation.rb +87 -48
- data/lib/aspera/ascp/management.rb +27 -6
- data/lib/aspera/cli/formatter.rb +148 -154
- data/lib/aspera/cli/info.rb +1 -1
- data/lib/aspera/cli/main.rb +12 -0
- data/lib/aspera/cli/manager.rb +2 -2
- data/lib/aspera/cli/plugin.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +28 -18
- data/lib/aspera/cli/plugins/config.rb +106 -54
- 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 +21 -9
- data/lib/aspera/cli/plugins/node.rb +45 -38
- 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/environment.rb +48 -14
- data/lib/aspera/node_simulator.rb +230 -112
- data/lib/aspera/oauth/base.rb +34 -47
- 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 +1 -1
- data/lib/aspera/preview/utils.rb +8 -3
- data/lib/aspera/products/alpha.rb +30 -0
- data/lib/aspera/products/connect.rb +48 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/transferd.rb +54 -0
- data/lib/aspera/rest.rb +18 -13
- data/lib/aspera/secret_hider.rb +2 -2
- data/lib/aspera/ssh.rb +31 -24
- 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/transferd_pb.rb +86 -0
- data/lib/transferd_services_pb.rb +84 -0
- data.tar.gz.sig +0 -0
- metadata +38 -21
- metadata.gz.sig +0 -0
- data/lib/aspera/ascp/products.rb +0 -168
- data/lib/transfer_pb.rb +0 -84
- data/lib/transfer_services_pb.rb +0 -82
@@ -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/alpha'
|
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,7 +26,7 @@ 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=""
|
@@ -46,12 +52,35 @@ module Aspera
|
|
46
52
|
TRANSFER_SDK_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
47
53
|
FILE_SCHEME_PREFIX = 'file:///'
|
48
54
|
SDK_ARCHIVE_FOLDERS = ['/bin/', '/aspera/'].freeze
|
55
|
+
# filename for ascp with optional extension (Windows)
|
49
56
|
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_SUBFOLDER, :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL, :FILE_SCHEME_PREFIX
|
50
57
|
# options for SSH client private key
|
51
58
|
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
|
52
59
|
|
60
|
+
class << self
|
61
|
+
def transfer_sdk_location_url
|
62
|
+
ENV.fetch('ASCLI_TRANSFER_SDK_LOCATION_URL', TRANSFER_SDK_LOCATION_URL)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Loads YAML from cloud with locations of SDK archives for all platforms
|
66
|
+
# @return location structure
|
67
|
+
def sdk_locations
|
68
|
+
location_url = transfer_sdk_location_url
|
69
|
+
transferd_locations = UriReader.read(location_url)
|
70
|
+
Log.log.debug{"Retrieving SDK locations from #{location_url}"}
|
71
|
+
begin
|
72
|
+
return YAML.load(transferd_locations)
|
73
|
+
rescue Psych::SyntaxError
|
74
|
+
raise "Error when parsing yaml data from: #{location_url}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
53
79
|
# set ascp executable path
|
54
80
|
def ascp_path=(v)
|
81
|
+
Aspera.assert_type(v, String)
|
82
|
+
Aspera.assert(!v.empty?) {'ascp path cannot be empty: check your config file'}
|
83
|
+
Aspera.assert(File.exist?(v)) {"No such file: [#{v}]"}
|
55
84
|
@path_to_ascp = v
|
56
85
|
end
|
57
86
|
|
@@ -59,31 +88,19 @@ module Aspera
|
|
59
88
|
path(:ascp)
|
60
89
|
end
|
61
90
|
|
62
|
-
#
|
91
|
+
# Compatibility
|
63
92
|
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
|
93
|
+
Products::Transferd.sdk_directory = v
|
77
94
|
end
|
78
95
|
|
79
96
|
# find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
|
80
|
-
# or select one from
|
97
|
+
# or select one from installed_products()
|
81
98
|
def use_ascp_from_product(product_name)
|
82
99
|
if product_name.eql?(FIRST_FOUND)
|
83
|
-
pl =
|
84
|
-
raise "
|
100
|
+
pl = installed_products.first
|
101
|
+
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
102
|
else
|
86
|
-
pl =
|
103
|
+
pl = installed_products.find{|i|i[:name].eql?(product_name)}
|
87
104
|
raise "no such product installed: #{product_name}" if pl.nil?
|
88
105
|
end
|
89
106
|
self.ascp_path = pl[:ascp_path]
|
@@ -96,6 +113,8 @@ module Aspera
|
|
96
113
|
m[v.to_s] =
|
97
114
|
begin
|
98
115
|
path(v)
|
116
|
+
rescue Errno::ENOENT => e
|
117
|
+
e.message.gsub(/.*assertion failed: /, '').gsub(/\): .*/, ')')
|
99
118
|
rescue => e
|
100
119
|
e.message
|
101
120
|
end
|
@@ -103,7 +122,7 @@ module Aspera
|
|
103
122
|
end
|
104
123
|
|
105
124
|
def check_or_create_sdk_file(filename, force: false, &block)
|
106
|
-
return Environment.write_file_restricted(File.join(
|
125
|
+
return Environment.write_file_restricted(File.join(Products::Transferd.sdk_directory, filename), force: force, mode: 0o644, &block)
|
107
126
|
end
|
108
127
|
|
109
128
|
# get path of one resource file of currently activated product
|
@@ -118,7 +137,7 @@ module Aspera
|
|
118
137
|
file = @path_to_ascp.gsub('ascp', k.to_s)
|
119
138
|
when :transferd
|
120
139
|
file_is_optional = true
|
121
|
-
file =
|
140
|
+
file = Products::Transferd.transferd_path
|
122
141
|
when :ssh_private_dsa, :ssh_private_rsa
|
123
142
|
# assume last 3 letters are type
|
124
143
|
type = k.to_s[-3..-1].to_sym
|
@@ -128,8 +147,8 @@ module Aspera
|
|
128
147
|
when :aspera_conf
|
129
148
|
file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
|
130
149
|
when :fallback_certificate, :fallback_private_key
|
131
|
-
file_key = File.join(
|
132
|
-
file_cert = File.join(
|
150
|
+
file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
|
151
|
+
file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
|
133
152
|
if !File.exist?(file_key) || !File.exist?(file_cert)
|
134
153
|
require 'openssl'
|
135
154
|
# create new self signed certificate for http fallback
|
@@ -143,7 +162,7 @@ module Aspera
|
|
143
162
|
else Aspera.error_unexpected_value(k)
|
144
163
|
end
|
145
164
|
return nil if file_is_optional && !File.exist?(file)
|
146
|
-
Aspera.assert(File.exist?(file)){"
|
165
|
+
Aspera.assert(File.exist?(file), exception_class: Errno::ENOENT){"#{k} not found (#{file})"}
|
147
166
|
return file
|
148
167
|
end
|
149
168
|
|
@@ -231,21 +250,14 @@ module Aspera
|
|
231
250
|
def ascp_info
|
232
251
|
ascp_data = file_paths
|
233
252
|
ascp_data.merge!(ascp_pvcl_info)
|
234
|
-
ascp_data['sdk_locations'] =
|
253
|
+
ascp_data['sdk_locations'] = self.class.transfer_sdk_location_url
|
235
254
|
ascp_data.merge!(ascp_ssl_info)
|
236
255
|
return ascp_data
|
237
256
|
end
|
238
257
|
|
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
258
|
# @return the url for download of SDK archive for the given platform and version
|
247
259
|
def sdk_url_for_platform(platform: nil, version: nil)
|
248
|
-
locations = sdk_locations
|
260
|
+
locations = self.class.sdk_locations
|
249
261
|
platform = Environment.architecture if platform.nil?
|
250
262
|
locations = locations.select{|l|l['platform'].eql?(platform)}
|
251
263
|
raise "No SDK for platform: #{platform}" if locations.empty?
|
@@ -255,6 +267,7 @@ module Aspera
|
|
255
267
|
return info.first['url']
|
256
268
|
end
|
257
269
|
|
270
|
+
# @param &block called with entry information
|
258
271
|
def extract_archive_files(sdk_archive_path)
|
259
272
|
raise 'missing block' unless block_given?
|
260
273
|
case sdk_archive_path
|
@@ -265,7 +278,7 @@ module Aspera
|
|
265
278
|
Zip::File.open(sdk_archive_path) do |zip_file|
|
266
279
|
zip_file.each do |entry|
|
267
280
|
next if entry.name.end_with?('/')
|
268
|
-
yield(entry.name, entry.get_input_stream)
|
281
|
+
yield(entry.name, entry.get_input_stream, nil)
|
269
282
|
end
|
270
283
|
end
|
271
284
|
# Other Unixes use tar.gz
|
@@ -276,7 +289,7 @@ module Aspera
|
|
276
289
|
Gem::Package::TarReader.new(gzip) do |tar|
|
277
290
|
tar.each do |entry|
|
278
291
|
next if entry.directory?
|
279
|
-
yield(entry.full_name, entry)
|
292
|
+
yield(entry.full_name, entry, entry.symlink? ? entry.header.linkname : nil)
|
280
293
|
end
|
281
294
|
end
|
282
295
|
end
|
@@ -287,11 +300,15 @@ module Aspera
|
|
287
300
|
|
288
301
|
# download aspera SDK or use local file
|
289
302
|
# extracts ascp binary for current system architecture
|
290
|
-
# @param url
|
303
|
+
# @param url [String] URL to SDK archive, or SpecialValues::DEF
|
304
|
+
# @param folder [String] destination
|
305
|
+
# @param backup [Bool]
|
306
|
+
# @param with_exe [Bool]
|
307
|
+
# @param &block [Proc] a lambda that receives a file path from archive and tells detination sub folder, or nil to not extract
|
291
308
|
# @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 =
|
309
|
+
def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
|
310
|
+
url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
|
311
|
+
folder = Products::Transferd.sdk_directory if folder.nil?
|
295
312
|
subfolder_lambda = block
|
296
313
|
if subfolder_lambda.nil?
|
297
314
|
subfolder_lambda = ->(name) do
|
@@ -318,13 +335,16 @@ module Aspera
|
|
318
335
|
File.rename(folder, "#{folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
|
319
336
|
# TODO: delete old archives ?
|
320
337
|
end
|
321
|
-
extract_archive_files(sdk_archive_path) do |entry_name, entry_stream|
|
338
|
+
extract_archive_files(sdk_archive_path) do |entry_name, entry_stream, link_target|
|
322
339
|
subfolder = subfolder_lambda.call(entry_name)
|
323
340
|
next if subfolder.nil?
|
324
341
|
dest_folder = File.join(folder, subfolder)
|
325
342
|
FileUtils.mkdir_p(dest_folder)
|
326
|
-
File.
|
327
|
-
|
343
|
+
new_file = File.join(dest_folder, File.basename(entry_name))
|
344
|
+
if link_target.nil?
|
345
|
+
File.open(new_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
|
346
|
+
else
|
347
|
+
File.symlink(link_target, new_file)
|
328
348
|
end
|
329
349
|
end
|
330
350
|
File.unlink(sdk_archive_path) rescue nil if delete_archive # Windows may give error
|
@@ -332,7 +352,7 @@ module Aspera
|
|
332
352
|
# ensure license file are generated so that ascp invocation for version works
|
333
353
|
path(:aspera_license)
|
334
354
|
path(:aspera_conf)
|
335
|
-
sdk_ascp_file =
|
355
|
+
sdk_ascp_file = Environment.exe_file('ascp')
|
336
356
|
sdk_ascp_path = File.join(folder, sdk_ascp_file)
|
337
357
|
raise "No #{sdk_ascp_file} found in SDK archive" unless File.exist?(sdk_ascp_path)
|
338
358
|
EXE_FILES.each do |exe_sym|
|
@@ -340,13 +360,13 @@ module Aspera
|
|
340
360
|
Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
|
341
361
|
end
|
342
362
|
sdk_ascp_version = get_ascp_version(sdk_ascp_path)
|
343
|
-
sdk_daemon_path =
|
363
|
+
sdk_daemon_path = Products::Transferd.transferd_path
|
344
364
|
Log.log.warn{"No #{sdk_daemon_path} in SDK archive"} unless File.exist?(sdk_daemon_path)
|
345
365
|
Environment.restrict_file_access(sdk_daemon_path, mode: 0o755) if File.exist?(sdk_daemon_path)
|
346
366
|
transferd_version = get_exe_version(sdk_daemon_path, 'version')
|
347
367
|
sdk_name = 'IBM Aspera Transfer SDK'
|
348
368
|
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>")
|
369
|
+
File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
350
370
|
return sdk_name, sdk_version
|
351
371
|
end
|
352
372
|
|
@@ -358,10 +378,29 @@ module Aspera
|
|
358
378
|
def initialize
|
359
379
|
@path_to_ascp = nil
|
360
380
|
@sdk_dir = nil
|
381
|
+
@found_products = nil
|
361
382
|
end
|
362
383
|
|
363
|
-
|
364
|
-
|
384
|
+
public
|
385
|
+
|
386
|
+
# @return the list of installed products in format of product_locations_on_current_os
|
387
|
+
def installed_products
|
388
|
+
if @found_products.nil?
|
389
|
+
# :expected M app name is taken from the manifest if present, else defaults to this value
|
390
|
+
# :app_root M main folder for the application
|
391
|
+
# :log_root O location of log files (Linux uses syslog)
|
392
|
+
# :run_root O only for Connect Client, location of http port file
|
393
|
+
# :sub_bin O subfolder with executables, default : bin
|
394
|
+
scan_locations = Products::Transferd.locations.concat(
|
395
|
+
Products::Alpha.locations,
|
396
|
+
Products::Connect.locations,
|
397
|
+
Products::Other::LOCATION_ON_THIS_OS
|
398
|
+
)
|
399
|
+
# .each {|item| item.deep_do {|h, _k, _v, _m|h.freeze}}.freeze
|
400
|
+
# search installed products: with ascp
|
401
|
+
@found_products = Products::Other.find(scan_locations)
|
402
|
+
end
|
403
|
+
return @found_products
|
365
404
|
end
|
366
405
|
end
|
367
406
|
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
|
|