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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +29 -3
  4. data/CONTRIBUTING.md +2 -0
  5. data/README.md +571 -375
  6. data/bin/asession +2 -2
  7. data/examples/get_proto_file.rb +1 -1
  8. data/lib/aspera/agent/alpha.rb +10 -16
  9. data/lib/aspera/agent/connect.rb +20 -2
  10. data/lib/aspera/agent/direct.rb +21 -30
  11. data/lib/aspera/agent/node.rb +1 -11
  12. data/lib/aspera/agent/{trsdk.rb → transferd.rb} +13 -34
  13. data/lib/aspera/api/aoc.rb +13 -8
  14. data/lib/aspera/api/node.rb +45 -28
  15. data/lib/aspera/ascp/installation.rb +87 -48
  16. data/lib/aspera/ascp/management.rb +27 -6
  17. data/lib/aspera/cli/formatter.rb +148 -154
  18. data/lib/aspera/cli/info.rb +1 -1
  19. data/lib/aspera/cli/main.rb +12 -0
  20. data/lib/aspera/cli/manager.rb +2 -2
  21. data/lib/aspera/cli/plugin.rb +2 -2
  22. data/lib/aspera/cli/plugins/aoc.rb +28 -18
  23. data/lib/aspera/cli/plugins/config.rb +106 -54
  24. data/lib/aspera/cli/plugins/cos.rb +1 -0
  25. data/lib/aspera/cli/plugins/faspex.rb +4 -2
  26. data/lib/aspera/cli/plugins/faspex5.rb +21 -9
  27. data/lib/aspera/cli/plugins/node.rb +45 -38
  28. data/lib/aspera/cli/transfer_progress.rb +2 -0
  29. data/lib/aspera/cli/version.rb +1 -1
  30. data/lib/aspera/command_line_builder.rb +1 -1
  31. data/lib/aspera/environment.rb +48 -14
  32. data/lib/aspera/node_simulator.rb +230 -112
  33. data/lib/aspera/oauth/base.rb +34 -47
  34. data/lib/aspera/oauth/factory.rb +41 -2
  35. data/lib/aspera/oauth/jwt.rb +4 -1
  36. data/lib/aspera/persistency_action_once.rb +1 -1
  37. data/lib/aspera/persistency_folder.rb +20 -2
  38. data/lib/aspera/preview/generator.rb +1 -1
  39. data/lib/aspera/preview/utils.rb +8 -3
  40. data/lib/aspera/products/alpha.rb +30 -0
  41. data/lib/aspera/products/connect.rb +48 -0
  42. data/lib/aspera/products/other.rb +82 -0
  43. data/lib/aspera/products/transferd.rb +54 -0
  44. data/lib/aspera/rest.rb +18 -13
  45. data/lib/aspera/secret_hider.rb +2 -2
  46. data/lib/aspera/ssh.rb +31 -24
  47. data/lib/aspera/transfer/parameters.rb +2 -1
  48. data/lib/aspera/transfer/spec.yaml +22 -20
  49. data/lib/aspera/transfer/sync.rb +1 -5
  50. data/lib/aspera/transfer/uri.rb +2 -2
  51. data/lib/transferd_pb.rb +86 -0
  52. data/lib/transferd_services_pb.rb +84 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +38 -21
  55. metadata.gz.sig +0 -0
  56. data/lib/aspera/ascp/products.rb +0 -168
  57. data/lib/transfer_pb.rb +0 -84
  58. 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
- # but the user can specify ascp location by calling:
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
- # location of SDK files
91
+ # Compatibility
63
92
  def sdk_folder=(v)
64
- Log.log.debug{"sdk_folder=#{v}"}
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 Products.installed_products()
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 = Products.installed_products.first
84
- raise "ascp found: no Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf ascp install" if pl.nil?
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 = Products.installed_products.find{|i|i[:name].eql?(product_name)}
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(sdk_folder, filename), force: force, mode: 0o644, &block)
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 = transferd_filepath
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(sdk_folder, 'aspera_fallback_cert_private_key.pem')
132
- file_cert = File.join(sdk_folder, 'aspera_fallback_cert.pem')
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)){"no such file: #{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'] = TRANSFER_SDK_LOCATION_URL
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 [String] URL to SDK archive, or SpecialValues::DEF
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 = sdk_folder if folder.nil?
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.open(File.join(dest_folder, File.basename(entry_name)), 'wb') do |output_stream|
327
- IO.copy_stream(entry_stream, output_stream)
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 = Products.ascp_filename
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 = transferd_filepath
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
- def transferd_filepath
364
- return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension) # cspell:disable-line
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
- new_name = e.capital_to_snake.gsub(/(usec)$/, '_\1').downcase
208
- value = event[e]
209
- value = value.to_i if INTEGER_FIELDS.include?(e)
210
- value = value.eql?(BOOLEAN_TRUE) if BOOLEAN_FIELDS.include?(e)
211
- h[new_name] = value
212
- end
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