aspera-cli 4.25.4 → 4.25.6
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 +33 -11
- data/CONTRIBUTING.md +41 -23
- 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 +10 -6
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -0
- data/lib/aspera/api/faspex.rb +17 -19
- data/lib/aspera/api/httpgw.rb +19 -3
- data/lib/aspera/api/node.rb +56 -2
- data/lib/aspera/ascp/installation.rb +32 -34
- data/lib/aspera/cli/error.rb +8 -0
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/main.rb +30 -12
- data/lib/aspera/cli/manager.rb +31 -28
- data/lib/aspera/cli/plugins/aoc.rb +2 -2
- data/lib/aspera/cli/plugins/base.rb +1 -88
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +41 -53
- data/lib/aspera/cli/plugins/node.rb +26 -68
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/plugins/shares.rb +4 -2
- data/lib/aspera/cli/transfer_agent.rb +3 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/dot_container.rb +10 -10
- data/lib/aspera/environment.rb +29 -19
- data/lib/aspera/keychain/macos_security.rb +1 -1
- 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/preview/utils.rb +2 -2
- data/lib/aspera/rest.rb +39 -36
- data/lib/aspera/rest_list.rb +121 -0
- data/lib/aspera/sync/operations.rb +2 -2
- data/lib/aspera/transfer/parameters.rb +8 -8
- data/lib/aspera/yaml.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
data/lib/aspera/environment.rb
CHANGED
|
@@ -84,22 +84,31 @@ module Aspera
|
|
|
84
84
|
"'#{str.gsub("'", %q('\'\''))}'"
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
#
|
|
87
|
+
# Executes a command without invoking a shell.
|
|
88
88
|
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# - :background -> Process.spawn, return pid
|
|
92
|
-
# - :capture -> Open3.capture3, return stdout
|
|
89
|
+
# The command is provided as an array to avoid shell interpolation and
|
|
90
|
+
# ensure safer execution.
|
|
93
91
|
#
|
|
94
|
-
# @param cmd [Array
|
|
95
|
-
# @param mode [:execute
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# @
|
|
101
|
-
#
|
|
102
|
-
# @
|
|
92
|
+
# @param cmd [Array<#to_s>] The executable and its arguments.
|
|
93
|
+
# @param mode [:execute, :background, :capture] The execution strategy:
|
|
94
|
+
# - `:execute` Uses {Kernel.system}. Returns `true`, `false`, or `nil`.
|
|
95
|
+
# - `:background` Uses {Process.spawn}. Returns the spawned process PID.
|
|
96
|
+
# - `:capture` Uses {Open3.capture3}. Returns captured output and status.
|
|
97
|
+
#
|
|
98
|
+
# @param kwargs [Hash] Additional options forwarded to the underlying call.
|
|
99
|
+
#
|
|
100
|
+
# @option kwargs [Hash{String => String}] :env Environment variables to set for the process.
|
|
101
|
+
# @option kwargs [Boolean] :exception (false) When `true` in `:capture` mode,
|
|
102
|
+
# raises an error if the command exits with a non-zero status.
|
|
103
|
+
# @option kwargs [Boolean] :close_others (true) When `true` in `:background` mode,
|
|
104
|
+
# closes all other file descriptors in the child process.
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean, nil] For `:execute` mode (`true`, `false`, or `nil`).
|
|
107
|
+
# @return [Integer] For `:background` mode (process ID).
|
|
108
|
+
# @return [Array(String, String, Process::Status)] For `:capture` mode
|
|
109
|
+
# (`stdout`, `stderr`, `status`).
|
|
110
|
+
#
|
|
111
|
+
# @raise [RuntimeError] If `:exception` is `true` and the process fails in `:capture` mode.
|
|
103
112
|
def secure_execute(*cmd, mode: :execute, **kwargs)
|
|
104
113
|
cmd = cmd.map(&:to_s)
|
|
105
114
|
Aspera.assert(cmd.size.positive?, type: ArgumentError){'executable must be present'}
|
|
@@ -128,11 +137,12 @@ module Aspera
|
|
|
128
137
|
# https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
|
|
129
138
|
argv = [kwargs.delete(:env)].compact + cmd
|
|
130
139
|
exception = kwargs.delete(:exception){true}
|
|
131
|
-
|
|
132
|
-
Log.
|
|
133
|
-
Log.
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
result = Open3.capture3(*argv, **kwargs)
|
|
141
|
+
Log.dump(:stdout, result[0], level: :trace1)
|
|
142
|
+
Log.dump(:stderr, result[1], level: :trace1)
|
|
143
|
+
Log.dump(:status, result[2])
|
|
144
|
+
raise "Process failed: #{result[2].exitstatus} (#{result[1]})" if exception && !result[2].success?
|
|
145
|
+
result
|
|
136
146
|
else Aspera.error_unreachable_line
|
|
137
147
|
end
|
|
138
148
|
end
|
|
@@ -57,7 +57,7 @@ module Aspera
|
|
|
57
57
|
command_args.push(v.shellescape) unless v.empty?
|
|
58
58
|
end
|
|
59
59
|
command_args.push(last_opt) unless last_opt.nil?
|
|
60
|
-
|
|
60
|
+
Environment.secure_execute(*command_args, mode: :capture).first
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def key_chains(output)
|
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/preview/utils.rb
CHANGED
|
@@ -51,11 +51,11 @@ module Aspera
|
|
|
51
51
|
Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# Execute external command and
|
|
54
|
+
# Execute external command and get stdout
|
|
55
55
|
# @return [String]
|
|
56
56
|
def external_capture(command_sym, command_args)
|
|
57
57
|
Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
|
|
58
|
-
|
|
58
|
+
Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture).first
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
|
data/lib/aspera/rest.rb
CHANGED
|
@@ -72,11 +72,6 @@ module Aspera
|
|
|
72
72
|
# rest call errors are raised as exception RestCallError
|
|
73
73
|
# and error are analyzed in RestErrorAnalyzer
|
|
74
74
|
class Rest
|
|
75
|
-
# Special query parameter: max number of items for list command
|
|
76
|
-
MAX_ITEMS = 'max'
|
|
77
|
-
# Special query parameter: max number of pages for list command
|
|
78
|
-
MAX_PAGES = 'pmax'
|
|
79
|
-
|
|
80
75
|
class << self
|
|
81
76
|
# @return [String] Basic auth token
|
|
82
77
|
def basic_authorization(user, pass); return "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"; end
|
|
@@ -155,6 +150,37 @@ module Aspera
|
|
|
155
150
|
end
|
|
156
151
|
end
|
|
157
152
|
|
|
153
|
+
# Parse Link header according to RFC 8288 to extract a specific relation
|
|
154
|
+
# @param link_header [String, nil] The Link header value
|
|
155
|
+
# @param rel [String] The relation to look for (default: 'next')
|
|
156
|
+
# @return [String, nil] The URL of the link with the specified relation, or nil
|
|
157
|
+
def parse_link_header(link_header, rel: 'next')
|
|
158
|
+
return if link_header.nil? || link_header.empty?
|
|
159
|
+
# RFC 8288: Link header format is: <URI>; param1=value1; param2=value2, <URI2>; ...
|
|
160
|
+
# We look for the link with the specified rel
|
|
161
|
+
link_header.split(',').each do |link_part|
|
|
162
|
+
link_part = link_part.strip
|
|
163
|
+
# Extract URL between < and >
|
|
164
|
+
url_match = link_part.match(/<([^>]+)>/)
|
|
165
|
+
next unless url_match
|
|
166
|
+
url = url_match[1]
|
|
167
|
+
# Extract parameters after the URL
|
|
168
|
+
params_str = link_part[url_match.end(0)..-1]
|
|
169
|
+
# Check if this link has the specified rel (with or without quotes, case insensitive)
|
|
170
|
+
next unless /;\s*rel\s*=\s*"?#{Regexp.escape(rel)}"?/i.match?(params_str)
|
|
171
|
+
return url
|
|
172
|
+
end
|
|
173
|
+
# Fallback: if no rel found and looking for 'next', try the first link (backward compatibility)
|
|
174
|
+
if rel.eql?('next')
|
|
175
|
+
first_link = link_header.split(',').first&.strip
|
|
176
|
+
if first_link
|
|
177
|
+
url_match = first_link.match(/<([^>]+)>/)
|
|
178
|
+
return url_match[1] if url_match
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
|
|
158
184
|
# Start a HTTP/S session, also used for web sockets
|
|
159
185
|
# @param base_url [String] Base url of HTTP/S session
|
|
160
186
|
# @return [Net::HTTP] A started HTTP session
|
|
@@ -174,6 +200,8 @@ module Aspera
|
|
|
174
200
|
# get Net::HTTP underlying socket i/o
|
|
175
201
|
# little hack, handy because HTTP debug, proxy, etc... will be available
|
|
176
202
|
# used implement web sockets after `start_http_session`
|
|
203
|
+
# @param http_session [Net::HTTP] the session object
|
|
204
|
+
# @return [Net::BufferedIO] The underlying socket i/o
|
|
177
205
|
def io_http_session(http_session)
|
|
178
206
|
Aspera.assert_type(http_session, Net::HTTP)
|
|
179
207
|
# Net::BufferedIO in net/protocol.rb
|
|
@@ -371,8 +399,11 @@ module Aspera
|
|
|
371
399
|
end
|
|
372
400
|
result_http = nil
|
|
373
401
|
result_data = nil
|
|
402
|
+
# initialize with number of initial retries allowed, nil gives zero
|
|
403
|
+
tries_remain_redirect = @redirect_max
|
|
374
404
|
# start a block to be able to retry the actual HTTP request in case of OAuth token expiration
|
|
375
405
|
begin
|
|
406
|
+
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
|
376
407
|
# TODO: shall we percent encode subpath (spaces) test with access key delete with space in id
|
|
377
408
|
# URI.escape()
|
|
378
409
|
separator = ['', '/'].include?(subpath) ? '' : '/'
|
|
@@ -406,9 +437,6 @@ module Aspera
|
|
|
406
437
|
Log.dump(:req_body, req.body, level: :trace1)
|
|
407
438
|
# we try the call, and will retry on some error types
|
|
408
439
|
error_tries ||= 1 + RestParameters.instance.retry_max
|
|
409
|
-
# initialize with number of initial retries allowed, nil gives zero
|
|
410
|
-
tries_remain_redirect = @redirect_max if tries_remain_redirect.nil?
|
|
411
|
-
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
|
412
440
|
result_mime = nil
|
|
413
441
|
file_saved = false
|
|
414
442
|
# make http request (pipelined)
|
|
@@ -458,6 +486,9 @@ module Aspera
|
|
|
458
486
|
# Log.log.debug{"result: body=#{result_http.body}"}
|
|
459
487
|
result_data = result_http.body
|
|
460
488
|
Log.dump(:result_data_raw, result_data, level: :trace1)
|
|
489
|
+
# TODO: Remove next 2 lines when bug in async node api is fixed. (Aspera/core/issues/4490)
|
|
490
|
+
node_api_bug = result_data&.index('}HTTP/1.1 400 Bad Request') if result_data.is_a?(String)
|
|
491
|
+
result_data = result_data[0..node_api_bug] if node_api_bug
|
|
461
492
|
result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
|
|
462
493
|
Log.dump(:result_data, result_data)
|
|
463
494
|
RestErrorAnalyzer.instance.raise_on_error(req, result_data, result_http)
|
|
@@ -572,34 +603,6 @@ module Aspera
|
|
|
572
603
|
return call(operation: 'CANCEL', subpath: subpath, **kwargs)
|
|
573
604
|
end
|
|
574
605
|
|
|
575
|
-
# Query entity by general search (read with parameter `q`)
|
|
576
|
-
# TODO: not generic enough ? move somewhere ? inheritance ?
|
|
577
|
-
# @param subpath [String] Path of entity in API
|
|
578
|
-
# @param search_name [String] Name of searched entity
|
|
579
|
-
# @param query [Hash] Additional search query parameters
|
|
580
|
-
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
581
|
-
def lookup_by_name(subpath, search_name, query: nil)
|
|
582
|
-
query = {} if query.nil?
|
|
583
|
-
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
584
|
-
matching_items = read(subpath, query.merge({'q' => search_name}))
|
|
585
|
-
# API style: {totalcount:, ...} cspell: disable-line
|
|
586
|
-
matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
|
|
587
|
-
Aspera.assert_type(matching_items, Array)
|
|
588
|
-
case matching_items.length
|
|
589
|
-
when 1 then return matching_items.first
|
|
590
|
-
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
591
|
-
else
|
|
592
|
-
# multiple case insensitive partial matches, try case insensitive full match
|
|
593
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
594
|
-
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
|
595
|
-
case name_matches.length
|
|
596
|
-
when 1 then return name_matches.first
|
|
597
|
-
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.)
|
|
598
|
-
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
|
599
|
-
end
|
|
600
|
-
end
|
|
601
|
-
end
|
|
602
|
-
|
|
603
606
|
UNAVAILABLE_CODES = ['503']
|
|
604
607
|
|
|
605
608
|
private_constant :UNAVAILABLE_CODES
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aspera
|
|
4
|
+
# List and lookup methods for Rest
|
|
5
|
+
# To be included in classes inheriting Rest that require those methods.
|
|
6
|
+
module RestList
|
|
7
|
+
# `max`: special query parameter: max number of items for list command
|
|
8
|
+
MAX_ITEMS = 'max'
|
|
9
|
+
# `pmax`: special query parameter: max number of pages for list command
|
|
10
|
+
MAX_PAGES = 'pmax'
|
|
11
|
+
|
|
12
|
+
# Query entity by general search (read with parameter `q`)
|
|
13
|
+
#
|
|
14
|
+
# @param subpath [String] Path of entity in API
|
|
15
|
+
# @param search_name [String] Name of searched entity
|
|
16
|
+
# @param query [Hash] Optional additional search query parameters
|
|
17
|
+
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
18
|
+
def lookup_by_name(subpath, search_name, query: nil)
|
|
19
|
+
query = {} if query.nil?
|
|
20
|
+
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
21
|
+
matching_items = read(subpath, query.merge({'q' => search_name}))
|
|
22
|
+
# API style: {totalcount:, ...} cspell: disable-line
|
|
23
|
+
matching_items = matching_items[subpath] if matching_items.is_a?(Hash)
|
|
24
|
+
Aspera.assert_type(matching_items, Array)
|
|
25
|
+
case matching_items.length
|
|
26
|
+
when 1 then return matching_items.first
|
|
27
|
+
when 0 then raise EntityNotFound, %Q{No such #{subpath}: "#{search_name}"}
|
|
28
|
+
else
|
|
29
|
+
# multiple case insensitive partial matches, try case insensitive full match
|
|
30
|
+
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
|
31
|
+
name_matches = matching_items.select{ |i| i['name'].casecmp?(search_name)}
|
|
32
|
+
case name_matches.length
|
|
33
|
+
when 1 then return name_matches.first
|
|
34
|
+
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.)
|
|
35
|
+
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{ |i| i['name']}}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
41
|
+
# @param entity [String,Symbol] API endpoint of entity to list
|
|
42
|
+
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
43
|
+
# @param query [Hash,nil] Additional query parameters
|
|
44
|
+
# @return [Array<(Array<Hash>, Integer)>] items, total_count
|
|
45
|
+
def list_entities_limit_offset_total_count(
|
|
46
|
+
entity:,
|
|
47
|
+
items_key: nil,
|
|
48
|
+
query: nil
|
|
49
|
+
)
|
|
50
|
+
entity = entity.to_s if entity.is_a?(Symbol)
|
|
51
|
+
items_key = entity.split('/').last if items_key.nil?
|
|
52
|
+
query = {} if query.nil?
|
|
53
|
+
Aspera.assert_type(entity, String)
|
|
54
|
+
Aspera.assert_type(items_key, String)
|
|
55
|
+
Aspera.assert_type(query, Hash)
|
|
56
|
+
Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
|
|
57
|
+
result = []
|
|
58
|
+
offset = 0
|
|
59
|
+
max_items = query.delete(MAX_ITEMS)
|
|
60
|
+
remain_pages = query.delete(MAX_PAGES)
|
|
61
|
+
# Merge default parameters, by default 100 per page
|
|
62
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
63
|
+
total_count = nil
|
|
64
|
+
loop do
|
|
65
|
+
query['offset'] = offset
|
|
66
|
+
page_result = read(entity, query)
|
|
67
|
+
Aspera.assert_type(page_result[items_key], Array)
|
|
68
|
+
result.concat(page_result[items_key])
|
|
69
|
+
# Reach the limit set by user ?
|
|
70
|
+
if !max_items.nil? && (result.length >= max_items)
|
|
71
|
+
result = result.slice(0, max_items)
|
|
72
|
+
break
|
|
73
|
+
end
|
|
74
|
+
total_count ||= page_result['total_count']
|
|
75
|
+
break if result.length >= total_count
|
|
76
|
+
remain_pages -= 1 unless remain_pages.nil?
|
|
77
|
+
break if remain_pages == 0
|
|
78
|
+
offset += page_result[items_key].length
|
|
79
|
+
RestParameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
|
|
80
|
+
end
|
|
81
|
+
RestParameters.instance.spinner_cb.call(action: :success)
|
|
82
|
+
return result, total_count
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Lookup an entity id from its name.
|
|
86
|
+
# Uses query `q` if `query` is `:default` and `field` is `name`.
|
|
87
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
88
|
+
# @param value [String] Value to lookup
|
|
89
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
90
|
+
# @param items_key [String] Key in the result to get the list of items (override entity)
|
|
91
|
+
# @param query [Hash] Additional query parameters (Default: `:default`)
|
|
92
|
+
def lookup_entity_by_field(entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
93
|
+
if query.eql?(:default)
|
|
94
|
+
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
95
|
+
query = {'q'=> value}
|
|
96
|
+
end
|
|
97
|
+
lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(entity: entity, items_key: items_key, query: query).first}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Lookup entity by field and value.
|
|
101
|
+
# Extracts a single result from the list returned by the block.
|
|
102
|
+
#
|
|
103
|
+
# @param entity [String] Type of entity to lookup (path, and by default it is also the field name in result)
|
|
104
|
+
# @param value [String] Value to match against the field.
|
|
105
|
+
# @param field [String] Field to match in the hashes (defaults to 'name').
|
|
106
|
+
# @yield [] A mandatory block that returns an Array of Hashes.
|
|
107
|
+
# @return [Hash] The unique matching object.
|
|
108
|
+
# @raise [Cli::BadIdentifier] If 0 or >1 matches are found.
|
|
109
|
+
def lookup_entity_generic(entity:, value:, field: 'name')
|
|
110
|
+
Aspera.assert(block_given?)
|
|
111
|
+
found = yield
|
|
112
|
+
Aspera.assert_array_all(found, Hash)
|
|
113
|
+
found = found.select{ |i| i[field].eql?(value)}
|
|
114
|
+
return found.first if found.length.eql?(1)
|
|
115
|
+
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
116
|
+
end
|
|
117
|
+
PER_PAGE_DEFAULT = 1000
|
|
118
|
+
private_constant :PER_PAGE_DEFAULT
|
|
119
|
+
module_function :lookup_entity_generic
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -197,7 +197,7 @@ module Aspera
|
|
|
197
197
|
raise Error, 'Missing either local_db_dir or local_dir'
|
|
198
198
|
end
|
|
199
199
|
end
|
|
200
|
-
stdout = Environment.secure_execute(*arguments, mode: :capture)
|
|
200
|
+
stdout = Environment.secure_execute(*arguments, mode: :capture).first
|
|
201
201
|
return parse_status(stdout)
|
|
202
202
|
end
|
|
203
203
|
|
|
@@ -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/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.25.
|
|
4
|
+
version: 4.25.6
|
|
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
|