aspera-cli 4.18.1 → 4.19.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +13 -0
- data/CONTRIBUTING.md +5 -12
- data/README.md +60 -29
- data/examples/build_exec +85 -0
- data/lib/aspera/agent/base.rb +2 -0
- data/lib/aspera/agent/direct.rb +108 -104
- data/lib/aspera/api/aoc.rb +2 -2
- data/lib/aspera/api/httpgw.rb +91 -56
- data/lib/aspera/ascp/installation.rb +47 -32
- data/lib/aspera/ascp/management.rb +4 -1
- data/lib/aspera/ascp/products.rb +1 -7
- data/lib/aspera/cli/formatter.rb +24 -18
- data/lib/aspera/cli/manager.rb +10 -10
- data/lib/aspera/cli/plugin.rb +2 -2
- data/lib/aspera/cli/plugin_factory.rb +10 -1
- data/lib/aspera/cli/plugins/config.rb +15 -10
- data/lib/aspera/cli/plugins/node.rb +4 -3
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/plugins/shares.rb +11 -7
- data/lib/aspera/cli/sync_actions.rb +72 -31
- data/lib/aspera/cli/transfer_agent.rb +1 -0
- data/lib/aspera/cli/transfer_progress.rb +1 -1
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +43 -10
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -0
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -0
- data/lib/aspera/oauth/web.rb +5 -4
- data/lib/aspera/secret_hider.rb +3 -2
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +7 -5
- data/lib/aspera/transfer/parameters.rb +27 -19
- data/lib/aspera/transfer/spec.rb +8 -10
- data/lib/aspera/transfer/sync.rb +52 -47
- data/lib/aspera/web_auth.rb +0 -1
- data/lib/aspera/web_server_simple.rb +24 -13
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
- data/examples/rubyc +0 -24
data/lib/aspera/transfer/sync.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
require 'aspera/command_line_builder'
|
6
6
|
require 'aspera/ascp/installation'
|
7
|
+
require 'aspera/agent/direct'
|
7
8
|
require 'aspera/log'
|
8
9
|
require 'aspera/assert'
|
9
10
|
require 'json'
|
@@ -13,12 +14,12 @@ require 'English'
|
|
13
14
|
|
14
15
|
module Aspera
|
15
16
|
module Transfer
|
16
|
-
# builds command line arg for async
|
17
|
+
# builds command line arg for async and execute it
|
17
18
|
module Sync
|
18
19
|
# sync direction, default is push
|
19
20
|
DIRECTIONS = %i[push pull bidi].freeze
|
20
|
-
#
|
21
|
-
|
21
|
+
# JSON for async instance command line options
|
22
|
+
CMDLINE_PARAMS_INSTANCE =
|
22
23
|
{
|
23
24
|
'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
24
25
|
'watchd' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
@@ -28,7 +29,7 @@ module Aspera
|
|
28
29
|
}.freeze
|
29
30
|
|
30
31
|
# map sync session parameters to transfer spec: sync -> ts, true if same
|
31
|
-
|
32
|
+
CMDLINE_PARAMS_SESSION =
|
32
33
|
{
|
33
34
|
'name' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
34
35
|
'local_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
@@ -65,13 +66,13 @@ module Aspera
|
|
65
66
|
'license' => { cli: { type: :envvar, variable: 'ASPERA_SCP_LICENSE'}}
|
66
67
|
}.freeze
|
67
68
|
|
68
|
-
CommandLineBuilder.normalize_description(
|
69
|
-
CommandLineBuilder.normalize_description(
|
69
|
+
CommandLineBuilder.normalize_description(CMDLINE_PARAMS_INSTANCE)
|
70
|
+
CommandLineBuilder.normalize_description(CMDLINE_PARAMS_SESSION)
|
70
71
|
|
71
|
-
|
72
|
+
CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
|
72
73
|
|
73
74
|
# Translation of transfer spec parameters to async v2 API (asyncs)
|
74
|
-
|
75
|
+
TSPEC_TO_ASYNC_CONF = {
|
75
76
|
'remote_host' => 'remote.host',
|
76
77
|
'remote_user' => 'remote.user',
|
77
78
|
'remote_password' => 'remote.pass',
|
@@ -83,10 +84,9 @@ module Aspera
|
|
83
84
|
'tags' => 'tags'
|
84
85
|
}.freeze
|
85
86
|
|
86
|
-
ASYNC_EXECUTABLE = 'async'
|
87
87
|
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
88
88
|
|
89
|
-
private_constant :
|
89
|
+
private_constant :CMDLINE_PARAMS_INSTANCE, :CMDLINE_PARAMS_SESSION, :CMDLINE_PARAMS_KEYS, :TSPEC_TO_ASYNC_CONF, :ASYNC_ADMIN_EXECUTABLE
|
90
90
|
|
91
91
|
class << self
|
92
92
|
# Set remote_dir in sync parameters based on transfer spec
|
@@ -126,7 +126,7 @@ module Aspera
|
|
126
126
|
remote.delete('ws_port')
|
127
127
|
# add SSH bypass keys when authentication is token and no auth is provided
|
128
128
|
if remote.key?('token') && !remote.key?('pass')
|
129
|
-
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths)
|
129
|
+
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa))
|
130
130
|
end
|
131
131
|
end
|
132
132
|
return certificates_to_use
|
@@ -134,23 +134,27 @@ module Aspera
|
|
134
134
|
|
135
135
|
# @param sync_params [Hash] sync parameters, old or new format
|
136
136
|
# @param block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
|
137
|
-
def start(
|
137
|
+
def start(
|
138
|
+
sync_params,
|
139
|
+
&block
|
140
|
+
)
|
141
|
+
Log.log.debug{Log.dump(:sync_params_initial, sync_params)}
|
138
142
|
Aspera.assert_type(sync_params, Hash)
|
139
143
|
env_args = {
|
140
144
|
args: [],
|
141
145
|
env: {}
|
142
146
|
}
|
143
147
|
if sync_params.key?('local')
|
148
|
+
# async native JSON format (conf option)
|
149
|
+
Aspera.assert_type(sync_params['local'], Hash){'local'}
|
144
150
|
remote = sync_params['remote']
|
145
|
-
|
146
|
-
Aspera.assert_type(remote,
|
151
|
+
Aspera.assert_type(remote, Hash){'remote'}
|
152
|
+
Aspera.assert_type(remote['path'], String){'remote path'}
|
147
153
|
# get transfer spec if possible, and feed back to new structure
|
148
154
|
if block
|
149
155
|
transfer_spec = yield((sync_params['direction'] || 'push').to_sym, sync_params['local']['path'], remote['path'])
|
150
|
-
# async native JSON format
|
151
|
-
Aspera.assert_type(sync_params['local'], Hash)
|
152
156
|
# translate transfer spec to async parameters
|
153
|
-
|
157
|
+
TSPEC_TO_ASYNC_CONF.each do |ts_param, sy_path|
|
154
158
|
next unless transfer_spec.key?(ts_param)
|
155
159
|
sy_dig = sy_path.split('.')
|
156
160
|
param = sy_dig.pop
|
@@ -160,36 +164,41 @@ module Aspera
|
|
160
164
|
end
|
161
165
|
update_remote_dir(remote, 'path', transfer_spec)
|
162
166
|
end
|
163
|
-
remote['connect_mode'] ||=
|
167
|
+
remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
|
164
168
|
add_certificates = remote_certificates(remote)
|
165
169
|
if !add_certificates.empty?
|
166
170
|
remote['private_key_paths'] ||= []
|
167
171
|
remote['private_key_paths'].concat(add_certificates)
|
168
172
|
end
|
169
|
-
|
173
|
+
# '--exclusive-mgmt-port=12345', '--arg-err-path=-',
|
170
174
|
env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"]
|
175
|
+
Log.log.debug{Log.dump(:sync_params_enriched, sync_params)}
|
176
|
+
agent = Agent::Direct.new
|
177
|
+
agent.start_and_monitor_process(session: {}, name: :async, **env_args)
|
171
178
|
elsif sync_params.key?('sessions')
|
172
|
-
# ascli JSON format (
|
179
|
+
# ascli JSON format (cmdline)
|
180
|
+
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
181
|
+
sync_params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
|
182
|
+
Aspera.assert_type(sync_params['sessions'], Array)
|
183
|
+
Aspera.assert_type(sync_params['sessions'].first, Hash)
|
173
184
|
if block
|
174
185
|
sync_params['sessions'].each do |session|
|
186
|
+
Aspera.assert_type(session['local_dir'], String){'local_dir'}
|
187
|
+
Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
|
175
188
|
transfer_spec = yield((session['direction'] || 'push').to_sym, session['local_dir'], session['remote_dir'])
|
176
|
-
|
189
|
+
CMDLINE_PARAMS_SESSION.each do |async_param, behavior|
|
177
190
|
if behavior.key?(:ts)
|
178
191
|
tspec_param = behavior[:ts].is_a?(TrueClass) ? async_param : behavior[:ts].to_s
|
179
192
|
session[async_param] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
|
180
193
|
end
|
181
194
|
end
|
182
|
-
session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths if transfer_spec.key?('token')
|
195
|
+
session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
|
183
196
|
update_remote_dir(session, 'remote_dir', transfer_spec)
|
184
197
|
end
|
185
198
|
end
|
186
|
-
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
187
|
-
sync_params.keys.push('instance').uniq.sort.eql?(PARAMS_VX_KEYS)
|
188
|
-
Aspera.assert_type(sync_params['sessions'], Array)
|
189
|
-
Aspera.assert_type(sync_params['sessions'].first, Hash)
|
190
199
|
if sync_params.key?('instance')
|
191
200
|
Aspera.assert_type(sync_params['instance'], Hash)
|
192
|
-
instance_builder = CommandLineBuilder.new(sync_params['instance'],
|
201
|
+
instance_builder = CommandLineBuilder.new(sync_params['instance'], CMDLINE_PARAMS_INSTANCE)
|
193
202
|
instance_builder.process_params
|
194
203
|
instance_builder.add_env_args(env_args)
|
195
204
|
end
|
@@ -197,23 +206,19 @@ module Aspera
|
|
197
206
|
sync_params['sessions'].each do |session_params|
|
198
207
|
Aspera.assert_type(session_params, Hash)
|
199
208
|
Aspera.assert(session_params.key?('name')){'session must contain at least name'}
|
200
|
-
session_builder = CommandLineBuilder.new(session_params,
|
209
|
+
session_builder = CommandLineBuilder.new(session_params, CMDLINE_PARAMS_SESSION)
|
201
210
|
session_builder.process_params
|
202
211
|
session_builder.add_env_args(env_args)
|
203
212
|
end
|
213
|
+
async_exec = Ascp::Installation.instance.path(:async)
|
214
|
+
Process.wait(Environment.secure_spawn(env: env_args[:env], exec: async_exec, args: env_args[:args]))
|
215
|
+
if $CHILD_STATUS.exitstatus != 0
|
216
|
+
raise "Sync failed with exit: #{$CHILD_STATUS.exitstatus}"
|
217
|
+
end
|
204
218
|
else
|
205
219
|
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
206
220
|
end
|
207
|
-
|
208
|
-
Log.log.debug{"execute: #{env_args[:env].map{|k, v| "#{k}=\"#{v}\""}.join(' ')} \"#{ASYNC_EXECUTABLE}\" \"#{env_args[:args].join('" "')}\""}
|
209
|
-
res = system(env_args[:env], [ASYNC_EXECUTABLE, ASYNC_EXECUTABLE], *env_args[:args])
|
210
|
-
Log.log.debug{"result=#{res}"}
|
211
|
-
case res
|
212
|
-
when true then return nil
|
213
|
-
when false then raise "failed: #{$CHILD_STATUS}"
|
214
|
-
when nil then raise "not started: #{$CHILD_STATUS}"
|
215
|
-
else Aspera.error_unexpected_value(res)
|
216
|
-
end
|
221
|
+
return nil
|
217
222
|
end
|
218
223
|
|
219
224
|
def parse_status(stdout)
|
@@ -234,15 +239,15 @@ module Aspera
|
|
234
239
|
end
|
235
240
|
|
236
241
|
def admin_status(sync_params, session_name)
|
237
|
-
|
242
|
+
arguments = ['--quiet']
|
238
243
|
if sync_params.key?('local')
|
239
244
|
Aspera.assert(!sync_params['name'].nil?){'Missing session name'}
|
240
245
|
Aspera.assert(session_name.nil? || session_name.eql?(sync_params['name'])){'Session not found'}
|
241
|
-
|
246
|
+
arguments.push("--name=#{sync_params['name']}")
|
242
247
|
if sync_params.key?('local_db_dir')
|
243
|
-
|
248
|
+
arguments.push("--local-db-dir=#{sync_params['local_db_dir']}")
|
244
249
|
elsif sync_params.dig('local', 'path')
|
245
|
-
|
250
|
+
arguments.push("--local-dir=#{sync_params.dig('local', 'path')}")
|
246
251
|
else
|
247
252
|
raise 'Missing either local_db_dir or local.path'
|
248
253
|
end
|
@@ -250,19 +255,19 @@ module Aspera
|
|
250
255
|
session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{|s|s['name'].eql?(session_name)}
|
251
256
|
raise "Session #{session_name} not found in #{sync_params['sessions'].map{|s|s['name']}.join(',')}" if session.nil?
|
252
257
|
raise 'Missing session name' if session['name'].nil?
|
253
|
-
|
258
|
+
arguments.push("--name=#{session['name']}")
|
254
259
|
if session.key?('local_db_dir')
|
255
|
-
|
260
|
+
arguments.push("--local-db-dir=#{session['local_db_dir']}")
|
256
261
|
elsif session.key?('local_dir')
|
257
|
-
|
262
|
+
arguments.push("--local-dir=#{session['local_dir']}")
|
258
263
|
else
|
259
264
|
raise 'Missing either local_db_dir or local_dir'
|
260
265
|
end
|
261
266
|
else
|
262
267
|
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
263
268
|
end
|
264
|
-
|
265
|
-
stdout, stderr, status = Open3.capture3(*
|
269
|
+
Environment.secure_spawn(env: {}, exec: ASYNC_ADMIN_EXECUTABLE, args: arguments, log_only: true)
|
270
|
+
stdout, stderr, status = Open3.capture3(*[ASYNC_ADMIN_EXECUTABLE].concat(arguments))
|
266
271
|
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
267
272
|
Log.log.trace1{"stdout=#{stdout}"}
|
268
273
|
raise "Sync failed: #{status.exitstatus} : #{stderr}" unless status.success?
|
data/lib/aspera/web_auth.rb
CHANGED
@@ -4,12 +4,13 @@ require 'webrick'
|
|
4
4
|
require 'webrick/https'
|
5
5
|
require 'aspera/log'
|
6
6
|
require 'aspera/assert'
|
7
|
+
require 'aspera/hash_ext'
|
7
8
|
require 'openssl'
|
8
9
|
|
9
10
|
module Aspera
|
10
11
|
# Simple WEBrick server with HTTPS support
|
11
12
|
class WebServerSimple < WEBrick::HTTPServer
|
12
|
-
CERT_PARAMETERS = %i[key cert chain].freeze
|
13
|
+
CERT_PARAMETERS = %i[key cert chain pkcs12].freeze
|
13
14
|
GENERIC_ISSUER = '/C=FR/O=Test/OU=Test/CN=Test'
|
14
15
|
ONE_YEAR_SECONDS = 365 * 24 * 60 * 60
|
15
16
|
|
@@ -58,19 +59,29 @@ module Aspera
|
|
58
59
|
Aspera.assert_type(certificate, Hash)
|
59
60
|
certificate = certificate.symbolize_keys
|
60
61
|
raise "unexpected key in certificate config: only: #{CERT_PARAMETERS.join(', ')}" if certificate.keys.any?{|key|!CERT_PARAMETERS.include?(key)}
|
61
|
-
|
62
|
-
|
62
|
+
if certificate.key?(:pkcs12)
|
63
|
+
Log.log.debug('Using PKCS12 certificate')
|
64
|
+
raise 'pkcs12 requires a password' unless certificate.key?(:key)
|
65
|
+
pkcs12 = OpenSSL::PKCS12.new(File.read(certificate[:pkcs12]), certificate[:key])
|
66
|
+
webrick_options[:SSLCertificate] = pkcs12.certificate
|
67
|
+
webrick_options[:SSLPrivateKey] = pkcs12.key
|
68
|
+
webrick_options[:SSLExtraChainCert] = pkcs12.ca_certs
|
63
69
|
else
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
Log.log.debug('Using PEM certificate')
|
71
|
+
webrick_options[:SSLPrivateKey] = if certificate.key?(:key)
|
72
|
+
OpenSSL::PKey::RSA.new(File.read(certificate[:key]))
|
73
|
+
else
|
74
|
+
OpenSSL::PKey::RSA.new(4096)
|
75
|
+
end
|
76
|
+
if certificate.key?(:cert)
|
77
|
+
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(certificate[:cert]))
|
78
|
+
else
|
79
|
+
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new
|
80
|
+
self.class.fill_self_signed_cert(webrick_options[:SSLCertificate], webrick_options[:SSLPrivateKey])
|
81
|
+
end
|
82
|
+
if certificate.key?(:chain)
|
83
|
+
webrick_options[:SSLExtraChainCert] = [OpenSSL::X509::Certificate.new(File.read(certificate[:chain]))]
|
84
|
+
end
|
74
85
|
end
|
75
86
|
end
|
76
87
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspera-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Martin
|
@@ -37,7 +37,7 @@ cert_chain:
|
|
37
37
|
eTf9kxhVM40wGQOECVNA8UsEEZHD48eF+csUYZtAJOF5oxTI8UyV9T/o6CgO0c9/
|
38
38
|
Gzz+Qm5ULOUcPiJLjSpaiTrkiIVYiDGnqNSr6R1Hb1c=
|
39
39
|
-----END CERTIFICATE-----
|
40
|
-
date: 2024-
|
40
|
+
date: 2024-10-02 00:00:00.000000000 Z
|
41
41
|
dependencies:
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: blankslate
|
@@ -390,10 +390,10 @@ files:
|
|
390
390
|
- README.md
|
391
391
|
- bin/ascli
|
392
392
|
- bin/asession
|
393
|
+
- examples/build_exec
|
393
394
|
- examples/build_package.sh
|
394
395
|
- examples/dascli
|
395
396
|
- examples/proxy.pac
|
396
|
-
- examples/rubyc
|
397
397
|
- lib/aspera/agent/alpha.rb
|
398
398
|
- lib/aspera/agent/base.rb
|
399
399
|
- lib/aspera/agent/connect.rb
|
metadata.gz.sig
CHANGED
Binary file
|
data/examples/rubyc
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
# https://github.com/you54f/ruby-packer
|
3
|
-
# https://github.com/YOU54F/ruby-packer/releases
|
4
|
-
set -e
|
5
|
-
FOLDER="$(dirname $0)/../tmp"
|
6
|
-
RUBYC="$FOLDER/rubyc"
|
7
|
-
if test ! -e "$RUBYC"; then
|
8
|
-
mkdir -p "$FOLDER"
|
9
|
-
case $(uname -sm|tr ' ' -) in
|
10
|
-
Darwin-arm64)
|
11
|
-
curl -L https://github.com/YOU54F/ruby-packer/releases/download/rel-20230812/rubyc-Darwin-arm64.tar.gz | tar -xz -C "$FOLDER"
|
12
|
-
mv "$FOLDER/rubyc-Darwin-arm64" "$RUBYC"
|
13
|
-
;;
|
14
|
-
Linux-x86_64)
|
15
|
-
curl -L https://github.com/YOU54F/ruby-packer/releases/download/rel-20230812/rubyc-Linux-x86_64.tar.gz | tar -xz -C "$FOLDER"
|
16
|
-
mv "$FOLDER/rubyc-Linux-x86_64" "$RUBYC"
|
17
|
-
;;
|
18
|
-
*)
|
19
|
-
echo "This architecture is not supported." >&2
|
20
|
-
exit 1
|
21
|
-
;;
|
22
|
-
esac
|
23
|
-
fi
|
24
|
-
exec "$RUBYC" "$@"
|