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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +405 -364
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +1856 -961
  6. data/bin/ascli +2 -1
  7. data/bin/asession +4 -4
  8. data/lib/aspera/agent/base.rb +4 -0
  9. data/lib/aspera/agent/connect.rb +20 -18
  10. data/lib/aspera/agent/desktop.rb +14 -11
  11. data/lib/aspera/agent/direct.rb +39 -31
  12. data/lib/aspera/agent/httpgw.rb +2 -2
  13. data/lib/aspera/agent/node.rb +9 -11
  14. data/lib/aspera/agent/transferd.rb +18 -11
  15. data/lib/aspera/api/aoc.rb +53 -43
  16. data/lib/aspera/api/cos_node.rb +7 -5
  17. data/lib/aspera/api/httpgw.rb +23 -22
  18. data/lib/aspera/api/node.rb +104 -22
  19. data/lib/aspera/ascmd.rb +35 -21
  20. data/lib/aspera/ascp/installation.rb +43 -43
  21. data/lib/aspera/ascp/management.rb +5 -4
  22. data/lib/aspera/assert.rb +55 -24
  23. data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
  24. data/lib/aspera/cli/error.rb +1 -1
  25. data/lib/aspera/cli/extended_value.rb +28 -29
  26. data/lib/aspera/cli/formatter.rb +191 -168
  27. data/lib/aspera/cli/hints.rb +38 -4
  28. data/lib/aspera/cli/main.rb +139 -108
  29. data/lib/aspera/cli/manager.rb +51 -31
  30. data/lib/aspera/cli/plugin.rb +149 -78
  31. data/lib/aspera/cli/plugin_factory.rb +2 -2
  32. data/lib/aspera/cli/plugins/aoc.rb +217 -88
  33. data/lib/aspera/cli/plugins/ats.rb +15 -13
  34. data/lib/aspera/cli/plugins/config.rb +105 -227
  35. data/lib/aspera/cli/plugins/console.rb +49 -18
  36. data/lib/aspera/cli/plugins/cos.rb +4 -4
  37. data/lib/aspera/cli/plugins/faspex.rb +45 -51
  38. data/lib/aspera/cli/plugins/faspex5.rb +162 -163
  39. data/lib/aspera/cli/plugins/faspio.rb +6 -5
  40. data/lib/aspera/cli/plugins/httpgw.rb +2 -2
  41. data/lib/aspera/cli/plugins/node.rb +233 -247
  42. data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
  43. data/lib/aspera/cli/plugins/preview.rb +26 -29
  44. data/lib/aspera/cli/plugins/server.rb +29 -28
  45. data/lib/aspera/cli/plugins/shares.rb +40 -28
  46. data/lib/aspera/cli/sync_actions.rb +101 -80
  47. data/lib/aspera/cli/transfer_agent.rb +55 -58
  48. data/lib/aspera/cli/transfer_progress.rb +29 -20
  49. data/lib/aspera/cli/version.rb +1 -1
  50. data/lib/aspera/cli/wizard.rb +160 -0
  51. data/lib/aspera/colors.rb +13 -8
  52. data/lib/aspera/command_line_builder.rb +28 -22
  53. data/lib/aspera/command_line_converter.rb +31 -0
  54. data/lib/aspera/data_repository.rb +1 -0
  55. data/lib/aspera/environment.rb +144 -100
  56. data/lib/aspera/faspex_gw.rb +1 -1
  57. data/lib/aspera/faspex_postproc.rb +3 -2
  58. data/lib/aspera/hash_ext.rb +1 -1
  59. data/lib/aspera/id_generator.rb +10 -10
  60. data/lib/aspera/keychain/base.rb +18 -0
  61. data/lib/aspera/keychain/encrypted_hash.rb +6 -12
  62. data/lib/aspera/keychain/factory.rb +9 -3
  63. data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
  64. data/lib/aspera/keychain/macos_security.rb +13 -13
  65. data/lib/aspera/log.rb +70 -20
  66. data/lib/aspera/nagios.rb +5 -6
  67. data/lib/aspera/node_simulator.rb +12 -7
  68. data/lib/aspera/oauth/base.rb +6 -2
  69. data/lib/aspera/oauth/factory.rb +25 -18
  70. data/lib/aspera/oauth/jwt.rb +13 -1
  71. data/lib/aspera/oauth/url_json.rb +3 -3
  72. data/lib/aspera/oauth/web.rb +5 -3
  73. data/lib/aspera/persistency_folder.rb +2 -2
  74. data/lib/aspera/preview/file_types.rb +43 -35
  75. data/lib/aspera/preview/generator.rb +26 -13
  76. data/lib/aspera/preview/terminal.rb +10 -7
  77. data/lib/aspera/preview/utils.rb +11 -9
  78. data/lib/aspera/products/connect.rb +2 -1
  79. data/lib/aspera/products/desktop.rb +1 -1
  80. data/lib/aspera/products/other.rb +2 -2
  81. data/lib/aspera/products/transferd.rb +8 -6
  82. data/lib/aspera/proxy_auto_config.rb +1 -1
  83. data/lib/aspera/rest.rb +46 -28
  84. data/lib/aspera/rest_call_error.rb +1 -1
  85. data/lib/aspera/rest_error_analyzer.rb +1 -0
  86. data/lib/aspera/resumer.rb +1 -1
  87. data/lib/aspera/secret_hider.rb +46 -40
  88. data/lib/aspera/ssh.rb +14 -4
  89. data/lib/aspera/sync/args.schema.yaml +102 -0
  90. data/lib/aspera/sync/conf.schema.yaml +701 -0
  91. data/lib/aspera/sync/database.rb +83 -0
  92. data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
  93. data/lib/aspera/temp_file_manager.rb +4 -2
  94. data/lib/aspera/timer_limiter.rb +7 -5
  95. data/lib/aspera/transfer/error.rb +1 -1
  96. data/lib/aspera/transfer/error_info.rb +1 -2
  97. data/lib/aspera/transfer/faux_file.rb +11 -10
  98. data/lib/aspera/transfer/parameters.rb +6 -5
  99. data/lib/aspera/transfer/spec.rb +15 -1
  100. data/lib/aspera/transfer/spec.schema.yaml +316 -293
  101. data/lib/aspera/transfer/spec_doc.rb +34 -16
  102. data/lib/aspera/transfer/uri.rb +5 -5
  103. data/lib/aspera/uri_reader.rb +14 -10
  104. data/lib/aspera/web_auth.rb +2 -2
  105. data/lib/aspera/web_server_simple.rb +2 -2
  106. data.tar.gz.sig +0 -0
  107. metadata +15 -15
  108. metadata.gz.sig +0 -0
  109. data/examples/dascli +0 -30
  110. data/examples/get_proto_file.rb +0 -8
  111. data/examples/proxy.pac +0 -60
  112. data/lib/aspera/transfer/convert.rb +0 -29
  113. data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
  114. 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.log.debug{Log.dump('query', query)}
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.gsub(%r{/+$}, '')
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.log.debug{Log.dump('oauth parameters', oauth_parameters)}
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.log.debug{Log.dump(:body, body)}
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.log.trace1{Log.dump(:req_body, req.body)}
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'].to_i
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(:end, session_id: session_id)
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.log.debug{Log.dump('result_data', result[:data])}
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
- File.write(save_to_file, result[:http].body, binmode: true) unless file_saved || save_to_file.nil?
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['location']
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(base_url: new_url, redirect_max: tries_remain_redirect).call(
448
- operation: operation, query: query, body: body, content_type: content_type,
449
- save_to_file: save_to_file, return_error: return_error, headers: headers)
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=#{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.) # rubocop:disable Layout/LineLength
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
@@ -8,7 +8,7 @@ module Aspera
8
8
  # @param req HTTP Request object
9
9
  # @param resp HTTP Response object
10
10
  # @param msg Error message
11
- def initialize(msg, req=nil, resp=nil)
11
+ def initialize(msg, req = nil, resp = nil)
12
12
  @request = req
13
13
  @response = resp
14
14
  super(msg)
@@ -8,6 +8,7 @@ module Aspera
8
8
  # analyze error codes returned by REST calls and raise ruby exception
9
9
  class RestErrorAnalyzer
10
10
  include Singleton
11
+
11
12
  attr_accessor :log_file
12
13
 
13
14
  # the singleton object is registered with application specific handlers
@@ -16,7 +16,7 @@ module Aspera
16
16
  }.freeze
17
17
 
18
18
  # @param params see DEFAULTS
19
- def initialize(params=nil)
19
+ def initialize(params = nil)
20
20
  @parameters = DEFAULTS.dup
21
21
  if !params.nil?
22
22
  Aspera.assert_type(params, Hash)
@@ -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
- @log_secrets = false
38
- class << self
39
- attr_accessor :log_secrets
40
+ attr_accessor :log_secrets
40
41
 
41
- # @return new log formatter that hides secrets
42
- def log_formatter(original_formatter)
43
- original_formatter ||= Logger::Formatter.new
44
- # NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
45
- return lambda do |severity, date_time, program_name, msg|
46
- if msg.is_a?(String) && !@log_secrets
47
- REGEX_LOG_REPLACES.each do |reg_ex|
48
- msg = msg.gsub(reg_ex){"#{Regexp.last_match(:begin)}#{HIDDEN_PASSWORD}#{Regexp.last_match(:end)}"}
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
- def hide_secrets_in_string(value)
56
- return value.gsub(REGEX_LOG_REPLACES.first){"#{Regexp.last_match(:begin)}#{HIDDEN_PASSWORD}#{Regexp.last_match(:end)}"}
57
- end
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
- # @return true if the key denotes a secret
60
- def secret?(keyword, value)
61
- keyword = keyword.to_s if keyword.is_a?(Symbol)
62
- # only Strings can be secrets, not booleans, or hash, arrays
63
- return false unless keyword.is_a?(String) && value.is_a?(String)
64
- # those are not secrets
65
- return false if KEY_FALSE_POSITIVES.any?{ |f| f.match?(keyword)}
66
- return true if ADDITIONAL_KEYS_TO_HIDE.include?(keyword)
67
- # check if keyword (name) contains an element that designate it as a secret
68
- ALL_SECRETS.any?{ |kw| keyword.include?(kw)}
69
- end
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
- # Hides recursively secrets in Hash or Array of Hash
72
- def deep_remove_secret(obj)
73
- case obj
74
- when Array
75
- obj.each{ |i| deep_remove_secret(i)}
76
- when Hash
77
- obj.each do |k, v|
78
- if secret?(k, v)
79
- obj[k] = HIDDEN_PASSWORD
80
- elsif obj[k].is_a?(Hash)
81
- deep_remove_secret(obj[k])
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
- def execute(cmd, input=nil)
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
- error_message = "#{cmd}: [#{data.chomp}]"
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
- raise error_message
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 ssh session loop
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