aspera-cli 4.19.0 → 4.21.1
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 +46 -0
- data/CONTRIBUTING.md +18 -4
- data/README.md +886 -510
- data/bin/asession +27 -20
- data/examples/build_exec +65 -76
- data/examples/build_exec_rubyc +40 -0
- data/examples/get_proto_file.rb +7 -0
- data/lib/aspera/agent/alpha.rb +18 -24
- data/lib/aspera/agent/base.rb +2 -18
- data/lib/aspera/agent/connect.rb +34 -15
- data/lib/aspera/agent/direct.rb +44 -54
- data/lib/aspera/agent/httpgw.rb +2 -3
- data/lib/aspera/agent/node.rb +11 -21
- data/lib/aspera/agent/{trsdk.rb → transferd.rb} +27 -51
- data/lib/aspera/api/alee.rb +15 -0
- data/lib/aspera/api/aoc.rb +139 -105
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/httpgw.rb +15 -10
- data/lib/aspera/api/node.rb +70 -32
- data/lib/aspera/ascmd.rb +56 -48
- data/lib/aspera/ascp/installation.rb +166 -70
- data/lib/aspera/ascp/management.rb +30 -8
- data/lib/aspera/assert.rb +10 -5
- data/lib/aspera/cli/formatter.rb +166 -162
- data/lib/aspera/cli/hints.rb +2 -1
- data/lib/aspera/cli/info.rb +12 -10
- data/lib/aspera/cli/main.rb +28 -13
- data/lib/aspera/cli/manager.rb +7 -2
- data/lib/aspera/cli/plugin.rb +17 -31
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +246 -208
- data/lib/aspera/cli/plugins/ats.rb +16 -14
- data/lib/aspera/cli/plugins/config.rb +154 -94
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/cos.rb +1 -0
- data/lib/aspera/cli/plugins/faspex.rb +15 -23
- data/lib/aspera/cli/plugins/faspex5.rb +64 -50
- data/lib/aspera/cli/plugins/faspio.rb +2 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/node.rb +174 -109
- data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
- data/lib/aspera/cli/plugins/preview.rb +8 -9
- data/lib/aspera/cli/plugins/server.rb +5 -9
- data/lib/aspera/cli/plugins/shares.rb +2 -2
- data/lib/aspera/cli/sync_actions.rb +2 -2
- data/lib/aspera/cli/transfer_agent.rb +12 -14
- data/lib/aspera/cli/transfer_progress.rb +37 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +4 -5
- data/lib/aspera/coverage.rb +13 -1
- data/lib/aspera/environment.rb +75 -25
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/json_rpc.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +7 -12
- data/lib/aspera/log.rb +3 -4
- data/lib/aspera/node_simulator.rb +230 -112
- data/lib/aspera/oauth/base.rb +64 -83
- data/lib/aspera/oauth/factory.rb +52 -6
- data/lib/aspera/oauth/generic.rb +4 -8
- data/lib/aspera/oauth/jwt.rb +6 -3
- data/lib/aspera/oauth/url_json.rb +1 -2
- data/lib/aspera/oauth/web.rb +5 -2
- data/lib/aspera/persistency_action_once.rb +16 -8
- data/lib/aspera/persistency_folder.rb +20 -2
- data/lib/aspera/preview/generator.rb +1 -1
- data/lib/aspera/preview/utils.rb +11 -17
- data/lib/aspera/products/alpha.rb +30 -0
- data/lib/aspera/products/connect.rb +48 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/transferd.rb +54 -0
- data/lib/aspera/rest.rb +116 -87
- data/lib/aspera/secret_hider.rb +2 -2
- data/lib/aspera/ssh.rb +31 -24
- data/lib/aspera/transfer/faux_file.rb +4 -4
- data/lib/aspera/transfer/parameters.rb +16 -17
- data/lib/aspera/transfer/spec.rb +12 -12
- data/lib/aspera/transfer/spec.yaml +22 -20
- data/lib/aspera/transfer/sync.rb +2 -10
- data/lib/aspera/transfer/uri.rb +3 -3
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +166 -17
- data/lib/aspera/web_server_simple.rb +4 -3
- data/lib/transferd_pb.rb +86 -0
- data/lib/transferd_services_pb.rb +84 -0
- data.tar.gz.sig +0 -0
- metadata +58 -22
- metadata.gz.sig +0 -0
- data/lib/aspera/ascp/products.rb +0 -156
@@ -4,7 +4,7 @@
|
|
4
4
|
require 'aspera/cli/info'
|
5
5
|
require 'aspera/log'
|
6
6
|
require 'aspera/assert'
|
7
|
-
require '
|
7
|
+
require 'aspera/environment'
|
8
8
|
|
9
9
|
# enhance the gem to support other key chains
|
10
10
|
module Aspera
|
@@ -48,20 +48,15 @@ module Aspera
|
|
48
48
|
options[:path] = uri.path unless ['', '/'].include?(uri.path)
|
49
49
|
options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
50
50
|
end
|
51
|
-
|
51
|
+
command_args = [command]
|
52
52
|
options&.each do |k, v|
|
53
53
|
Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
|
54
54
|
next if v.nil?
|
55
|
-
|
56
|
-
|
55
|
+
command_args.push("-#{supported[k]}")
|
56
|
+
command_args.push(v.shellescape) unless v.empty?
|
57
57
|
end
|
58
|
-
|
59
|
-
|
60
|
-
stdout, stderr, status = Open3.capture3(*command_line)
|
61
|
-
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
62
|
-
Log.log.trace1{"stdout=#{stdout}"}
|
63
|
-
raise "#{SECURITY_UTILITY} failed: #{status.exitstatus} : #{stderr}" unless status.success?
|
64
|
-
return stdout
|
58
|
+
command_args.push(last_opt) unless last_opt.nil?
|
59
|
+
return Environment.secure_capture(exec: SECURITY_UTILITY, args: command_args)
|
65
60
|
end
|
66
61
|
|
67
62
|
def key_chains(output)
|
@@ -78,7 +73,7 @@ module Aspera
|
|
78
73
|
|
79
74
|
def list(options={})
|
80
75
|
Aspera.assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
|
81
|
-
key_chains(execute('list-
|
76
|
+
key_chains(execute('list-keychains', options, LIST_OPTIONS))
|
82
77
|
end
|
83
78
|
|
84
79
|
def by_name(name)
|
data/lib/aspera/log.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aspera/assert'
|
3
4
|
require 'aspera/colors'
|
4
5
|
require 'aspera/secret_hider'
|
5
6
|
require 'logger'
|
@@ -73,8 +74,7 @@ module Aspera
|
|
73
74
|
JSON.pretty_generate(object) rescue PP.pp(object, +'')
|
74
75
|
when :ruby
|
75
76
|
PP.pp(object, +'')
|
76
|
-
else
|
77
|
-
raise 'wrong parameter, expect ruby or json'
|
77
|
+
else error_unexpected_value(@@format){'dump format'}
|
78
78
|
end
|
79
79
|
"#{name.to_s.green} (#{@@format})=\n#{result}"
|
80
80
|
end
|
@@ -127,8 +127,7 @@ module Aspera
|
|
127
127
|
Syslog::Logger.make_methods(severity.downcase)
|
128
128
|
end
|
129
129
|
@logger = Syslog::Logger.new(@program_name, Syslog::LOG_LOCAL2)
|
130
|
-
else
|
131
|
-
raise "unknown log type: #{new_log_type}, use one of: #{LOG_TYPES.join(', ')}"
|
130
|
+
else error_unexpected_value(new_log_type){"log type (#{LOG_TYPES.join(', ')})"}
|
132
131
|
end
|
133
132
|
@logger.level = current_severity_integer
|
134
133
|
@logger_type = new_log_type
|
@@ -7,28 +7,241 @@ require 'webrick'
|
|
7
7
|
require 'json'
|
8
8
|
|
9
9
|
module Aspera
|
10
|
+
class NodeSimulator
|
11
|
+
def initialize
|
12
|
+
@agent = Agent::Direct.new(management_cb: ->(event){process_event(event)})
|
13
|
+
@sessions = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(ts)
|
17
|
+
@agent.start_transfer(ts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_sessions
|
21
|
+
@agent.sessions.map { |session| session[:job_id] }.uniq.each.map{|job_id|job_to_transfer(job_id)}
|
22
|
+
end
|
23
|
+
|
24
|
+
# status: ('waiting', 'partially_completed', 'unknown', 'waiting(read error)',] 'running', 'completed', 'failed'
|
25
|
+
def job_to_transfer(job_id)
|
26
|
+
jobs = @agent.sessions_by_job(job_id)
|
27
|
+
ts = nil
|
28
|
+
sessions = jobs.map do |j|
|
29
|
+
ts ||= j[:ts]
|
30
|
+
{
|
31
|
+
id: j[:id],
|
32
|
+
client_node_id: '',
|
33
|
+
server_node_id: '2bbdcc39-f789-4d47-8163-6767fc14f421',
|
34
|
+
client_ip_address: '192.168.0.100',
|
35
|
+
server_ip_address: '5.10.114.4',
|
36
|
+
status: 'running',
|
37
|
+
retry_timeout: 3600,
|
38
|
+
retry_count: 0,
|
39
|
+
start_time_usec: 1701094040000000,
|
40
|
+
end_time_usec: nil,
|
41
|
+
elapsed_usec: 405312,
|
42
|
+
bytes_transferred: 26,
|
43
|
+
bytes_written: 26,
|
44
|
+
bytes_lost: 0,
|
45
|
+
files_completed: 1,
|
46
|
+
directories_completed: 0,
|
47
|
+
target_rate_kbps: 500000,
|
48
|
+
min_rate_kbps: 0,
|
49
|
+
calc_rate_kbps: 9900,
|
50
|
+
network_delay_usec: 40000,
|
51
|
+
avg_rate_kbps: 0.51,
|
52
|
+
error_code: 0,
|
53
|
+
error_desc: '',
|
54
|
+
source_statistics: {
|
55
|
+
args_scan_attempted: 1,
|
56
|
+
args_scan_completed: 1,
|
57
|
+
paths_scan_attempted: 1,
|
58
|
+
paths_scan_failed: 0,
|
59
|
+
paths_scan_skipped: 0,
|
60
|
+
paths_scan_excluded: 0,
|
61
|
+
dirs_scan_completed: 0,
|
62
|
+
files_scan_completed: 1,
|
63
|
+
dirs_xfer_attempted: 0,
|
64
|
+
dirs_xfer_fail: 0,
|
65
|
+
files_xfer_attempted: 1,
|
66
|
+
files_xfer_fail: 0,
|
67
|
+
files_xfer_noxfer: 0
|
68
|
+
},
|
69
|
+
precalc: {
|
70
|
+
enabled: true,
|
71
|
+
status: 'ready',
|
72
|
+
bytes_expected: 0,
|
73
|
+
directories_expected: 0,
|
74
|
+
files_expected: 0,
|
75
|
+
files_excluded: 0,
|
76
|
+
files_special: 0,
|
77
|
+
files_failed: 1
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
ts ||= {}
|
82
|
+
result = {
|
83
|
+
id: job_id,
|
84
|
+
status: 'running',
|
85
|
+
start_spec: ts,
|
86
|
+
sessions: sessions,
|
87
|
+
bytes_transferred: 26,
|
88
|
+
bytes_written: 26,
|
89
|
+
bytes_lost: 0,
|
90
|
+
avg_rate_kbps: 0.51,
|
91
|
+
files_completed: 1,
|
92
|
+
files_skipped: 0,
|
93
|
+
directories_completed: 0,
|
94
|
+
start_time_usec: 1701094040000000,
|
95
|
+
end_time_usec: 1701094040405312,
|
96
|
+
elapsed_usec: 405312,
|
97
|
+
error_code: 0,
|
98
|
+
error_desc: '',
|
99
|
+
precalc: {
|
100
|
+
status: 'ready',
|
101
|
+
bytes_expected: 0,
|
102
|
+
files_expected: 0,
|
103
|
+
directories_expected: 0,
|
104
|
+
files_special: 0,
|
105
|
+
files_failed: 1
|
106
|
+
},
|
107
|
+
files: [{
|
108
|
+
id: 'd1b5c112-82b75425-860745fc-93851671-64541bdd',
|
109
|
+
path: '/workspaces/45071/packages/bYA_ilq73g.asp-package/contents/data_file.bin',
|
110
|
+
start_time_usec: 1701094040000000,
|
111
|
+
elapsed_usec: 105616,
|
112
|
+
end_time_usec: 1701094040001355,
|
113
|
+
status: 'completed',
|
114
|
+
error_code: 0,
|
115
|
+
error_desc: '',
|
116
|
+
size: 26,
|
117
|
+
type: 'file',
|
118
|
+
checksum_type: 'none',
|
119
|
+
checksum: nil,
|
120
|
+
start_byte: 0,
|
121
|
+
bytes_written: 26,
|
122
|
+
session_id: 'bafc72b8-366c-4501-8095-47208183d6b8'}]
|
123
|
+
}
|
124
|
+
Log.log.trace2{Log.dump(:job, result)}
|
125
|
+
return result
|
126
|
+
end
|
127
|
+
|
128
|
+
# Process event from management port
|
129
|
+
def process_event(event)
|
130
|
+
case event['Type']
|
131
|
+
when 'NOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
132
|
+
# rubocop:disable Lint/DuplicateBranch
|
133
|
+
when 'START' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
134
|
+
when 'QUERY' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
135
|
+
when 'QUERYRSP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
136
|
+
when 'STATS' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
137
|
+
when 'STOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
138
|
+
when 'ERROR' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
139
|
+
when 'CANCEL' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
140
|
+
when 'DONE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
141
|
+
when 'RATE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
142
|
+
when 'FILEERROR' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
143
|
+
when 'SESSION' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
144
|
+
when 'NOTIFICATION' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
145
|
+
when 'INIT' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
146
|
+
when 'VLINK' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
147
|
+
when 'PUT' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
148
|
+
when 'WRITE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
149
|
+
when 'CLOSE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
150
|
+
when 'SKIP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
151
|
+
when 'ARGSTOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
152
|
+
# rubocop:enable Lint/DuplicateBranch
|
153
|
+
else Aspera.error_unreachable_line
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
10
158
|
# this class answers the Faspex /send API and creates a package on Aspera on Cloud
|
159
|
+
# a new instance is created for each request
|
11
160
|
class NodeSimulatorServlet < WEBrick::HTTPServlet::AbstractServlet
|
12
161
|
PATH_TRANSFERS = '/ops/transfers'
|
13
162
|
PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
|
163
|
+
PATH_BROWSE = '/files/browse'
|
14
164
|
# @param app_api [Api::AoC]
|
15
165
|
# @param app_context [String]
|
16
|
-
def initialize(server, credentials,
|
166
|
+
def initialize(server, credentials, simulator)
|
17
167
|
super(server)
|
18
168
|
@credentials = credentials
|
19
|
-
@
|
169
|
+
@simulator = simulator
|
170
|
+
end
|
171
|
+
|
172
|
+
require 'json'
|
173
|
+
require 'time'
|
174
|
+
|
175
|
+
def folder_to_structure(folder_path)
|
176
|
+
raise "Path does not exist or is not a directory: #{folder_path}" unless Dir.exist?(folder_path)
|
177
|
+
|
178
|
+
# Build self structure
|
179
|
+
folder_stat = File.stat(folder_path)
|
180
|
+
structure = {
|
181
|
+
'self' => {
|
182
|
+
'path' => folder_path,
|
183
|
+
'basename' => File.basename(folder_path),
|
184
|
+
'type' => 'directory',
|
185
|
+
'size' => folder_stat.size,
|
186
|
+
'mtime' => folder_stat.mtime.utc.iso8601,
|
187
|
+
'permissions' => [
|
188
|
+
{ 'name' => 'view' },
|
189
|
+
{ 'name' => 'edit' },
|
190
|
+
{ 'name' => 'delete' }
|
191
|
+
]
|
192
|
+
},
|
193
|
+
'items' => []
|
194
|
+
}
|
195
|
+
|
196
|
+
# Iterate over folder contents
|
197
|
+
Dir.foreach(folder_path) do |entry|
|
198
|
+
next if entry == '.' || entry == '..' # Skip current and parent directory
|
199
|
+
|
200
|
+
item_path = File.join(folder_path, entry)
|
201
|
+
item_type = File.ftype(item_path) rescue 'unknown' # Get the type of file
|
202
|
+
item_stat = File.lstat(item_path) # Use lstat to handle symbolic links correctly
|
203
|
+
|
204
|
+
item = {
|
205
|
+
'path' => item_path,
|
206
|
+
'basename' => entry,
|
207
|
+
'type' => item_type,
|
208
|
+
'size' => item_stat.size,
|
209
|
+
'mtime' => item_stat.mtime.utc.iso8601,
|
210
|
+
'permissions' => [
|
211
|
+
{ 'name' => 'view' },
|
212
|
+
{ 'name' => 'edit' },
|
213
|
+
{ 'name' => 'delete' }
|
214
|
+
]
|
215
|
+
}
|
216
|
+
|
217
|
+
# Add additional details for specific types
|
218
|
+
case item_type
|
219
|
+
when 'file'
|
220
|
+
item['partial_file'] = false
|
221
|
+
when 'link'
|
222
|
+
item['target'] = File.readlink(item_path) rescue nil # Add the target of the symlink
|
223
|
+
when 'unknown'
|
224
|
+
item['note'] = 'File type could not be determined'
|
225
|
+
end
|
226
|
+
|
227
|
+
structure['items'] << item
|
228
|
+
end
|
229
|
+
|
230
|
+
structure
|
20
231
|
end
|
21
232
|
|
22
233
|
def do_POST(request, response)
|
23
234
|
case request.path
|
24
235
|
when PATH_TRANSFERS
|
25
|
-
job_id = @
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
236
|
+
job_id = @simulator.start(JSON.parse(request.body))
|
237
|
+
sleep(0.5)
|
238
|
+
set_json_response(request, response, @simulator.job_to_transfer(job_id))
|
239
|
+
when PATH_BROWSE
|
240
|
+
req = JSON.parse(request.body)
|
241
|
+
# req['count']
|
242
|
+
set_json_response(request, response, folder_to_structure(req['path']))
|
30
243
|
else
|
31
|
-
set_json_response(response, [{error: 'Bad request'}], code: 400)
|
244
|
+
set_json_response(request, response, [{error: 'Bad request'}], code: 400)
|
32
245
|
end
|
33
246
|
end
|
34
247
|
|
@@ -36,7 +249,7 @@ module Aspera
|
|
36
249
|
case request.path
|
37
250
|
when '/info'
|
38
251
|
info = Ascp::Installation.instance.ascp_info
|
39
|
-
set_json_response(response, {
|
252
|
+
set_json_response(request, response, {
|
40
253
|
application: 'node',
|
41
254
|
current_time: Time.now.utc.iso8601(0),
|
42
255
|
version: info['sdk_ascp_version'].gsub(/ .*$/, ''),
|
@@ -44,13 +257,13 @@ module Aspera
|
|
44
257
|
license_max_rate: info['maximum_bandwidth'],
|
45
258
|
os: %x(uname -srv).chomp,
|
46
259
|
aej_status: 'disconnected',
|
47
|
-
async_reporting: '
|
48
|
-
transfer_activity_reporting: '
|
260
|
+
async_reporting: 'no',
|
261
|
+
transfer_activity_reporting: 'no',
|
49
262
|
transfer_user: 'xfer',
|
50
263
|
docroot: 'file:////data/aoc/eudemo-sedemo',
|
51
264
|
node_id: '2bbdcc39-f789-4d47-8163-6767fc14f421',
|
52
265
|
cluster_id: '6dae2844-d1a9-47a5-916d-9b3eac3ea466',
|
53
|
-
acls: [],
|
266
|
+
acls: ['impersonation'],
|
54
267
|
access_key_configuration_capabilities: {
|
55
268
|
transfer: %w[
|
56
269
|
cipher
|
@@ -99,115 +312,20 @@ module Aspera
|
|
99
312
|
{name: 'wss_port', value: 443}
|
100
313
|
]})
|
101
314
|
when PATH_TRANSFERS
|
102
|
-
|
103
|
-
set_json_response(response, result)
|
315
|
+
set_json_response(request, response, @simulator.all_sessions)
|
104
316
|
when PATH_ONE_TRANSFER
|
105
317
|
job_id = request.path.match(PATH_ONE_TRANSFER)[1]
|
106
|
-
set_json_response(response,
|
318
|
+
set_json_response(request, response, @simulator.job_to_transfer(job_id))
|
107
319
|
else
|
108
|
-
set_json_response(response, [{error: 'Unknown request'}], code: 400)
|
320
|
+
set_json_response(request, response, [{error: 'Unknown request'}], code: 400)
|
109
321
|
end
|
110
322
|
end
|
111
323
|
|
112
|
-
def set_json_response(response, json, code: 200)
|
324
|
+
def set_json_response(request, response, json, code: 200)
|
113
325
|
response.status = code
|
114
326
|
response['Content-Type'] = 'application/json'
|
115
327
|
response.body = json.to_json
|
116
|
-
Log.log.trace1{Log.dump(
|
117
|
-
end
|
118
|
-
|
119
|
-
def job_to_transfer(job)
|
120
|
-
session = {
|
121
|
-
id: 'bafc72b8-366c-4501-8095-47208183d6b8',
|
122
|
-
client_node_id: '',
|
123
|
-
server_node_id: '2bbdcc39-f789-4d47-8163-6767fc14f421',
|
124
|
-
client_ip_address: '192.168.0.100',
|
125
|
-
server_ip_address: '5.10.114.4',
|
126
|
-
status: 'running',
|
127
|
-
retry_timeout: 3600,
|
128
|
-
retry_count: 0,
|
129
|
-
start_time_usec: 1701094040000000,
|
130
|
-
end_time_usec: nil,
|
131
|
-
elapsed_usec: 405312,
|
132
|
-
bytes_transferred: 26,
|
133
|
-
bytes_written: 26,
|
134
|
-
bytes_lost: 0,
|
135
|
-
files_completed: 1,
|
136
|
-
directories_completed: 0,
|
137
|
-
target_rate_kbps: 500000,
|
138
|
-
min_rate_kbps: 0,
|
139
|
-
calc_rate_kbps: 9900,
|
140
|
-
network_delay_usec: 40000,
|
141
|
-
avg_rate_kbps: 0.51,
|
142
|
-
error_code: 0,
|
143
|
-
error_desc: '',
|
144
|
-
source_statistics: {
|
145
|
-
args_scan_attempted: 1,
|
146
|
-
args_scan_completed: 1,
|
147
|
-
paths_scan_attempted: 1,
|
148
|
-
paths_scan_failed: 0,
|
149
|
-
paths_scan_skipped: 0,
|
150
|
-
paths_scan_excluded: 0,
|
151
|
-
dirs_scan_completed: 0,
|
152
|
-
files_scan_completed: 1,
|
153
|
-
dirs_xfer_attempted: 0,
|
154
|
-
dirs_xfer_fail: 0,
|
155
|
-
files_xfer_attempted: 1,
|
156
|
-
files_xfer_fail: 0,
|
157
|
-
files_xfer_noxfer: 0
|
158
|
-
},
|
159
|
-
precalc: {
|
160
|
-
enabled: true,
|
161
|
-
status: 'ready',
|
162
|
-
bytes_expected: 0,
|
163
|
-
directories_expected: 0,
|
164
|
-
files_expected: 0,
|
165
|
-
files_excluded: 0,
|
166
|
-
files_special: 0,
|
167
|
-
files_failed: 1
|
168
|
-
}}
|
169
|
-
return {
|
170
|
-
id: '609a667d-642e-4290-9312-b4d20d3c0159',
|
171
|
-
status: 'running',
|
172
|
-
start_spec: job[:ts],
|
173
|
-
sessions: [session],
|
174
|
-
bytes_transferred: 26,
|
175
|
-
bytes_written: 26,
|
176
|
-
bytes_lost: 0,
|
177
|
-
avg_rate_kbps: 0.51,
|
178
|
-
files_completed: 1,
|
179
|
-
files_skipped: 0,
|
180
|
-
directories_completed: 0,
|
181
|
-
start_time_usec: 1701094040000000,
|
182
|
-
end_time_usec: 1701094040405312,
|
183
|
-
elapsed_usec: 405312,
|
184
|
-
error_code: 0,
|
185
|
-
error_desc: '',
|
186
|
-
precalc: {
|
187
|
-
status: 'ready',
|
188
|
-
bytes_expected: 0,
|
189
|
-
files_expected: 0,
|
190
|
-
directories_expected: 0,
|
191
|
-
files_special: 0,
|
192
|
-
files_failed: 1
|
193
|
-
},
|
194
|
-
files: [{
|
195
|
-
id: 'd1b5c112-82b75425-860745fc-93851671-64541bdd',
|
196
|
-
path: '/workspaces/45071/packages/bYA_ilq73g.asp-package/contents/data_file.bin',
|
197
|
-
start_time_usec: 1701094040000000,
|
198
|
-
elapsed_usec: 105616,
|
199
|
-
end_time_usec: 1701094040001355,
|
200
|
-
status: 'completed',
|
201
|
-
error_code: 0,
|
202
|
-
error_desc: '',
|
203
|
-
size: 26,
|
204
|
-
type: 'file',
|
205
|
-
checksum_type: 'none',
|
206
|
-
checksum: nil,
|
207
|
-
start_byte: 0,
|
208
|
-
bytes_written: 26,
|
209
|
-
session_id: 'bafc72b8-366c-4501-8095-47208183d6b8'}]
|
210
|
-
}
|
328
|
+
Log.log.trace1{Log.dump("response for #{request.request_method} #{request.path}", json)}
|
211
329
|
end
|
212
330
|
end
|
213
331
|
end
|
data/lib/aspera/oauth/base.rb
CHANGED
@@ -3,64 +3,64 @@
|
|
3
3
|
require 'aspera/oauth/factory'
|
4
4
|
require 'aspera/log'
|
5
5
|
require 'aspera/assert'
|
6
|
-
require 'aspera/id_generator'
|
7
6
|
require 'date'
|
8
7
|
|
9
8
|
module Aspera
|
10
9
|
module OAuth
|
11
|
-
#
|
12
|
-
# bearer
|
10
|
+
# OAuth 2 client for the REST client
|
11
|
+
# Generate bearer token
|
12
|
+
# Bearer tokens are cached in memory and in a file cache for later re-use
|
13
13
|
# https://tools.ietf.org/html/rfc6749
|
14
14
|
class Base
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
# @param
|
20
|
-
# @param
|
21
|
-
# @param
|
22
|
-
# @param
|
23
|
-
# @param scope [O]
|
24
|
-
# @param path_token [D] API end point to create a token
|
25
|
-
# @param token_field [D] field in result that contains the token
|
15
|
+
# @param ** Parameters for REST
|
16
|
+
# @param client_id [String, nil]
|
17
|
+
# @param client_secret [String, nil]
|
18
|
+
# @param scope [String, nil]
|
19
|
+
# @param use_query [bool] Provide parameters in query instead of body
|
20
|
+
# @param path_token [String] API end point to create a token
|
21
|
+
# @param token_field [String] Field in result that contains the token
|
22
|
+
# @param cache_ids [Array, nil] List of unique identifiers for cache id generation
|
26
23
|
def initialize(
|
27
|
-
base_url:,
|
28
|
-
auth: nil,
|
29
24
|
client_id: nil,
|
30
25
|
client_secret: nil,
|
31
26
|
scope: nil,
|
32
27
|
use_query: false,
|
33
|
-
path_token: 'token',
|
34
|
-
token_field:
|
28
|
+
path_token: 'token',
|
29
|
+
token_field: Factory::TOKEN_FIELD,
|
30
|
+
cache_ids: nil,
|
31
|
+
**rest_params
|
35
32
|
)
|
36
|
-
Aspera.assert_type(base_url, String)
|
37
33
|
Aspera.assert(respond_to?(:create_token), 'create_token method must be defined', exception_class: InternalError)
|
38
|
-
|
34
|
+
# this is the OAuth API
|
35
|
+
@api = Rest.new(**rest_params)
|
39
36
|
@path_token = path_token
|
40
37
|
@token_field = token_field
|
41
38
|
@client_id = client_id
|
42
39
|
@client_secret = client_secret
|
43
|
-
@scope = scope
|
44
40
|
@use_query = use_query
|
45
|
-
@
|
46
|
-
|
47
|
-
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
41
|
+
@base_cache_ids = cache_ids.nil? ? [] : cache_ids.clone
|
42
|
+
Aspera.assert_type(@base_cache_ids, Array)
|
43
|
+
@base_cache_ids.push(@api.auth_params[:username]) if @api.auth_params.key?(:username)
|
44
|
+
@base_cache_ids.compact!
|
45
|
+
@base_cache_ids.freeze
|
46
|
+
self.scope = scope
|
47
|
+
end
|
48
|
+
|
49
|
+
# Scope can be modified after creation, then update identifier for cache
|
50
|
+
def scope=(scope)
|
51
|
+
@scope = scope
|
52
|
+
# generate token unique identifier for persistency (memory/disk cache)
|
53
|
+
@token_cache_id = Factory.cache_id(@api.base_url, self.class, @base_cache_ids, @scope)
|
52
54
|
end
|
53
55
|
|
54
56
|
# helper method to create token as per RFC
|
55
57
|
def create_token_call(creation_params)
|
56
58
|
Log.log.debug{'Generating a new token'.bg_green}
|
57
|
-
payload = {
|
58
|
-
body: creation_params,
|
59
|
-
body_type: :www
|
60
|
-
}
|
59
|
+
payload = { body_type: :www }
|
61
60
|
if @use_query
|
62
61
|
payload[:query] = creation_params
|
63
|
-
|
62
|
+
else
|
63
|
+
payload[:body] = creation_params
|
64
64
|
end
|
65
65
|
return @api.call(
|
66
66
|
operation: 'POST',
|
@@ -85,56 +85,37 @@ module Aspera
|
|
85
85
|
# @param cache set to false to disable cache
|
86
86
|
# @param refresh set to true to force refresh or re-generation (if previous failed)
|
87
87
|
def token(cache: true, refresh: false)
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
88
|
+
# get token info from cache (or nil), decoded with date and expiration status
|
89
|
+
token_info = Factory.instance.get_token_info(@token_cache_id) if cache
|
90
|
+
token_data = nil
|
91
|
+
unless token_info.nil?
|
92
|
+
token_data = token_info[:data]
|
93
|
+
# Optional optimization:
|
94
|
+
# check if node token is expired based on decoded content then force refresh if close enough
|
95
|
+
# might help in case the transfer agent cannot refresh himself
|
96
|
+
# `direct` agent is equipped with refresh code
|
97
|
+
# an API was already called, but failed, we need to regenerate or refresh
|
98
|
+
if refresh || token_info[:expired]
|
99
|
+
if token_data.key?('refresh_token') && token_data['refresh_token'].eql?('not_supported')
|
100
|
+
# save possible refresh token, before deleting the cache
|
101
|
+
refresh_token = token_data['refresh_token']
|
102
|
+
end
|
103
|
+
# delete cache
|
104
|
+
Factory.instance.persist_mgr.delete(@token_cache_id)
|
105
|
+
token_data = nil
|
106
|
+
# lets try the existing refresh token
|
107
|
+
if !refresh_token.nil?
|
108
|
+
Log.log.info{"refresh=[#{refresh_token}]".bg_green}
|
109
|
+
# NOTE: AoC admin token has no refresh, and lives by default 1800secs
|
110
|
+
resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
|
111
|
+
if resp[:http].code.start_with?('2')
|
112
|
+
# save only if success
|
113
|
+
json_data = resp[:http].body
|
114
|
+
token_data = JSON.parse(json_data)
|
115
|
+
Factory.instance.persist_mgr.put(@token_cache_id, json_data)
|
116
|
+
else
|
117
|
+
Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
|
109
118
|
end
|
110
|
-
# force refresh if we see a token too close from expiration
|
111
|
-
refresh = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < OAuth::Factory.instance.parameters[:token_expiration_guard_sec]
|
112
|
-
Log.log.debug{"Expiration: #{expires_at_sec} / #{refresh}"}
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# an API was already called, but failed, we need to regenerate or refresh
|
117
|
-
if refresh
|
118
|
-
if token_data.is_a?(Hash) && token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
|
119
|
-
# save possible refresh token, before deleting the cache
|
120
|
-
refresh_token = token_data['refresh_token']
|
121
|
-
end
|
122
|
-
# delete cache
|
123
|
-
Factory.instance.persist_mgr.delete(token_id)
|
124
|
-
token_data = nil
|
125
|
-
# lets try the existing refresh token
|
126
|
-
if !refresh_token.nil?
|
127
|
-
Log.log.info{"refresh=[#{refresh_token}]".bg_green}
|
128
|
-
# try to refresh
|
129
|
-
# note: AoC admin token has no refresh, and lives by default 1800secs
|
130
|
-
resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
|
131
|
-
if resp[:http].code.start_with?('2')
|
132
|
-
# save only if success
|
133
|
-
json_data = resp[:http].body
|
134
|
-
token_data = JSON.parse(json_data)
|
135
|
-
Factory.instance.persist_mgr.put(token_id, json_data)
|
136
|
-
else
|
137
|
-
Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
|
138
119
|
end
|
139
120
|
end
|
140
121
|
end
|
@@ -144,7 +125,7 @@ module Aspera
|
|
144
125
|
resp = create_token
|
145
126
|
json_data = resp[:http].body
|
146
127
|
token_data = JSON.parse(json_data)
|
147
|
-
Factory.instance.persist_mgr.put(
|
128
|
+
Factory.instance.persist_mgr.put(@token_cache_id, json_data)
|
148
129
|
end
|
149
130
|
Aspera.assert(token_data.key?(@token_field)){"API error: No such field in answer: #{@token_field}"}
|
150
131
|
# ok we shall have a token here
|