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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +13 -0
  4. data/CONTRIBUTING.md +5 -12
  5. data/README.md +60 -29
  6. data/examples/build_exec +85 -0
  7. data/lib/aspera/agent/base.rb +2 -0
  8. data/lib/aspera/agent/direct.rb +108 -104
  9. data/lib/aspera/api/aoc.rb +2 -2
  10. data/lib/aspera/api/httpgw.rb +91 -56
  11. data/lib/aspera/ascp/installation.rb +47 -32
  12. data/lib/aspera/ascp/management.rb +4 -1
  13. data/lib/aspera/ascp/products.rb +1 -7
  14. data/lib/aspera/cli/formatter.rb +24 -18
  15. data/lib/aspera/cli/manager.rb +10 -10
  16. data/lib/aspera/cli/plugin.rb +2 -2
  17. data/lib/aspera/cli/plugin_factory.rb +10 -1
  18. data/lib/aspera/cli/plugins/config.rb +15 -10
  19. data/lib/aspera/cli/plugins/node.rb +4 -3
  20. data/lib/aspera/cli/plugins/server.rb +1 -1
  21. data/lib/aspera/cli/plugins/shares.rb +11 -7
  22. data/lib/aspera/cli/sync_actions.rb +72 -31
  23. data/lib/aspera/cli/transfer_agent.rb +1 -0
  24. data/lib/aspera/cli/transfer_progress.rb +1 -1
  25. data/lib/aspera/cli/version.rb +1 -1
  26. data/lib/aspera/environment.rb +43 -10
  27. data/lib/aspera/faspex_gw.rb +1 -1
  28. data/lib/aspera/keychain/encrypted_hash.rb +2 -0
  29. data/lib/aspera/log.rb +1 -0
  30. data/lib/aspera/node_simulator.rb +1 -1
  31. data/lib/aspera/oauth/jwt.rb +1 -1
  32. data/lib/aspera/oauth/url_json.rb +2 -0
  33. data/lib/aspera/oauth/web.rb +5 -4
  34. data/lib/aspera/secret_hider.rb +3 -2
  35. data/lib/aspera/ssh.rb +1 -1
  36. data/lib/aspera/transfer/faux_file.rb +7 -5
  37. data/lib/aspera/transfer/parameters.rb +27 -19
  38. data/lib/aspera/transfer/spec.rb +8 -10
  39. data/lib/aspera/transfer/sync.rb +52 -47
  40. data/lib/aspera/web_auth.rb +0 -1
  41. data/lib/aspera/web_server_simple.rb +24 -13
  42. data.tar.gz.sig +0 -0
  43. metadata +3 -3
  44. metadata.gz.sig +0 -0
  45. data/examples/rubyc +0 -24
@@ -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
- # custom JSON for async instance command line options
21
- PARAMS_VX_INSTANCE =
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
- PARAMS_VX_SESSION =
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(PARAMS_VX_INSTANCE)
69
- CommandLineBuilder.normalize_description(PARAMS_VX_SESSION)
69
+ CommandLineBuilder.normalize_description(CMDLINE_PARAMS_INSTANCE)
70
+ CommandLineBuilder.normalize_description(CMDLINE_PARAMS_SESSION)
70
71
 
71
- PARAMS_VX_KEYS = %w[instance sessions].freeze
72
+ CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
72
73
 
73
74
  # Translation of transfer spec parameters to async v2 API (asyncs)
74
- TS_TO_PARAMS_V2 = {
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 :PARAMS_VX_INSTANCE, :PARAMS_VX_SESSION, :PARAMS_VX_KEYS, :TS_TO_PARAMS_V2, :ASYNC_EXECUTABLE, :ASYNC_ADMIN_EXECUTABLE
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(sync_params, &block)
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
- # async native JSON format (v2)
146
- Aspera.assert_type(remote, Hash)
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
- TS_TO_PARAMS_V2.each do |ts_param, sy_path|
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'] ||= remote.key?('ws_port') ? 'ws' : 'ssh'
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
- Aspera.assert_type(sync_params, Hash)
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 (v1)
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
- PARAMS_VX_SESSION.each do |async_param, behavior|
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'], PARAMS_VX_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, PARAMS_VX_SESSION)
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
- Log.log.debug{Log.dump(:sync_params, sync_params)}
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
- command_line = [ASYNC_ADMIN_EXECUTABLE, '--quiet']
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
- command_line.push("--name=#{sync_params['name']}")
246
+ arguments.push("--name=#{sync_params['name']}")
242
247
  if sync_params.key?('local_db_dir')
243
- command_line.push("--local-db-dir=#{sync_params['local_db_dir']}")
248
+ arguments.push("--local-db-dir=#{sync_params['local_db_dir']}")
244
249
  elsif sync_params.dig('local', 'path')
245
- command_line.push("--local-dir=#{sync_params.dig('local', 'path')}")
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
- command_line.push("--name=#{session['name']}")
258
+ arguments.push("--name=#{session['name']}")
254
259
  if session.key?('local_db_dir')
255
- command_line.push("--local-db-dir=#{session['local_db_dir']}")
260
+ arguments.push("--local-db-dir=#{session['local_db_dir']}")
256
261
  elsif session.key?('local_dir')
257
- command_line.push("--local-dir=#{session['local_dir']}")
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
- Log.log.debug{"execute: #{command_line.join(' ')}"}
265
- stdout, stderr, status = Open3.capture3(*command_line)
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?
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/web_server_simple'
4
- require 'stringio'
5
4
 
6
5
  module Aspera
7
6
  # servlet called on callback: it records the callback request
@@ -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
- webrick_options[:SSLPrivateKey] = if certificate.key?(:key)
62
- OpenSSL::PKey::RSA.new(File.read(certificate[:key]))
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
- OpenSSL::PKey::RSA.new(4096)
65
- end
66
- if certificate.key?(:cert)
67
- webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(certificate[:cert]))
68
- else
69
- webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new
70
- self.class.fill_self_signed_cert(webrick_options[:SSLCertificate], webrick_options[:SSLPrivateKey])
71
- end
72
- if certificate.key?(:chain)
73
- webrick_options[:SSLExtraChainCert] = [OpenSSL::X509::Certificate.new(File.read(certificate[:chain]))]
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.18.1
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-08-21 00:00:00.000000000 Z
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" "$@"