aspera-cli 4.10.0 → 4.12.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 (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +19 -0
  4. data/CHANGELOG.md +528 -0
  5. data/CONTRIBUTING.md +143 -0
  6. data/README.md +977 -589
  7. data/bin/ascli +4 -4
  8. data/bin/asession +12 -12
  9. data/docs/test_env.conf +29 -19
  10. data/examples/aoc.rb +6 -6
  11. data/examples/dascli +18 -16
  12. data/examples/faspex4.rb +15 -15
  13. data/examples/node.rb +12 -12
  14. data/examples/proxy.pac +2 -2
  15. data/examples/server.rb +12 -12
  16. data/lib/aspera/aoc.rb +344 -272
  17. data/lib/aspera/ascmd.rb +56 -54
  18. data/lib/aspera/ats_api.rb +4 -4
  19. data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
  20. data/lib/aspera/cli/extended_value.rb +9 -9
  21. data/lib/aspera/cli/{formater.rb → formatter.rb} +69 -69
  22. data/lib/aspera/cli/listener/line_dump.rb +1 -1
  23. data/lib/aspera/cli/listener/logger.rb +1 -1
  24. data/lib/aspera/cli/listener/progress.rb +5 -6
  25. data/lib/aspera/cli/listener/progress_multi.rb +16 -21
  26. data/lib/aspera/cli/main.rb +72 -73
  27. data/lib/aspera/cli/manager.rb +112 -112
  28. data/lib/aspera/cli/plugin.rb +68 -48
  29. data/lib/aspera/cli/plugins/alee.rb +4 -4
  30. data/lib/aspera/cli/plugins/aoc.rb +322 -720
  31. data/lib/aspera/cli/plugins/ats.rb +50 -52
  32. data/lib/aspera/cli/plugins/bss.rb +10 -10
  33. data/lib/aspera/cli/plugins/config.rb +514 -410
  34. data/lib/aspera/cli/plugins/console.rb +12 -12
  35. data/lib/aspera/cli/plugins/cos.rb +18 -20
  36. data/lib/aspera/cli/plugins/faspex.rb +134 -136
  37. data/lib/aspera/cli/plugins/faspex5.rb +235 -70
  38. data/lib/aspera/cli/plugins/node.rb +378 -309
  39. data/lib/aspera/cli/plugins/orchestrator.rb +52 -49
  40. data/lib/aspera/cli/plugins/preview.rb +129 -120
  41. data/lib/aspera/cli/plugins/server.rb +137 -83
  42. data/lib/aspera/cli/plugins/shares.rb +77 -52
  43. data/lib/aspera/cli/plugins/sync.rb +13 -33
  44. data/lib/aspera/cli/transfer_agent.rb +61 -61
  45. data/lib/aspera/cli/version.rb +2 -1
  46. data/lib/aspera/colors.rb +3 -3
  47. data/lib/aspera/command_line_builder.rb +78 -74
  48. data/lib/aspera/cos_node.rb +31 -29
  49. data/lib/aspera/data_repository.rb +1 -1
  50. data/lib/aspera/environment.rb +30 -28
  51. data/lib/aspera/fasp/agent_base.rb +17 -15
  52. data/lib/aspera/fasp/agent_connect.rb +34 -32
  53. data/lib/aspera/fasp/agent_direct.rb +70 -73
  54. data/lib/aspera/fasp/agent_httpgw.rb +79 -74
  55. data/lib/aspera/fasp/agent_node.rb +26 -26
  56. data/lib/aspera/fasp/agent_trsdk.rb +20 -20
  57. data/lib/aspera/fasp/error.rb +3 -2
  58. data/lib/aspera/fasp/error_info.rb +11 -8
  59. data/lib/aspera/fasp/installation.rb +80 -80
  60. data/lib/aspera/fasp/listener.rb +2 -2
  61. data/lib/aspera/fasp/parameters.rb +103 -92
  62. data/lib/aspera/fasp/parameters.yaml +313 -214
  63. data/lib/aspera/fasp/resume_policy.rb +10 -10
  64. data/lib/aspera/fasp/transfer_spec.rb +22 -2
  65. data/lib/aspera/fasp/uri.rb +7 -7
  66. data/lib/aspera/faspex_gw.rb +80 -159
  67. data/lib/aspera/faspex_postproc.rb +77 -0
  68. data/lib/aspera/hash_ext.rb +3 -3
  69. data/lib/aspera/id_generator.rb +5 -5
  70. data/lib/aspera/keychain/encrypted_hash.rb +23 -28
  71. data/lib/aspera/keychain/macos_security.rb +21 -20
  72. data/lib/aspera/log.rb +13 -13
  73. data/lib/aspera/nagios.rb +24 -23
  74. data/lib/aspera/node.rb +217 -38
  75. data/lib/aspera/oauth.rb +78 -74
  76. data/lib/aspera/open_application.rb +19 -11
  77. data/lib/aspera/persistency_action_once.rb +4 -4
  78. data/lib/aspera/persistency_folder.rb +13 -13
  79. data/lib/aspera/preview/file_types.rb +8 -8
  80. data/lib/aspera/preview/generator.rb +67 -67
  81. data/lib/aspera/preview/utils.rb +27 -27
  82. data/lib/aspera/proxy_auto_config.js +63 -63
  83. data/lib/aspera/proxy_auto_config.rb +19 -19
  84. data/lib/aspera/rest.rb +65 -67
  85. data/lib/aspera/rest_call_error.rb +2 -1
  86. data/lib/aspera/rest_error_analyzer.rb +22 -21
  87. data/lib/aspera/rest_errors_aspera.rb +16 -16
  88. data/lib/aspera/secret_hider.rb +17 -14
  89. data/lib/aspera/ssh.rb +15 -14
  90. data/lib/aspera/sync.rb +177 -62
  91. data/lib/aspera/temp_file_manager.rb +2 -2
  92. data/lib/aspera/uri_reader.rb +4 -4
  93. data/lib/aspera/web_auth.rb +13 -64
  94. data/lib/aspera/web_server_simple.rb +76 -0
  95. data.tar.gz.sig +0 -0
  96. metadata +11 -6
  97. metadata.gz.sig +0 -0
@@ -7,16 +7,17 @@ module Aspera
7
7
  class SecretHider
8
8
  # display string for hidden secrets
9
9
  HIDDEN_PASSWORD = '🔑'
10
+ # env vars for ascp with secrets
11
+ ASCP_ENV_SECRETS = %w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS ASPERA_SCP_TOKEN].freeze
10
12
  # keys in hash that contain secrets
11
- ASCP_SECRETS=%w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS].freeze
12
- KEY_SECRETS =%w[password secret private_key passphrase].freeze
13
- ALL_SECRETS =[ASCP_SECRETS,KEY_SECRETS].flatten.freeze
14
- # regex that define namec captures :begin and :end
15
- REGEX_LOG_REPLACES=[
13
+ KEY_SECRETS = %w[password secret passphrase _key apikey crn token].freeze
14
+ ALL_SECRETS = [ASCP_ENV_SECRETS, KEY_SECRETS].flatten.freeze
15
+ # regex that define named captures :begin and :end
16
+ REGEX_LOG_REPLACES = [
16
17
  # CLI manager get/set options
17
18
  /(?<begin>[sg]et (#{KEY_SECRETS.join('|')})=).*(?<end>)/,
18
19
  # env var ascp exec
19
- /(?<begin> (#{ASCP_SECRETS.join('|')})=)[^ ]*(?<end> )/,
20
+ /(?<begin> (#{ASCP_ENV_SECRETS.join('|')})=)(\\.|[^ ])*(?<end> )/,
20
21
  # rendered JSON
21
22
  /(?<begin>["':][^"]*(#{ALL_SECRETS.join('|')})[^"]*["']?[=>: ]+")[^"]+(?<end>")/,
22
23
  # option "secret"
@@ -26,13 +27,14 @@ module Aspera
26
27
  # private key values
27
28
  /(?<begin>--+BEGIN .+ KEY--+)[[:ascii:]]+?(?<end>--+?END .+ KEY--+)/
28
29
  ].freeze
29
- private_constant :HIDDEN_PASSWORD,:ASCP_SECRETS,:KEY_SECRETS,:ALL_SECRETS,:REGEX_LOG_REPLACES
30
+ private_constant :HIDDEN_PASSWORD, :ASCP_ENV_SECRETS, :KEY_SECRETS, :ALL_SECRETS, :REGEX_LOG_REPLACES
30
31
  @log_secrets = false
31
32
  class << self
32
33
  attr_accessor :log_secrets
34
+
33
35
  def log_formatter(original_formatter)
34
36
  original_formatter ||= Logger::Formatter.new
35
- # note that @log_secrets may be set AFTER this init is done, so it's done at runtime
37
+ # NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
36
38
  return lambda do |severity, datetime, progname, msg|
37
39
  if msg.is_a?(String) && !@log_secrets
38
40
  REGEX_LOG_REPLACES.each do |regx|
@@ -43,31 +45,32 @@ module Aspera
43
45
  end
44
46
  end
45
47
 
46
- def secret?(keyword,value)
47
- keyword=keyword.to_s if keyword.is_a?(Symbol)
48
+ def secret?(keyword, value)
49
+ keyword = keyword.to_s if keyword.is_a?(Symbol)
48
50
  # only Strings can be secrets, not booleans, or hash, arrays
49
51
  keyword.is_a?(String) && ALL_SECRETS.any?{|kw|keyword.include?(kw)} && value.is_a?(String)
50
52
  end
51
53
 
52
- def deep_remove_secret(obj,is_name_value: false)
54
+ def deep_remove_secret(obj, is_name_value: false)
53
55
  case obj
54
56
  when Array
55
57
  if is_name_value
56
58
  obj.each do |i|
57
- i['value']=HIDDEN_PASSWORD if secret?(i['parameter'],i['value'])
59
+ i['value'] = HIDDEN_PASSWORD if secret?(i['parameter'], i['value'])
58
60
  end
59
61
  else
60
62
  obj.each{|i|deep_remove_secret(i)}
61
63
  end
62
64
  when Hash
63
- obj.each do |k,v|
64
- if secret?(k,v)
65
+ obj.each do |k, v|
66
+ if secret?(k, v)
65
67
  obj[k] = HIDDEN_PASSWORD
66
68
  elsif obj[k].is_a?(Hash)
67
69
  deep_remove_secret(obj[k])
68
70
  end
69
71
  end
70
72
  end
73
+ return obj
71
74
  end
72
75
  end
73
76
  end
data/lib/aspera/ssh.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'net/ssh'
4
4
 
5
- # Hack: deactivate ed25519 and ecdsa private keys from ssh identities, as it usually hurts
5
+ # HACK: deactivate ed25519 and ecdsa private keys from ssh identities, as it usually hurts
6
6
  begin
7
- module Net;module SSH;module Authentication;class Session;private; def default_keys; %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa];end;end;end;end;end # rubocop:disable Layout/AccessModifierIndentation, Layout/EmptyLinesAroundAccessModifier, Layout/LineLength
7
+ module Net; module SSH; module Authentication; class Session; private; def default_keys; %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa]; end; end; end; end; end # rubocop:disable Layout/AccessModifierIndentation, Layout/EmptyLinesAroundAccessModifier, Layout/LineLength, Style/Semicolon
8
8
  rescue StandardError
9
9
  # ignore errors
10
10
  end
@@ -15,43 +15,44 @@ module Aspera
15
15
  class Ssh
16
16
  # ssh_options: same as Net::SSH.start
17
17
  # see: https://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
18
- def initialize(host,username,ssh_options)
19
- Log.log.debug("ssh:#{username}@#{host}")
20
- Log.log.debug("ssh_options:#{ssh_options}")
18
+ def initialize(host, username, ssh_options)
19
+ Log.log.debug{"ssh:#{username}@#{host}"}
20
+ Log.log.debug{"ssh_options:#{ssh_options}"}
21
21
  @host = host
22
22
  @username = username
23
23
  @ssh_options = ssh_options
24
24
  @ssh_options[:logger] = Log.log
25
25
  end
26
26
 
27
- def execute(cmd,input=nil)
27
+ def execute(cmd, input=nil)
28
28
  if cmd.is_a?(Array)
29
29
  # concatenate arguments, enclose in double quotes
30
30
  cmd = cmd.map{|v|%Q("#{v}")}.join(' ')
31
31
  end
32
- Log.log.debug("cmd=#{cmd}")
32
+ Log.log.debug{"cmd=#{cmd}"}
33
33
  response = []
34
34
  Net::SSH.start(@host, @username, @ssh_options) do |session|
35
35
  ssh_channel = session.open_channel do |channel|
36
36
  # prepare stdout processing
37
- channel.on_data{|_chan,data|response.push(data)}
37
+ channel.on_data{|_chan, data|response.push(data)}
38
38
  # prepare stderr processing, stderr if type = 1
39
39
  channel.on_extended_data do |_chan, _type, data|
40
- errormsg = "#{cmd}: [#{data.chomp}]"
40
+ error_message = "#{cmd}: [#{data.chomp}]"
41
41
  # Happens when windows user hasn't logged in and created home account.
42
42
  if data.include?('Could not chdir to home directory')
43
- errormsg += "\nHint: home not created in Windows?"
43
+ error_message += "\nHint: home not created in Windows?"
44
44
  end
45
- raise errormsg
45
+ raise error_message
46
46
  end
47
- # send commannd to SSH channel (execute)
48
- channel.send('cexe'.reverse,cmd){|_ch,_success|channel.send_data(input) unless input.nil?}
47
+ # send command to SSH channel (execute)
48
+ channel.send('cexe'.reverse, cmd){|_ch, _success|channel.send_data(input) unless input.nil?}
49
49
  end
50
50
  # wait for channel to finish (command exit)
51
51
  ssh_channel.wait
52
52
  # main ssh session loop
53
53
  session.loop
54
- end # session
54
+ end # start
55
+ # response as single string
55
56
  return response.join
56
57
  end
57
58
  end
data/lib/aspera/sync.rb CHANGED
@@ -1,90 +1,205 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/command_line_builder'
4
+ require 'aspera/fasp/installation'
5
+ require 'json'
6
+ require 'base64'
4
7
 
5
8
  module Aspera
6
9
  # builds command line arg for async
7
10
  class Sync
8
- INSTANCE_PARAMS =
11
+ PARAMS_VX_INSTANCE =
9
12
  {
10
- 'alt_logdir' => { cltype: :opt_with_arg, accepted_types: :string},
11
- 'watchd' => { cltype: :opt_with_arg, accepted_types: :string},
12
- 'apply_local_docroot' => { cltype: :opt_without_arg},
13
- 'quiet' => { cltype: :opt_without_arg}
13
+ 'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
14
+ 'watchd' => { cli: { type: :opt_with_arg}, accepted_types: :string},
15
+ 'apply_local_docroot' => { cli: { type: :opt_without_arg}},
16
+ 'quiet' => { cli: { type: :opt_without_arg}},
17
+ 'ws_connect' => { cli: { type: :opt_without_arg}}
14
18
  }.freeze
15
- SESSION_PARAMS =
19
+
20
+ # map sync session parameters to transfer spec: sync -> ts, true if same
21
+ PARAMS_VX_SESSION =
16
22
  {
17
- 'name' => { cltype: :opt_with_arg, accepted_types: :string},
18
- 'local_dir' => { cltype: :opt_with_arg, accepted_types: :string},
19
- 'remote_dir' => { cltype: :opt_with_arg, accepted_types: :string},
20
- 'local_db_dir' => { cltype: :opt_with_arg, accepted_types: :string},
21
- 'remote_db_dir' => { cltype: :opt_with_arg, accepted_types: :string},
22
- 'host' => { cltype: :opt_with_arg, accepted_types: :string},
23
- 'user' => { cltype: :opt_with_arg, accepted_types: :string},
24
- 'private_key_path' => { cltype: :opt_with_arg, accepted_types: :string},
25
- 'direction' => { cltype: :opt_with_arg, accepted_types: :string},
26
- 'checksum' => { cltype: :opt_with_arg, accepted_types: :string},
27
- 'tcp_port' => { cltype: :opt_with_arg, accepted_types: :int},
28
- 'rate_policy' => { cltype: :opt_with_arg, accepted_types: :string},
29
- 'target_rate' => { cltype: :opt_with_arg, accepted_types: :string},
30
- 'cooloff' => { cltype: :opt_with_arg, accepted_types: :int},
31
- 'pending_max' => { cltype: :opt_with_arg, accepted_types: :int},
32
- 'scan_intensity' => { cltype: :opt_with_arg, accepted_types: :string},
33
- 'cipher' => { cltype: :opt_with_arg, accepted_types: :string},
34
- 'transfer_threads' => { cltype: :opt_with_arg, accepted_types: :int},
35
- 'preserve_time' => { cltype: :opt_without_arg},
36
- 'preserve_access_time' => { cltype: :opt_without_arg},
37
- 'preserve_modification_time' => { cltype: :opt_without_arg},
38
- 'preserve_uid' => { cltype: :opt_without_arg},
39
- 'preserve_gid' => { cltype: :opt_without_arg},
40
- 'create_dir' => { cltype: :opt_without_arg},
41
- 'reset' => { cltype: :opt_without_arg},
42
- # note: only one env var, but multiple sessions... may be a problem
43
- 'remote_password' => { cltype: :envvar, clvarname: 'ASPERA_SCP_PASS'},
44
- 'cookie' => { cltype: :envvar, clvarname: 'ASPERA_SCP_COOKIE'},
45
- 'token' => { cltype: :envvar, clvarname: 'ASPERA_SCP_TOKEN'},
46
- 'license' => { cltype: :envvar, clvarname: 'ASPERA_SCP_LICENSE'}
23
+ 'name' => { cli: { type: :opt_with_arg}, accepted_types: :string},
24
+ 'local_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
25
+ 'remote_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
26
+ 'local_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
27
+ 'remote_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
28
+ 'host' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_host},
29
+ 'user' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_user},
30
+ 'private_key_paths' => { cli: { type: :opt_with_arg, switch: '--private-key-path'}, accepted_types: :array},
31
+ 'direction' => { cli: { type: :opt_with_arg}, accepted_types: :string},
32
+ 'checksum' => { cli: { type: :opt_with_arg}, accepted_types: :string},
33
+ 'tags' => { cli: { type: :opt_with_arg, switch: '--tags64', convert: 'Aspera::Fasp::Parameters.convert_json64'},
34
+ accepted_types: :hash, ts: true},
35
+ 'tcp_port' => { cli: { type: :opt_with_arg}, accepted_types: :int, ts: :ssh_port},
36
+ 'rate_policy' => { cli: { type: :opt_with_arg}, accepted_types: :string},
37
+ 'target_rate' => { cli: { type: :opt_with_arg}, accepted_types: :string},
38
+ 'cooloff' => { cli: { type: :opt_with_arg}, accepted_types: :int},
39
+ 'pending_max' => { cli: { type: :opt_with_arg}, accepted_types: :int},
40
+ 'scan_intensity' => { cli: { type: :opt_with_arg}, accepted_types: :string},
41
+ 'cipher' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: true},
42
+ 'transfer_threads' => { cli: { type: :opt_with_arg}, accepted_types: :int},
43
+ 'preserve_time' => { cli: { type: :opt_without_arg}, ts: :preserve_times},
44
+ 'preserve_access_time' => { cli: { type: :opt_without_arg}, ts: nil},
45
+ 'preserve_modification_time' => { cli: { type: :opt_without_arg}, ts: nil},
46
+ 'preserve_uid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_uid},
47
+ 'preserve_gid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_gid},
48
+ 'create_dir' => { cli: { type: :opt_without_arg}, ts: true},
49
+ 'reset' => { cli: { type: :opt_without_arg}},
50
+ # NOTE: only one env var, but multiple sessions... could be a problem
51
+ 'remote_password' => { cli: { type: :envvar, variable: 'ASPERA_SCP_PASS'}, ts: true},
52
+ 'cookie' => { cli: { type: :envvar, variable: 'ASPERA_SCP_COOKIE'}, ts: true},
53
+ 'token' => { cli: { type: :envvar, variable: 'ASPERA_SCP_TOKEN'}, ts: true},
54
+ 'license' => { cli: { type: :envvar, variable: 'ASPERA_SCP_LICENSE'}}
47
55
  }.freeze
48
56
 
49
- Aspera::CommandLineBuilder.normalize_description(INSTANCE_PARAMS)
50
- Aspera::CommandLineBuilder.normalize_description(SESSION_PARAMS)
57
+ Aspera::CommandLineBuilder.normalize_description(PARAMS_VX_INSTANCE)
58
+ Aspera::CommandLineBuilder.normalize_description(PARAMS_VX_SESSION)
51
59
 
52
- private_constant :INSTANCE_PARAMS,:SESSION_PARAMS
60
+ PARAMS_VX_KEYS = %w[instance sessions].freeze
53
61
 
54
- def initialize(sync_params)
55
- @sync_params = sync_params
56
- end
62
+ # new API
63
+ TS_TO_PARAMS = {
64
+ 'remote_host' => 'remote.host',
65
+ 'remote_user' => 'remote.user',
66
+ 'remote_password' => 'remote.pass',
67
+ 'sshfp' => 'remote.fingerprint',
68
+ 'ssh_port' => 'remote.port',
69
+ 'wss_port' => 'remote.ws_port',
70
+ 'proxy' => 'remote.proxy',
71
+ 'token' => 'remote.token',
72
+ 'tags' => 'tags'
73
+ }.freeze
74
+
75
+ ASYNC_EXECUTABLE = 'async'
57
76
 
58
- MANDATORY_KEYS = %w[instance sessions].freeze
77
+ private_constant :PARAMS_VX_INSTANCE, :PARAMS_VX_SESSION, :PARAMS_VX_KEYS, :ASYNC_EXECUTABLE
59
78
 
60
- def compute_args
61
- raise StandardError,'parameter must be Hash' unless @sync_params.is_a?(Hash)
62
- raise StandardError,"parameter hash must have at least 'sessions', and optionally 'instance' keys." unless
63
- @sync_params.keys.push('instance').uniq.sort.eql?(MANDATORY_KEYS)
64
- raise StandardError,'sessions key must be Array' unless @sync_params['sessions'].is_a?(Array)
65
- raise StandardError,'sessions key must has at least one element (hash)' unless @sync_params['sessions'].first.is_a?(Hash)
79
+ class << self
80
+ def update_parameters_with_transfer_spec(sync_params, transfer_spec)
81
+ if sync_params.key?('local')
82
+ # async native JSON format
83
+ raise StandardError, 'local must be Hash' unless sync_params['local'].is_a?(Hash)
84
+ TS_TO_PARAMS.each do |ts_param, sy_path|
85
+ next unless transfer_spec.key?(ts_param)
86
+ sy_dig = sy_path.split('.')
87
+ param = sy_dig.pop
88
+ hash = sy_dig.empty? ? sync_params : sync_params[sy_dig.first]
89
+ hash = sync_params[sy_dig.first] = {} if hash.nil?
90
+ hash[param] = transfer_spec[ts_param]
91
+ end
92
+ # 'remote.path',
93
+ sync_params['remote']['connect_mode'] ||= sync_params['remote'].key?('ws_port') ? 'ws' : 'ssh'
94
+ sync_params['remote']['private_key_paths'] ||= Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
95
+ sync_params['remote']['path'] ||= '/' if transfer_spec.dig(*%w[tags aspera node file_id])
96
+ elsif sync_params.key?('sessions')
97
+ sync_params['sessions'].each do |session|
98
+ PARAMS_VX_SESSION.each do |async_param, behaviour|
99
+ if behaviour.key?(:ts)
100
+ tspec_param = behaviour[:ts].is_a?(TrueClass) ? async_param : behaviour[:ts].to_s
101
+ session[async_param] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
102
+ end
103
+ end
104
+ session['private_key_paths'] = Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
105
+ session['remote_dir'] = '/' if transfer_spec.dig(*%w[tags aspera node file_id])
106
+ end
107
+ else
108
+ raise 'At least one of `local` or `sessions` must be present in async parameters'
109
+ end
110
+ Log.dump(:sync, sync_params)
111
+ end
112
+ end
66
113
 
67
- env_args = {
114
+ attr_reader :env_args
115
+
116
+ def initialize(sync_params)
117
+ raise StandardError, 'parameter must be Hash' unless sync_params.is_a?(Hash)
118
+ @env_args = {
68
119
  args: [],
69
120
  env: {}
70
121
  }
122
+ if sync_params.key?('local')
123
+ # async native JSON format
124
+ raise StandardError, 'remote must be Hash' unless sync_params['remote'].is_a?(Hash)
125
+ @env_args[:args] = "--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"
126
+ elsif sync_params.key?('sessions')
127
+ # ascli JSON format
128
+ raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
129
+ sync_params.keys.push('instance').uniq.sort.eql?(PARAMS_VX_KEYS)
130
+ raise StandardError, 'sessions key must be Array' unless sync_params['sessions'].is_a?(Array)
131
+ raise StandardError, 'sessions key requires at least one Hash' unless sync_params['sessions'].first.is_a?(Hash)
71
132
 
72
- if @sync_params.has_key?('instance')
73
- raise StandardError,'instance key must be hash' unless @sync_params['instance'].is_a?(Hash)
74
- instance_builder = CommandLineBuilder.new(@sync_params['instance'],INSTANCE_PARAMS)
75
- instance_builder.process_params
76
- instance_builder.add_env_args(env_args[:env],env_args[:args])
133
+ if sync_params.key?('instance')
134
+ raise StandardError, 'instance key must be Hash' unless sync_params['instance'].is_a?(Hash)
135
+ instance_builder = Aspera::CommandLineBuilder.new(sync_params['instance'], PARAMS_VX_INSTANCE)
136
+ instance_builder.process_params
137
+ instance_builder.add_env_args(@env_args[:env], @env_args[:args])
138
+ end
139
+
140
+ sync_params['sessions'].each do |session_params|
141
+ raise StandardError, 'sessions must contain hashes' unless session_params.is_a?(Hash)
142
+ raise StandardError, 'session must contain at leat name' unless session_params.key?('name')
143
+ session_builder = Aspera::CommandLineBuilder.new(session_params, PARAMS_VX_SESSION)
144
+ session_builder.process_params
145
+ session_builder.add_env_args(@env_args[:env], @env_args[:args])
146
+ end
147
+ else
148
+ raise 'At least one of `local` or `sessions` must be present in async parameters'
77
149
  end
150
+ end
78
151
 
79
- @sync_params['sessions'].each do |session_params|
80
- raise StandardError,'sessions must contain hashes' unless session_params.is_a?(Hash)
81
- raise StandardError,'session must contain at leat name' unless session_params.has_key?('name')
82
- session_builder = CommandLineBuilder.new(session_params,SESSION_PARAMS)
83
- session_builder.process_params
84
- session_builder.add_env_args(env_args[:env],env_args[:args])
152
+ def start
153
+ Log.log.debug{"execute: #{@env_args[:env].map{|k, v| "#{k}=\"#{v}\""}.join(' ')} \"#{ASYNC_EXECUTABLE}\" \"#{@env_args[:args].join('" "')}\""}
154
+ res = system(@env_args[:env], [ASYNC_EXECUTABLE, ASYNC_EXECUTABLE], *@env_args[:args])
155
+ Log.log.debug{"result=#{res}"}
156
+ case res
157
+ when true then return nil
158
+ when false then raise "failed: #{$CHILD_STATUS}"
159
+ when nil then raise "not started: #{$CHILD_STATUS}"
160
+ else raise 'internal error: unspecified case'
85
161
  end
162
+ end
163
+ end
164
+
165
+ class SyncAdmin
166
+ ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
167
+ private_constant :ASYNC_ADMIN_EXECUTABLE
168
+ def initialize(sync_params, session_name)
169
+ @cmdline = [ASYNC_ADMIN_EXECUTABLE, '--quiet']
170
+ if sync_params.key?('local')
171
+ raise 'Missing session name' if sync_params['name'].nil?
172
+ raise 'Session not found' unless session_name.nil? || session_name.eql?(sync_params['name'])
173
+ @cmdline.push("--name=#{sync_params['name']}")
174
+ if sync_params.key?('local_db_dir')
175
+ @cmdline.push("--local-db-dir=#{sync_params['local_db_dir']}")
176
+ elsif sync_params.dig('local', 'path')
177
+ @cmdline.push("--local-dir=#{sync_params.dig('local', 'path')}")
178
+ else
179
+ raise 'Missing either local_db_dir or local.path'
180
+ end
181
+ elsif sync_params.key?('sessions')
182
+ session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{|s|s['name'].eql?(session_name)}
183
+ raise 'Session not found' if session.nil?
184
+ raise 'Missing session name' if session['name'].nil?
185
+ @cmdline.push("--name=#{session['name']}")
186
+ if session.key?('local_db_dir')
187
+ @cmdline.push("--local-db-dir=#{session['local_db_dir']}")
188
+ elsif session.key?('local_dir')
189
+ @cmdline.push("--local-dir=#{session['local_dir']}")
190
+ else
191
+ raise 'Missing either local_db_dir or local_dir'
192
+ end
193
+ else
194
+ raise 'At least one of `local` or `sessions` must be present in async parameters'
195
+ end
196
+ end
86
197
 
87
- return env_args
198
+ def status
199
+ stdout, stderr, status = Open3.capture3(*@cmdline)
200
+ Log.log.debug{"status=#{status}, stderr=#{stderr}"}
201
+ raise "Sync failed: #{status.exitstatus} : #{stderr}" unless status.success?
202
+ return stdout.split("\n").each_with_object({}){|l, m|i = l.split(/: */); m[i.first.lstrip] = i.last.lstrip} # rubocop:disable Style/Semicolon
88
203
  end
89
204
  end
90
205
  end
@@ -11,7 +11,7 @@ module Aspera
11
11
  SEC_IN_DAY = 86_400
12
12
  # assume no transfer last longer than this
13
13
  # (garbage collect file list which were not deleted after transfer)
14
- FILE_LIST_AGE_MAX_SEC = 5 * SEC_IN_DAY
14
+ FILE_LIST_AGE_MAX_SEC = SEC_IN_DAY * 5
15
15
  private_constant :SEC_IN_DAY, :FILE_LIST_AGE_MAX_SEC
16
16
  include Singleton
17
17
  def initialize
@@ -53,7 +53,7 @@ module Aspera
53
53
  age_sec = (Time.now - File.stat(file_path).mtime).to_i
54
54
  # check age of file, delete too old
55
55
  if File.file?(file_path) && (age_sec > FILE_LIST_AGE_MAX_SEC)
56
- Log.log.debug("garbage collecting #{name}")
56
+ Log.log.debug{"garbage collecting #{name}"}
57
57
  File.delete(file_path)
58
58
  end
59
59
  end
@@ -10,12 +10,12 @@ module Aspera
10
10
  def read(uri_to_read)
11
11
  proxy_uri = URI.parse(uri_to_read)
12
12
  case proxy_uri.scheme
13
- when 'http','https'
14
- return Rest.new(base_url: uri_to_read,redirect_max: 5).call(operation: 'GET', subpath: '', headers: {'Accept' => 'text/plain'})[:data]
15
- when 'file',NilClass
13
+ when 'http', 'https'
14
+ return Rest.new(base_url: uri_to_read, redirect_max: 5).call(operation: 'GET', subpath: '', headers: {'Accept' => 'text/plain'})[:data]
15
+ when 'file', NilClass
16
16
  local_file_path = proxy_uri.path
17
17
  raise 'URL shall have a path, check syntax' if local_file_path.nil?
18
- local_file_path = File.expand_path(local_file_path.gsub(/^\//,'')) if /^\/(~|.|..)\//.match?(local_file_path)
18
+ local_file_path = File.expand_path(local_file_path.gsub(%r{^/}, '')) if %r{^/(~|.|..)/}.match?(local_file_path)
19
19
  return File.read(local_file_path)
20
20
  else
21
21
  raise "unknown scheme: [#{proxy_uri.scheme}] for [#{uri_to_read}]"
@@ -1,22 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'webrick'
4
- require 'webrick/https'
3
+ require 'aspera/web_server_simple'
5
4
  require 'stringio'
6
5
 
7
6
  module Aspera
8
7
  # servlet called on callback: it records the callback request
9
8
  class WebAuthServlet < WEBrick::HTTPServlet::AbstractServlet
10
- def initialize(server,application) # additional args get here
9
+ def initialize(server, application) # additional args get here
11
10
  Log.log.debug('WebAuthServlet initialize')
12
11
  super(server)
13
12
  @app = application
14
13
  end
15
14
 
16
15
  def service(request, response)
17
- Log.log.debug("received request from browser #{request.request_method} #{request.path}")
18
- raise WEBrick::HTTPStatus::MethodNotAllowed,"unexpected method: #{request.request_method}" unless request.request_method.eql?('GET')
19
- raise WEBrick::HTTPStatus::NotFound,"unexpected path: #{request.path}" unless request.path.eql?(@app.expected_path)
16
+ Log.log.debug{"received request from browser #{request.request_method} #{request.path}"}
17
+ raise WEBrick::HTTPStatus::MethodNotAllowed, "unexpected method: #{request.request_method}" unless request.request_method.eql?('GET')
18
+ raise WEBrick::HTTPStatus::NotFound, "unexpected path: #{request.path}" unless request.path.eql?(@app.expected_path)
20
19
  # acquire lock and signal change
21
20
  @app.mutex.synchronize do
22
21
  @app.query = request.query
@@ -29,81 +28,31 @@ module Aspera
29
28
  end
30
29
  end # WebAuthServlet
31
30
 
32
- # generates and adds self signed cert to provided webrick options
33
- #def fill_self_signed_cert(cert,key)
34
- # cert.subject = cert.issuer = OpenSSL::X509::Name.parse('/C=FR/O=Test/OU=Test/CN=Test')
35
- # cert.not_before = Time.now
36
- # cert.not_after = Time.now + 365 * 24 * 60 * 60
37
- # cert.public_key = key.public_key
38
- # cert.serial = 0x0
39
- # cert.version = 2
40
- # ef = OpenSSL::X509::ExtensionFactory.new
41
- # ef.issuer_certificate = cert
42
- # ef.subject_certificate = cert
43
- # cert.extensions = [
44
- # ef.create_extension('basicConstraints','CA:TRUE', true),
45
- # ef.create_extension('subjectKeyIdentifier', 'hash'),
46
- # # ef.create_extension('keyUsage', 'cRLSign,keyCertSign', true),
47
- # ]
48
- # cert.add_extension(ef.create_extension('authorityKeyIdentifier','keyid:always,issuer:always'))
49
- # cert.sign(key, OpenSSL::Digest::SHA256.new)
50
- #end
51
-
52
31
  # start a local web server, then start a browser that will callback the local server upon authentication
53
- class WebAuth
54
- attr_reader :expected_path,:mutex,:cond
32
+ class WebAuth < WebServerSimple
33
+ attr_reader :expected_path, :mutex, :cond
55
34
  attr_writer :query
35
+
56
36
  # @param endpoint_url [String] e.g. 'https://127.0.0.1:12345'
57
37
  def initialize(endpoint_url)
58
38
  uri = URI.parse(endpoint_url)
39
+ super(uri)
59
40
  # parameters for servlet
60
- @query = nil
61
41
  @mutex = Mutex.new
62
42
  @cond = ConditionVariable.new
63
43
  @expected_path = uri.path.empty? ? '/' : uri.path
64
- # see https://www.rubydoc.info/stdlib/webrick/WEBrick/Config
65
- webrick_options = {
66
- BindAddress: uri.host,
67
- Port: uri.port,
68
- Logger: Log.log,
69
- AccessLog: [[self, WEBrick::AccessLog::COMMON_LOG_FORMAT]] # replace default access log to call local method "<<" below
70
- }
71
- case uri.scheme
72
- when 'http'
73
- Log.log.debug('HTTP mode')
74
- when 'https'
75
- webrick_options[:SSLEnable] = true
76
- # a- automatic certificate generation
77
- webrick_options[:SSLCertName] = [['CN',WEBrick::Utils.getservername]]
78
- # b- generate self signed cert
79
- #webrick_options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(4096)
80
- #webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new
81
- #self.class.fill_self_signed_cert(webrick_options[:SSLCertificate],webrick_options[:SSLPrivateKey])
82
- ## c- good cert
83
- #webrick_options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.read('.../myserver.key'))
84
- #webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read('.../myserver.crt'))
85
- end
86
- # self signed certificate generates characters on STDERR, see create_self_signed_cert in webrick/ssl.rb
87
- Log.capture_stderr { @server = WEBrick::HTTPServer.new(webrick_options) }
88
- @server.mount(@expected_path, WebAuthServlet, self) # additional args provided to constructor
89
- Thread.new { @server.start }
90
- end
91
-
92
- # log web server access ( option AccessLog )
93
- def <<(access_log)
94
- Log.log.debug{"webrick log #{access_log.chomp}"}
44
+ @query = nil
45
+ mount(@expected_path, WebAuthServlet, self) # additional args provided to constructor
46
+ Thread.new { start }
95
47
  end
96
48
 
97
49
  # wait for request on web server
98
50
  # @return Hash the query
99
51
  def received_request
100
- # shall be called only once
101
- raise 'error, received_request called twice ?' if @server.nil?
102
52
  # wait for signal from thread
103
53
  @mutex.synchronize{@cond.wait(@mutex)}
104
54
  # tell server thread to stop
105
- @server.shutdown
106
- @server = nil
55
+ shutdown
107
56
  return @query
108
57
  end
109
58
  end