aspera-cli 4.21.1 → 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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +4 -2
  3. data/CHANGELOG.md +22 -10
  4. data/CONTRIBUTING.md +68 -143
  5. data/README.md +175 -145
  6. data/bin/ascli +5 -14
  7. data/bin/asession +1 -3
  8. data/examples/get_proto_file.rb +4 -3
  9. data/examples/proxy.pac +20 -20
  10. data/lib/aspera/agent/base.rb +2 -0
  11. data/lib/aspera/agent/{alpha.rb → desktop.rb} +7 -7
  12. data/lib/aspera/agent/direct.rb +9 -1
  13. data/lib/aspera/agent/transferd.rb +24 -17
  14. data/lib/aspera/api/alee.rb +1 -1
  15. data/lib/aspera/api/cos_node.rb +1 -1
  16. data/lib/aspera/api/node.rb +4 -4
  17. data/lib/aspera/ascp/installation.rb +40 -58
  18. data/lib/aspera/cli/extended_value.rb +9 -3
  19. data/lib/aspera/cli/formatter.rb +8 -1
  20. data/lib/aspera/cli/info.rb +1 -0
  21. data/lib/aspera/cli/main.rb +2 -2
  22. data/lib/aspera/cli/manager.rb +3 -3
  23. data/lib/aspera/cli/plugins/aoc.rb +106 -55
  24. data/lib/aspera/cli/plugins/config.rb +10 -31
  25. data/lib/aspera/cli/plugins/faspex5.rb +8 -5
  26. data/lib/aspera/cli/plugins/node.rb +6 -3
  27. data/lib/aspera/cli/version.rb +1 -1
  28. data/lib/aspera/coverage.rb +5 -3
  29. data/lib/aspera/environment.rb +20 -11
  30. data/lib/aspera/faspex_postproc.rb +3 -5
  31. data/lib/aspera/hash_ext.rb +2 -12
  32. data/lib/aspera/oauth/base.rb +6 -1
  33. data/lib/aspera/preview/generator.rb +12 -9
  34. data/lib/aspera/preview/options.rb +2 -2
  35. data/lib/aspera/preview/terminal.rb +1 -1
  36. data/lib/aspera/preview/utils.rb +4 -4
  37. data/lib/aspera/products/connect.rb +34 -0
  38. data/lib/aspera/products/{alpha.rb → desktop.rb} +2 -2
  39. data/lib/aspera/products/transferd.rb +8 -1
  40. data/lib/aspera/rest.rb +4 -4
  41. data/lib/aspera/secret_hider.rb +7 -0
  42. data/lib/aspera/temp_file_manager.rb +5 -4
  43. data/lib/aspera/uri_reader.rb +18 -1
  44. data.tar.gz.sig +0 -0
  45. metadata +4 -174
  46. metadata.gz.sig +0 -0
  47. data/examples/build_exec +0 -74
  48. data/examples/build_exec_rubyc +0 -40
data/bin/ascli CHANGED
@@ -9,17 +9,8 @@ Encoding.default_internal = Encoding::UTF_8
9
9
  Encoding.default_external = Encoding::UTF_8
10
10
  $VERBOSE = old_verbose
11
11
 
12
- begin
13
- gem_lib_folder = File.join(File.dirname(File.dirname(File.realpath(__FILE__))), 'lib')
14
- Kernel.load(File.join(gem_lib_folder, 'aspera/coverage.rb'))
15
- begin
16
- require 'aspera/cli/main'
17
- rescue LoadError
18
- # if in development, add path toward gem
19
- $LOAD_PATH.unshift(gem_lib_folder)
20
- require 'aspera/cli/main'
21
- end
22
- require 'aspera/environment'
23
- Aspera::Environment.fix_home
24
- Aspera::Cli::Main.new(ARGV).process_command_line
25
- end
12
+ require 'aspera/coverage'
13
+ require 'aspera/environment'
14
+ require 'aspera/cli/main'
15
+ Aspera::Environment.fix_home
16
+ Aspera::Cli::Main.new(ARGV).process_command_line
data/bin/asession CHANGED
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- gem_lib_folder = File.join(File.dirname(File.dirname(File.realpath(__FILE__))), 'lib')
5
- Kernel.load(File.join(gem_lib_folder, 'aspera/coverage.rb'))
6
- $LOAD_PATH.unshift(gem_lib_folder)
4
+ require 'aspera/coverage'
7
5
  require 'aspera/agent/direct'
8
6
  require 'aspera/cli/extended_value'
9
7
  require 'aspera/products/transferd'
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Retrieve `transfer.proto` from the web
5
- $LOAD_PATH.unshift(File.join(File.dirname(File.dirname(File.realpath(__FILE__))), 'lib'))
6
4
  require 'aspera/ascp/installation'
7
- Aspera::Ascp::Installation.instance.install_sdk(folder: ARGV.first, backup: false, with_exe: false) {|name| '/' if name.end_with?('transferd.proto')}
5
+ require 'aspera/cli/transfer_progress'
6
+ Aspera::RestParameters.instance.progress_bar = Aspera::Cli::TransferProgress.new
7
+ # Retrieve `transfer.proto` from the web
8
+ Aspera::Ascp::Installation.instance.install_sdk(folder: ARGV.first, backup: false, with_exe: false) {|name| name.end_with?('.proto') ? '/' : nil }
data/examples/proxy.pac CHANGED
@@ -11,20 +11,20 @@ function FindProxyForURL(url, host) {
11
11
 
12
12
  /* Don't proxy local domains */
13
13
  if (dnsDomainIs(host, ".example1.com") || (host == "example1.com")
14
- || dnsDomainIs(host, ".example2.com") || (host == "example2.com")
15
- || dnsDomainIs(host, ".example3.com") || (host == "example3.com")) {
14
+ || dnsDomainIs(host, ".example2.com") || (host == "example2.com")
15
+ || dnsDomainIs(host, ".example3.com") || (host == "example3.com")) {
16
16
  return 'DIRECT';
17
17
  }
18
18
 
19
19
  /* Don't proxy Windows Update */
20
20
  if ((host == "download.microsoft.com")
21
- || (host == "ntservicepack.microsoft.com")
22
- || (host == "cdm.microsoft.com") || (host == "wustat.windows.com")
23
- || (host == "windowsupdate.microsoft.com")
24
- || (dnsDomainIs(host, ".windowsupdate.microsoft.com"))
25
- || (host == "update.microsoft.com")
26
- || (dnsDomainIs(host, ".update.microsoft.com"))
27
- || (dnsDomainIs(host, ".windowsupdate.com"))) {
21
+ || (host == "ntservicepack.microsoft.com")
22
+ || (host == "cdm.microsoft.com") || (host == "wustat.windows.com")
23
+ || (host == "windowsupdate.microsoft.com")
24
+ || (dnsDomainIs(host, ".windowsupdate.microsoft.com"))
25
+ || (host == "update.microsoft.com")
26
+ || (dnsDomainIs(host, ".update.microsoft.com"))
27
+ || (dnsDomainIs(host, ".windowsupdate.com"))) {
28
28
  return 'DIRECT';
29
29
  }
30
30
 
@@ -33,22 +33,22 @@ function FindProxyForURL(url, host) {
33
33
 
34
34
  /* Don't proxy private addresses (RFC 3330) */
35
35
  if (isInNet(hostIP, '0.0.0.0', '255.0.0.0')
36
- || isInNet(hostIP, '10.0.0.0', '255.0.0.0')
37
- || isInNet(hostIP, '127.0.0.0', '255.0.0.0')
38
- || isInNet(hostIP, '169.254.0.0', '255.255.0.0')
39
- || isInNet(hostIP, '172.16.0.0', '255.240.0.0')
40
- || isInNet(hostIP, '192.0.2.0', '255.255.255.0')
41
- || isInNet(hostIP, '192.88.99.0', '255.255.255.0')
42
- || isInNet(hostIP, '192.168.0.0', '255.255.0.0')
43
- || isInNet(hostIP, '198.18.0.0', '255.254.0.0')
44
- || isInNet(hostIP, '224.0.0.0', '240.0.0.0')
45
- || isInNet(hostIP, '240.0.0.0', '240.0.0.0')) {
36
+ || isInNet(hostIP, '10.0.0.0', '255.0.0.0')
37
+ || isInNet(hostIP, '127.0.0.0', '255.0.0.0')
38
+ || isInNet(hostIP, '169.254.0.0', '255.255.0.0')
39
+ || isInNet(hostIP, '172.16.0.0', '255.240.0.0')
40
+ || isInNet(hostIP, '192.0.2.0', '255.255.255.0')
41
+ || isInNet(hostIP, '192.88.99.0', '255.255.255.0')
42
+ || isInNet(hostIP, '192.168.0.0', '255.255.0.0')
43
+ || isInNet(hostIP, '198.18.0.0', '255.254.0.0')
44
+ || isInNet(hostIP, '224.0.0.0', '240.0.0.0')
45
+ || isInNet(hostIP, '240.0.0.0', '240.0.0.0')) {
46
46
  return 'DIRECT';
47
47
  }
48
48
  }
49
49
 
50
50
  if (url.substring(0, 5) == 'http:' || url.substring(0, 6) == 'https:'
51
- || url.substring(0, 4) == 'ftp:') {
51
+ || url.substring(0, 4) == 'ftp:') {
52
52
  return 'PROXY proxy.example.com:8080';
53
53
  }
54
54
 
@@ -7,6 +7,7 @@ module Aspera
7
7
  # Base class for transfer agents
8
8
  class Base
9
9
  RUBY_EXT = '.rb'
10
+ private_constant :RUBY_EXT
10
11
  class << self
11
12
  def factory_create(agent, options)
12
13
  # Aspera.assert_values(agent, agent_list)
@@ -15,6 +16,7 @@ module Aspera
15
16
  end
16
17
 
17
18
  # discover available agents
19
+ # @return [Array] list of symbols of agents
18
20
  def agent_list
19
21
  base_class = File.basename(__FILE__)
20
22
  Dir.entries(File.dirname(File.expand_path(__FILE__))).select do |file|
@@ -4,13 +4,13 @@ require 'aspera/agent/base'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/environment'
6
6
  require 'aspera/json_rpc'
7
- require 'aspera/products/alpha'
7
+ require 'aspera/products/desktop'
8
8
  require 'securerandom'
9
9
 
10
10
  module Aspera
11
11
  module Agent
12
- # Aspera Desktop Alpha Client
13
- class Alpha < Base
12
+ # Client: Aspera for Desktop
13
+ class Desktop < Base
14
14
  # try twice the main init url in sequence
15
15
  START_URIS = ['aspera://', 'aspera://', 'aspera://']
16
16
  # delay between each try to start the app
@@ -33,11 +33,11 @@ module Aspera
33
33
  rescue Errno::ECONNREFUSED => e
34
34
  start_url = START_URIS[method_index]
35
35
  method_index += 1
36
- raise StandardError, "Unable to start #{Products::Alpha::APP_NAME} #{method_index} times" if start_url.nil?
37
- Log.log.warn{"#{Products::Alpha::APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
36
+ raise StandardError, "Unable to start #{Products::Desktop::APP_NAME} #{method_index} times" if start_url.nil?
37
+ Log.log.warn{"#{Products::Desktop::APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
38
38
  if !Environment.open_uri_graphical(start_url)
39
39
  Environment.open_uri_graphical('https://www.ibm.com/aspera/connect/')
40
- raise StandardError, "#{Products::Alpha::APP_NAME} is not installed"
40
+ raise StandardError, "#{Products::Desktop::APP_NAME} is not installed"
41
41
  end
42
42
  sleep(SLEEP_SEC_BETWEEN_RETRY)
43
43
  retry
@@ -45,7 +45,7 @@ module Aspera
45
45
  end
46
46
 
47
47
  def aspera_client_api_url
48
- log_file = Products::Alpha.log_file
48
+ log_file = Products::Desktop.log_file
49
49
  url = 'http://127.0.0.1:33024'
50
50
  File.open(log_file, 'r') do |file|
51
51
  file.each_line do |line|
@@ -168,9 +168,12 @@ module Aspera
168
168
  ['-M', mgt_server_socket.local_address.ip_port.to_s]
169
169
  end
170
170
  command_arguments.concat(args)
171
+ # capture process stderr
172
+ stderr_r, stderr_w = IO.pipe
171
173
  # get location of command executable (ascp, async)
172
174
  command_path = Ascp::Installation.instance.path(name)
173
- command_pid = Environment.secure_spawn(env: env, exec: command_path, args: command_arguments)
175
+ command_pid = Environment.secure_spawn(env: env, exec: command_path, args: command_arguments, err: stderr_w)
176
+ stderr_w.close
174
177
  notify_progress(:pre_start, session_id: nil, info: "waiting for #{name} to start")
175
178
  # TODO: timeout does not work when Process.spawn is used... until process exits, then it works
176
179
  # So we use select to detect that anything happens on the socket (connection)
@@ -203,6 +206,10 @@ module Aspera
203
206
  Log.log.debug('management io closed')
204
207
  # check that last status was received before process exit
205
208
  last_event = processor.last_event
209
+ # process stderr of ascp
210
+ stderr_r&.each_line do |line|
211
+ Log.log.error(line.chomp)
212
+ end
206
213
  raise Transfer::Error, "internal: no management event (#{last_event.class})" unless last_event.is_a?(Hash)
207
214
  case last_event['Type']
208
215
  when 'ERROR'
@@ -226,6 +233,7 @@ module Aspera
226
233
  raise Transfer::Error, 'transfer interrupted by user'
227
234
  ensure
228
235
  mgt_server_socket.close
236
+ stderr_r&.close
229
237
  # if command was successfully started, check its status
230
238
  unless command_pid.nil?
231
239
  # "wait" for process to avoid zombie
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/environment'
3
4
  require 'aspera/agent/base'
4
5
  require 'aspera/products/transferd'
5
6
  require 'aspera/temp_file_manager'
@@ -17,20 +18,22 @@ module Aspera
17
18
  # port zero means select a random available high port
18
19
  AUTO_LOCAL_TCP_PORT = "#{PORT_SEP}0"
19
20
 
20
- # @param url [String] URL of the transfer manager daemon
21
- # @param external [Boolean] if true, expect that an external daemon is already running
22
- # @param keep [Boolean] if true, do not shutdown daemon on exit
23
- # @param base_options [Hash] base options
21
+ private_constant :LOCAL_SOCKET_ADDR, :PORT_SEP, :AUTO_LOCAL_TCP_PORT
22
+
23
+ # @param url [String] URL of the transfer manager daemon
24
+ # @param start [Bool] if false, expect that an external daemon is already running
25
+ # @param stop [Bool] if false, do not shutdown daemon on exit
26
+ # @param base [Hash] base class options
24
27
  def initialize(
25
- url: AUTO_LOCAL_TCP_PORT,
26
- external: false,
27
- keep: false,
28
- **base_options
28
+ url: AUTO_LOCAL_TCP_PORT,
29
+ start: true,
30
+ stop: true,
31
+ **base
29
32
  )
30
- super(**base_options)
31
- @keep = keep
33
+ super(**base)
34
+ @stop = stop
32
35
  is_local_auto_port = url.eql?(AUTO_LOCAL_TCP_PORT)
33
- raise 'Cannot use options `keep` or `external` with port zero' if is_local_auto_port && (@keep || external)
36
+ raise 'Cannot set options `stop` or `start` to false with port zero' if is_local_auto_port && (!@stop || !start)
34
37
  # keep PID for optional shutdown
35
38
  @daemon_pid = nil
36
39
  daemon_endpoint = url
@@ -44,14 +47,14 @@ module Aspera
44
47
  # Initiate actual connection
45
48
  get_info_response = @transfer_client.get_info(::Transferd::Api::InstanceInfoRequest.new)
46
49
  Log.log.debug{"Daemon info: #{get_info_response}"}
47
- Log.log.warn{'Attached to existing daemon'} unless @daemon_pid || external || @keep
50
+ Log.log.warn('Attached to existing daemon') unless @daemon_pid || !start || !@stop
48
51
  at_exit{shutdown}
49
52
  rescue GRPC::Unavailable => e
50
53
  # if transferd is external: do not start it, or other error
51
- raise if external || !e.message.include?('failed to connect')
54
+ raise if !start || !e.message.include?('failed to connect')
52
55
  # we already tried to start a daemon, but it failed
53
56
  Aspera.assert(@daemon_pid.nil?){"Daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_endpoint}}"}
54
- Log.log.warn('no daemon present, starting daemon...') if external
57
+ Log.log.warn('no daemon present, starting daemon...') if !start
55
58
  # transferd only supports local ip and port
56
59
  daemon_uri = URI.parse("ipv4://#{daemon_endpoint}")
57
60
  Aspera.assert(daemon_uri.scheme.eql?('ipv4')){"Invalid scheme daemon URI #{daemon_endpoint}"}
@@ -74,7 +77,11 @@ module Aspera
74
77
  log_stdout = "#{transferd_base_tmp}.out"
75
78
  log_stderr = "#{transferd_base_tmp}.err"
76
79
  File.write(conf_file, config.to_json)
77
- @daemon_pid = Process.spawn(Ascp::Installation.instance.path(:transferd), '--config', conf_file, out: log_stdout, err: log_stderr)
80
+ @daemon_pid = Environment.secure_spawn(
81
+ exec: Ascp::Installation.instance.path(:transferd),
82
+ args: ['--config', conf_file],
83
+ out: log_stdout,
84
+ err: log_stderr)
78
85
  begin
79
86
  # wait for process to initialize, max 2 seconds
80
87
  Timeout.timeout(2.0) do
@@ -86,7 +93,7 @@ module Aspera
86
93
  nil
87
94
  end
88
95
  Log.log.debug{"Daemon started with pid #{@daemon_pid}"}
89
- Process.detach(@daemon_pid) if @keep
96
+ Process.detach(@daemon_pid) unless @stop
90
97
  at_exit {shutdown}
91
98
  # update port for next connection attempt (if auto high port was requested)
92
99
  daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{Products::Transferd.daemon_port_from_log(log_stdout)}" if is_local_auto_port
@@ -146,7 +153,7 @@ module Aspera
146
153
  end
147
154
 
148
155
  def shutdown
149
- stop_daemon unless @keep
156
+ stop_daemon if @stop
150
157
  end
151
158
 
152
159
  def stop_daemon
@@ -7,7 +7,7 @@ module Aspera
7
7
  def initialize(entitlement_id, customer_id, api_domain: AoC::SAAS_DOMAIN_PROD, version: 'v1')
8
8
  super(
9
9
  base_url: "https://api.#{api_domain}/metering/#{version}",
10
- headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_token(entitlement_id, customer_id)}
10
+ headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_authorization(entitlement_id, customer_id)}
11
11
  )
12
12
  end
13
13
  end
@@ -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] = OAuth::Factory.bearer_extract(delegated_oauth.token)
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
@@ -280,7 +280,7 @@ module Aspera
280
280
  end
281
281
 
282
282
  def refreshed_transfer_token
283
- return oauth.token(refresh: true)
283
+ return oauth.authorization(refresh: true)
284
284
  end
285
285
 
286
286
  # @return part of transfer spec with transport parameters only
@@ -310,12 +310,12 @@ module Aspera
310
310
  when :basic
311
311
  ak_name = auth_params[:username]
312
312
  Aspera.assert(auth_params[:password]){'no secret in node object'}
313
- ak_token = Rest.basic_token(auth_params[:username], auth_params[:password])
313
+ ak_token = Rest.basic_authorization(auth_params[:username], auth_params[:password])
314
314
  when :oauth2
315
315
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
316
- # TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
316
+ # TODO: token_generation_lambda = lambda{|do_refresh|oauth.authorization(refresh: do_refresh)}
317
317
  # get bearer token, possibly use cache
318
- ak_token = oauth.token
318
+ ak_token = oauth.authorization
319
319
  when :none
320
320
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
321
321
  ak_token = params[:headers]['Authorization']
@@ -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,29 +47,21 @@ 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
 
@@ -250,14 +239,13 @@ module Aspera
250
239
  def ascp_info
251
240
  ascp_data = file_paths
252
241
  ascp_data.merge!(ascp_pvcl_info)
253
- ascp_data['sdk_locations'] = self.class.transfer_sdk_location_url
254
242
  ascp_data.merge!(ascp_ssl_info)
255
243
  return ascp_data
256
244
  end
257
245
 
258
246
  # @return the url for download of SDK archive for the given platform and version
259
247
  def sdk_url_for_platform(platform: nil, version: nil)
260
- locations = self.class.sdk_locations
248
+ locations = sdk_locations
261
249
  platform = Environment.architecture if platform.nil?
262
250
  locations = locations.select{|l|l['platform'].eql?(platform)}
263
251
  raise "No SDK for platform: #{platform}" if locations.empty?
@@ -298,56 +286,47 @@ module Aspera
298
286
  end
299
287
  end
300
288
 
301
- # download aspera SDK or use local file
302
- # extracts ascp binary for current system architecture
289
+ # Retrieves ascp binary for current system architecture from URL or file
303
290
  # @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
+ # @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
308
295
  # @return ascp version (from execution)
309
296
  def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
310
297
  url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
311
298
  folder = Products::Transferd.sdk_directory if folder.nil?
312
299
  subfolder_lambda = block
313
300
  if subfolder_lambda.nil?
301
+ # default files to extract directly to main folder if in selected source folders
314
302
  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
303
+ Products::Transferd::RUNTIME_FOLDERS.any?{|i|name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
320
304
  end
321
305
  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
306
  # rename old install
333
307
  if backup && !Dir.empty?(folder)
334
308
  Log.log.warn('Previous install exists, renaming folder.')
335
309
  File.rename(folder, "#{folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
336
310
  # TODO: delete old archives ?
337
311
  end
312
+ sdk_archive_path = UriReader.read_as_file(url)
338
313
  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)
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
342
323
  FileUtils.mkdir_p(dest_folder)
343
- new_file = File.join(dest_folder, File.basename(entry_name))
344
324
  if link_target.nil?
345
- File.open(new_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
325
+ File.open(dest_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
346
326
  else
347
- File.symlink(link_target, new_file)
327
+ File.symlink(link_target, dest_file)
348
328
  end
349
329
  end
350
- File.unlink(sdk_archive_path) rescue nil if delete_archive # Windows may give error
351
330
  return unless with_exe
352
331
  # ensure license file are generated so that ascp invocation for version works
353
332
  path(:aspera_license)
@@ -360,16 +339,18 @@ module Aspera
360
339
  Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
361
340
  end
362
341
  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')
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')
367
346
  sdk_name = 'IBM Aspera Transfer SDK'
368
347
  sdk_version = transferd_version || sdk_ascp_version
369
348
  File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
370
349
  return sdk_name, sdk_version
371
350
  end
372
351
 
352
+ attr_accessor :transferd_urls
353
+
373
354
  private
374
355
 
375
356
  # policy for product selection
@@ -379,6 +360,7 @@ module Aspera
379
360
  @path_to_ascp = nil
380
361
  @sdk_dir = nil
381
362
  @found_products = nil
363
+ @transferd_urls = TRANSFER_SDK_LOCATION_URL
382
364
  end
383
365
 
384
366
  public
@@ -392,7 +374,7 @@ module Aspera
392
374
  # :run_root O only for Connect Client, location of http port file
393
375
  # :sub_bin O subfolder with executables, default : bin
394
376
  scan_locations = Products::Transferd.locations.concat(
395
- Products::Alpha.locations,
377
+ Products::Desktop.locations,
396
378
  Products::Connect.locations,
397
379
  Products::Other::LOCATION_ON_THIS_OS
398
380
  )
@@ -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)
@@ -64,7 +71,7 @@ module Aspera
64
71
  ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
65
72
  secret: lambda{|v|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
66
73
  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
74
+ stdbin: lambda{|v|ExtendedValue.assert_no_value(v, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
68
75
  yaml: lambda{|v|YAML.load(v)},
69
76
  zlib: lambda{|v|Zlib::Inflate.inflate(v)},
70
77
  extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
@@ -107,8 +114,7 @@ module Aspera
107
114
  handler = m[1].to_sym
108
115
  handlers_reversed.unshift(handler)
109
116
  value = m[2]
110
- # stop processing if handler is extend (it will be processed later)
111
- break if handler.eql?(:extend)
117
+ break if SPECIAL_HANDLERS.include?(handler)
112
118
  end
113
119
  Log.log.trace1{"evaluating: #{handlers_reversed}, value: #{value}"}
114
120
  handlers_reversed.each do |handler|
@@ -217,6 +217,7 @@ module Aspera
217
217
  # info: additional info, displayed if level==info
218
218
  # error: always displayed on stderr
219
219
  def display_message(message_level, message)
220
+ message = SecretHider.hide_secrets_in_string(message) if message.is_a?(String) && hide_secrets?
220
221
  case message_level
221
222
  when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
222
223
  when :info then $stdout.puts(message) if @options[:display].eql?(:info)
@@ -238,8 +239,13 @@ module Aspera
238
239
  display_status(count_msg)
239
240
  end
240
241
 
242
+ def hide_secrets?
243
+ !@options[:show_secrets] && !@options[:display].eql?(:data)
244
+ end
245
+
246
+ # hides secrets in Hash or Array
241
247
  def hide_secrets(data)
242
- SecretHider.deep_remove_secret(data) unless @options[:show_secrets] || @options[:display].eql?(:data)
248
+ SecretHider.deep_remove_secret(data) if hide_secrets?
243
249
  end
244
250
 
245
251
  # this method displays the results, especially the table format
@@ -255,6 +261,7 @@ module Aspera
255
261
  Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
256
262
  display_item_count(data.length, total) unless total.nil?
257
263
  hide_secrets(data)
264
+ data = SecretHider.hide_secrets_in_string(data) if data.is_a?(String) && hide_secrets?
258
265
  case @options[:format]
259
266
  when :text
260
267
  display_message(:data, data.to_s)
@@ -10,6 +10,7 @@ module Aspera
10
10
  DOC_URL = "https://www.rubydoc.info/gems/#{GEM_NAME}"
11
11
  GEM_URL = "https://rubygems.org/gems/#{GEM_NAME}"
12
12
  SRC_URL = 'https://github.com/IBM/aspera-cli'
13
+ CONTAINER = 'docker.io/martinlaurent/ascli'
13
14
  # set this to warn in advance when minimum required ruby version will increase
14
15
  # see also required_ruby_version in gemspec file
15
16
  RUBY_FUTURE_MINIMUM_VERSION = '3.2'
@@ -64,8 +64,8 @@ module Aspera
64
64
  return Main.result_status(formatter.status_image(blob))
65
65
  end
66
66
 
67
- def result_single_object(data)
68
- return {type: :single_object, data: data}
67
+ def result_single_object(data, fields: nil)
68
+ return {type: :single_object, data: data, fields: fields}
69
69
  end
70
70
 
71
71
  def result_object_list(data, fields: nil, total: nil)
@@ -112,7 +112,7 @@ module Aspera
112
112
  value_list = check_array ? to_check : [to_check]
113
113
  value_list.each do |value|
114
114
  raise Cli::BadArgument,
115
- "#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of ' : ''}#{type_list.map(&:name).join(',')}" unless
115
+ "#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of: ' : ''}#{type_list.map(&:name).join(', ')}" unless
116
116
  type_list.any?{|t|value.is_a?(t)}
117
117
  end
118
118
  end
@@ -182,7 +182,7 @@ module Aspera
182
182
 
183
183
  # @param descr [String] description for help
184
184
  # @param mandatory [Boolean] if true, raise error if option not set
185
- # @param multiple [Boolean] if true, return remaining arguments
185
+ # @param multiple [Boolean] if true, return remaining arguments (Array)
186
186
  # @param accept_list [Array] list of allowed values (Symbol)
187
187
  # @param validation [Class, Array] accepted value type(s) or list of Symbols
188
188
  # @param aliases [Hash] map of aliases: key = alias, value = real value
@@ -204,7 +204,7 @@ module Aspera
204
204
  values = @unprocessed_cmd_line_arguments.shift(how_many)
205
205
  values = values.map{|v|evaluate_extended_value(v, allowed_types)}
206
206
  # if expecting list and only one arg of type array : it is the list
207
- values = values.first if values.length.eql?(1) && values.first.is_a?(Array)
207
+ values = values.first if multiple && values.length.eql?(1) && values.first.is_a?(Array)
208
208
  if accept_list
209
209
  allowed_values = [].concat(accept_list)
210
210
  allowed_values.concat(aliases.keys) unless aliases.nil?