aspera-cli 4.25.3 → 4.26.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 +39 -6
- data/CONTRIBUTING.md +119 -111
- data/README.md +9 -7
- data/lib/aspera/agent/direct.rb +10 -8
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/node.rb +1 -1
- data/lib/aspera/api/alee.rb +1 -0
- data/lib/aspera/api/aoc.rb +13 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +5 -0
- data/lib/aspera/api/faspex.rb +15 -2
- data/lib/aspera/api/httpgw.rb +2 -0
- data/lib/aspera/api/node.rb +82 -29
- data/lib/aspera/ascp/installation.rb +9 -10
- data/lib/aspera/cli/error.rb +8 -0
- data/lib/aspera/cli/formatter.rb +27 -11
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/main.rb +30 -12
- data/lib/aspera/cli/manager.rb +43 -31
- data/lib/aspera/cli/plugins/aoc.rb +7 -5
- data/lib/aspera/cli/plugins/base.rb +2 -79
- data/lib/aspera/cli/plugins/config.rb +2 -1
- data/lib/aspera/cli/plugins/faspex.rb +1 -1
- data/lib/aspera/cli/plugins/faspex5.rb +51 -51
- data/lib/aspera/cli/plugins/node.rb +9 -14
- data/lib/aspera/cli/plugins/shares.rb +4 -2
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/transfer_agent.rb +3 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +2 -1
- data/lib/aspera/dot_container.rb +10 -10
- data/lib/aspera/log.rb +1 -1
- data/lib/aspera/markdown.rb +1 -1
- data/lib/aspera/persistency_folder.rb +1 -1
- data/lib/aspera/rest.rb +34 -49
- data/lib/aspera/rest_list.rb +116 -0
- data/lib/aspera/sync/operations.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +8 -8
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/yaml.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'aspera/cli/plugins/basic_auth'
|
|
4
4
|
require 'aspera/cli/plugins/node'
|
|
5
5
|
require 'aspera/assert'
|
|
6
|
+
require 'aspera/rest_list'
|
|
7
|
+
|
|
6
8
|
module Aspera
|
|
7
9
|
module Cli
|
|
8
10
|
module Plugins
|
|
@@ -128,7 +130,7 @@ module Aspera
|
|
|
128
130
|
when :admin
|
|
129
131
|
api_shares_admin = basic_auth_api(ADMIN_API_PATH)
|
|
130
132
|
admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
|
|
131
|
-
lookup_share = ->(field, value){lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
|
|
133
|
+
lookup_share = ->(field, value){RestList.lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
|
|
132
134
|
case admin_command
|
|
133
135
|
when :node
|
|
134
136
|
return entity_execute(api: api_shares_admin, entity: 'data/nodes')
|
|
@@ -177,7 +179,7 @@ module Aspera
|
|
|
177
179
|
entity_commands = %i[import].freeze
|
|
178
180
|
end
|
|
179
181
|
entity_verb = options.get_next_command(entity_commands)
|
|
180
|
-
lookup_block = ->(field, value){lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
|
|
182
|
+
lookup_block = ->(field, value){RestList.lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
|
|
181
183
|
case entity_verb
|
|
182
184
|
when *ALL_OPS # list, show, delete, create, modify
|
|
183
185
|
display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
|
|
@@ -33,6 +33,9 @@ module Aspera
|
|
|
33
33
|
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
|
34
34
|
|
|
35
35
|
class << self
|
|
36
|
+
# Analyze transfer session statuses and return a global status
|
|
37
|
+
#
|
|
38
|
+
# @param statuses [Array] list of session status, each status is :success or an error message string
|
|
36
39
|
# @return [:success] if all sessions statuses returned by "start" are success
|
|
37
40
|
# @return [Exception] if one sessions statuses returned by "start" is failed
|
|
38
41
|
def session_status(statuses)
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Aspera
|
|
|
62
62
|
detection_info = nil
|
|
63
63
|
begin
|
|
64
64
|
Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
|
|
65
|
-
|
|
65
|
+
RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
|
|
66
66
|
detection_info = plugin_klass.detect(app_url)
|
|
67
67
|
rescue OpenSSL::SSL::SSLError => e
|
|
68
68
|
Log.log.warn(e.message)
|
|
@@ -78,6 +78,7 @@ module Aspera
|
|
|
78
78
|
# If there is a redirect, then the detector can override the url.
|
|
79
79
|
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
80
80
|
end
|
|
81
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
81
82
|
raise "No known application found at #{app_url}" if found_apps.empty?
|
|
82
83
|
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
|
|
83
84
|
return found_apps
|
data/lib/aspera/dot_container.rb
CHANGED
|
@@ -7,13 +7,14 @@ module Aspera
|
|
|
7
7
|
class DotContainer
|
|
8
8
|
class << self
|
|
9
9
|
# Insert extended value `value` into struct `result` at `path`
|
|
10
|
-
# @param path [String]
|
|
11
|
-
# @param value [
|
|
12
|
-
# @param result [
|
|
13
|
-
# @return [Hash, Array]
|
|
10
|
+
# @param path [Array<String>] Path in container
|
|
11
|
+
# @param value [Object] Value to insert in deep container
|
|
12
|
+
# @param result [nil, Hash, Array] Current container to use (or nil to create a new one)
|
|
13
|
+
# @return [Hash, Array] Container
|
|
14
14
|
def dotted_to_container(path, value, result = nil)
|
|
15
|
+
Aspera.assert_array_all(path, String)
|
|
15
16
|
# Typed keys
|
|
16
|
-
keys = path.
|
|
17
|
+
keys = path.map{ |k| int_or_string(k)}
|
|
17
18
|
# Create, or re-use first level container
|
|
18
19
|
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
19
20
|
# walk the path, and create sub-containers if necessary
|
|
@@ -78,7 +79,7 @@ module Aspera
|
|
|
78
79
|
to_insert = current.map{ |i| i['name']}
|
|
79
80
|
# Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
|
|
80
81
|
elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
|
|
81
|
-
add_elements(path, current.
|
|
82
|
+
add_elements(path, current.to_h{ |i| [i['name'], i['value']]})
|
|
82
83
|
else
|
|
83
84
|
add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
|
|
84
85
|
end
|
|
@@ -86,7 +87,7 @@ module Aspera
|
|
|
86
87
|
to_insert = current
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
|
-
result[path.
|
|
90
|
+
result[path.join(SEPARATOR)] = to_insert unless to_insert.nil?
|
|
90
91
|
end
|
|
91
92
|
result
|
|
92
93
|
end
|
|
@@ -101,8 +102,7 @@ module Aspera
|
|
|
101
102
|
nil
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
private_constant :OPTION_DOTTED_SEPARATOR
|
|
105
|
+
# Dot-path separator: `.`
|
|
106
|
+
SEPARATOR = '.'
|
|
107
107
|
end
|
|
108
108
|
end
|
data/lib/aspera/log.rb
CHANGED
|
@@ -26,7 +26,7 @@ class Logger
|
|
|
26
26
|
# Hash
|
|
27
27
|
# key [Integer] Log level (e.g. 0 for DEBUG)
|
|
28
28
|
# value [Symbol] Uppercase log level label (e.g. :DEBUG)
|
|
29
|
-
SEVERITY_LABEL = Severity.constants.
|
|
29
|
+
SEVERITY_LABEL = Severity.constants.to_h{ |name| [Severity.const_get(name), name]}
|
|
30
30
|
|
|
31
31
|
# Override
|
|
32
32
|
# @param severity [Integer] Log severity as int
|
data/lib/aspera/markdown.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Aspera
|
|
|
33
33
|
|
|
34
34
|
# type: NOTE CAUTION WARNING IMPORTANT TIP INFO
|
|
35
35
|
def admonition(lines, type: 'INFO')
|
|
36
|
-
"> [
|
|
36
|
+
"> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def code(lines, type: 'shell')
|
|
@@ -79,7 +79,7 @@ module Aspera
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def current_items(persist_category)
|
|
82
|
-
current_files(persist_category).
|
|
82
|
+
current_files(persist_category).to_h{ |i| [File.basename(i, FILE_SUFFIX), File.read(i)]}
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
private
|
data/lib/aspera/rest.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Aspera
|
|
|
33
33
|
class RestParameters
|
|
34
34
|
include Singleton
|
|
35
35
|
|
|
36
|
-
attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_on_timeout, :retry_on_unavailable, :retry_max, :retry_sleep, :session_cb, :progress_bar
|
|
36
|
+
attr_accessor :user_agent, :download_partial_suffix, :retry_on_error, :retry_on_timeout, :retry_on_unavailable, :retry_max, :retry_sleep, :session_cb, :progress_bar, :spinner_cb
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
@@ -47,6 +47,7 @@ module Aspera
|
|
|
47
47
|
@retry_sleep = 4
|
|
48
48
|
@session_cb = nil
|
|
49
49
|
@progress_bar = nil
|
|
50
|
+
@spinner_cb = nil
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
|
|
@@ -88,8 +89,9 @@ module Aspera
|
|
|
88
89
|
query
|
|
89
90
|
end
|
|
90
91
|
|
|
91
|
-
# Build URI from URL and parameters and check it is http or https
|
|
92
|
-
# Check
|
|
92
|
+
# Build URI from URL and parameters and check it is `http` or `https`.
|
|
93
|
+
# Check if php style is specified.
|
|
94
|
+
# `nil` values in query result in key without value, e.g. `?a`, while empty string values result in `?a=`.
|
|
93
95
|
# @param url [String] The URL without query.
|
|
94
96
|
# @param query [Hash,Array,String] The query.
|
|
95
97
|
def build_uri(url, query)
|
|
@@ -105,18 +107,19 @@ module Aspera
|
|
|
105
107
|
URI.encode_www_form(h_to_query_array(query))
|
|
106
108
|
when Array
|
|
107
109
|
Aspera.assert(query.all?{ |i| i.is_a?(Array) && i.length.eql?(2)}){'Query must be array of arrays of 2 elements'}
|
|
108
|
-
URI.encode_www_form(query)
|
|
110
|
+
URI.encode_www_form(query) # remove nil values
|
|
109
111
|
else Aspera.error_unexpected_value(query.class){'query type'}
|
|
110
112
|
end.gsub('%5B%5D=', '[]=')
|
|
111
113
|
# [] is allowed in url parameters
|
|
112
114
|
uri
|
|
113
115
|
end
|
|
114
116
|
|
|
117
|
+
# Support array for query parameter, there is no standard.
|
|
118
|
+
# Either p=1&p=2 (default)
|
|
119
|
+
# or p[]=1&p[]=2 (if `:x_array_php_style` is set to true in query)
|
|
115
120
|
# @param query [Hash] HTTP query as hash
|
|
116
121
|
def h_to_query_array(query)
|
|
117
122
|
Aspera.assert_type(query, Hash)
|
|
118
|
-
# Support array for query parameter, there is no standard.
|
|
119
|
-
# Either p[]=1&p[]=2, or p=1&p=2
|
|
120
123
|
suffix = query.delete(:x_array_php_style) ? '[]' : nil
|
|
121
124
|
query.each_with_object([]) do |(k, v), query_array|
|
|
122
125
|
case v
|
|
@@ -368,8 +371,11 @@ module Aspera
|
|
|
368
371
|
end
|
|
369
372
|
result_http = nil
|
|
370
373
|
result_data = nil
|
|
374
|
+
# initialize with number of initial retries allowed, nil gives zero
|
|
375
|
+
tries_remain_redirect = @redirect_max
|
|
371
376
|
# start a block to be able to retry the actual HTTP request in case of OAuth token expiration
|
|
372
377
|
begin
|
|
378
|
+
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
|
373
379
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
|
374
380
|
# URI.escape()
|
|
375
381
|
separator = ['', '/'].include?(subpath) ? '' : '/'
|
|
@@ -403,16 +409,13 @@ module Aspera
|
|
|
403
409
|
Log.dump(:req_body, req.body, level: :trace1)
|
|
404
410
|
# we try the call, and will retry on some error types
|
|
405
411
|
error_tries ||= 1 + RestParameters.instance.retry_max
|
|
406
|
-
# initialize with number of initial retries allowed, nil gives zero
|
|
407
|
-
tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
|
|
408
|
-
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
|
409
412
|
result_mime = nil
|
|
410
413
|
file_saved = false
|
|
411
414
|
# make http request (pipelined)
|
|
412
415
|
http_session.request(req) do |response|
|
|
413
416
|
result_http = response
|
|
414
417
|
result_mime = self.class.parse_header(result_http['Content-Type'] || Mime::TEXT)[:type]
|
|
415
|
-
Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime},
|
|
418
|
+
Log.log.debug{"response: code=#{result_http.code}, mime=#{result_mime}, content-type=#{response['Content-Type']}"}
|
|
416
419
|
# JSON data needs to be parsed, in case it contains an error code
|
|
417
420
|
if !save_to_file.nil? &&
|
|
418
421
|
result_http.code.to_s.start_with?('2') &&
|
|
@@ -453,12 +456,10 @@ module Aspera
|
|
|
453
456
|
# TODO : related to mime type encoding ?
|
|
454
457
|
# result_http.body.force_encoding('UTF-8') if result_http.body.is_a?(String)
|
|
455
458
|
# Log.log.debug{"result: body=#{result_http.body}"}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
result_data = result_http.body
|
|
461
|
-
end
|
|
459
|
+
result_data = result_http.body
|
|
460
|
+
Log.dump(:result_data_raw, result_data, level: :trace1)
|
|
461
|
+
result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
|
|
462
|
+
Log.dump(:result_data, result_data)
|
|
462
463
|
RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
|
|
463
464
|
unless file_saved || save_to_file.nil?
|
|
464
465
|
FileUtils.mkdir_p(File.dirname(save_to_file))
|
|
@@ -536,55 +537,39 @@ module Aspera
|
|
|
536
537
|
|
|
537
538
|
# Create: `POST`
|
|
538
539
|
def create(subpath, params, **kwargs)
|
|
539
|
-
|
|
540
|
+
kwargs[:headers] ||= {}
|
|
541
|
+
kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
|
|
542
|
+
kwargs[:content_type] = Mime::JSON unless kwargs.key?(:content_type)
|
|
543
|
+
return call(operation: 'POST', subpath: subpath, body: params, **kwargs)
|
|
540
544
|
end
|
|
541
545
|
|
|
542
546
|
# Read: `GET`
|
|
543
547
|
def read(subpath, query = nil, **kwargs)
|
|
544
|
-
|
|
548
|
+
kwargs[:headers] ||= {}
|
|
549
|
+
kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
|
|
550
|
+
return call(operation: 'GET', subpath: subpath, query: query, **kwargs)
|
|
545
551
|
end
|
|
546
552
|
|
|
547
553
|
# Update: `PUT`
|
|
548
554
|
def update(subpath, params, **kwargs)
|
|
549
|
-
|
|
555
|
+
kwargs[:headers] ||= {}
|
|
556
|
+
kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
|
|
557
|
+
kwargs[:content_type] = Mime::JSON unless kwargs.key?(:content_type)
|
|
558
|
+
return call(operation: 'PUT', subpath: subpath, body: params, **kwargs)
|
|
550
559
|
end
|
|
551
560
|
|
|
552
561
|
# Delete: `DELETE`
|
|
553
562
|
def delete(subpath, params = nil, **kwargs)
|
|
554
|
-
|
|
563
|
+
kwargs[:headers] ||= {}
|
|
564
|
+
kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
|
|
565
|
+
return call(operation: 'DELETE', subpath: subpath, query: params, **kwargs)
|
|
555
566
|
end
|
|
556
567
|
|
|
557
568
|
# Cancel: `CANCEL`
|
|
558
569
|
def cancel(subpath, **kwargs)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# Query entity by general search (read with parameter `q`)
|
|
563
|
-
# TODO: not generic enough ? move somewhere ? inheritance ?
|
|
564
|
-
# @param subpath [String] Path of entity in API
|
|
565
|
-
# @param search_name [String] Name of searched entity
|
|
566
|
-
# @param query [Hash] Additional search query parameters
|
|
567
|
-
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
568
|
-
def lookup_by_name(subpath, search_name, query: nil)
|
|
569
|
-
query = {} if query.nil?
|
|
570
|
-
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
571
|
-
matching_items = read(subpath, query.merge({'q' => search_name}))
|
|
572
|
-
# API style: {totalcount:, ...} cspell: disable-line
|
|
573
|
-
matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
|
|
574
|
-
Aspera.assert_type(matching_items, Array)
|
|
575
|
-
case matching_items.length
|
|
576
|
-
when 1 then return matching_items.first
|
|
577
|
-
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
578
|
-
else
|
|
579
|
-
# multiple case insensitive partial matches, try case insensitive full match
|
|
580
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
581
|
-
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
|
582
|
-
case name_matches.length
|
|
583
|
-
when 1 then return name_matches.first
|
|
584
|
-
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.)
|
|
585
|
-
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
|
586
|
-
end
|
|
587
|
-
end
|
|
570
|
+
kwargs[:headers] ||= {}
|
|
571
|
+
kwargs[:headers]['Accept'] = Mime::JSON unless kwargs[:headers].key?('Accept')
|
|
572
|
+
return call(operation: 'CANCEL', subpath: subpath, **kwargs)
|
|
588
573
|
end
|
|
589
574
|
|
|
590
575
|
UNAVAILABLE_CODES = ['503']
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aspera
|
|
4
|
+
# List and lookup methods for Rest
|
|
5
|
+
# include in classes inheriting Rest
|
|
6
|
+
module RestList
|
|
7
|
+
# Query entity by general search (read with parameter `q`)
|
|
8
|
+
#
|
|
9
|
+
# @param subpath [String] Path of entity in API
|
|
10
|
+
# @param search_name [String] Name of searched entity
|
|
11
|
+
# @param query [Hash] Optional additional search query parameters
|
|
12
|
+
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
13
|
+
def lookup_by_name(subpath, search_name, query: nil)
|
|
14
|
+
query = {} if query.nil?
|
|
15
|
+
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
16
|
+
matching_items = read(subpath, query.merge({'q' => search_name}))
|
|
17
|
+
# API style: {totalcount:, ...} cspell: disable-line
|
|
18
|
+
matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
|
|
19
|
+
Aspera.assert_type(matching_items, Array)
|
|
20
|
+
case matching_items.length
|
|
21
|
+
when 1 then return matching_items.first
|
|
22
|
+
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
23
|
+
else
|
|
24
|
+
# multiple case insensitive partial matches, try case insensitive full match
|
|
25
|
+
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
26
|
+
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
|
27
|
+
case name_matches.length
|
|
28
|
+
when 1 then return name_matches.first
|
|
29
|
+
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.)
|
|
30
|
+
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
36
|
+
# @param entity [String,Symbol] API endpoint of entity to list
|
|
37
|
+
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
38
|
+
# @param query [Hash,nil] Additional query parameters
|
|
39
|
+
# @return [Array<(Array<Hash>, Integer)>] items, total_count
|
|
40
|
+
def list_entities_limit_offset_total_count(
|
|
41
|
+
entity:,
|
|
42
|
+
items_key: nil,
|
|
43
|
+
query: nil
|
|
44
|
+
)
|
|
45
|
+
entity = entity.to_s if entity.is_a?(Symbol)
|
|
46
|
+
items_key = entity.split('/').last if items_key.nil?
|
|
47
|
+
query = {} if query.nil?
|
|
48
|
+
Aspera.assert_type(entity, String)
|
|
49
|
+
Aspera.assert_type(items_key, String)
|
|
50
|
+
Aspera.assert_type(query, Hash)
|
|
51
|
+
Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
|
|
52
|
+
result = []
|
|
53
|
+
offset = 0
|
|
54
|
+
max_items = query.delete(MAX_ITEMS)
|
|
55
|
+
remain_pages = query.delete(MAX_PAGES)
|
|
56
|
+
# Merge default parameters, by default 100 per page
|
|
57
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
58
|
+
total_count = nil
|
|
59
|
+
loop do
|
|
60
|
+
query['offset'] = offset
|
|
61
|
+
page_result = read(entity, query)
|
|
62
|
+
Aspera.assert_type(page_result[items_key], Array)
|
|
63
|
+
result.concat(page_result[items_key])
|
|
64
|
+
# Reach the limit set by user ?
|
|
65
|
+
if !max_items.nil? && (result.length >= max_items)
|
|
66
|
+
result = result.slice(0, max_items)
|
|
67
|
+
break
|
|
68
|
+
end
|
|
69
|
+
total_count ||= page_result['total_count']
|
|
70
|
+
break if result.length >= total_count
|
|
71
|
+
remain_pages -= 1 unless remain_pages.nil?
|
|
72
|
+
break if remain_pages == 0
|
|
73
|
+
offset += page_result[items_key].length
|
|
74
|
+
RestParameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
|
|
75
|
+
end
|
|
76
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
77
|
+
return result, total_count
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Lookup an entity id from its name.
|
|
81
|
+
# Uses query `q` if `query` is `:default` and `field` is `name`.
|
|
82
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
83
|
+
# @param value [String] Value to lookup
|
|
84
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
85
|
+
# @param items_key [String] Key in the result to get the list of items (override entity)
|
|
86
|
+
# @param query [Hash] Additional query parameters (Default: `:default`)
|
|
87
|
+
def lookup_entity_by_field(entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
88
|
+
if query.eql?(:default)
|
|
89
|
+
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
90
|
+
query = {'q'=> value}
|
|
91
|
+
end
|
|
92
|
+
lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(entity: entity, items_key: items_key, query: query).first}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Lookup entity by field and value.
|
|
96
|
+
# Extracts a single result from the list returned by the block.
|
|
97
|
+
#
|
|
98
|
+
# @param entity [String] Type of entity to lookup (path, and by default it is also the field name in result)
|
|
99
|
+
# @param value [String] Value to match against the field.
|
|
100
|
+
# @param field [String] Field to match in the hashes (defaults to 'name').
|
|
101
|
+
# @yield [] A mandatory block that returns an Array of Hashes.
|
|
102
|
+
# @return [Hash] The unique matching object.
|
|
103
|
+
# @raise [Cli::BadIdentifier] If 0 or >1 matches are found.
|
|
104
|
+
def lookup_entity_generic(entity:, value:, field: 'name')
|
|
105
|
+
Aspera.assert(block_given?)
|
|
106
|
+
found = yield
|
|
107
|
+
Aspera.assert_array_all(found, Hash)
|
|
108
|
+
found = found.select{ |i| i[field].eql?(value)}
|
|
109
|
+
return found.first if found.length.eql?(1)
|
|
110
|
+
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
111
|
+
end
|
|
112
|
+
PER_PAGE_DEFAULT = 1000
|
|
113
|
+
private_constant :PER_PAGE_DEFAULT
|
|
114
|
+
module_function :lookup_entity_generic
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -342,7 +342,7 @@ module Aspera
|
|
|
342
342
|
CONF_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'conf')
|
|
343
343
|
CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
|
|
344
344
|
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
|
345
|
-
PRIVATE_FOLDER = '.private-asp
|
|
345
|
+
PRIVATE_FOLDER = "#{Environment.instance.os.eql?(Environment::OS_WINDOWS) ? '~' : '.'}private-asp"
|
|
346
346
|
ASYNC_DB = 'snap.db'
|
|
347
347
|
PARAM_KEYS = %w[local sessions].freeze
|
|
348
348
|
|
|
@@ -38,19 +38,19 @@ module Aspera
|
|
|
38
38
|
@file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# File list is provided directly with ascp arguments
|
|
42
|
-
# @columns ascp_args [Array,NilClass] ascp arguments
|
|
41
|
+
# File list is provided directly with `ascp` arguments
|
|
42
|
+
# @columns ascp_args [Array,NilClass] `ascp` arguments
|
|
43
43
|
def ascp_args_file_list?(ascp_args)
|
|
44
44
|
ascp_args&.any?{ |i| FILE_LIST_OPTIONS.include?(i)}
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# @param job_spec [Hash]
|
|
49
|
-
# @param ascp_args [Array]
|
|
50
|
-
# @param quiet [Boolean]
|
|
51
|
-
# @param
|
|
52
|
-
# @param
|
|
53
|
-
# @param check_ignore_cb [Proc]
|
|
48
|
+
# @param job_spec [Hash] Transfer specification
|
|
49
|
+
# @param ascp_args [Array] Other `ascp` args
|
|
50
|
+
# @param quiet [Boolean] Remove `ascp` progress bar if `true`
|
|
51
|
+
# @param client_ssh_key [:rsa,:dsa] Type of Aspera Client SSH key to use.
|
|
52
|
+
# @param trusted_certs [Array<String>] List of path to trusted certificates stores when using WSS
|
|
53
|
+
# @param check_ignore_cb [Proc] Callback to check if WSS connection shall ignore certificate validity
|
|
54
54
|
def initialize(
|
|
55
55
|
job_spec,
|
|
56
56
|
ascp_args: nil,
|
data/lib/aspera/transfer/spec.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Aspera
|
|
|
25
25
|
TRANSPORT_FIELDS = (%w[remote_host] + AK_TSPEC_BASE.keys + WSS_FIELDS).freeze
|
|
26
26
|
# reserved tag for Aspera
|
|
27
27
|
TAG_RESERVED = 'aspera'
|
|
28
|
+
SPECIFIC = %w{token paths direction source_root destination_root}.freeze
|
|
28
29
|
class << self
|
|
29
30
|
# wrong def in transferd
|
|
30
31
|
POLICY_FIX = {
|
data/lib/aspera/yaml.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Aspera
|
|
|
25
25
|
counts.each do |key_str, count|
|
|
26
26
|
next if count <= 1
|
|
27
27
|
path = (parent_path + [key_str]).join('.')
|
|
28
|
-
occurrences = key_nodes[key_str].map{ |kn| kn.start_line ? kn.start_line + 1 : 'unknown'}.
|
|
28
|
+
occurrences = key_nodes[key_str].map{ |kn| kn.start_line ? kn.start_line + 1 : 'unknown'}.join(', ')
|
|
29
29
|
duplicate_keys << "#{path}: #{occurrences}"
|
|
30
30
|
end
|
|
31
31
|
else
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aspera-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.26.0.pre
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Laurent Martin
|
|
@@ -424,6 +424,7 @@ files:
|
|
|
424
424
|
- lib/aspera/rest_call_error.rb
|
|
425
425
|
- lib/aspera/rest_error_analyzer.rb
|
|
426
426
|
- lib/aspera/rest_errors_aspera.rb
|
|
427
|
+
- lib/aspera/rest_list.rb
|
|
427
428
|
- lib/aspera/secret_hider.rb
|
|
428
429
|
- lib/aspera/ssh.rb
|
|
429
430
|
- lib/aspera/ssl.rb
|
|
@@ -456,7 +457,7 @@ metadata:
|
|
|
456
457
|
source_code_uri: https://github.com/IBM/aspera-cli/tree/main/lib/aspera
|
|
457
458
|
changelog_uri: https://github.com/IBM/aspera-cli/blob/main/CHANGELOG.md
|
|
458
459
|
rubygems_uri: https://rubygems.org/gems/aspera-cli
|
|
459
|
-
documentation_uri: https://
|
|
460
|
+
documentation_uri: https://ibm.biz/ascli-doc
|
|
460
461
|
rdoc_options: []
|
|
461
462
|
require_paths:
|
|
462
463
|
- lib
|
|
@@ -472,7 +473,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
472
473
|
version: '0'
|
|
473
474
|
requirements:
|
|
474
475
|
- Read the manual for any requirement
|
|
475
|
-
rubygems_version: 4.0.
|
|
476
|
+
rubygems_version: 4.0.6
|
|
476
477
|
specification_version: 4
|
|
477
478
|
summary: 'Execute actions using command line on IBM Aspera Server products: Aspera
|
|
478
479
|
on Cloud, Faspex, Shares, Node, Console, Orchestrator, High Speed Transfer Server'
|
metadata.gz.sig
CHANGED
|
Binary file
|