aspera-cli 4.21.1 → 4.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +52 -22
  5. data/CONTRIBUTING.md +69 -148
  6. data/README.md +929 -668
  7. data/bin/ascli +5 -14
  8. data/bin/asession +1 -3
  9. data/examples/get_proto_file.rb +4 -3
  10. data/examples/proxy.pac +20 -20
  11. data/lib/aspera/agent/base.rb +11 -5
  12. data/lib/aspera/agent/connect.rb +30 -28
  13. data/lib/aspera/agent/{alpha.rb → desktop.rb} +35 -31
  14. data/lib/aspera/agent/direct.rb +141 -121
  15. data/lib/aspera/agent/httpgw.rb +22 -26
  16. data/lib/aspera/agent/node.rb +14 -11
  17. data/lib/aspera/agent/transferd.rb +30 -19
  18. data/lib/aspera/api/alee.rb +1 -1
  19. data/lib/aspera/api/aoc.rb +6 -6
  20. data/lib/aspera/api/cos_node.rb +2 -2
  21. data/lib/aspera/api/httpgw.rb +7 -3
  22. data/lib/aspera/api/node.rb +10 -8
  23. data/lib/aspera/ascmd.rb +3 -3
  24. data/lib/aspera/ascp/installation.rb +53 -72
  25. data/lib/aspera/ascp/management.rb +1 -1
  26. data/lib/aspera/assert.rb +11 -2
  27. data/lib/aspera/cli/error.rb +2 -2
  28. data/lib/aspera/cli/extended_value.rb +46 -21
  29. data/lib/aspera/cli/formatter.rb +55 -48
  30. data/lib/aspera/cli/hints.rb +1 -1
  31. data/lib/aspera/cli/info.rb +1 -0
  32. data/lib/aspera/cli/main.rb +192 -170
  33. data/lib/aspera/cli/manager.rb +18 -18
  34. data/lib/aspera/cli/plugin.rb +23 -20
  35. data/lib/aspera/cli/plugin_factory.rb +1 -1
  36. data/lib/aspera/cli/plugins/alee.rb +1 -1
  37. data/lib/aspera/cli/plugins/aoc.rb +247 -159
  38. data/lib/aspera/cli/plugins/ats.rb +19 -17
  39. data/lib/aspera/cli/plugins/config.rb +76 -113
  40. data/lib/aspera/cli/plugins/console.rb +5 -3
  41. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  42. data/lib/aspera/cli/plugins/faspex5.rb +111 -84
  43. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  44. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  45. data/lib/aspera/cli/plugins/node.rb +312 -182
  46. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  47. data/lib/aspera/cli/plugins/preview.rb +3 -3
  48. data/lib/aspera/cli/plugins/server.rb +6 -6
  49. data/lib/aspera/cli/plugins/shares.rb +5 -5
  50. data/lib/aspera/cli/sync_actions.rb +19 -18
  51. data/lib/aspera/cli/transfer_agent.rb +5 -5
  52. data/lib/aspera/cli/transfer_progress.rb +2 -2
  53. data/lib/aspera/cli/version.rb +1 -1
  54. data/lib/aspera/command_line_builder.rb +116 -95
  55. data/lib/aspera/coverage.rb +8 -5
  56. data/lib/aspera/environment.rb +26 -17
  57. data/lib/aspera/faspex_gw.rb +14 -14
  58. data/lib/aspera/faspex_postproc.rb +10 -11
  59. data/lib/aspera/hash_ext.rb +4 -14
  60. data/lib/aspera/json_rpc.rb +1 -1
  61. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  62. data/lib/aspera/keychain/factory.rb +41 -0
  63. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  64. data/lib/aspera/keychain/macos_security.rb +19 -11
  65. data/lib/aspera/log.rb +28 -34
  66. data/lib/aspera/nagios.rb +6 -6
  67. data/lib/aspera/node_simulator.rb +8 -8
  68. data/lib/aspera/oauth/base.rb +14 -7
  69. data/lib/aspera/oauth/factory.rb +5 -6
  70. data/lib/aspera/oauth/url_json.rb +6 -6
  71. data/lib/aspera/persistency_action_once.rb +6 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +13 -10
  74. data/lib/aspera/preview/options.rb +16 -16
  75. data/lib/aspera/preview/terminal.rb +4 -4
  76. data/lib/aspera/preview/utils.rb +15 -17
  77. data/lib/aspera/products/connect.rb +35 -1
  78. data/lib/aspera/products/{alpha.rb → desktop.rb} +3 -3
  79. data/lib/aspera/products/transferd.rb +9 -2
  80. data/lib/aspera/proxy_auto_config.rb +2 -2
  81. data/lib/aspera/rest.rb +56 -47
  82. data/lib/aspera/rest_errors_aspera.rb +1 -1
  83. data/lib/aspera/secret_hider.rb +12 -5
  84. data/lib/aspera/ssh.rb +4 -4
  85. data/lib/aspera/temp_file_manager.rb +5 -4
  86. data/lib/aspera/transfer/convert.rb +29 -0
  87. data/lib/aspera/transfer/error_info.rb +66 -66
  88. data/lib/aspera/transfer/parameters.rb +13 -68
  89. data/lib/aspera/transfer/spec.rb +5 -6
  90. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  91. data/lib/aspera/transfer/spec_doc.rb +62 -0
  92. data/lib/aspera/transfer/sync.rb +23 -72
  93. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  94. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  95. data/lib/aspera/transfer/uri.rb +6 -6
  96. data/lib/aspera/uri_reader.rb +18 -1
  97. data/lib/aspera/web_auth.rb +1 -1
  98. data/lib/aspera/web_server_simple.rb +53 -44
  99. data.tar.gz.sig +0 -0
  100. metadata +28 -165
  101. metadata.gz.sig +0 -0
  102. data/examples/build_exec +0 -74
  103. data/examples/build_exec_rubyc +0 -40
  104. data/examples/build_package.sh +0 -28
  105. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -87,8 +87,8 @@ 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] = OAuth::Factory.bearer_extract(delegated_oauth.token)
91
- @headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
90
+ @storage_credentials['token'][TOKEN_FIELD] = delegated_oauth.token
91
+ headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
92
92
  end
93
93
  end
94
94
  end
@@ -171,7 +171,7 @@ module Aspera
171
171
  cond_var: ConditionVariable.new
172
172
  }
173
173
  # start read thread after handshake
174
- @ws_read_thread = Thread.new {process_read_thread}
174
+ @ws_read_thread = Thread.new{process_read_thread}
175
175
  @notify_cb&.call(:session_start, session_id: session_id)
176
176
  @notify_cb&.call(:session_size, session_id: session_id, info: total_bytes_to_transfer)
177
177
  sleep(1)
@@ -309,11 +309,15 @@ module Aspera
309
309
  # web socket endpoint: by default use v2 (newer gateways), without base64 encoding
310
310
  # is the latest supported? else revert to old api
311
311
  if !@upload_version.eql?(API_V1)
312
- if !@api_info['endpoints'].any?{|i|i.include?(@upload_version)}
312
+ if !@api_info['endpoints'].any?{ |i| i.include?(@upload_version)}
313
313
  Log.log.warn{"API version #{@upload_version} not supported, reverting to #{API_V1}"}
314
314
  @upload_version = API_V1
315
315
  end
316
316
  end
317
+ @shared_info = nil
318
+ @ws_handshake = nil
319
+ @ws_io = nil
320
+ @ws_read_thread = nil
317
321
  end
318
322
 
319
323
  private
@@ -373,7 +377,7 @@ module Aspera
373
377
  raise "File not found: #{source_path}"
374
378
  end
375
379
  end
376
- transfer_spec['paths'] = files_to_send.map{|i|{'source' => i[:name]}}
380
+ transfer_spec['paths'] = files_to_send.map{ |i| {'source' => i[:name]}}
377
381
  files_to_send.push(total_bytes_to_transfer)
378
382
  return files_to_send
379
383
  end
@@ -27,7 +27,7 @@ module Aspera
27
27
  :SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
28
28
  :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
29
29
 
30
- # node api permissions
30
+ # Node API permissions
31
31
  ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
32
32
  HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
33
33
  HEADER_X_TOTAL_COUNT = 'X-Total-Count'
@@ -38,7 +38,7 @@ module Aspera
38
38
  PATH_SEPARATOR = '/'
39
39
 
40
40
  # register node special token decoder
41
- OAuth::Factory.instance.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
41
+ OAuth::Factory.instance.register_decoder(lambda{ |token| Node.decode_bearer_token(token)})
42
42
 
43
43
  # class instance variable, access with accessors on class
44
44
  @use_standard_ports = true
@@ -81,6 +81,7 @@ module Aspera
81
81
  end
82
82
 
83
83
  # Create an Aspera Node bearer token
84
+ # @param access_key [String] Access key identifier
84
85
  # @param payload [String] JSON payload to be included in the token
85
86
  # @param private_key [OpenSSL::PKey::RSA] Private key to sign the token
86
87
  def bearer_token(access_key:, payload:, private_key:)
@@ -106,6 +107,7 @@ module Aspera
106
107
  ].join("\n")))
107
108
  end
108
109
 
110
+ # Decode an Aspera Node bearer token
109
111
  def decode_bearer_token(token)
110
112
  return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
111
113
  end
@@ -149,7 +151,7 @@ module Aspera
149
151
 
150
152
  # Call node API, possibly adding cache control header, as globally specified
151
153
  def read_with_cache(subpath, query=nil)
152
- headers = {'Accept' => 'application/json'}
154
+ headers = {'Accept' => Rest::MIME_JSON}
153
155
  headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
154
156
  return call(
155
157
  operation: 'GET',
@@ -280,7 +282,7 @@ module Aspera
280
282
  end
281
283
 
282
284
  def refreshed_transfer_token
283
- return oauth.token(refresh: true)
285
+ return oauth.authorization(refresh: true)
284
286
  end
285
287
 
286
288
  # @return part of transfer spec with transport parameters only
@@ -310,12 +312,12 @@ module Aspera
310
312
  when :basic
311
313
  ak_name = auth_params[:username]
312
314
  Aspera.assert(auth_params[:password]){'no secret in node object'}
313
- ak_token = Rest.basic_token(auth_params[:username], auth_params[:password])
315
+ ak_token = Rest.basic_authorization(auth_params[:username], auth_params[:password])
314
316
  when :oauth2
315
317
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
316
- # TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
318
+ # TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
317
319
  # get bearer token, possibly use cache
318
- ak_token = oauth.token
320
+ ak_token = oauth.authorization
319
321
  when :none
320
322
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
321
323
  ak_token = params[:headers]['Authorization']
@@ -352,7 +354,7 @@ module Aspera
352
354
  # get the transfer user from info on access key
353
355
  transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
354
356
  # get settings from name.value array to hash key.value
355
- settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
357
+ settings = info['settings']&.each_with_object({}){ |i, h| h[i['name']] = i['value']}
356
358
  # check WSS ports
357
359
  Transfer::Spec::WSS_FIELDS.each do |i|
358
360
  transfer_spec[i] = settings[i] if settings.key?(i)
data/lib/aspera/ascmd.rb CHANGED
@@ -104,7 +104,7 @@ module Aspera
104
104
  # enclose arguments in double quotes, protect backslash and double quotes
105
105
  # ascmd uses space as token separator, and optional quotes ('") or \ to escape
106
106
  args.each do |v|
107
- command.push(%Q{"#{v.gsub(/["\\]/){|s|"\\#{s}"}}"})
107
+ command.push(%Q{"#{v.gsub(/["\\]/){ |s| "\\#{s}"}}"})
108
108
  end
109
109
  command_lines.push(command.join(' '))
110
110
  end
@@ -141,7 +141,7 @@ module Aspera
141
141
  end
142
142
  # raise error as exception
143
143
  raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
144
- result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
144
+ result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{ |i| i[:name]}.sort)
145
145
  return result
146
146
  end
147
147
 
@@ -209,7 +209,7 @@ module Aspera
209
209
  result[field_info[:name]] ||= []
210
210
  result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
211
211
  when :list_tlv_list # field is an array of values in a list of buffers
212
- result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
212
+ result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{ |r| parse(r[:buffer], field_info[:is_a], indent_level)}
213
213
  when :list_tlv_restart # field is an array of values, but a new value is started on index 1
214
214
  fl = result[field_info[:name]] = []
215
215
  parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
@@ -10,7 +10,7 @@ require 'aspera/assert'
10
10
  require 'aspera/web_server_simple'
11
11
  require 'aspera/cli/info'
12
12
  require 'aspera/cli/version'
13
- require 'aspera/products/alpha'
13
+ require 'aspera/products/desktop'
14
14
  require 'aspera/products/connect'
15
15
  require 'aspera/products/transferd'
16
16
  require 'aspera/products/other'
@@ -32,9 +32,6 @@ module Aspera
32
32
  # Installation.instance.ascp_path=""
33
33
  class Installation
34
34
  include Singleton
35
- # protobuf generated files from sdk
36
- EXT_RUBY_PROTOBUF = '_pb.rb'
37
- RB_SDK_SUBFOLDER = 'lib'
38
35
  DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
39
36
  <?xml version='1.0' encoding='UTF-8'?>
40
37
  <CONF version="2">
@@ -50,37 +47,29 @@ module Aspera
50
47
  EXE_FILES = %i[ascp ascp4 async].freeze
51
48
  FILES = %i[transferd ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].unshift(*EXE_FILES).freeze
52
49
  TRANSFER_SDK_LOCATION_URL = 'https://ibm.biz/sdk_location'
53
- FILE_SCHEME_PREFIX = 'file:///'
54
- SDK_ARCHIVE_FOLDERS = ['/bin/', '/aspera/'].freeze
55
50
  # filename for ascp with optional extension (Windows)
56
- private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_SUBFOLDER, :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL, :FILE_SCHEME_PREFIX
51
+ private_constant :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL
57
52
  # options for SSH client private key
58
53
  CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
59
54
 
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
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}"
76
65
  end
77
66
  end
78
67
 
79
68
  # set ascp executable path
80
69
  def ascp_path=(v)
81
70
  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}]"}
71
+ Aspera.assert(!v.empty?){'ascp path cannot be empty: check your config file'}
72
+ Aspera.assert(File.exist?(v)){"No such file: [#{v}]"}
84
73
  @path_to_ascp = v
85
74
  end
86
75
 
@@ -100,7 +89,7 @@ module Aspera
100
89
  pl = installed_products.first
101
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?
102
91
  else
103
- pl = installed_products.find{|i|i[:name].eql?(product_name)}
92
+ pl = installed_products.find{ |i| i[:name].eql?(product_name)}
104
93
  raise "no such product installed: #{product_name}" if pl.nil?
105
94
  end
106
95
  self.ascp_path = pl[:ascp_path]
@@ -141,22 +130,21 @@ module Aspera
141
130
  when :ssh_private_dsa, :ssh_private_rsa
142
131
  # assume last 3 letters are type
143
132
  type = k.to_s[-3..-1].to_sym
144
- file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") {DataRepository.instance.item(type)}
133
+ file = check_or_create_sdk_file("aspera_bypass_#{type}.pem"){DataRepository.instance.item(type)}
145
134
  when :aspera_license
146
- file = check_or_create_sdk_file('aspera-license') {DataRepository.instance.item(:license)}
135
+ file = check_or_create_sdk_file('aspera-license'){DataRepository.instance.item(:license)}
147
136
  when :aspera_conf
148
- file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
137
+ file = check_or_create_sdk_file('aspera.conf'){DEFAULT_ASPERA_CONF}
149
138
  when :fallback_certificate, :fallback_private_key
150
139
  file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
151
140
  file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
152
141
  if !File.exist?(file_key) || !File.exist?(file_cert)
153
142
  require 'openssl'
154
143
  # create new self signed certificate for http fallback
155
- cert = OpenSSL::X509::Certificate.new
156
144
  private_key = OpenSSL::PKey::RSA.new(4096)
157
- WebServerSimple.fill_self_signed_cert(cert, private_key)
158
- check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) {private_key.to_pem}
159
- check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
145
+ cert = WebServerSimple.self_signed_cert(private_key)
146
+ check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true){private_key.to_pem}
147
+ check_or_create_sdk_file('aspera_fallback_cert.pem', force: true){cert.to_pem}
160
148
  end
161
149
  file = k.eql?(:fallback_certificate) ? file_cert : file_key
162
150
  else Aspera.error_unexpected_value(k)
@@ -177,7 +165,7 @@ module Aspera
177
165
  Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
178
166
  return case types
179
167
  when :dsa_rsa, :rsa
180
- types.to_s.split('_').map{|i|Installation.instance.path("ssh_private_#{i}".to_sym)}
168
+ types.to_s.split('_').map{ |i| Installation.instance.path("ssh_private_#{i}".to_sym)}
181
169
  when :per_client
182
170
  raise 'Not yet implemented'
183
171
  end
@@ -250,19 +238,18 @@ module Aspera
250
238
  def ascp_info
251
239
  ascp_data = file_paths
252
240
  ascp_data.merge!(ascp_pvcl_info)
253
- ascp_data['sdk_locations'] = self.class.transfer_sdk_location_url
254
241
  ascp_data.merge!(ascp_ssl_info)
255
242
  return ascp_data
256
243
  end
257
244
 
258
245
  # @return the url for download of SDK archive for the given platform and version
259
246
  def sdk_url_for_platform(platform: nil, version: nil)
260
- locations = self.class.sdk_locations
247
+ locations = sdk_locations
261
248
  platform = Environment.architecture if platform.nil?
262
- locations = locations.select{|l|l['platform'].eql?(platform)}
249
+ locations = locations.select{ |l| l['platform'].eql?(platform)}
263
250
  raise "No SDK for platform: #{platform}" if locations.empty?
264
- version = locations.max_by { |entry| Gem::Version.new(entry['version']) }['version'] if version.nil?
265
- info = locations.select{|entry| entry['version'].eql?(version)}
251
+ version = locations.max_by{ |entry| Gem::Version.new(entry['version'])}['version'] if version.nil?
252
+ info = locations.select{ |entry| entry['version'].eql?(version)}
266
253
  raise "No such version: #{version} for #{platform}" if info.empty?
267
254
  return info.first['url']
268
255
  end
@@ -298,56 +285,47 @@ module Aspera
298
285
  end
299
286
  end
300
287
 
301
- # download aspera SDK or use local file
302
- # extracts ascp binary for current system architecture
288
+ # Retrieves ascp binary for current system architecture from URL or file
303
289
  # @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
290
+ # @param folder [String] Destination folder path
291
+ # @param backup [Bool] If destination folder exists, then rename
292
+ # @param with_exe [Bool] If false, only retrieves files, but do not generate or restrict access
293
+ # @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
308
294
  # @return ascp version (from execution)
309
295
  def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
310
296
  url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
311
297
  folder = Products::Transferd.sdk_directory if folder.nil?
312
298
  subfolder_lambda = block
313
299
  if subfolder_lambda.nil?
300
+ # default files to extract directly to main folder if in selected source folders
314
301
  subfolder_lambda = ->(name) do
315
- if SDK_ARCHIVE_FOLDERS.any?{|i|name.include?(i)}
316
- '/'
317
- elsif name.end_with?(EXT_RUBY_PROTOBUF)
318
- RB_SDK_SUBFOLDER
319
- end
302
+ Products::Transferd::RUNTIME_FOLDERS.any?{ |i| name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
320
303
  end
321
304
  end
322
- if url.start_with?('file:')
323
- # require specific file scheme: the path part is "relative", or absolute if there are 4 slash
324
- raise 'use format: file:///<path>' unless url.start_with?(FILE_SCHEME_PREFIX)
325
- sdk_archive_path = url[FILE_SCHEME_PREFIX.length..-1]
326
- delete_archive = false
327
- else
328
- sdk_archive_path = File.join(Dir.tmpdir, File.basename(url))
329
- Aspera::Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', save_to_file: sdk_archive_path)
330
- delete_archive = true
331
- end
332
305
  # rename old install
333
306
  if backup && !Dir.empty?(folder)
334
307
  Log.log.warn('Previous install exists, renaming folder.')
335
308
  File.rename(folder, "#{folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
336
309
  # TODO: delete old archives ?
337
310
  end
311
+ sdk_archive_path = UriReader.read_as_file(url)
338
312
  extract_archive_files(sdk_archive_path) do |entry_name, entry_stream, link_target|
339
- subfolder = subfolder_lambda.call(entry_name)
340
- next if subfolder.nil?
341
- dest_folder = File.join(folder, subfolder)
313
+ dest_folder = subfolder_lambda.call(entry_name)
314
+ next if dest_folder.nil?
315
+ dest_folder = File.join(folder, dest_folder)
316
+ if dest_folder.end_with?('/')
317
+ dest_file = File.join(dest_folder, File.basename(entry_name))
318
+ else
319
+ dest_file = dest_folder
320
+ dest_folder = File.dirname(dest_file)
321
+ end
342
322
  FileUtils.mkdir_p(dest_folder)
343
- new_file = File.join(dest_folder, File.basename(entry_name))
344
323
  if link_target.nil?
345
- File.open(new_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
324
+ File.open(dest_file, 'wb'){ |output_stream| IO.copy_stream(entry_stream, output_stream)}
346
325
  else
347
- File.symlink(link_target, new_file)
326
+ File.symlink(link_target, dest_file)
348
327
  end
349
328
  end
350
- File.unlink(sdk_archive_path) rescue nil if delete_archive # Windows may give error
351
329
  return unless with_exe
352
330
  # ensure license file are generated so that ascp invocation for version works
353
331
  path(:aspera_license)
@@ -360,16 +338,18 @@ module Aspera
360
338
  Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
361
339
  end
362
340
  sdk_ascp_version = get_ascp_version(sdk_ascp_path)
363
- sdk_daemon_path = Products::Transferd.transferd_path
364
- Log.log.warn{"No #{sdk_daemon_path} in SDK archive"} unless File.exist?(sdk_daemon_path)
365
- Environment.restrict_file_access(sdk_daemon_path, mode: 0o755) if File.exist?(sdk_daemon_path)
366
- transferd_version = get_exe_version(sdk_daemon_path, 'version')
341
+ transferd_exe_path = Products::Transferd.transferd_path
342
+ Log.log.warn{"No #{transferd_exe_path} in SDK archive"} unless File.exist?(transferd_exe_path)
343
+ Environment.restrict_file_access(transferd_exe_path, mode: 0o755) if File.exist?(transferd_exe_path)
344
+ transferd_version = get_exe_version(transferd_exe_path, 'version')
367
345
  sdk_name = 'IBM Aspera Transfer SDK'
368
346
  sdk_version = transferd_version || sdk_ascp_version
369
347
  File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
370
348
  return sdk_name, sdk_version
371
349
  end
372
350
 
351
+ attr_accessor :transferd_urls
352
+
373
353
  private
374
354
 
375
355
  # policy for product selection
@@ -379,6 +359,7 @@ module Aspera
379
359
  @path_to_ascp = nil
380
360
  @sdk_dir = nil
381
361
  @found_products = nil
362
+ @transferd_urls = TRANSFER_SDK_LOCATION_URL
382
363
  end
383
364
 
384
365
  public
@@ -392,7 +373,7 @@ module Aspera
392
373
  # :run_root O only for Connect Client, location of http port file
393
374
  # :sub_bin O subfolder with executables, default : bin
394
375
  scan_locations = Products::Transferd.locations.concat(
395
- Products::Alpha.locations,
376
+ Products::Desktop.locations,
396
377
  Products::Connect.locations,
397
378
  Products::Other::LOCATION_ON_THIS_OS
398
379
  )
@@ -227,7 +227,7 @@ module Aspera
227
227
  # TODO: translate enhanced to capitalized ?
228
228
  data
229
229
  .keys
230
- .map{|k|"#{k.capitalize}: #{data[k]}"}
230
+ .map{ |k| "#{k.capitalize}: #{data[k]}"}
231
231
  .unshift(MGT_HEADER)
232
232
  .push('', '')
233
233
  .join("\n")
data/lib/aspera/assert.rb CHANGED
@@ -14,7 +14,7 @@ module Aspera
14
14
  message = 'assertion failed'
15
15
  info = yield if block_given?
16
16
  message = "#{message}: #{info}" if info
17
- message = "#{message}: #{caller.find{|call|!call.start_with?(__FILE__)}}"
17
+ message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
18
18
  raise exception_class, message
19
19
  end
20
20
 
@@ -42,9 +42,18 @@ module Aspera
42
42
  raise InternalError, "unreachable line reached: #{caller(2..2).first}"
43
43
  end
44
44
 
45
- # the value is not one of the expected values
45
+ # The value is not one of the expected values
46
+ # @param value the wrong value
47
+ # @param exception_class exception to raise
48
+ # @param block additional description in front
46
49
  def error_unexpected_value(value, exception_class: InternalError)
47
50
  raise exception_class, "#{block_given? ? "#{yield}: " : nil}unexpected value: #{value.inspect}"
48
51
  end
52
+
53
+ def require_method!(name)
54
+ define_method(name) do |*_args|
55
+ raise NotImplementedError, "#{self.class} must implement the #{name} method"
56
+ end
57
+ end
49
58
  end
50
59
  end
@@ -4,11 +4,11 @@ module Aspera
4
4
  module Cli
5
5
  # CLI base exception
6
6
  class Error < StandardError; end
7
-
8
7
  # raised when an unexpected argument is provided
9
8
  class BadArgument < Error; end
9
+ class NoSuchElement < Error; end
10
10
 
11
- class NoSuchIdentifier < Error
11
+ class BadIdentifier < Error
12
12
  def initialize(res_type, res_id)
13
13
  super("#{res_type} with identifier #{res_id} not found")
14
14
  end
@@ -21,6 +21,13 @@ module Aspera
21
21
  MARKER_END = ':'
22
22
  MARKER_IN_END = '@'
23
23
 
24
+ # special handlers stop processing of handlers on right
25
+ # extend includes processing of other handlers in itself
26
+ # val keeps the value intact
27
+ SPECIAL_HANDLERS = %i[extend val].freeze
28
+
29
+ private_constant :MARKER_START, :MARKER_END, :MARKER_IN_END, :SPECIAL_HANDLERS
30
+
24
31
  class << self
25
32
  # decode comma separated table text
26
33
  def decode_csvt(value)
@@ -49,25 +56,25 @@ module Aspera
49
56
  # base handlers
50
57
  # other handlers can be set using set_handler, e.g. `preset` is reader in config plugin
51
58
  @handlers = {
52
- val: lambda{|v|v},
53
- base64: lambda{|v|Base64.decode64(v)},
54
- csvt: lambda{|v|ExtendedValue.decode_csvt(v)},
55
- env: lambda{|v|ENV.fetch(v, nil)},
56
- file: lambda{|v|File.read(File.expand_path(v))},
57
- uri: lambda{|v|UriReader.read(v)},
58
- json: lambda{|v|JSON.parse(v)},
59
- lines: lambda{|v|v.split("\n")},
60
- list: lambda{|v|v[1..-1].split(v[0])},
61
- none: lambda{|v|ExtendedValue.assert_no_value(v, :none); nil}, # rubocop:disable Style/Semicolon
62
- path: lambda{|v|File.expand_path(v)},
63
- re: lambda{|v|Regexp.new(v, Regexp::MULTILINE)},
64
- ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
65
- secret: lambda{|v|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
66
- stdin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
67
- stdbin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
68
- yaml: lambda{|v|YAML.load(v)},
69
- zlib: lambda{|v|Zlib::Inflate.inflate(v)},
70
- extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
59
+ val: lambda{ |v| v},
60
+ base64: lambda{ |v| Base64.decode64(v)},
61
+ csvt: lambda{ |v| ExtendedValue.decode_csvt(v)},
62
+ env: lambda{ |v| ENV.fetch(v, nil)},
63
+ file: lambda{ |v| File.read(File.expand_path(v))},
64
+ uri: lambda{ |v| UriReader.read(v)},
65
+ json: lambda{ |v| JSON_parse(v)},
66
+ lines: lambda{ |v| v.split("\n")},
67
+ list: lambda{ |v| v[1..-1].split(v[0])},
68
+ none: lambda{ |v| ExtendedValue.assert_no_value(v, :none); nil}, # rubocop:disable Style/Semicolon
69
+ path: lambda{ |v| File.expand_path(v)},
70
+ re: lambda{ |v| Regexp.new(v, Regexp::MULTILINE)},
71
+ ruby: lambda{ |v| Environment.secure_eval(v, __FILE__, __LINE__)},
72
+ secret: lambda{ |v| prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
73
+ stdin: lambda{ |v| ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
74
+ stdbin: lambda{ |v| ExtendedValue.assert_no_value(v, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
75
+ yaml: lambda{ |v| YAML.load(v)},
76
+ zlib: lambda{ |v| Zlib::Inflate.inflate(v)},
77
+ extend: lambda{ |v| ExtendedValue.instance.evaluate_all(v)}
71
78
  }
72
79
  @default_decoder = nil
73
80
  end
@@ -77,6 +84,25 @@ module Aspera
77
84
  "#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
78
85
  end
79
86
 
87
+ # JSON Parser, with more information on error location
88
+ def JSON_parse(v)
89
+ JSON.parse(v)
90
+ rescue JSON::ParserError => e
91
+ m = /at line (\d+) column (\d+)/.match(e.message)
92
+ raise if m.nil?
93
+ line = m[1].to_i - 1
94
+ column = m[2].to_i - 1
95
+ lines = v.lines
96
+ raise if line >= lines.size
97
+ error_line = lines[line].chomp
98
+ context_col_beg = [column - 10, 0].max
99
+ context_col_end = [column + 10, error_line.length].min
100
+ context = error_line[context_col_beg...context_col_end]
101
+ cursor_pos = column - context_col_beg
102
+ pointer = ' ' * cursor_pos + '^'.blink
103
+ raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
104
+ end
105
+
80
106
  public
81
107
 
82
108
  def default_decoder=(value)
@@ -107,8 +133,7 @@ module Aspera
107
133
  handler = m[1].to_sym
108
134
  handlers_reversed.unshift(handler)
109
135
  value = m[2]
110
- # stop processing if handler is extend (it will be processed later)
111
- break if handler.eql?(:extend)
136
+ break if SPECIAL_HANDLERS.include?(handler)
112
137
  end
113
138
  Log.log.trace1{"evaluating: #{handlers_reversed}, value: #{value}"}
114
139
  handlers_reversed.each do |handler|