aspera-cli 4.24.1 → 4.25.0.pre
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 +1064 -745
- data/CONTRIBUTING.md +43 -100
- data/README.md +1281 -720
- data/bin/ascli +20 -1
- data/bin/asession +23 -27
- data/lib/aspera/agent/base.rb +10 -21
- data/lib/aspera/agent/connect.rb +2 -3
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +49 -32
- data/lib/aspera/agent/factory.rb +31 -0
- data/lib/aspera/api/aoc.rb +134 -76
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +213 -0
- data/lib/aspera/api/node.rb +107 -94
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +73 -58
- data/lib/aspera/ascp/management.rb +119 -23
- data/lib/aspera/assert.rb +39 -11
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +91 -67
- data/lib/aspera/cli/formatter.rb +62 -27
- data/lib/aspera/cli/hints.rb +8 -0
- data/lib/aspera/cli/info.rb +4 -4
- data/lib/aspera/cli/main.rb +76 -84
- data/lib/aspera/cli/manager.rb +352 -248
- data/lib/aspera/cli/plugins/alee.rb +5 -4
- data/lib/aspera/cli/plugins/aoc.rb +175 -195
- data/lib/aspera/cli/plugins/ats.rb +4 -4
- data/lib/aspera/cli/plugins/base.rb +343 -0
- data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
- data/lib/aspera/cli/plugins/config.rb +283 -269
- data/lib/aspera/cli/plugins/console.rb +27 -22
- data/lib/aspera/cli/plugins/cos.rb +3 -3
- data/lib/aspera/cli/plugins/factory.rb +78 -0
- data/lib/aspera/cli/plugins/faspex.rb +49 -46
- data/lib/aspera/cli/plugins/faspex5.rb +113 -225
- data/lib/aspera/cli/plugins/faspio.rb +19 -18
- data/lib/aspera/cli/plugins/httpgw.rb +14 -13
- data/lib/aspera/cli/plugins/node.rb +162 -149
- data/lib/aspera/cli/plugins/oauth.rb +48 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
- data/lib/aspera/cli/plugins/preview.rb +30 -50
- data/lib/aspera/cli/plugins/server.rb +21 -21
- data/lib/aspera/cli/plugins/shares.rb +45 -47
- data/lib/aspera/cli/sync_actions.rb +50 -39
- data/lib/aspera/cli/transfer_agent.rb +35 -49
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +3 -3
- data/lib/aspera/cli/wizard.rb +70 -55
- data/lib/aspera/colors.rb +6 -0
- data/lib/aspera/command_line_builder.rb +59 -61
- data/lib/aspera/command_line_converter.rb +2 -1
- data/lib/aspera/coverage.rb +2 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +51 -41
- data/lib/aspera/faspex_gw.rb +7 -5
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +37 -9
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +7 -6
- data/lib/aspera/oauth/base.rb +25 -28
- data/lib/aspera/oauth/factory.rb +9 -9
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/products/connect.rb +7 -6
- data/lib/aspera/products/desktop.rb +1 -4
- data/lib/aspera/products/other.rb +9 -1
- data/lib/aspera/products/transferd.rb +0 -1
- data/lib/aspera/rest.rb +168 -113
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +7 -4
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/args.schema.yaml +46 -3
- data/lib/aspera/sync/conf.schema.yaml +307 -123
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +135 -79
- data/lib/aspera/temp_file_manager.rb +17 -5
- data/lib/aspera/transfer/error.rb +16 -7
- data/lib/aspera/transfer/parameters.rb +35 -22
- data/lib/aspera/transfer/resumer.rb +74 -0
- data/lib/aspera/transfer/spec.rb +5 -5
- data/lib/aspera/transfer/spec.schema.yaml +170 -59
- data/lib/aspera/transfer/spec_doc.rb +49 -43
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +6 -6
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +26 -11
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
- data/lib/aspera/cli/plugin.rb +0 -333
- data/lib/aspera/cli/plugin_factory.rb +0 -81
- data/lib/aspera/resumer.rb +0 -77
- data/lib/aspera/transfer/error_info.rb +0 -91
data/lib/aspera/rest.rb
CHANGED
|
@@ -50,84 +50,95 @@ module Aspera
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
class EntityNotFound < Error
|
|
54
|
+
end
|
|
55
|
+
|
|
53
56
|
# a simple class to make HTTP calls, equivalent to rest-client
|
|
54
57
|
# rest call errors are raised as exception RestCallError
|
|
55
58
|
# and error are analyzed in RestErrorAnalyzer
|
|
56
59
|
class Rest
|
|
57
|
-
# flag for array parameters prefixed with []
|
|
58
|
-
ARRAY_PARAMS = '[]'
|
|
59
|
-
|
|
60
|
-
private_constant :ARRAY_PARAMS
|
|
61
|
-
|
|
62
|
-
# error message when entity not found (TODO: use specific exception)
|
|
63
|
-
ENTITY_NOT_FOUND = 'No such'
|
|
64
|
-
|
|
65
60
|
MIME_JSON = 'application/json'
|
|
66
61
|
MIME_WWW = 'application/x-www-form-urlencoded'
|
|
67
62
|
MIME_TEXT = 'text/plain'
|
|
68
63
|
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
# Special query parameter: max number of items for list command
|
|
65
|
+
MAX_ITEMS = 'max'
|
|
66
|
+
# Special query parameter: max number of pages for list command
|
|
67
|
+
MAX_PAGES = 'pmax'
|
|
73
68
|
|
|
74
69
|
class << self
|
|
75
70
|
# @return [String] Basic auth token
|
|
76
71
|
def basic_authorization(user, pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"; end
|
|
77
72
|
|
|
78
|
-
#
|
|
79
|
-
# @param
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def array_params?(values)
|
|
85
|
-
return values.first.eql?(ARRAY_PARAMS)
|
|
73
|
+
# Indicate that the given Hash query uses php style for array parameters
|
|
74
|
+
# @param query [Hash] A key can have Array value and result will use PHP format: a[]=1&a[]=2
|
|
75
|
+
def php_style(query)
|
|
76
|
+
Aspera.assert_type(query, Hash){'query'}
|
|
77
|
+
query[:x_array_php_style] = true
|
|
78
|
+
query
|
|
86
79
|
end
|
|
87
80
|
|
|
88
81
|
# Build URI from URL and parameters and check it is http or https
|
|
89
|
-
#
|
|
90
|
-
# @param
|
|
91
|
-
|
|
82
|
+
# Check iof php style is specified
|
|
83
|
+
# @param url [String] The URL without query.
|
|
84
|
+
# @param query [Hash,Array,String] The query.
|
|
85
|
+
def build_uri(url, query)
|
|
92
86
|
uri = URI.parse(url)
|
|
93
|
-
Aspera.
|
|
87
|
+
Aspera.assert_values(uri.scheme, %w[http https]){'URI scheme'}
|
|
94
88
|
return uri if query.nil? || query.respond_to?(:empty?) && query.empty?
|
|
95
89
|
Log.dump(:query, query)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
90
|
+
uri.query =
|
|
91
|
+
case query
|
|
92
|
+
when String
|
|
93
|
+
query
|
|
94
|
+
when Hash
|
|
95
|
+
URI.encode_www_form(h_to_query_array(query))
|
|
96
|
+
when Array
|
|
97
|
+
Aspera.assert(query.all?{ |i| i.is_a?(Array) && i.length.eql?(2)}){'Query must be array of arrays of 2 elements'}
|
|
98
|
+
URI.encode_www_form(query)
|
|
99
|
+
else Aspera.error_unexpected_value(query.class){'query type'}
|
|
100
|
+
end.gsub('%5B%5D=', '[]=')
|
|
101
|
+
# [] is allowed in url parameters
|
|
102
|
+
uri
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @param query [Hash] HTTP query as hash
|
|
106
|
+
def h_to_query_array(query)
|
|
107
|
+
Aspera.assert_type(query, Hash)
|
|
108
|
+
# Support array for query parameter, there is no standard.
|
|
109
|
+
# Either p[]=1&p[]=2, or p=1&p=2
|
|
110
|
+
suffix = query.delete(:x_array_php_style) ? '[]' : nil
|
|
111
|
+
query.each_with_object([]) do |(k, v), query_array|
|
|
112
|
+
case v
|
|
113
|
+
when Array
|
|
114
|
+
v.each do |e|
|
|
115
|
+
query_array.push(["#{k}#{suffix}", e])
|
|
109
116
|
end
|
|
117
|
+
else
|
|
118
|
+
query_array.push([k, v])
|
|
110
119
|
end
|
|
111
|
-
when Array
|
|
112
|
-
Aspera.assert(query.all?{ |i| i.is_a?(Array) && i.length.eql?(2)}){'Query must be array of arrays or 2 elements'}
|
|
113
|
-
query_array = query
|
|
114
|
-
else
|
|
115
|
-
raise "Query must be Hash or Array, not #{query.class}"
|
|
116
120
|
end
|
|
117
|
-
# [] is allowed in url parameters
|
|
118
|
-
uri.query = URI.encode_www_form(query_array).gsub('%5B%5D=', '[]=')
|
|
119
|
-
return uri
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
# Decode query string as Hash
|
|
123
|
-
#
|
|
124
|
+
# if parameter is only once, then it's a scalar
|
|
125
|
+
# if a parameter is several, then it's array
|
|
126
|
+
# if parameter has [] then it's an array, and [] is removed
|
|
127
|
+
# Support arrays in query string, e.g. PHP's way is p[]=1&p[]=2
|
|
124
128
|
# @param query [String] query string as in URI.query
|
|
125
129
|
# @return [Hash] decoded query
|
|
126
130
|
def query_to_h(query)
|
|
127
|
-
URI.decode_www_form(query).each_with_object({}) do |
|
|
128
|
-
key
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
URI.decode_www_form(query).each_with_object({}) do |(key, value), h|
|
|
132
|
+
if key.end_with?('[]')
|
|
133
|
+
key = key[..-3]
|
|
134
|
+
h[key] = [] unless h.key?(key)
|
|
135
|
+
end
|
|
136
|
+
if h.key?(key)
|
|
137
|
+
h[key] = [h[key]] if !h[key].is_a?(Array)
|
|
138
|
+
h[key].push(value)
|
|
139
|
+
else
|
|
140
|
+
h[key] = value
|
|
141
|
+
end
|
|
131
142
|
end
|
|
132
143
|
end
|
|
133
144
|
|
|
@@ -135,13 +146,14 @@ module Aspera
|
|
|
135
146
|
# @param base_url [String] base url of HTTP/S session
|
|
136
147
|
# @return [Net::HTTP] a started HTTP session
|
|
137
148
|
def start_http_session(base_url)
|
|
138
|
-
uri =
|
|
139
|
-
|
|
149
|
+
uri = URI.parse(base_url)
|
|
150
|
+
Aspera.assert_values(uri.scheme, %w[http https]){'URI scheme'}
|
|
151
|
+
# This honors http_proxy env var
|
|
140
152
|
http_session = Net::HTTP.new(uri.host, uri.port)
|
|
141
153
|
http_session.use_ssl = uri.scheme.eql?('https')
|
|
142
|
-
#
|
|
154
|
+
# Set http options in callback, such as timeout and cert. verification
|
|
143
155
|
RestParameters.instance.session_cb&.call(http_session)
|
|
144
|
-
#
|
|
156
|
+
# Manually start session for keep alive (if supported by server, else, session is closed every time)
|
|
145
157
|
http_session.start
|
|
146
158
|
return http_session
|
|
147
159
|
end
|
|
@@ -174,19 +186,31 @@ module Aspera
|
|
|
174
186
|
return result
|
|
175
187
|
end
|
|
176
188
|
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
189
|
+
# Parses an HTTP Content-Type header string into its media type and parameters
|
|
190
|
+
# according to RFC 9110 and RFC 6838.
|
|
191
|
+
# TODO: use gem: content_type
|
|
192
|
+
#
|
|
193
|
+
# @param header [String] The Content-Type header string, e.g., "application/json; charset=utf-8"
|
|
194
|
+
# @return [Hash] A hash with :type and :parameters keys.
|
|
195
|
+
# Example:
|
|
196
|
+
# {
|
|
197
|
+
# type: "application/json",
|
|
198
|
+
# parameters: {
|
|
199
|
+
# charset: "utf-8",
|
|
200
|
+
# version: "1.0"
|
|
201
|
+
# }
|
|
202
|
+
# }
|
|
181
203
|
def parse_header(header)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
204
|
+
parts = header.split(';').map(&:strip)
|
|
205
|
+
media_type = parts.shift.downcase
|
|
206
|
+
parameters = parts.filter_map do |param|
|
|
207
|
+
key, value = param.split('=', 2)
|
|
208
|
+
next unless key && value
|
|
209
|
+
key = key.strip.downcase.to_sym
|
|
210
|
+
value = value.strip.gsub(/\A"|"\z/, '')
|
|
211
|
+
[key, value]
|
|
188
212
|
end.to_h
|
|
189
|
-
{type:
|
|
213
|
+
{type: media_type, parameters: parameters}
|
|
190
214
|
end
|
|
191
215
|
end
|
|
192
216
|
|
|
@@ -200,8 +224,13 @@ module Aspera
|
|
|
200
224
|
|
|
201
225
|
public
|
|
202
226
|
|
|
203
|
-
|
|
227
|
+
# All original constructor parameters
|
|
204
228
|
attr_reader :auth_params
|
|
229
|
+
|
|
230
|
+
# The root URL for the API
|
|
231
|
+
attr_reader :base_url
|
|
232
|
+
|
|
233
|
+
# Base common headers of API
|
|
205
234
|
attr_reader :headers
|
|
206
235
|
|
|
207
236
|
# @return creation parameters
|
|
@@ -271,14 +300,16 @@ module Aspera
|
|
|
271
300
|
end
|
|
272
301
|
|
|
273
302
|
# HTTP/S REST call
|
|
274
|
-
# @param operation
|
|
275
|
-
# @param subpath
|
|
276
|
-
# @param query
|
|
277
|
-
# @param content_type [String,nil] Type of body parameters (one of MIME_*) and serialization, else use headers
|
|
278
|
-
# @param body
|
|
279
|
-
# @param headers
|
|
280
|
-
# @param save_to_file (filepath)
|
|
281
|
-
# @param
|
|
303
|
+
# @param operation [String] HTTP operation (GET, POST, PUT, DELETE)
|
|
304
|
+
# @param subpath [String] subpath of REST API
|
|
305
|
+
# @param query [Hash] URL parameters
|
|
306
|
+
# @param content_type [String, nil] Type of body parameters (one of MIME_*) and serialization, else use headers
|
|
307
|
+
# @param body [Hash, String] body parameters
|
|
308
|
+
# @param headers [Hash] additional headers (override Content-Type)
|
|
309
|
+
# @param save_to_file [String, nil](filepath)
|
|
310
|
+
# @param exception [Bool] `true`, error raise exception
|
|
311
|
+
# @param ret [:data, :resp, :both] Tell to return only data, only http response, or both
|
|
312
|
+
# @return [Object, Array] only data, only http response, or both
|
|
282
313
|
def call(
|
|
283
314
|
operation:,
|
|
284
315
|
subpath: nil,
|
|
@@ -287,13 +318,18 @@ module Aspera
|
|
|
287
318
|
body: nil,
|
|
288
319
|
headers: nil,
|
|
289
320
|
save_to_file: nil,
|
|
290
|
-
|
|
321
|
+
exception: true,
|
|
322
|
+
ret: :data
|
|
291
323
|
)
|
|
292
324
|
subpath = subpath.to_s if subpath.is_a?(Symbol)
|
|
293
325
|
subpath = '' if subpath.nil?
|
|
294
326
|
Log.log.debug{"call #{operation} [#{subpath}]".red.bold.bg_green}
|
|
295
|
-
Log.dump(:body, body)
|
|
327
|
+
Log.dump(:body, body, level: :trace1)
|
|
328
|
+
Log.dump(:query, query, level: :trace1)
|
|
329
|
+
Log.dump(:headers, headers, level: :trace1)
|
|
296
330
|
Aspera.assert_type(subpath, String)
|
|
331
|
+
# We must have a way to check return code
|
|
332
|
+
Aspera.assert(exception || !ret.eql?(:data))
|
|
297
333
|
if headers.nil?
|
|
298
334
|
headers = @headers.clone
|
|
299
335
|
else
|
|
@@ -317,7 +353,8 @@ module Aspera
|
|
|
317
353
|
end
|
|
318
354
|
else Aspera.error_unexpected_value(@auth_params[:type])
|
|
319
355
|
end
|
|
320
|
-
|
|
356
|
+
result_http = nil
|
|
357
|
+
result_data = nil
|
|
321
358
|
# start a block to be able to retry the actual HTTP request in case of OAuth token expiration
|
|
322
359
|
begin
|
|
323
360
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
|
@@ -360,14 +397,14 @@ module Aspera
|
|
|
360
397
|
file_saved = false
|
|
361
398
|
# make http request (pipelined)
|
|
362
399
|
http_session.request(req) do |response|
|
|
363
|
-
|
|
364
|
-
result_mime = self.class.parse_header(
|
|
365
|
-
Log.log.debug{"response: code=#{
|
|
400
|
+
result_http = response
|
|
401
|
+
result_mime = self.class.parse_header(result_http['Content-Type'] || MIME_TEXT)[:type]
|
|
402
|
+
Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime}, mime2= #{response['Content-Type']}"}
|
|
366
403
|
# JSON data needs to be parsed, in case it contains an error code
|
|
367
404
|
if !save_to_file.nil? &&
|
|
368
|
-
|
|
405
|
+
result_http.code.to_s.start_with?('2') &&
|
|
369
406
|
!JSON_DECODE.include?(result_mime)
|
|
370
|
-
total_size =
|
|
407
|
+
total_size = result_http['Content-Length']&.to_i
|
|
371
408
|
Log.log.debug('before write file')
|
|
372
409
|
target_file = save_to_file
|
|
373
410
|
# override user's path to path in header
|
|
@@ -385,7 +422,7 @@ module Aspera
|
|
|
385
422
|
FileUtils.mkdir_p(File.dirname(target_file_tmp))
|
|
386
423
|
limiter = TimerLimiter.new(0.5)
|
|
387
424
|
File.open(target_file_tmp, 'wb') do |file|
|
|
388
|
-
|
|
425
|
+
result_http.read_body do |fragment|
|
|
389
426
|
file.write(fragment)
|
|
390
427
|
written_size += fragment.length
|
|
391
428
|
RestParameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
|
@@ -398,32 +435,32 @@ module Aspera
|
|
|
398
435
|
file_saved = true
|
|
399
436
|
end
|
|
400
437
|
end
|
|
401
|
-
Log.log.debug{"result: code=#{
|
|
438
|
+
Log.log.debug{"result: code=#{result_http.code} mime=#{result_mime}"}
|
|
402
439
|
# sometimes there is a UTF8 char (e.g. (c) ), TODO : related to mime type encoding ?
|
|
403
|
-
#
|
|
404
|
-
# Log.log.debug{"result: body=#{
|
|
440
|
+
# result_http.body.force_encoding('UTF-8') if result_http.body.is_a?(String)
|
|
441
|
+
# Log.log.debug{"result: body=#{result_http.body}"}
|
|
405
442
|
case result_mime
|
|
406
443
|
when *JSON_DECODE
|
|
407
|
-
|
|
408
|
-
Log.dump(:result_data,
|
|
444
|
+
result_data = JSON.parse(result_http.body) rescue result_http.body
|
|
445
|
+
Log.dump(:result_data, result_data)
|
|
409
446
|
else # when MIME_TEXT
|
|
410
|
-
|
|
447
|
+
result_data = result_http.body
|
|
411
448
|
end
|
|
412
|
-
RestErrorAnalyzer.instance.raise_on_error(req,
|
|
449
|
+
RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
|
|
413
450
|
unless file_saved || save_to_file.nil?
|
|
414
451
|
FileUtils.mkdir_p(File.dirname(save_to_file))
|
|
415
|
-
File.write(save_to_file,
|
|
452
|
+
File.write(save_to_file, result_http.body, binmode: true)
|
|
416
453
|
end
|
|
417
454
|
rescue RestCallError => e
|
|
418
455
|
do_retry = false
|
|
419
456
|
# AoC have some timeout , like Connect to platform.bss.asperasoft.com:443 ...
|
|
420
457
|
do_retry ||= true if e.response.body.include?('failed: connect timed out') && RestParameters.instance.retry_on_timeout
|
|
421
458
|
# AoC sometimes not available
|
|
422
|
-
do_retry ||= true if RestParameters.instance.retry_on_unavailable && UNAVAILABLE_CODES.include?(
|
|
459
|
+
do_retry ||= true if RestParameters.instance.retry_on_unavailable && UNAVAILABLE_CODES.include?(result_http.code.to_s)
|
|
423
460
|
# possibility to retry anything if it fails
|
|
424
461
|
do_retry ||= true if RestParameters.instance.retry_on_error
|
|
425
462
|
# not authorized: oauth token expired
|
|
426
|
-
if @not_auth_codes.include?(
|
|
463
|
+
if @not_auth_codes.include?(result_http.code.to_s) && @auth_params[:type].eql?(:oauth2)
|
|
427
464
|
begin
|
|
428
465
|
# try to use refresh token
|
|
429
466
|
req['Authorization'] = oauth.authorization(refresh: true)
|
|
@@ -462,15 +499,21 @@ module Aspera
|
|
|
462
499
|
body: body,
|
|
463
500
|
content_type: content_type,
|
|
464
501
|
save_to_file: save_to_file,
|
|
465
|
-
|
|
466
|
-
headers: headers
|
|
502
|
+
exception: exception,
|
|
503
|
+
headers: headers,
|
|
504
|
+
ret: ret
|
|
467
505
|
)
|
|
468
506
|
end
|
|
469
507
|
# raise exception if could not retry and not return error in result
|
|
470
|
-
raise e
|
|
508
|
+
raise e if exception
|
|
471
509
|
end
|
|
472
|
-
Log.log.debug{"result=http:#{
|
|
473
|
-
return
|
|
510
|
+
Log.log.debug{"result=http:#{result_http}, data:#{result_data.class}"}
|
|
511
|
+
return case ret
|
|
512
|
+
when :data then result_data
|
|
513
|
+
when :resp then result_http
|
|
514
|
+
when :both then [result_data, result_http]
|
|
515
|
+
else Aspera.error_unexpected_value(ret){'Type of result for REST'}
|
|
516
|
+
end
|
|
474
517
|
end
|
|
475
518
|
|
|
476
519
|
#
|
|
@@ -478,31 +521,36 @@ module Aspera
|
|
|
478
521
|
# If specific elements are needed, then use the full `call` method
|
|
479
522
|
#
|
|
480
523
|
|
|
481
|
-
|
|
482
|
-
|
|
524
|
+
# Create: `POST`
|
|
525
|
+
def create(subpath, params, **kwargs)
|
|
526
|
+
return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
483
527
|
end
|
|
484
528
|
|
|
485
|
-
|
|
486
|
-
|
|
529
|
+
# Read: `GET`
|
|
530
|
+
def read(subpath, query = nil, **kwargs)
|
|
531
|
+
return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query, **kwargs)
|
|
487
532
|
end
|
|
488
533
|
|
|
489
|
-
|
|
490
|
-
|
|
534
|
+
# Update: `PUT`
|
|
535
|
+
def update(subpath, params, **kwargs)
|
|
536
|
+
return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)
|
|
491
537
|
end
|
|
492
538
|
|
|
493
|
-
|
|
494
|
-
|
|
539
|
+
# Delete: `DELETE`
|
|
540
|
+
def delete(subpath, params = nil, **kwargs)
|
|
541
|
+
return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params, **kwargs)
|
|
495
542
|
end
|
|
496
543
|
|
|
497
|
-
|
|
498
|
-
|
|
544
|
+
# Cancel: `CANCEL`
|
|
545
|
+
def cancel(subpath, **kwargs)
|
|
546
|
+
return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => MIME_JSON}, **kwargs)
|
|
499
547
|
end
|
|
500
548
|
|
|
501
549
|
# Query entity by general search (read with parameter `q`)
|
|
502
550
|
# TODO: not generic enough ? move somewhere ? inheritance ?
|
|
503
|
-
# @param subpath
|
|
504
|
-
# @param search_name
|
|
505
|
-
# @param query
|
|
551
|
+
# @param subpath [String] Path of entity in API
|
|
552
|
+
# @param search_name [String] Name of searched entity
|
|
553
|
+
# @param query [Hash] Additional search query parameters
|
|
506
554
|
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
507
555
|
def lookup_by_name(subpath, search_name, query: nil)
|
|
508
556
|
query = {} if query.nil?
|
|
@@ -513,17 +561,24 @@ module Aspera
|
|
|
513
561
|
Aspera.assert_type(matching_items, Array)
|
|
514
562
|
case matching_items.length
|
|
515
563
|
when 1 then return matching_items.first
|
|
516
|
-
when 0 then raise %Q{
|
|
564
|
+
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
517
565
|
else
|
|
518
566
|
# multiple case insensitive partial matches, try case insensitive full match
|
|
519
567
|
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
520
568
|
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
|
521
569
|
case name_matches.length
|
|
522
570
|
when 1 then return name_matches.first
|
|
523
|
-
when 0 then raise %Q(#{subpath}:
|
|
571
|
+
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.)
|
|
524
572
|
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
|
525
573
|
end
|
|
526
574
|
end
|
|
527
575
|
end
|
|
576
|
+
|
|
577
|
+
# Content-Type that are JSON
|
|
578
|
+
JSON_DECODE = [MIME_JSON, 'application/vnd.api+json', 'application/x-javascript'].freeze
|
|
579
|
+
|
|
580
|
+
UNAVAILABLE_CODES = ['503']
|
|
581
|
+
|
|
582
|
+
private_constant :JSON_DECODE, :UNAVAILABLE_CODES
|
|
528
583
|
end
|
|
529
584
|
end
|
|
@@ -27,13 +27,13 @@ module Aspera
|
|
|
27
27
|
# Use this method to analyze a EST result and raise an exception
|
|
28
28
|
# Analyzes REST call response and raises a RestCallError exception
|
|
29
29
|
# if HTTP result code is not 2XX
|
|
30
|
-
def raise_on_error(req,
|
|
31
|
-
Log.log.debug{"raise_on_error #{req.method} #{req.path} #{
|
|
30
|
+
def raise_on_error(req, data, http)
|
|
31
|
+
Log.log.debug{"raise_on_error #{req.method} #{req.path} #{http.code}"}
|
|
32
32
|
call_context = {
|
|
33
33
|
messages: [],
|
|
34
34
|
request: req,
|
|
35
|
-
response:
|
|
36
|
-
data:
|
|
35
|
+
response: http,
|
|
36
|
+
data: data
|
|
37
37
|
}
|
|
38
38
|
# multiple error messages can be found
|
|
39
39
|
# analyze errors from provided handlers
|
data/lib/aspera/ssh.rb
CHANGED
|
@@ -11,19 +11,21 @@ module Aspera
|
|
|
11
11
|
class Error < Aspera::Error
|
|
12
12
|
end
|
|
13
13
|
class << self
|
|
14
|
+
# HACK: disable some key type
|
|
14
15
|
def disable_ed25519_keys
|
|
15
16
|
Log.log.debug('Disabling SSH ed25519 user keys')
|
|
16
17
|
old_verbose = $VERBOSE
|
|
17
18
|
$VERBOSE = nil
|
|
18
19
|
Net::SSH::Authentication::Session.class_eval do
|
|
19
20
|
define_method(:default_keys) do
|
|
20
|
-
%w[
|
|
21
|
+
%w[.ssh .ssh2].product(%w[rsa dsa ecdsa]).map{"~/#{_1}/id_#{_2}"}.freeze
|
|
21
22
|
end
|
|
22
23
|
private(:default_keys)
|
|
23
24
|
end rescue nil
|
|
24
25
|
$VERBOSE = old_verbose
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# HACK: disable some algorithms
|
|
27
29
|
def disable_ecd_sha2_algorithms
|
|
28
30
|
Log.log.debug('Disabling SSH ecdsa')
|
|
29
31
|
Net::SSH::Transport::Algorithms::ALGORITHMS.each_value{ |a| a.reject!{ |a| a =~ /^ecd(sa|h)-sha2/}}
|
|
@@ -38,6 +40,7 @@ module Aspera
|
|
|
38
40
|
Aspera.assert_type(host, String)
|
|
39
41
|
Aspera.assert_type(username, String)
|
|
40
42
|
Aspera.assert_type(ssh_options, Hash)
|
|
43
|
+
ssh_options[:use_agent] = false unless ssh_options.key?(:use_agent)
|
|
41
44
|
@host = host
|
|
42
45
|
@username = username
|
|
43
46
|
@ssh_options = ssh_options
|
|
@@ -62,7 +65,7 @@ module Aspera
|
|
|
62
65
|
exit_code = data.read_long
|
|
63
66
|
next if exit_code.zero?
|
|
64
67
|
error_message = "#{cmd}: exit #{exit_code}, #{error.join.chomp}"
|
|
65
|
-
raise Error, error_message if
|
|
68
|
+
raise Error, error_message if exception
|
|
66
69
|
# Happens when windows user hasn't logged in and created home account.
|
|
67
70
|
error_message += "\nHint: home not created in Windows?" if data.include?('Could not chdir to home directory')
|
|
68
71
|
Log.log.debug(error_message)
|
|
@@ -81,6 +84,6 @@ module Aspera
|
|
|
81
84
|
end
|
|
82
85
|
end
|
|
83
86
|
|
|
84
|
-
#
|
|
85
|
-
Aspera::Ssh.disable_ed25519_keys if ENV.fetch('ASCLI_ENABLE_ED25519', '
|
|
87
|
+
# Deactivate ed25519 and ecdsa private keys from SSH identities, as it usually causes problems
|
|
88
|
+
Aspera::Ssh.disable_ed25519_keys if Gem::Specification.find_all_by_name('ed25519').none? || ENV.fetch('ASCLI_ENABLE_ED25519', 'true').eql?('false')
|
|
86
89
|
Aspera::Ssh.disable_ecd_sha2_algorithms if defined?(JRUBY_VERSION) && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')
|
data/lib/aspera/ssl.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'aspera/assert'
|
|
5
|
+
require 'aspera/log'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
# Give possibility to globally override SSL options
|
|
9
|
+
module SSL
|
|
10
|
+
@extra_options = 0
|
|
11
|
+
class << self
|
|
12
|
+
@extra_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
|
13
|
+
attr_reader :extra_options
|
|
14
|
+
|
|
15
|
+
def option_list=(v)
|
|
16
|
+
Aspera.assert_type(v, Array){'ssl_options'}
|
|
17
|
+
v.each do |opt|
|
|
18
|
+
Aspera.assert_type(opt, String, Integer){'Expected String or Integer in ssl_options'}
|
|
19
|
+
case opt
|
|
20
|
+
when Integer
|
|
21
|
+
@extra_options = opt
|
|
22
|
+
when String
|
|
23
|
+
name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
|
|
24
|
+
raise Cli::BadArgument, "Unknown ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
|
|
25
|
+
if opt.start_with?('-')
|
|
26
|
+
@extra_options &= ~OpenSSL::SSL.const_get(name)
|
|
27
|
+
else
|
|
28
|
+
@extra_options |= OpenSSL::SSL.const_get(name)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
def set_params(params = {})
|
|
35
|
+
super(params)
|
|
36
|
+
self.options = Aspera::SSL.extra_options unless Aspera::SSL.extra_options.nil?
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
OpenSSL::SSL::SSLContext.prepend(Aspera::SSL)
|