aspera-cli 4.15.0 → 4.17.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 (108) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +375 -280
  5. data/CONTRIBUTING.md +71 -18
  6. data/README.md +1978 -1656
  7. data/bin/ascli +13 -31
  8. data/bin/asession +32 -22
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/agent/alpha.rb +117 -0
  11. data/lib/aspera/agent/base.rb +61 -0
  12. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  13. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
  14. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
  15. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
  16. data/lib/aspera/agent/trsdk.rb +188 -0
  17. data/lib/aspera/api/aoc.rb +586 -0
  18. data/lib/aspera/api/ats.rb +46 -0
  19. data/lib/aspera/api/cos_node.rb +95 -0
  20. data/lib/aspera/api/node.rb +344 -0
  21. data/lib/aspera/ascmd.rb +47 -14
  22. data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
  23. data/lib/aspera/{fasp → ascp}/management.rb +14 -14
  24. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  25. data/lib/aspera/assert.rb +45 -0
  26. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  27. data/lib/aspera/cli/extended_value.rb +5 -5
  28. data/lib/aspera/cli/formatter.rb +27 -14
  29. data/lib/aspera/cli/hints.rb +7 -6
  30. data/lib/aspera/cli/main.rb +49 -29
  31. data/lib/aspera/cli/manager.rb +46 -36
  32. data/lib/aspera/cli/plugin.rb +34 -20
  33. data/lib/aspera/cli/plugin_factory.rb +61 -0
  34. data/lib/aspera/cli/plugins/alee.rb +7 -7
  35. data/lib/aspera/cli/plugins/aoc.rb +168 -132
  36. data/lib/aspera/cli/plugins/ats.rb +33 -33
  37. data/lib/aspera/cli/plugins/bss.rb +3 -4
  38. data/lib/aspera/cli/plugins/config.rb +250 -272
  39. data/lib/aspera/cli/plugins/console.rb +8 -6
  40. data/lib/aspera/cli/plugins/cos.rb +20 -19
  41. data/lib/aspera/cli/plugins/faspex.rb +71 -60
  42. data/lib/aspera/cli/plugins/faspex5.rb +212 -133
  43. data/lib/aspera/cli/plugins/node.rb +83 -75
  44. data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
  45. data/lib/aspera/cli/plugins/preview.rb +33 -31
  46. data/lib/aspera/cli/plugins/server.rb +33 -32
  47. data/lib/aspera/cli/plugins/shares.rb +39 -33
  48. data/lib/aspera/cli/sync_actions.rb +9 -9
  49. data/lib/aspera/cli/transfer_agent.rb +45 -25
  50. data/lib/aspera/cli/transfer_progress.rb +2 -3
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/colors.rb +5 -0
  53. data/lib/aspera/command_line_builder.rb +16 -14
  54. data/lib/aspera/coverage.rb +21 -0
  55. data/lib/aspera/data_repository.rb +33 -2
  56. data/lib/aspera/environment.rb +5 -4
  57. data/lib/aspera/faspex_gw.rb +13 -11
  58. data/lib/aspera/faspex_postproc.rb +6 -5
  59. data/lib/aspera/id_generator.rb +4 -2
  60. data/lib/aspera/json_rpc.rb +10 -8
  61. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  62. data/lib/aspera/keychain/macos_security.rb +29 -22
  63. data/lib/aspera/log.rb +5 -4
  64. data/lib/aspera/nagios.rb +7 -2
  65. data/lib/aspera/node_simulator.rb +213 -0
  66. data/lib/aspera/oauth/base.rb +143 -0
  67. data/lib/aspera/oauth/factory.rb +124 -0
  68. data/lib/aspera/oauth/generic.rb +34 -0
  69. data/lib/aspera/oauth/jwt.rb +51 -0
  70. data/lib/aspera/oauth/url_json.rb +31 -0
  71. data/lib/aspera/oauth/web.rb +50 -0
  72. data/lib/aspera/oauth.rb +5 -328
  73. data/lib/aspera/open_application.rb +7 -7
  74. data/lib/aspera/persistency_action_once.rb +13 -14
  75. data/lib/aspera/persistency_folder.rb +3 -2
  76. data/lib/aspera/preview/file_types.rb +53 -267
  77. data/lib/aspera/preview/generator.rb +7 -5
  78. data/lib/aspera/preview/terminal.rb +17 -7
  79. data/lib/aspera/preview/utils.rb +8 -7
  80. data/lib/aspera/proxy_auto_config.rb +6 -3
  81. data/lib/aspera/rest.rb +187 -140
  82. data/lib/aspera/rest_error_analyzer.rb +1 -0
  83. data/lib/aspera/rest_errors_aspera.rb +5 -3
  84. data/lib/aspera/resumer.rb +77 -0
  85. data/lib/aspera/secret_hider.rb +5 -2
  86. data/lib/aspera/ssh.rb +15 -8
  87. data/lib/aspera/temp_file_manager.rb +1 -1
  88. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  89. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  90. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  91. data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
  92. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
  93. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  94. data/lib/aspera/transfer/sync.rb +273 -0
  95. data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
  96. data/lib/aspera/web_server_simple.rb +12 -3
  97. data.tar.gz.sig +0 -0
  98. metadata +92 -68
  99. metadata.gz.sig +0 -0
  100. data/lib/aspera/aoc.rb +0 -606
  101. data/lib/aspera/ats_api.rb +0 -47
  102. data/lib/aspera/cos_node.rb +0 -93
  103. data/lib/aspera/fasp/agent_aspera.rb +0 -126
  104. data/lib/aspera/fasp/agent_base.rb +0 -48
  105. data/lib/aspera/fasp/agent_trsdk.rb +0 -146
  106. data/lib/aspera/fasp/resume_policy.rb +0 -77
  107. data/lib/aspera/node.rb +0 -338
  108. data/lib/aspera/sync.rb +0 -219
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/agent/base'
4
+ require 'aspera/ascp/installation'
5
+ require 'aspera/temp_file_manager'
6
+ require 'aspera/log'
7
+ require 'aspera/assert'
8
+ require 'json'
9
+ require 'uri'
10
+
11
+ module Aspera
12
+ module Agent
13
+ class Trsdk < Base
14
+ # see https://github.com/grpc/grpc/blob/master/doc/naming.md
15
+ # https://grpc.io/docs/guides/custom-name-resolution/
16
+ LOCAL_SOCKET_ADDR = '127.0.0.1'
17
+ PORT_SEP = ':'
18
+ # port zero means select a random available high port
19
+ AUTO_LOCAL_TCP_PORT = "#{PORT_SEP}0"
20
+ DEFAULT_OPTIONS = {
21
+ url: AUTO_LOCAL_TCP_PORT,
22
+ external: false, # expect that an external daemon is already running
23
+ keep: false # do not shutdown daemon on exit
24
+ }.freeze
25
+ private_constant :DEFAULT_OPTIONS
26
+
27
+ class << self
28
+ # Well, the port number is only in log file
29
+ def daemon_port_from_log(log_file)
30
+ result = nil
31
+ # if port is zero, a dynamic port was created, get it
32
+ File.open(log_file, 'r') do |file|
33
+ file.each_line do |line|
34
+ # Well, it's tricky to depend on log
35
+ if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
36
+ result = m[2].to_i
37
+ # no "break" , need to read last matching log line
38
+ end
39
+ end
40
+ end
41
+ raise 'Port not found in daemon logs' if result.nil?
42
+ Log.log.debug{"Got port #{result} from log"}
43
+ return result
44
+ end
45
+ end
46
+
47
+ # options come from transfer_info
48
+ def initialize(user_opts={})
49
+ super(user_opts)
50
+ @options = Base.options(default: DEFAULT_OPTIONS, options: user_opts)
51
+ is_local_auto_port = @options[:url].eql?(AUTO_LOCAL_TCP_PORT)
52
+ raise 'Cannot use options `keep` or `external` with port zero' if is_local_auto_port && (@options[:keep] || @options[:external])
53
+ Log.log.debug{Log.dump(:agent_options, @options)}
54
+ # load SDK stub class on demand, as it's an optional gem
55
+ $LOAD_PATH.unshift(Ascp::Installation.instance.sdk_ruby_folder)
56
+ require 'transfer_services_pb'
57
+ # keep PID for optional shutdown
58
+ @daemon_pid = nil
59
+ daemon_endpoint = @options[:url]
60
+ Log.log.debug{Log.dump(:daemon_endpoint, daemon_endpoint)}
61
+ # retry loop
62
+ begin
63
+ # no address: local bind
64
+ daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{daemon_endpoint}" if daemon_endpoint.match?(/^#{PORT_SEP}[0-9]+$/o)
65
+ # Create stub (without credentials)
66
+ @transfer_client = Transfersdk::TransferService::Stub.new(daemon_endpoint, :this_channel_is_insecure)
67
+ # Initiate actual connection
68
+ get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
69
+ Log.log.debug{"Daemon info: #{get_info_response}"}
70
+ Log.log.warn{'Attached to existing daemon'} unless @daemon_pid || @options[:external] || @options[:keep]
71
+ at_exit{shutdown}
72
+ rescue GRPC::Unavailable => e
73
+ # if transferd is external: do not start it, or other error
74
+ raise if @options[:external] || !e.message.include?('failed to connect')
75
+ # we already tried to start a daemon, but it failed
76
+ Aspera.assert(@daemon_pid.nil?){"Daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_endpoint}}"}
77
+ Log.log.warn('no daemon present, starting daemon...') if @options[:external]
78
+ # location of daemon binary
79
+ sdk_folder = File.realpath(File.join(Ascp::Installation.instance.sdk_ruby_folder, '..'))
80
+ # transferd only supports local ip and port
81
+ daemon_uri = URI.parse("ipv4://#{daemon_endpoint}")
82
+ Aspera.assert(daemon_uri.scheme.eql?('ipv4')){"Invalid scheme daemon URI #{daemon_endpoint}"}
83
+ # create a config file for daemon
84
+ config = {
85
+ address: daemon_uri.host,
86
+ port: daemon_uri.port,
87
+ fasp_runtime: {
88
+ use_embedded: false,
89
+ user_defined: {
90
+ bin: sdk_folder,
91
+ etc: sdk_folder
92
+ }
93
+ }
94
+ }
95
+ # config file and logs are created in same folder
96
+ transferd_base_tmp = TempFileManager.instance.new_file_path_global('transferd')
97
+ Log.log.debug{"transferd base tmp #{transferd_base_tmp}"}
98
+ conf_file = "#{transferd_base_tmp}.conf"
99
+ log_stdout = "#{transferd_base_tmp}.out"
100
+ log_stderr = "#{transferd_base_tmp}.err"
101
+ File.write(conf_file, config.to_json)
102
+ @daemon_pid = Process.spawn(Ascp::Installation.instance.path(:transferd), '--config', conf_file, out: log_stdout, err: log_stderr)
103
+ begin
104
+ # wait for process to initialize, max 2 seconds
105
+ Timeout.timeout(2.0) do
106
+ # this returns if process dies (within 2 seconds)
107
+ _, status = Process.wait2(@daemon_pid)
108
+ raise "Transfer daemon exited with status #{status.exitstatus}. Check files: #{log_stdout} and #{log_stderr}"
109
+ end
110
+ rescue Timeout::Error
111
+ nil
112
+ end
113
+ Log.log.debug{"Daemon started with pid #{@daemon_pid}"}
114
+ Process.detach(@daemon_pid) if @options[:keep]
115
+ at_exit {shutdown}
116
+ # update port for next connection attempt (if auto high port was requested)
117
+ daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{self.class.daemon_port_from_log(log_stdout)}" if is_local_auto_port
118
+ # local daemon started, try again
119
+ retry
120
+ end
121
+ end
122
+
123
+ def start_transfer(transfer_spec, token_regenerator: nil)
124
+ # create a transfer request
125
+ transfer_request = Transfersdk::TransferRequest.new(
126
+ transferType: Transfersdk::TransferType::FILE_REGULAR, # transfer type (file/stream)
127
+ config: Transfersdk::TransferConfig.new, # transfer configuration
128
+ transferSpec: transfer_spec.to_json) # transfer definition
129
+ # send start transfer request to the transfer manager daemon
130
+ start_transfer_response = @transfer_client.start_transfer(transfer_request)
131
+ Log.log.debug{"start transfer response #{start_transfer_response}"}
132
+ @transfer_id = start_transfer_response.transferId
133
+ Log.log.debug{"transfer started with id #{@transfer_id}"}
134
+ end
135
+
136
+ def wait_for_transfers_completion
137
+ # set to true when we know the total size of the transfer
138
+ session_started = false
139
+ bytes_expected = nil
140
+ # monitor transfer status
141
+ @transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
142
+ Log.log.debug{Log.dump(:response, response.to_h)}
143
+ # Log.log.debug{"#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}"}
144
+ case response.status
145
+ when :RUNNING
146
+ if !session_started
147
+ notify_progress(session_id: @transfer_id, type: :session_start)
148
+ session_started = true
149
+ end
150
+ if bytes_expected.nil? &&
151
+ !response.sessionInfo.preTransferBytes.eql?(0)
152
+ bytes_expected = response.sessionInfo.preTransferBytes
153
+ notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
154
+ end
155
+ notify_progress(type: :transfer, session_id: @transfer_id, info: response.transferInfo.bytesTransferred)
156
+ when :COMPLETED
157
+ notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
158
+ notify_progress(type: :end, session_id: @transfer_id)
159
+ break
160
+ when :FAILED, :CANCELED
161
+ notify_progress(type: :end, session_id: @transfer_id)
162
+ raise Transfer::Error, JSON.parse(response.message)['Description']
163
+ when :QUEUED, :UNKNOWN_STATUS, :PAUSED, :ORPHANED
164
+ notify_progress(session_id: nil, type: :pre_start, info: response.status.to_s.downcase)
165
+ else
166
+ Log.log.error{"unknown status#{response.status}"}
167
+ end
168
+ end
169
+ # TODO: return status
170
+ return []
171
+ end
172
+
173
+ def shutdown
174
+ stop_daemon unless @options[:keep]
175
+ end
176
+
177
+ def stop_daemon
178
+ if !@daemon_pid.nil?
179
+ Log.log.debug("Stopping daemon #{@daemon_pid}")
180
+ Process.kill('INT', @daemon_pid)
181
+ _, status = Process.wait2(@daemon_pid)
182
+ Log.log.debug("daemon stopped #{status}")
183
+ @daemon_pid = nil
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end