aspera-cli 4.24.1 → 4.24.2

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 (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +15 -2
  4. data/README.md +745 -436
  5. data/bin/ascli +20 -1
  6. data/bin/asession +23 -27
  7. data/lib/aspera/agent/base.rb +10 -21
  8. data/lib/aspera/agent/connect.rb +2 -3
  9. data/lib/aspera/agent/desktop.rb +2 -2
  10. data/lib/aspera/agent/direct.rb +49 -32
  11. data/lib/aspera/agent/factory.rb +31 -0
  12. data/lib/aspera/api/aoc.rb +79 -49
  13. data/lib/aspera/api/faspex.rb +212 -0
  14. data/lib/aspera/api/node.rb +99 -84
  15. data/lib/aspera/ascp/installation.rb +22 -21
  16. data/lib/aspera/ascp/management.rb +119 -23
  17. data/lib/aspera/assert.rb +14 -8
  18. data/lib/aspera/cli/extended_value.rb +15 -15
  19. data/lib/aspera/cli/formatter.rb +7 -5
  20. data/lib/aspera/cli/hints.rb +8 -0
  21. data/lib/aspera/cli/info.rb +4 -4
  22. data/lib/aspera/cli/main.rb +55 -70
  23. data/lib/aspera/cli/manager.rb +7 -4
  24. data/lib/aspera/cli/plugins/alee.rb +2 -1
  25. data/lib/aspera/cli/plugins/aoc.rb +110 -186
  26. data/lib/aspera/cli/plugins/ats.rb +4 -4
  27. data/lib/aspera/cli/plugins/base.rb +335 -0
  28. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  29. data/lib/aspera/cli/plugins/config.rb +249 -220
  30. data/lib/aspera/cli/plugins/console.rb +15 -15
  31. data/lib/aspera/cli/plugins/cos.rb +2 -2
  32. data/lib/aspera/cli/plugins/factory.rb +78 -0
  33. data/lib/aspera/cli/plugins/faspex.rb +17 -20
  34. data/lib/aspera/cli/plugins/faspex5.rb +79 -193
  35. data/lib/aspera/cli/plugins/faspio.rb +14 -13
  36. data/lib/aspera/cli/plugins/httpgw.rb +13 -12
  37. data/lib/aspera/cli/plugins/node.rb +34 -32
  38. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  39. data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
  40. data/lib/aspera/cli/plugins/preview.rb +4 -4
  41. data/lib/aspera/cli/plugins/server.rb +15 -13
  42. data/lib/aspera/cli/plugins/shares.rb +18 -15
  43. data/lib/aspera/cli/sync_actions.rb +1 -1
  44. data/lib/aspera/cli/transfer_agent.rb +24 -20
  45. data/lib/aspera/cli/transfer_progress.rb +6 -6
  46. data/lib/aspera/cli/version.rb +3 -3
  47. data/lib/aspera/cli/wizard.rb +65 -53
  48. data/lib/aspera/colors.rb +6 -0
  49. data/lib/aspera/command_line_builder.rb +45 -50
  50. data/lib/aspera/command_line_converter.rb +2 -1
  51. data/lib/aspera/coverage.rb +1 -1
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +10 -7
  54. data/lib/aspera/faspex_gw.rb +6 -4
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/keychain/macos_security.rb +1 -1
  57. data/lib/aspera/log.rb +37 -9
  58. data/lib/aspera/nagios.rb +1 -1
  59. data/lib/aspera/oauth/base.rb +17 -10
  60. data/lib/aspera/oauth/factory.rb +8 -8
  61. data/lib/aspera/oauth/web.rb +2 -2
  62. data/lib/aspera/products/connect.rb +4 -3
  63. data/lib/aspera/products/desktop.rb +1 -4
  64. data/lib/aspera/products/other.rb +9 -1
  65. data/lib/aspera/products/transferd.rb +0 -1
  66. data/lib/aspera/rest.rb +126 -83
  67. data/lib/aspera/ssh.rb +3 -3
  68. data/lib/aspera/sync/args.schema.yaml +46 -3
  69. data/lib/aspera/sync/conf.schema.yaml +130 -94
  70. data/lib/aspera/sync/operations.rb +16 -16
  71. data/lib/aspera/temp_file_manager.rb +17 -5
  72. data/lib/aspera/transfer/error.rb +16 -7
  73. data/lib/aspera/transfer/parameters.rb +34 -20
  74. data/lib/aspera/transfer/resumer.rb +74 -0
  75. data/lib/aspera/transfer/spec.rb +4 -3
  76. data/lib/aspera/transfer/spec.schema.yaml +132 -51
  77. data/lib/aspera/transfer/spec_doc.rb +41 -35
  78. data/lib/aspera/uri_reader.rb +1 -1
  79. data/lib/aspera/web_auth.rb +6 -6
  80. data.tar.gz.sig +0 -0
  81. metadata +9 -7
  82. metadata.gz.sig +0 -0
  83. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  84. data/lib/aspera/cli/plugin.rb +0 -333
  85. data/lib/aspera/cli/plugin_factory.rb +0 -81
  86. data/lib/aspera/resumer.rb +0 -77
  87. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -13,6 +13,7 @@ module Aspera
13
13
  # OAuth 2.0 Authorization Framework: https://tools.ietf.org/html/rfc6749
14
14
  # Bearer Token Usage: https://tools.ietf.org/html/rfc6750
15
15
  class Base
16
+ Aspera.require_method!(:create_token)
16
17
  # @param ** Parameters for REST
17
18
  # @param client_id [String, nil]
18
19
  # @param client_secret [String, nil]
@@ -31,8 +32,7 @@ module Aspera
31
32
  cache_ids: nil,
32
33
  **rest_params
33
34
  )
34
- Aspera.assert(respond_to?(:create_token), 'create_token method must be defined', type: InternalError)
35
- # this is the OAuth API
35
+ # This is the OAuth API
36
36
  @api = Rest.new(**rest_params)
37
37
  @scope = nil
38
38
  @token_cache_id = nil
@@ -43,6 +43,7 @@ module Aspera
43
43
  @use_query = use_query
44
44
  @base_cache_ids = cache_ids.nil? ? [] : cache_ids.clone
45
45
  Aspera.assert_type(@base_cache_ids, Array)
46
+ # TODO: this shall be done in class, using cache_ids
46
47
  @base_cache_ids.push(@api.auth_params[:username]) if @api.auth_params.key?(:username)
47
48
  @base_cache_ids.compact!
48
49
  @base_cache_ids.freeze
@@ -56,16 +57,20 @@ module Aspera
56
57
  @token_cache_id = Factory.cache_id(@api.base_url, self.class, @base_cache_ids, @scope)
57
58
  end
58
59
 
59
- attr_reader :scope, :api, :path_token
60
+ attr_reader :scope, :api, :path_token, :client_id
60
61
 
61
62
  # helper method to create token as per RFC
62
63
  def create_token_call(creation_params)
63
64
  Log.log.debug{'Generating a new token'.bg_green}
64
- payload = {content_type: Rest::MIME_WWW}
65
- if @use_query
66
- payload[:query] = creation_params
65
+ payload = if @use_query
66
+ {
67
+ query: creation_params
68
+ }
67
69
  else
68
- payload[:body] = creation_params
70
+ {
71
+ content_type: Rest::MIME_WWW,
72
+ body: creation_params
73
+ }
69
74
  end
70
75
  return @api.call(
71
76
  operation: 'POST',
@@ -75,12 +80,13 @@ module Aspera
75
80
  )
76
81
  end
77
82
 
78
- # @return Hash with optional general parameters
83
+ # @param add_secret [Boolean] Add secret in default call parameters
84
+ # @return [Hash] Optional general parameters
79
85
  def optional_scope_client_id(add_secret: false)
80
86
  call_params = {}
81
87
  call_params[:scope] = @scope unless @scope.nil?
82
88
  call_params[:client_id] = @client_id unless @client_id.nil?
83
- call_params[:client_secret] = @client_secret if add_secret && !@client_id.nil?
89
+ call_params[:client_secret] = @client_secret unless !add_secret || @client_id.nil? || @client_secret.nil?
84
90
  return call_params
85
91
  end
86
92
 
@@ -106,6 +112,7 @@ module Aspera
106
112
  # `direct` agent is equipped with refresh code
107
113
  # an API was already called, but failed, we need to regenerate or refresh
108
114
  if refresh || token_info[:expired]
115
+ Log.log.trace1{"refresh: #{refresh} expired: #{token_info[:expired]}"}
109
116
  refresh_token = nil
110
117
  if token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
111
118
  # save possible refresh token, before deleting the cache
@@ -118,7 +125,7 @@ module Aspera
118
125
  if !refresh_token.nil?
119
126
  Log.log.info{"refresh=[#{refresh_token}]".bg_green}
120
127
  # NOTE: AoC admin token has no refresh, and lives by default 1800secs
121
- resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
128
+ resp = create_token_call(optional_scope_client_id(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
122
129
  if resp[:http].code.start_with?('2')
123
130
  # save only if success
124
131
  json_data = resp[:http].body
@@ -63,10 +63,10 @@ module Aspera
63
63
  @decoders = []
64
64
  # default parameters, others can be added by handlers
65
65
  @parameters = {
66
- # tokens older than 30 minutes will be discarded from cache
67
- token_cache_expiry_sec: 1800,
68
- # tokens valid for less than this duration will be regenerated
69
- token_expiration_guard_sec: 120
66
+ # tokens older than this duration in sec. will be discarded from cache
67
+ token_cache_max_age: 1800,
68
+ # tokens valid for less than this duration in sec. will be regenerated
69
+ token_refresh_threshold: 120
70
70
  }
71
71
  end
72
72
 
@@ -77,7 +77,7 @@ module Aspera
77
77
  def persist_mgr=(manager)
78
78
  @persist = manager
79
79
  # cleanup expired tokens
80
- @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, @parameters[:token_cache_expiry_sec])
80
+ @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, @parameters[:token_cache_max_age])
81
81
  end
82
82
 
83
83
  def persist_mgr
@@ -108,7 +108,7 @@ module Aspera
108
108
  end
109
109
  end
110
110
 
111
- # get token information from cache
111
+ # Get token information from cache
112
112
  # @param id [String] identifier of token
113
113
  # @return [Hash] token internal information , including Date object for `expiration_date`
114
114
  def get_token_info(id)
@@ -118,7 +118,6 @@ module Aspera
118
118
  Aspera.assert_type(token_data, Hash)
119
119
  decoded_token = decode_token(token_data[TOKEN_FIELD])
120
120
  info = {data: token_data}
121
- Log.dump(:decoded_token, decoded_token)
122
121
  if decoded_token.is_a?(Hash)
123
122
  info[:decoded] = decoded_token
124
123
  # TODO: move date decoding to token decoder ?
@@ -129,9 +128,10 @@ module Aspera
129
128
  unless expiration_date.nil?
130
129
  info[:expiration] = expiration_date
131
130
  info[:ttl_sec] = expiration_date - Time.now
132
- info[:expired] = info[:ttl_sec] < @parameters[:token_expiration_guard_sec]
131
+ info[:expired] = info[:ttl_sec] < @parameters[:token_refresh_threshold]
133
132
  end
134
133
  end
134
+ Log.dump(:token_info, info)
135
135
  return info
136
136
  end
137
137
 
@@ -9,7 +9,7 @@ module Aspera
9
9
  # Authentication using Web browser
10
10
  class Web < Base
11
11
  class << self
12
- attr_accessor :additionnal_info
12
+ attr_accessor :additional_info
13
13
  end
14
14
  # @param redirect_uri url to receive the code after auth (to be exchanged for token)
15
15
  # @param path_authorize path to login page on web app
@@ -37,7 +37,7 @@ module Aspera
37
37
  # here, we need a human to authorize on a web page
38
38
  Log.log.info{"login_page_url=#{login_page_url}".bg_red.gray}
39
39
  # start a web server to receive request code
40
- web_server = WebAuth.new(@redirect_uri, self.class.additionnal_info)
40
+ web_server = WebAuth.new(@redirect_uri, self.class.additional_info)
41
41
  # start browser on login page
42
42
  Environment.instance.open_uri(login_page_url)
43
43
  # wait for code in request
@@ -48,11 +48,12 @@ module Aspera
48
48
  end
49
49
  end
50
50
 
51
+ # Base URL for CDN of Connect
51
52
  def cdn_api
52
53
  Rest.new(base_url: CDN_BASE_URL)
53
54
  end
54
55
 
55
- # retrieve structure from cloud (CDN) with all versions available
56
+ # Retrieve structure from cloud (CDN) with all versions available
56
57
  def versions
57
58
  if @connect_versions.nil?
58
59
  javascript = cdn_api.call(operation: 'GET', subpath: VERSION_INFO_FILE)
@@ -74,10 +75,10 @@ module Aspera
74
75
  @connect_versions = nil
75
76
  end
76
77
 
77
- VERSION_INFO_FILE = 'connectversions.js' # cspell: disable-line
78
78
  CDN_BASE_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
79
+ VERSION_INFO_FILE = 'connectversions.js' # cspell: disable-line
79
80
 
80
- private_constant :VERSION_INFO_FILE, :CDN_BASE_URL
81
+ private_constant :CDN_BASE_URL, :VERSION_INFO_FILE
81
82
  end
82
83
  end
83
84
  end
@@ -8,6 +8,7 @@ module Aspera
8
8
  class Desktop
9
9
  APP_NAME = 'IBM Aspera for Desktop'
10
10
  APP_IDENTIFIER = 'com.ibm.software.aspera.desktop'
11
+ LOG_FILENAME = 'ibm-aspera-desktop.log'
11
12
  class << self
12
13
  # standard folder locations
13
14
  def locations
@@ -20,10 +21,6 @@ module Aspera
20
21
  else []
21
22
  end.map{ |i| i.merge({expected: APP_NAME})}
22
23
  end
23
-
24
- def log_file
25
- File.join(Dir.home, 'Library', 'Logs', APP_IDENTIFIER, 'ibm-aspera-desktop.log')
26
- end
27
24
  end
28
25
  end
29
26
  end
@@ -54,8 +54,14 @@ module Aspera
54
54
  }]
55
55
  end
56
56
  class << self
57
+ # Find installed products and provide paths for it.
58
+ # @param scan_locations [Array] Array of Hash with keys: expected, app_root, sub_bin, ascp_path, name, version
59
+ # @return [Array] of products found, with filled missing fields
60
+ # @raise Exception if no installed product found
57
61
  def find(scan_locations)
58
- scan_locations.select do |item|
62
+ product_names = []
63
+ found = scan_locations.select do |item|
64
+ product_names.push(item[:expected]) unless product_names.include?(item[:expected])
59
65
  # skip if not main folder
60
66
  Log.log.trace1{"Checking #{item[:app_root]}"}
61
67
  next false unless Dir.exist?(item[:app_root])
@@ -75,6 +81,8 @@ module Aspera
75
81
  end
76
82
  true # select this version
77
83
  end
84
+ raise "Product: #{product_names.join(', ')} not found, please install." if found.empty?
85
+ found
78
86
  end
79
87
  end
80
88
  end
@@ -30,7 +30,6 @@ module Aspera
30
30
  # @return the path to folder where SDK is installed
31
31
  def sdk_directory
32
32
  Aspera.assert(!@sdk_dir.nil?){'SDK path was not initialized'}
33
- FileUtils.mkdir_p(@sdk_dir)
34
33
  @sdk_dir
35
34
  end
36
35
 
data/lib/aspera/rest.rb CHANGED
@@ -54,80 +54,91 @@ module Aspera
54
54
  # rest call errors are raised as exception RestCallError
55
55
  # and error are analyzed in RestErrorAnalyzer
56
56
  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)
57
+ # Error message when entity not found (TODO: use specific exception)
63
58
  ENTITY_NOT_FOUND = 'No such'
64
59
 
65
60
  MIME_JSON = 'application/json'
66
61
  MIME_WWW = 'application/x-www-form-urlencoded'
67
62
  MIME_TEXT = 'text/plain'
68
63
 
69
- # Content-Type that are JSON
70
- JSON_DECODE = [MIME_JSON, 'application/vnd.api+json', 'application/x-javascript'].freeze
71
-
72
- UNAVAILABLE_CODES = ['503']
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
- # Build a parameter list prefixed with "[]"
79
- # @param values [Array] list of values
80
- def array_params(values)
81
- return [ARRAY_PARAMS].concat(values)
82
- end
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
+ # 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
- # encode array [] parameters
90
- # @param query [Hash,Array]
91
- def build_uri(url, query = nil)
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.assert(%w[http https].include?(uri.scheme)){"REST endpoint shall be http/s not #{uri.scheme}"}
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
- query_array = []
97
- case query
98
- when Hash
99
- query.each do |k, v|
100
- case v
101
- when Array
102
- # support array for query parameter, there is no standard. Either p[]=1&p[]=2, or p=1&p=2
103
- suffix = array_params?(v) ? v.shift : ''
104
- v.each do |e|
105
- query_array.push(["#{k}#{suffix}", e])
106
- end
107
- else
108
- query_array.push([k, v])
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
- # Does not support arrays in query string, no standard, e.g. PHP's way is p[]=1&p[]=2
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 |pair, h|
128
- key = pair.first
129
- raise "Array not supported in query string: #{key}" if key.include?('[]') || h.key?(key)
130
- h[key] = pair.last
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 = build_uri(base_url)
139
- # this honors http_proxy env var
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
- # set http options in callback, such as timeout and cert. verification
154
+ # Set http options in callback, such as timeout and cert. verification
143
155
  RestParameters.instance.session_cb&.call(http_session)
144
- # manually start session for keep alive (if supported by server, else, session is closed every time)
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
- # Parse a header string as returned by HTTP
178
- # @param header [String] header string, e.g. "application/json; charset=utf-8"
179
- # @return [Hash] parsed header with type and parameters
180
- # {type: 'application/json', parameters: {charset: 'utf-8'}}
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
- type, *params = header.split(/;\s*/)
183
- parameters = params.map do |param|
184
- one = param.split(/=\s*/)
185
- one[0] = one[0].to_sym
186
- one[1] = one[1].gsub(/\A"|"\z/, '')
187
- one
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: type.downcase, parameters: parameters}
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
- attr_reader :base_url
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
@@ -278,7 +307,7 @@ module Aspera
278
307
  # @param body [Hash, String] body parameters
279
308
  # @param headers [Hash] additional headers (override Content-Type)
280
309
  # @param save_to_file (filepath)
281
- # @param return_error (bool)
310
+ # @param exception (bool) true, error raise exception
282
311
  def call(
283
312
  operation:,
284
313
  subpath: nil,
@@ -287,12 +316,14 @@ module Aspera
287
316
  body: nil,
288
317
  headers: nil,
289
318
  save_to_file: nil,
290
- return_error: false
319
+ exception: true
291
320
  )
292
321
  subpath = subpath.to_s if subpath.is_a?(Symbol)
293
322
  subpath = '' if subpath.nil?
294
323
  Log.log.debug{"call #{operation} [#{subpath}]".red.bold.bg_green}
295
- Log.dump(:body, body)
324
+ Log.dump(:body, body, level: :trace1)
325
+ Log.dump(:query, query, level: :trace1)
326
+ Log.dump(:headers, headers, level: :trace1)
296
327
  Aspera.assert_type(subpath, String)
297
328
  if headers.nil?
298
329
  headers = @headers.clone
@@ -462,12 +493,12 @@ module Aspera
462
493
  body: body,
463
494
  content_type: content_type,
464
495
  save_to_file: save_to_file,
465
- return_error: return_error,
496
+ exception: exception,
466
497
  headers: headers
467
498
  )
468
499
  end
469
500
  # raise exception if could not retry and not return error in result
470
- raise e unless return_error
501
+ raise e if exception
471
502
  end
472
503
  Log.log.debug{"result=http:#{result[:http]}, data:#{result[:data].class}"}
473
504
  return result
@@ -478,31 +509,36 @@ module Aspera
478
509
  # If specific elements are needed, then use the full `call` method
479
510
  #
480
511
 
481
- def create(subpath, params)
482
- return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON)[:data]
512
+ # Create: `POST`
513
+ def create(subpath, params, **kwargs)
514
+ return call(operation: 'POST', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)[:data]
483
515
  end
484
516
 
485
- def read(subpath, query = nil)
486
- return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query)[:data]
517
+ # Read: `GET`
518
+ def read(subpath, query = nil, **kwargs)
519
+ return call(operation: 'GET', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: query, **kwargs)[:data]
487
520
  end
488
521
 
489
- def update(subpath, params)
490
- return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON)[:data]
522
+ # Update: `PUT`
523
+ def update(subpath, params, **kwargs)
524
+ return call(operation: 'PUT', subpath: subpath, headers: {'Accept' => MIME_JSON}, body: params, content_type: MIME_JSON, **kwargs)[:data]
491
525
  end
492
526
 
493
- def delete(subpath, params = nil)
494
- return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params)[:data]
527
+ # Delete: `DELETE`
528
+ def delete(subpath, params = nil, **kwargs)
529
+ return call(operation: 'DELETE', subpath: subpath, headers: {'Accept' => MIME_JSON}, query: params, **kwargs)[:data]
495
530
  end
496
531
 
497
- def cancel(subpath)
498
- return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => MIME_JSON})[:data]
532
+ # Cancel: `CANCEL`
533
+ def cancel(subpath, **kwargs)
534
+ return call(operation: 'CANCEL', subpath: subpath, headers: {'Accept' => MIME_JSON}, **kwargs)[:data]
499
535
  end
500
536
 
501
537
  # Query entity by general search (read with parameter `q`)
502
538
  # TODO: not generic enough ? move somewhere ? inheritance ?
503
- # @param subpath path of entity in API
504
- # @param search_name name of searched entity
505
- # @param query additional search query parameters
539
+ # @param subpath [String] Path of entity in API
540
+ # @param search_name [String] Name of searched entity
541
+ # @param query [Hash] Additional search query parameters
506
542
  # @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
507
543
  def lookup_by_name(subpath, search_name, query: nil)
508
544
  query = {} if query.nil?
@@ -520,10 +556,17 @@ module Aspera
520
556
  name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
521
557
  case name_matches.length
522
558
  when 1 then return name_matches.first
523
- 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.)
559
+ 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
560
  else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
525
561
  end
526
562
  end
527
563
  end
564
+
565
+ # Content-Type that are JSON
566
+ JSON_DECODE = [MIME_JSON, 'application/vnd.api+json', 'application/x-javascript'].freeze
567
+
568
+ UNAVAILABLE_CODES = ['503']
569
+
570
+ private_constant :JSON_DECODE, :UNAVAILABLE_CODES
528
571
  end
529
572
  end
data/lib/aspera/ssh.rb CHANGED
@@ -17,7 +17,7 @@ module Aspera
17
17
  $VERBOSE = nil
18
18
  Net::SSH::Authentication::Session.class_eval do
19
19
  define_method(:default_keys) do
20
- %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa].freeze
20
+ %w[.ssh .ssh2].product(%w[rsa dsa ecdsa]).map{"~/#{_1}/id_#{_2}"}.freeze
21
21
  end
22
22
  private(:default_keys)
23
23
  end rescue nil
@@ -81,6 +81,6 @@ module Aspera
81
81
  end
82
82
  end
83
83
 
84
- # HACK: deactivate ed25519 and ecdsa private keys from SSH identities, as it usually causes problems
85
- Aspera::Ssh.disable_ed25519_keys if ENV.fetch('ASCLI_ENABLE_ED25519', 'false').eql?('false')
84
+ # Deactivate ed25519 and ecdsa private keys from SSH identities, as it usually causes problems
85
+ Aspera::Ssh.disable_ed25519_keys if Gem::Specification.find_all_by_name('ed25519').none?
86
86
  Aspera::Ssh.disable_ecd_sha2_algorithms if defined?(JRUBY_VERSION) && ENV.fetch('ASCLI_ENABLE_ECDSHA2', 'false').eql?('false')