aspera-cli 4.22.0 → 4.24.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 +405 -364
- data/CONTRIBUTING.md +86 -29
- data/README.md +1856 -961
- data/bin/ascli +2 -1
- data/bin/asession +4 -4
- data/lib/aspera/agent/base.rb +4 -0
- data/lib/aspera/agent/connect.rb +20 -18
- data/lib/aspera/agent/desktop.rb +14 -11
- data/lib/aspera/agent/direct.rb +39 -31
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +9 -11
- data/lib/aspera/agent/transferd.rb +18 -11
- data/lib/aspera/api/aoc.rb +53 -43
- data/lib/aspera/api/cos_node.rb +7 -5
- data/lib/aspera/api/httpgw.rb +23 -22
- data/lib/aspera/api/node.rb +104 -22
- data/lib/aspera/ascmd.rb +35 -21
- data/lib/aspera/ascp/installation.rb +43 -43
- data/lib/aspera/ascp/management.rb +5 -4
- data/lib/aspera/assert.rb +55 -24
- data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
- data/lib/aspera/cli/error.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +28 -29
- data/lib/aspera/cli/formatter.rb +191 -168
- data/lib/aspera/cli/hints.rb +38 -4
- data/lib/aspera/cli/main.rb +139 -108
- data/lib/aspera/cli/manager.rb +51 -31
- data/lib/aspera/cli/plugin.rb +149 -78
- data/lib/aspera/cli/plugin_factory.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +217 -88
- data/lib/aspera/cli/plugins/ats.rb +15 -13
- data/lib/aspera/cli/plugins/config.rb +105 -227
- data/lib/aspera/cli/plugins/console.rb +49 -18
- data/lib/aspera/cli/plugins/cos.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +45 -51
- data/lib/aspera/cli/plugins/faspex5.rb +162 -163
- data/lib/aspera/cli/plugins/faspio.rb +6 -5
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +233 -247
- data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
- data/lib/aspera/cli/plugins/preview.rb +26 -29
- data/lib/aspera/cli/plugins/server.rb +29 -28
- data/lib/aspera/cli/plugins/shares.rb +40 -28
- data/lib/aspera/cli/sync_actions.rb +101 -80
- data/lib/aspera/cli/transfer_agent.rb +55 -58
- data/lib/aspera/cli/transfer_progress.rb +29 -20
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +160 -0
- data/lib/aspera/colors.rb +13 -8
- data/lib/aspera/command_line_builder.rb +28 -22
- data/lib/aspera/command_line_converter.rb +31 -0
- data/lib/aspera/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +144 -100
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +3 -2
- data/lib/aspera/hash_ext.rb +1 -1
- data/lib/aspera/id_generator.rb +10 -10
- data/lib/aspera/keychain/base.rb +18 -0
- data/lib/aspera/keychain/encrypted_hash.rb +6 -12
- data/lib/aspera/keychain/factory.rb +9 -3
- data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +70 -20
- data/lib/aspera/nagios.rb +5 -6
- data/lib/aspera/node_simulator.rb +12 -7
- data/lib/aspera/oauth/base.rb +6 -2
- data/lib/aspera/oauth/factory.rb +25 -18
- data/lib/aspera/oauth/jwt.rb +13 -1
- data/lib/aspera/oauth/url_json.rb +3 -3
- data/lib/aspera/oauth/web.rb +5 -3
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +43 -35
- data/lib/aspera/preview/generator.rb +26 -13
- data/lib/aspera/preview/terminal.rb +10 -7
- data/lib/aspera/preview/utils.rb +11 -9
- data/lib/aspera/products/connect.rb +2 -1
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +8 -6
- data/lib/aspera/proxy_auto_config.rb +1 -1
- data/lib/aspera/rest.rb +46 -28
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +46 -40
- data/lib/aspera/ssh.rb +14 -4
- data/lib/aspera/sync/args.schema.yaml +102 -0
- data/lib/aspera/sync/conf.schema.yaml +701 -0
- data/lib/aspera/sync/database.rb +83 -0
- data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
- data/lib/aspera/temp_file_manager.rb +4 -2
- data/lib/aspera/timer_limiter.rb +7 -5
- data/lib/aspera/transfer/error.rb +1 -1
- data/lib/aspera/transfer/error_info.rb +1 -2
- data/lib/aspera/transfer/faux_file.rb +11 -10
- data/lib/aspera/transfer/parameters.rb +6 -5
- data/lib/aspera/transfer/spec.rb +15 -1
- data/lib/aspera/transfer/spec.schema.yaml +316 -293
- data/lib/aspera/transfer/spec_doc.rb +34 -16
- data/lib/aspera/transfer/uri.rb +5 -5
- data/lib/aspera/uri_reader.rb +14 -10
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +15 -15
- metadata.gz.sig +0 -0
- data/examples/dascli +0 -30
- data/examples/get_proto_file.rb +0 -8
- data/examples/proxy.pac +0 -60
- data/lib/aspera/transfer/convert.rb +0 -29
- data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
- data/lib/aspera/transfer/sync_session.schema.yaml +0 -79
data/lib/aspera/rest.rb
CHANGED
@@ -6,12 +6,14 @@ require 'aspera/log'
|
|
6
6
|
require 'aspera/assert'
|
7
7
|
require 'aspera/oauth'
|
8
8
|
require 'aspera/hash_ext'
|
9
|
+
require 'aspera/timer_limiter'
|
9
10
|
require 'net/http'
|
10
11
|
require 'net/https'
|
11
12
|
require 'json'
|
12
13
|
require 'base64'
|
13
14
|
require 'singleton'
|
14
15
|
require 'securerandom'
|
16
|
+
require 'fileutils'
|
15
17
|
|
16
18
|
# Cancel method for HTTP
|
17
19
|
class Net::HTTP::Cancel < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
|
@@ -86,11 +88,11 @@ module Aspera
|
|
86
88
|
# Build URI from URL and parameters and check it is http or https
|
87
89
|
# encode array [] parameters
|
88
90
|
# @param query [Hash,Array]
|
89
|
-
def build_uri(url, query=nil)
|
91
|
+
def build_uri(url, query = nil)
|
90
92
|
uri = URI.parse(url)
|
91
93
|
Aspera.assert(%w[http https].include?(uri.scheme)){"REST endpoint shall be http/s not #{uri.scheme}"}
|
92
94
|
return uri if query.nil? || query.respond_to?(:empty?) && query.empty?
|
93
|
-
Log.
|
95
|
+
Log.dump(:query, query)
|
94
96
|
query_array = []
|
95
97
|
case query
|
96
98
|
when Hash
|
@@ -172,6 +174,10 @@ module Aspera
|
|
172
174
|
return result
|
173
175
|
end
|
174
176
|
|
177
|
+
# Parse a header string as returned by HTTP
|
178
|
+
# @param header [String] header string, e.g. "application/json; charset=utf-8"
|
179
|
+
# @return [Hash] parsed header with type and parameters
|
180
|
+
# {type: 'application/json', parameters: {charset: 'utf-8'}}
|
175
181
|
def parse_header(header)
|
176
182
|
type, *params = header.split(/;\s*/)
|
177
183
|
parameters = params.map do |param|
|
@@ -188,9 +194,7 @@ module Aspera
|
|
188
194
|
|
189
195
|
# create and start keep alive connection on demand
|
190
196
|
def http_session
|
191
|
-
if @http_session.nil?
|
192
|
-
@http_session = self.class.start_http_session(@base_url)
|
193
|
-
end
|
197
|
+
@http_session = self.class.start_http_session(@base_url) if @http_session.nil?
|
194
198
|
return @http_session
|
195
199
|
end
|
196
200
|
|
@@ -233,7 +237,7 @@ module Aspera
|
|
233
237
|
)
|
234
238
|
Aspera.assert_type(base_url, String)
|
235
239
|
# base url with no trailing slashes (note: string may be frozen)
|
236
|
-
@base_url = base_url.
|
240
|
+
@base_url = base_url.chomp('/')
|
237
241
|
# remove trailing port if it is 443 and scheme is https
|
238
242
|
@base_url = @base_url.gsub(/:443$/, '') if @base_url.start_with?('https://')
|
239
243
|
@base_url = @base_url.gsub(/:80$/, '') if @base_url.start_with?('http://')
|
@@ -260,7 +264,7 @@ module Aspera
|
|
260
264
|
if @oauth.nil?
|
261
265
|
Aspera.assert(@auth_params[:type].eql?(:oauth2)){'no OAuth defined'}
|
262
266
|
oauth_parameters = @auth_params.reject{ |k, _v| k.eql?(:type)}
|
263
|
-
Log.
|
267
|
+
Log.dump(:oauth_parameters, oauth_parameters)
|
264
268
|
@oauth = OAuth::Factory.instance.create(**oauth_parameters)
|
265
269
|
end
|
266
270
|
return @oauth
|
@@ -287,8 +291,8 @@ module Aspera
|
|
287
291
|
)
|
288
292
|
subpath = subpath.to_s if subpath.is_a?(Symbol)
|
289
293
|
subpath = '' if subpath.nil?
|
290
|
-
Log.log.debug{"#{operation} [#{subpath}]".red.bold.bg_green}
|
291
|
-
Log.
|
294
|
+
Log.log.debug{"call #{operation} [#{subpath}]".red.bold.bg_green}
|
295
|
+
Log.dump(:body, body)
|
292
296
|
Aspera.assert_type(subpath, String)
|
293
297
|
if headers.nil?
|
294
298
|
headers = @headers.clone
|
@@ -346,7 +350,7 @@ module Aspera
|
|
346
350
|
end
|
347
351
|
# :type = :basic
|
348
352
|
req.basic_auth(@auth_params[:username], @auth_params[:password]) if @auth_params[:type].eql?(:basic)
|
349
|
-
Log.
|
353
|
+
Log.dump(:req_body, req.body, level: :trace1)
|
350
354
|
# we try the call, and will retry on some error types
|
351
355
|
error_tries ||= 1 + RestParameters.instance.retry_max
|
352
356
|
# initialize with number of initial retries allowed, nil gives zero
|
@@ -358,20 +362,18 @@ module Aspera
|
|
358
362
|
http_session.request(req) do |response|
|
359
363
|
result[:http] = response
|
360
364
|
result_mime = self.class.parse_header(result[:http]['Content-Type'] || MIME_TEXT)[:type]
|
365
|
+
Log.log.debug{"response: code=#{result[:http].code}, mime=#{result_mime}, mime2= #{response['Content-Type']}"}
|
361
366
|
# JSON data needs to be parsed, in case it contains an error code
|
362
367
|
if !save_to_file.nil? &&
|
363
368
|
result[:http].code.to_s.start_with?('2') &&
|
364
|
-
!result[:http]['Content-Length'].nil? &&
|
365
369
|
!JSON_DECODE.include?(result_mime)
|
366
|
-
total_size = result[:http]['Content-Length']
|
370
|
+
total_size = result[:http]['Content-Length']&.to_i
|
367
371
|
Log.log.debug('before write file')
|
368
372
|
target_file = save_to_file
|
369
373
|
# override user's path to path in header
|
370
374
|
if !response['Content-Disposition'].nil?
|
371
375
|
disposition = self.class.parse_header(response['Content-Disposition'])
|
372
|
-
if disposition[:parameters].key?(:filename)
|
373
|
-
target_file = File.join(File.dirname(target_file), disposition[:parameters][:filename])
|
374
|
-
end
|
376
|
+
target_file = File.join(File.dirname(target_file), disposition[:parameters][:filename]) if disposition[:parameters].key?(:filename) && !disposition[:parameters][:filename].eql?('.')
|
375
377
|
end
|
376
378
|
# download with temp filename
|
377
379
|
target_file_tmp = "#{target_file}#{RestParameters.instance.download_partial_suffix}"
|
@@ -379,15 +381,18 @@ module Aspera
|
|
379
381
|
written_size = 0
|
380
382
|
session_id = SecureRandom.uuid.freeze
|
381
383
|
RestParameters.instance.progress_bar&.event(:session_start, session_id: session_id)
|
382
|
-
RestParameters.instance.progress_bar&.event(:session_size, session_id: session_id, info: total_size)
|
384
|
+
RestParameters.instance.progress_bar&.event(:session_size, session_id: session_id, info: total_size) if total_size
|
385
|
+
FileUtils.mkdir_p(File.dirname(target_file_tmp))
|
386
|
+
limiter = TimerLimiter.new(0.5)
|
383
387
|
File.open(target_file_tmp, 'wb') do |file|
|
384
388
|
result[:http].read_body do |fragment|
|
385
389
|
file.write(fragment)
|
386
390
|
written_size += fragment.length
|
387
|
-
RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size)
|
391
|
+
RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
388
392
|
end
|
389
393
|
end
|
390
|
-
RestParameters.instance.progress_bar&.event(:
|
394
|
+
RestParameters.instance.progress_bar&.event(:session_end, session_id: session_id)
|
395
|
+
RestParameters.instance.progress_bar&.event(:end)
|
391
396
|
# rename at the end
|
392
397
|
File.rename(target_file_tmp, target_file)
|
393
398
|
file_saved = true
|
@@ -400,12 +405,15 @@ module Aspera
|
|
400
405
|
case result_mime
|
401
406
|
when *JSON_DECODE
|
402
407
|
result[:data] = JSON.parse(result[:http].body) rescue result[:http].body
|
403
|
-
Log.
|
408
|
+
Log.dump(:result_data, result[:data])
|
404
409
|
else # when MIME_TEXT
|
405
410
|
result[:data] = result[:http].body
|
406
411
|
end
|
407
412
|
RestErrorAnalyzer.instance.raise_on_error(req, result)
|
408
|
-
|
413
|
+
unless file_saved || save_to_file.nil?
|
414
|
+
FileUtils.mkdir_p(File.dirname(save_to_file))
|
415
|
+
File.write(save_to_file, result[:http].body, binmode: true)
|
416
|
+
end
|
409
417
|
rescue RestCallError => e
|
410
418
|
do_retry = false
|
411
419
|
# AoC have some timeout , like Connect to platform.bss.asperasoft.com:443 ...
|
@@ -436,7 +444,7 @@ module Aspera
|
|
436
444
|
if e.response.is_a?(Net::HTTPRedirection) && tries_remain_redirect.positive?
|
437
445
|
tries_remain_redirect -= 1
|
438
446
|
current_uri = URI.parse(@base_url)
|
439
|
-
new_url = e.response['
|
447
|
+
new_url = e.response['Location']
|
440
448
|
# special case: relative redirect
|
441
449
|
if URI.parse(new_url).host.nil?
|
442
450
|
# we don't manage relative redirects with non-absolute path
|
@@ -444,14 +452,24 @@ module Aspera
|
|
444
452
|
new_url = "#{current_uri.scheme}://#{current_uri.host}#{new_url}"
|
445
453
|
end
|
446
454
|
# forwards the request to the new location
|
447
|
-
return self.class.new(
|
448
|
-
|
449
|
-
|
455
|
+
return self.class.new(
|
456
|
+
base_url: new_url,
|
457
|
+
redirect_max: tries_remain_redirect
|
458
|
+
).call(
|
459
|
+
operation: operation,
|
460
|
+
subpath: new_url.end_with?('/') ? '/' : nil,
|
461
|
+
query: query,
|
462
|
+
body: body,
|
463
|
+
content_type: content_type,
|
464
|
+
save_to_file: save_to_file,
|
465
|
+
return_error: return_error,
|
466
|
+
headers: headers
|
467
|
+
)
|
450
468
|
end
|
451
469
|
# raise exception if could not retry and not return error in result
|
452
470
|
raise e unless return_error
|
453
471
|
end
|
454
|
-
Log.log.debug{"result
|
472
|
+
Log.log.debug{"result=http:#{result[:http]}, data:#{result[:data].class}"}
|
455
473
|
return result
|
456
474
|
end
|
457
475
|
|
@@ -464,7 +482,7 @@ module Aspera
|
|
464
482
|
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON)[:data]
|
465
483
|
end
|
466
484
|
|
467
|
-
def read(subpath, query=nil)
|
485
|
+
def read(subpath, query = nil)
|
468
486
|
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query)[:data]
|
469
487
|
end
|
470
488
|
|
@@ -472,7 +490,7 @@ module Aspera
|
|
472
490
|
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON)[:data]
|
473
491
|
end
|
474
492
|
|
475
|
-
def delete(subpath, params=nil)
|
493
|
+
def delete(subpath, params = nil)
|
476
494
|
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params)[:data]
|
477
495
|
end
|
478
496
|
|
@@ -502,7 +520,7 @@ module Aspera
|
|
502
520
|
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
503
521
|
case name_matches.length
|
504
522
|
when 1 then return name_matches.first
|
505
|
-
when 0 then raise %Q(#{subpath}: multiple case insensitive partial match for: "#{search_name}": #{matching_items.map{ |i| i['name']}} but no case insensitive full match. Please be more specific or give exact name.)
|
523
|
+
when 0 then raise %Q(#{subpath}: multiple case insensitive partial match for: "#{search_name}": #{matching_items.map{ |i| i['name']}} but no case insensitive full match. Please be more specific or give exact name.)
|
506
524
|
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
507
525
|
end
|
508
526
|
end
|
data/lib/aspera/resumer.rb
CHANGED
data/lib/aspera/secret_hider.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
# cspell:ignore FILEPASS
|
4
4
|
require 'logger'
|
5
|
+
require 'singleton'
|
5
6
|
|
6
7
|
module Aspera
|
7
8
|
# remove secret from logs and output
|
8
9
|
class SecretHider
|
10
|
+
include Singleton
|
11
|
+
|
9
12
|
# configurable:
|
10
13
|
ADDITIONAL_KEYS_TO_HIDE = []
|
11
14
|
# display string for hidden secrets
|
@@ -34,56 +37,59 @@ module Aspera
|
|
34
37
|
/(?<begin>(?:#{HTTP_SECRETS.join('|')}): )[^\\]+(?<end>\\)/i
|
35
38
|
].freeze
|
36
39
|
private_constant :HIDDEN_PASSWORD, :ASCP_ENV_SECRETS, :KEY_SECRETS, :HTTP_SECRETS, :ALL_SECRETS, :KEY_FALSE_POSITIVES, :REGEX_LOG_REPLACES
|
37
|
-
|
38
|
-
class << self
|
39
|
-
attr_accessor :log_secrets
|
40
|
+
attr_accessor :log_secrets
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
42
|
+
# @return new log formatter that hides secrets
|
43
|
+
def log_formatter(original_formatter)
|
44
|
+
original_formatter ||= Logger::Formatter.new
|
45
|
+
# NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
|
46
|
+
return lambda do |severity, date_time, program_name, msg|
|
47
|
+
if msg.is_a?(String) && !@log_secrets
|
48
|
+
REGEX_LOG_REPLACES.each do |reg_ex|
|
49
|
+
msg = msg.gsub(reg_ex){"#{Regexp.last_match(:begin)}#{HIDDEN_PASSWORD}#{Regexp.last_match(:end)}"}
|
50
50
|
end
|
51
|
-
original_formatter.call(severity, date_time, program_name, msg)
|
52
51
|
end
|
52
|
+
original_formatter.call(severity, date_time, program_name, msg)
|
53
53
|
end
|
54
|
+
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
def hide_secrets_in_string(value)
|
57
|
+
return value.gsub(REGEX_LOG_REPLACES.first){"#{Regexp.last_match(:begin)}#{HIDDEN_PASSWORD}#{Regexp.last_match(:end)}"}
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
60
|
+
# @return true if the key denotes a secret
|
61
|
+
def secret?(keyword, value)
|
62
|
+
keyword = keyword.to_s if keyword.is_a?(Symbol)
|
63
|
+
# only Strings can be secrets, not booleans, or hash, arrays
|
64
|
+
return false unless keyword.is_a?(String) && value.is_a?(String)
|
65
|
+
# those are not secrets
|
66
|
+
return false if KEY_FALSE_POSITIVES.any?{ |f| f.match?(keyword)}
|
67
|
+
return true if ADDITIONAL_KEYS_TO_HIDE.include?(keyword)
|
68
|
+
# check if keyword (name) contains an element that designate it as a secret
|
69
|
+
ALL_SECRETS.any?{ |kw| keyword.include?(kw)}
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
72
|
+
# Hides recursively secrets in Hash or Array of Hash
|
73
|
+
def deep_remove_secret(obj)
|
74
|
+
case obj
|
75
|
+
when Array
|
76
|
+
obj.each{ |i| deep_remove_secret(i)}
|
77
|
+
when Hash
|
78
|
+
obj.each do |k, v|
|
79
|
+
if secret?(k, v)
|
80
|
+
obj[k] = HIDDEN_PASSWORD
|
81
|
+
elsif obj[k].is_a?(Hash)
|
82
|
+
deep_remove_secret(obj[k])
|
83
83
|
end
|
84
84
|
end
|
85
|
-
return obj
|
86
85
|
end
|
86
|
+
return obj
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
@log_secrets = false
|
87
93
|
end
|
88
94
|
end
|
89
95
|
end
|
data/lib/aspera/ssh.rb
CHANGED
@@ -8,6 +8,8 @@ module Aspera
|
|
8
8
|
# A simple wrapper around Net::SSH
|
9
9
|
# executes one command and get its result from stdout
|
10
10
|
class Ssh
|
11
|
+
class Error < Aspera::Error
|
12
|
+
end
|
11
13
|
class << self
|
12
14
|
def disable_ed25519_keys
|
13
15
|
Log.log.debug('Disabling SSH ed25519 user keys')
|
@@ -42,27 +44,35 @@ module Aspera
|
|
42
44
|
@ssh_options[:logger] = Log.log
|
43
45
|
end
|
44
46
|
|
45
|
-
|
47
|
+
# Anything on stderr raises an exception
|
48
|
+
def execute(cmd, input: nil, exception: false)
|
46
49
|
Aspera.assert_type(cmd, String)
|
47
50
|
Log.log.debug{"cmd=#{cmd}"}
|
48
51
|
response = []
|
52
|
+
error = []
|
49
53
|
Net::SSH.start(@host, @username, @ssh_options) do |session|
|
50
54
|
ssh_channel = session.open_channel do |channel|
|
51
55
|
# prepare stdout processing
|
52
56
|
channel.on_data{ |_chan, data| response.push(data)}
|
53
57
|
# prepare stderr processing, stderr if type = 1
|
54
58
|
channel.on_extended_data do |_chan, _type, data|
|
55
|
-
|
59
|
+
error.push(data)
|
60
|
+
end
|
61
|
+
channel.on_request('exit-status') do |_ch, data|
|
62
|
+
exit_code = data.read_long
|
63
|
+
next if exit_code.zero?
|
64
|
+
error_message = "#{cmd}: exit #{exit_code}, #{error.join.chomp}"
|
65
|
+
raise Error, error_message if exception
|
56
66
|
# Happens when windows user hasn't logged in and created home account.
|
57
67
|
error_message += "\nHint: home not created in Windows?" if data.include?('Could not chdir to home directory')
|
58
|
-
|
68
|
+
Log.log.debug(error_message)
|
59
69
|
end
|
60
70
|
# send command to SSH channel (execute) cspell: disable-next-line
|
61
71
|
channel.send('cexe'.reverse, cmd){ |_ch, _success| channel.send_data(input) unless input.nil?}
|
62
72
|
end
|
63
73
|
# wait for channel to finish (command exit)
|
64
74
|
ssh_channel.wait
|
65
|
-
# main
|
75
|
+
# main SSH session loop
|
66
76
|
session.loop
|
67
77
|
end
|
68
78
|
# response as single string
|
@@ -0,0 +1,102 @@
|
|
1
|
+
$schema: https://json-schema.org/draft/2020-12/schema
|
2
|
+
$id: https://github.com/IBM/aspera-cli/tree/main/lib/aspera/transfer/sync_instance.schema.yaml
|
3
|
+
$comment: >-
|
4
|
+
`x-` fields documented in `command_line_builder.rb`
|
5
|
+
This spec is specific to `ascli`.
|
6
|
+
The native async `conf` format is now preferred.
|
7
|
+
title: SyncInstanceSpec
|
8
|
+
description: Instance (global) and Session (per-sync) parameters for async.
|
9
|
+
type: object
|
10
|
+
properties:
|
11
|
+
alt_logdir:
|
12
|
+
type: string
|
13
|
+
watchd:
|
14
|
+
type: string
|
15
|
+
apply_local_docroot:
|
16
|
+
x-cli-switch: true
|
17
|
+
quiet:
|
18
|
+
x-cli-switch: true
|
19
|
+
ws_connect:
|
20
|
+
x-cli-switch: true
|
21
|
+
sessions:
|
22
|
+
type: array
|
23
|
+
items:
|
24
|
+
description: Session parameters for async.
|
25
|
+
type: object
|
26
|
+
properties:
|
27
|
+
name:
|
28
|
+
type: string
|
29
|
+
local_dir:
|
30
|
+
type: string
|
31
|
+
remote_dir:
|
32
|
+
type: string
|
33
|
+
local_db_dir:
|
34
|
+
type: string
|
35
|
+
remote_db_dir:
|
36
|
+
type: string
|
37
|
+
host:
|
38
|
+
type: string
|
39
|
+
x-ts-name: remote_host
|
40
|
+
user:
|
41
|
+
type: string
|
42
|
+
x-ts-name: remote_user
|
43
|
+
private_key_paths:
|
44
|
+
type: array
|
45
|
+
x-cli-option: "--private-key-path"
|
46
|
+
direction:
|
47
|
+
type: string
|
48
|
+
checksum:
|
49
|
+
type: string
|
50
|
+
tags:
|
51
|
+
type: object
|
52
|
+
x-cli-option: "--tags64"
|
53
|
+
x-cli-convert: json64
|
54
|
+
x-ts-name: tags
|
55
|
+
tcp_port:
|
56
|
+
type: integer
|
57
|
+
x-ts-name: ssh_port
|
58
|
+
rate_policy:
|
59
|
+
type: string
|
60
|
+
target_rate:
|
61
|
+
type: string
|
62
|
+
cooloff:
|
63
|
+
type: integer
|
64
|
+
pending_max:
|
65
|
+
type: integer
|
66
|
+
scan_intensity:
|
67
|
+
type: string
|
68
|
+
cipher:
|
69
|
+
type: string
|
70
|
+
x-cli-convert: remove_hyphen
|
71
|
+
x-ts-name: cipher
|
72
|
+
transfer_threads:
|
73
|
+
type: integer
|
74
|
+
preserve_time:
|
75
|
+
x-cli-switch: true
|
76
|
+
x-ts-name: preserve_times
|
77
|
+
preserve_access_time:
|
78
|
+
x-cli-switch: true
|
79
|
+
preserve_modification_time:
|
80
|
+
x-cli-switch: true
|
81
|
+
preserve_uid:
|
82
|
+
x-cli-switch: true
|
83
|
+
x-ts-name: preserve_file_owner_uid
|
84
|
+
preserve_gid:
|
85
|
+
x-cli-switch: true
|
86
|
+
x-ts-name: preserve_file_owner_gid
|
87
|
+
create_dir:
|
88
|
+
x-cli-switch: true
|
89
|
+
x-ts-name: create_dir
|
90
|
+
reset:
|
91
|
+
x-cli-switch: true
|
92
|
+
remote_password:
|
93
|
+
x-cli-envvar: ASPERA_SCP_PASS
|
94
|
+
x-ts-name: remote_password
|
95
|
+
cookie:
|
96
|
+
x-cli-envvar: ASPERA_SCP_COOKIE
|
97
|
+
x-ts-name: cookie
|
98
|
+
token:
|
99
|
+
x-cli-envvar: ASPERA_SCP_TOKEN
|
100
|
+
x-ts-name: token
|
101
|
+
license:
|
102
|
+
x-cli-envvar: ASPERA_SCP_LICENSE
|