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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +4 -2
- data/CHANGELOG.md +22 -10
- data/CONTRIBUTING.md +68 -143
- data/README.md +175 -145
- data/bin/ascli +5 -14
- data/bin/asession +1 -3
- data/examples/get_proto_file.rb +4 -3
- data/examples/proxy.pac +20 -20
- data/lib/aspera/agent/base.rb +2 -0
- data/lib/aspera/agent/{alpha.rb → desktop.rb} +7 -7
- data/lib/aspera/agent/direct.rb +9 -1
- data/lib/aspera/agent/transferd.rb +24 -17
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/node.rb +4 -4
- data/lib/aspera/ascp/installation.rb +40 -58
- data/lib/aspera/cli/extended_value.rb +9 -3
- data/lib/aspera/cli/formatter.rb +8 -1
- data/lib/aspera/cli/info.rb +1 -0
- data/lib/aspera/cli/main.rb +2 -2
- data/lib/aspera/cli/manager.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +106 -55
- data/lib/aspera/cli/plugins/config.rb +10 -31
- data/lib/aspera/cli/plugins/faspex5.rb +8 -5
- data/lib/aspera/cli/plugins/node.rb +6 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/coverage.rb +5 -3
- data/lib/aspera/environment.rb +20 -11
- data/lib/aspera/faspex_postproc.rb +3 -5
- data/lib/aspera/hash_ext.rb +2 -12
- data/lib/aspera/oauth/base.rb +6 -1
- data/lib/aspera/preview/generator.rb +12 -9
- data/lib/aspera/preview/options.rb +2 -2
- data/lib/aspera/preview/terminal.rb +1 -1
- data/lib/aspera/preview/utils.rb +4 -4
- data/lib/aspera/products/connect.rb +34 -0
- data/lib/aspera/products/{alpha.rb → desktop.rb} +2 -2
- data/lib/aspera/products/transferd.rb +8 -1
- data/lib/aspera/rest.rb +4 -4
- data/lib/aspera/secret_hider.rb +7 -0
- data/lib/aspera/temp_file_manager.rb +5 -4
- data/lib/aspera/uri_reader.rb +18 -1
- data.tar.gz.sig +0 -0
- metadata +4 -174
- metadata.gz.sig +0 -0
- data/examples/build_exec +0 -74
- 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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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'
|
data/examples/get_proto_file.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
51
|
+
|| url.substring(0, 4) == 'ftp:') {
|
52
52
|
return 'PROXY proxy.example.com:8080';
|
53
53
|
}
|
54
54
|
|
data/lib/aspera/agent/base.rb
CHANGED
@@ -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/
|
7
|
+
require 'aspera/products/desktop'
|
8
8
|
require 'securerandom'
|
9
9
|
|
10
10
|
module Aspera
|
11
11
|
module Agent
|
12
|
-
# Aspera Desktop
|
13
|
-
class
|
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::
|
37
|
-
Log.log.warn{"#{Products::
|
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::
|
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::
|
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|
|
data/lib/aspera/agent/direct.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
# @param
|
23
|
-
# @param
|
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:
|
26
|
-
|
27
|
-
|
28
|
-
**
|
28
|
+
url: AUTO_LOCAL_TCP_PORT,
|
29
|
+
start: true,
|
30
|
+
stop: true,
|
31
|
+
**base
|
29
32
|
)
|
30
|
-
super(**
|
31
|
-
@
|
33
|
+
super(**base)
|
34
|
+
@stop = stop
|
32
35
|
is_local_auto_port = url.eql?(AUTO_LOCAL_TCP_PORT)
|
33
|
-
raise 'Cannot
|
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
|
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
|
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
|
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 =
|
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)
|
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
|
156
|
+
stop_daemon if @stop
|
150
157
|
end
|
151
158
|
|
152
159
|
def stop_daemon
|
data/lib/aspera/api/alee.rb
CHANGED
@@ -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.
|
10
|
+
headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_authorization(entitlement_id, customer_id)}
|
11
11
|
)
|
12
12
|
end
|
13
13
|
end
|
data/lib/aspera/api/cos_node.rb
CHANGED
@@ -87,7 +87,7 @@ module Aspera
|
|
87
87
|
receiver_client_ids: 'aspera_ats'
|
88
88
|
)
|
89
89
|
# get delegated token to be placed in rest call header and in transfer tags
|
90
|
-
@storage_credentials['token'][TOKEN_FIELD] =
|
90
|
+
@storage_credentials['token'][TOKEN_FIELD] = delegated_oauth.token
|
91
91
|
@headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
|
92
92
|
end
|
93
93
|
end
|
data/lib/aspera/api/node.rb
CHANGED
@@ -280,7 +280,7 @@ module Aspera
|
|
280
280
|
end
|
281
281
|
|
282
282
|
def refreshed_transfer_token
|
283
|
-
return oauth.
|
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.
|
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.
|
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.
|
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/
|
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 :
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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 =
|
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
|
-
#
|
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]
|
305
|
-
# @param backup [Bool]
|
306
|
-
# @param with_exe [Bool]
|
307
|
-
# @param &block [Proc]
|
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
|
-
|
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
|
-
|
340
|
-
next if
|
341
|
-
dest_folder = File.join(folder,
|
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(
|
325
|
+
File.open(dest_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
|
346
326
|
else
|
347
|
-
File.symlink(link_target,
|
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
|
-
|
364
|
-
Log.log.warn{"No #{
|
365
|
-
Environment.restrict_file_access(
|
366
|
-
transferd_version = get_exe_version(
|
342
|
+
transferd_exe_path = Products::Transferd.transferd_path
|
343
|
+
Log.log.warn{"No #{transferd_exe_path} in SDK archive"} unless File.exist?(transferd_exe_path)
|
344
|
+
Environment.restrict_file_access(transferd_exe_path, mode: 0o755) if File.exist?(transferd_exe_path)
|
345
|
+
transferd_version = get_exe_version(transferd_exe_path, 'version')
|
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::
|
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, :
|
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
|
-
|
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|
|
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -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)
|
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)
|
data/lib/aspera/cli/info.rb
CHANGED
@@ -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'
|
data/lib/aspera/cli/main.rb
CHANGED
@@ -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)
|
data/lib/aspera/cli/manager.rb
CHANGED
@@ -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?
|