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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +375 -280
- data/CONTRIBUTING.md +71 -18
- data/README.md +1978 -1656
- data/bin/ascli +13 -31
- data/bin/asession +32 -22
- data/examples/dascli +2 -2
- data/lib/aspera/agent/alpha.rb +117 -0
- data/lib/aspera/agent/base.rb +61 -0
- data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
- data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
- data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
- data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
- data/lib/aspera/agent/trsdk.rb +188 -0
- data/lib/aspera/api/aoc.rb +586 -0
- data/lib/aspera/api/ats.rb +46 -0
- data/lib/aspera/api/cos_node.rb +95 -0
- data/lib/aspera/api/node.rb +344 -0
- data/lib/aspera/ascmd.rb +47 -14
- data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
- data/lib/aspera/{fasp → ascp}/management.rb +14 -14
- data/lib/aspera/{fasp → ascp}/products.rb +1 -1
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formatter.rb +27 -14
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/main.rb +49 -29
- data/lib/aspera/cli/manager.rb +46 -36
- data/lib/aspera/cli/plugin.rb +34 -20
- data/lib/aspera/cli/plugin_factory.rb +61 -0
- data/lib/aspera/cli/plugins/alee.rb +7 -7
- data/lib/aspera/cli/plugins/aoc.rb +168 -132
- data/lib/aspera/cli/plugins/ats.rb +33 -33
- data/lib/aspera/cli/plugins/bss.rb +3 -4
- data/lib/aspera/cli/plugins/config.rb +250 -272
- data/lib/aspera/cli/plugins/console.rb +8 -6
- data/lib/aspera/cli/plugins/cos.rb +20 -19
- data/lib/aspera/cli/plugins/faspex.rb +71 -60
- data/lib/aspera/cli/plugins/faspex5.rb +212 -133
- data/lib/aspera/cli/plugins/node.rb +83 -75
- data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
- data/lib/aspera/cli/plugins/preview.rb +33 -31
- data/lib/aspera/cli/plugins/server.rb +33 -32
- data/lib/aspera/cli/plugins/shares.rb +39 -33
- data/lib/aspera/cli/sync_actions.rb +9 -9
- data/lib/aspera/cli/transfer_agent.rb +45 -25
- data/lib/aspera/cli/transfer_progress.rb +2 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +5 -0
- data/lib/aspera/command_line_builder.rb +16 -14
- data/lib/aspera/coverage.rb +21 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +5 -4
- data/lib/aspera/faspex_gw.rb +13 -11
- data/lib/aspera/faspex_postproc.rb +6 -5
- data/lib/aspera/id_generator.rb +4 -2
- data/lib/aspera/json_rpc.rb +10 -8
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +29 -22
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node_simulator.rb +213 -0
- data/lib/aspera/oauth/base.rb +143 -0
- data/lib/aspera/oauth/factory.rb +124 -0
- data/lib/aspera/oauth/generic.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +51 -0
- data/lib/aspera/oauth/url_json.rb +31 -0
- data/lib/aspera/oauth/web.rb +50 -0
- data/lib/aspera/oauth.rb +5 -328
- data/lib/aspera/open_application.rb +7 -7
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/preview/file_types.rb +53 -267
- data/lib/aspera/preview/generator.rb +7 -5
- data/lib/aspera/preview/terminal.rb +17 -7
- data/lib/aspera/preview/utils.rb +8 -7
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +187 -140
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +5 -3
- data/lib/aspera/resumer.rb +77 -0
- data/lib/aspera/secret_hider.rb +5 -2
- data/lib/aspera/ssh.rb +15 -8
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/{fasp → transfer}/error.rb +3 -3
- data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
- data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
- data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
- data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
- data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
- data/lib/aspera/transfer/sync.rb +273 -0
- data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
- data/lib/aspera/web_server_simple.rb +12 -3
- data.tar.gz.sig +0 -0
- metadata +92 -68
- metadata.gz.sig +0 -0
- data/lib/aspera/aoc.rb +0 -606
- data/lib/aspera/ats_api.rb +0 -47
- data/lib/aspera/cos_node.rb +0 -93
- data/lib/aspera/fasp/agent_aspera.rb +0 -126
- data/lib/aspera/fasp/agent_base.rb +0 -48
- data/lib/aspera/fasp/agent_trsdk.rb +0 -146
- data/lib/aspera/fasp/resume_policy.rb +0 -77
- data/lib/aspera/node.rb +0 -338
- 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
|