aspera-cli 4.24.2 → 4.25.0
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 +1070 -758
- data/CONTRIBUTING.md +130 -115
- data/README.md +961 -623
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/factory.rb +9 -6
- data/lib/aspera/agent/transferd.rb +8 -8
- data/lib/aspera/api/aoc.rb +104 -67
- data/lib/aspera/api/ats.rb +1 -0
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +17 -10
- data/lib/aspera/api/node.rb +10 -12
- data/lib/aspera/ascmd.rb +2 -3
- data/lib/aspera/ascp/installation.rb +60 -46
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +28 -6
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +94 -62
- data/lib/aspera/cli/formatter.rb +44 -58
- data/lib/aspera/cli/main.rb +21 -14
- data/lib/aspera/cli/manager.rb +317 -250
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +139 -78
- data/lib/aspera/cli/plugins/ats.rb +30 -36
- data/lib/aspera/cli/plugins/base.rb +68 -55
- data/lib/aspera/cli/plugins/config.rb +90 -100
- data/lib/aspera/cli/plugins/console.rb +15 -9
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +39 -30
- data/lib/aspera/cli/plugins/faspex5.rb +57 -52
- data/lib/aspera/cli/plugins/faspio.rb +10 -7
- data/lib/aspera/cli/plugins/httpgw.rb +3 -2
- data/lib/aspera/cli/plugins/node.rb +140 -125
- data/lib/aspera/cli/plugins/oauth.rb +13 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
- data/lib/aspera/cli/plugins/preview.rb +28 -48
- data/lib/aspera/cli/plugins/server.rb +9 -10
- data/lib/aspera/cli/plugins/shares.rb +77 -43
- data/lib/aspera/cli/sync_actions.rb +49 -38
- data/lib/aspera/cli/transfer_agent.rb +16 -35
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +8 -5
- data/lib/aspera/command_line_builder.rb +24 -21
- data/lib/aspera/coverage.rb +6 -2
- data/lib/aspera/dot_container.rb +108 -0
- data/lib/aspera/environment.rb +71 -84
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/id_generator.rb +7 -10
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +6 -5
- data/lib/aspera/oauth/base.rb +41 -64
- data/lib/aspera/oauth/factory.rb +6 -7
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +6 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +24 -38
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/products/connect.rb +3 -3
- data/lib/aspera/rest.rb +54 -39
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +10 -6
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/conf.schema.yaml +184 -36
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +128 -72
- data/lib/aspera/transfer/parameters.rb +9 -10
- data/lib/aspera/transfer/spec.rb +2 -3
- data/lib/aspera/transfer/spec.schema.yaml +52 -22
- data/lib/aspera/transfer/spec_doc.rb +20 -30
- data/lib/aspera/uri_reader.rb +18 -4
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
|
@@ -33,25 +33,12 @@ module Aspera
|
|
|
33
33
|
class Installation
|
|
34
34
|
include Singleton
|
|
35
35
|
|
|
36
|
-
DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
|
|
37
|
-
<?xml version='1.0' encoding='UTF-8'?>
|
|
38
|
-
<CONF version="2">
|
|
39
|
-
<default>
|
|
40
|
-
<file_system>
|
|
41
|
-
<resume_suffix>.aspera-ckpt</resume_suffix>
|
|
42
|
-
<partial_file_suffix>.partial</partial_file_suffix>
|
|
43
|
-
</file_system>
|
|
44
|
-
</default>
|
|
45
|
-
</CONF>
|
|
46
|
-
END_OF_CONFIG_FILE
|
|
47
|
-
# all executable files from SDK
|
|
48
|
-
EXE_FILES = %i[ascp ascp4 async transferd].freeze
|
|
49
|
-
SDK_FILES = %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].unshift(*EXE_FILES).freeze
|
|
50
|
-
TRANSFERD_ARCHIVE_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
|
51
|
-
# filename for ascp with optional extension (Windows)
|
|
52
|
-
private_constant :DEFAULT_ASPERA_CONF, :SDK_FILES, :TRANSFERD_ARCHIVE_LOCATION_URL
|
|
53
36
|
# options for SSH client private key
|
|
54
37
|
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
|
|
38
|
+
# prefix
|
|
39
|
+
USE_PRODUCT_PREFIX = 'product:'
|
|
40
|
+
# policy for product selection
|
|
41
|
+
FIRST_FOUND = 'FIRST'
|
|
55
42
|
|
|
56
43
|
# Loads YAML from cloud with locations of SDK archives for all platforms
|
|
57
44
|
# @return location structure
|
|
@@ -66,12 +53,13 @@ module Aspera
|
|
|
66
53
|
end
|
|
67
54
|
end
|
|
68
55
|
|
|
69
|
-
# set ascp executable
|
|
56
|
+
# set ascp executable "location"
|
|
70
57
|
def ascp_path=(v)
|
|
71
58
|
Aspera.assert_type(v, String)
|
|
72
|
-
Aspera.assert(!v.empty?){'ascp
|
|
73
|
-
|
|
74
|
-
@
|
|
59
|
+
Aspera.assert(!v.empty?){'ascp location cannot be empty: check your config file'}
|
|
60
|
+
@ascp_location = v
|
|
61
|
+
@ascp_path = nil
|
|
62
|
+
return
|
|
75
63
|
end
|
|
76
64
|
|
|
77
65
|
def ascp_path
|
|
@@ -93,8 +81,7 @@ module Aspera
|
|
|
93
81
|
pl = installed_products.find{ |i| i[:name].eql?(product_name)}
|
|
94
82
|
raise "No such product installed: #{product_name}" if pl.nil?
|
|
95
83
|
end
|
|
96
|
-
|
|
97
|
-
Log.log.debug{"ascp_path=#{@path_to_ascp}"}
|
|
84
|
+
@ascp_path = pl[:ascp_path]
|
|
98
85
|
end
|
|
99
86
|
|
|
100
87
|
# @return [Hash] with key = file name (String), and value = path to file
|
|
@@ -111,7 +98,9 @@ module Aspera
|
|
|
111
98
|
end
|
|
112
99
|
end
|
|
113
100
|
|
|
101
|
+
# TODO: if using another product than SDK, should use files from there
|
|
114
102
|
def check_or_create_sdk_file(filename, force: false, &block)
|
|
103
|
+
FileUtils.mkdir_p(Products::Transferd.sdk_directory)
|
|
115
104
|
return Environment.write_file_restricted(File.join(Products::Transferd.sdk_directory, filename), force: force, mode: 0o644, &block)
|
|
116
105
|
end
|
|
117
106
|
|
|
@@ -127,10 +116,18 @@ module Aspera
|
|
|
127
116
|
file = if k.eql?(:transferd)
|
|
128
117
|
Products::Transferd.transferd_path
|
|
129
118
|
else
|
|
130
|
-
#
|
|
131
|
-
|
|
119
|
+
# find ascp when needed
|
|
120
|
+
if @ascp_path.nil?
|
|
121
|
+
if @ascp_location.start_with?(USE_PRODUCT_PREFIX)
|
|
122
|
+
use_ascp_from_product(@ascp_location[USE_PRODUCT_PREFIX.length..-1])
|
|
123
|
+
else
|
|
124
|
+
@ascp_path = @ascp_location
|
|
125
|
+
end
|
|
126
|
+
Aspera.assert(File.exist?(@ascp_path)){"No such file: [#{@ascp_path}]"}
|
|
127
|
+
Log.log.debug{"ascp_path=#{@ascp_path}"}
|
|
128
|
+
end
|
|
132
129
|
# NOTE: that there might be a .exe at the end
|
|
133
|
-
@
|
|
130
|
+
@ascp_path.gsub('ascp', k.to_s)
|
|
134
131
|
end
|
|
135
132
|
when :ssh_private_dsa, :ssh_private_rsa
|
|
136
133
|
# assume last 3 letters are type
|
|
@@ -195,7 +192,9 @@ module Aspera
|
|
|
195
192
|
return exe_version
|
|
196
193
|
end
|
|
197
194
|
|
|
198
|
-
|
|
195
|
+
# Extract some stings from ascp logs
|
|
196
|
+
# Folder, PVCL, version, license information
|
|
197
|
+
def ascp_info_from_log
|
|
199
198
|
data = {}
|
|
200
199
|
# read PATHs from ascp directly, and pvcl modules as well
|
|
201
200
|
Open3.popen3(ascp_path, '-DDL-') do |_stdin, _stdout, stderr, thread|
|
|
@@ -227,8 +226,9 @@ module Aspera
|
|
|
227
226
|
return data
|
|
228
227
|
end
|
|
229
228
|
|
|
230
|
-
#
|
|
231
|
-
|
|
229
|
+
# Extract some stings from ascp binary
|
|
230
|
+
# Openssl information
|
|
231
|
+
def ascp_info_from_file
|
|
232
232
|
data = {}
|
|
233
233
|
File.binread(ascp_path).scan(/[\x20-\x7E]{10,}/) do |bin_string|
|
|
234
234
|
if (m = bin_string.match(/OPENSSLDIR.*"(.*)"/))
|
|
@@ -243,17 +243,17 @@ module Aspera
|
|
|
243
243
|
# information for `ascp info`
|
|
244
244
|
def ascp_info
|
|
245
245
|
ascp_data = file_paths
|
|
246
|
-
ascp_data.merge!(
|
|
247
|
-
ascp_data.merge!(
|
|
246
|
+
ascp_data.merge!(ascp_info_from_log)
|
|
247
|
+
ascp_data.merge!(ascp_info_from_file)
|
|
248
248
|
return ascp_data
|
|
249
249
|
end
|
|
250
250
|
|
|
251
251
|
# @return the url for download of SDK archive for the given platform and version
|
|
252
252
|
def sdk_url_for_platform(platform: nil, version: nil)
|
|
253
|
-
|
|
253
|
+
all_locations = sdk_locations
|
|
254
254
|
platform = Environment.instance.architecture if platform.nil?
|
|
255
|
-
locations =
|
|
256
|
-
raise "No SDK for platform: #{platform}" if locations.empty?
|
|
255
|
+
locations = all_locations.select{ |l| l['platform'].eql?(platform)}
|
|
256
|
+
raise "No SDK for platform: #{platform}, available: #{all_locations.map{ |i| i['platform']}.uniq}" if locations.empty?
|
|
257
257
|
version = locations.max_by{ |entry| Gem::Version.new(entry['version'])}['version'] if version.nil?
|
|
258
258
|
info = locations.select{ |entry| entry['version'].eql?(version)}
|
|
259
259
|
raise "No such version: #{version} for #{platform}" if info.empty?
|
|
@@ -294,12 +294,12 @@ module Aspera
|
|
|
294
294
|
end
|
|
295
295
|
|
|
296
296
|
# Retrieves ascp binary for current system architecture from URL or file
|
|
297
|
-
# @param url [String]
|
|
298
|
-
# @param folder [String]
|
|
299
|
-
# @param backup [
|
|
300
|
-
# @param with_exe [
|
|
301
|
-
# @param &block [Proc]
|
|
302
|
-
# @return ascp version (from execution)
|
|
297
|
+
# @param url [String] URL to SDK archive, or SpecialValues::DEF
|
|
298
|
+
# @param folder [String] Destination folder path
|
|
299
|
+
# @param backup [Boolean] If destination folder exists, then rename
|
|
300
|
+
# @param with_exe [Boolean] If false, only retrieves files, but do not generate or restrict access
|
|
301
|
+
# @param &block [Proc] A lambda that receives a file path from archive and tells destination sub folder(end with /) or file, or nil to not extract
|
|
302
|
+
# @return [Array] name, ascp version (from execution), folder
|
|
303
303
|
def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
|
|
304
304
|
url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
|
|
305
305
|
folder = Products::Transferd.sdk_directory if folder.nil?
|
|
@@ -348,20 +348,34 @@ module Aspera
|
|
|
348
348
|
sdk_name = 'IBM Aspera Transfer SDK'
|
|
349
349
|
sdk_version = transferd_version || sdk_ascp_version
|
|
350
350
|
File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
|
351
|
-
return sdk_name, sdk_version
|
|
351
|
+
return sdk_name, sdk_version, folder
|
|
352
352
|
end
|
|
353
353
|
|
|
354
354
|
attr_accessor :transferd_urls
|
|
355
355
|
|
|
356
356
|
private
|
|
357
357
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
|
|
359
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
360
|
+
<CONF version="2">
|
|
361
|
+
<default>
|
|
362
|
+
<file_system>
|
|
363
|
+
<resume_suffix>.aspera-ckpt</resume_suffix>
|
|
364
|
+
<partial_file_suffix>.partial</partial_file_suffix>
|
|
365
|
+
</file_system>
|
|
366
|
+
</default>
|
|
367
|
+
</CONF>
|
|
368
|
+
END_OF_CONFIG_FILE
|
|
369
|
+
# all executable files from SDK
|
|
370
|
+
EXE_FILES = %i[ascp ascp4 async transferd].freeze
|
|
371
|
+
SDK_FILES = %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].unshift(*EXE_FILES).freeze
|
|
372
|
+
TRANSFERD_ARCHIVE_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
|
373
|
+
# filename for ascp with optional extension (Windows)
|
|
374
|
+
private_constant :DEFAULT_ASPERA_CONF, :EXE_FILES, :SDK_FILES, :TRANSFERD_ARCHIVE_LOCATION_URL
|
|
362
375
|
|
|
363
376
|
def initialize
|
|
364
|
-
@
|
|
377
|
+
@ascp_path = nil
|
|
378
|
+
@ascp_location = nil
|
|
365
379
|
@sdk_dir = nil
|
|
366
380
|
@found_products = nil
|
|
367
381
|
@transferd_urls = TRANSFERD_ARCHIVE_LOCATION_URL
|
|
@@ -5,8 +5,12 @@ require 'aspera/assert'
|
|
|
5
5
|
module Aspera
|
|
6
6
|
module Ascp
|
|
7
7
|
# processing of ascp management port events
|
|
8
|
+
# Reference: `mgmtmess.c`
|
|
8
9
|
class Management
|
|
9
|
-
#
|
|
10
|
+
# References:
|
|
11
|
+
# https://www.google.com/search?q=FASP+error+codes
|
|
12
|
+
# https://www.ibm.com/support/pages/error-code-reference-tables
|
|
13
|
+
# mgmtmess.c : as_mgmt_err_is_retryable
|
|
10
14
|
# Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
|
|
11
15
|
# rubocop:disable Layout/FirstHashElementLineBreak
|
|
12
16
|
ERRORS = {
|
|
@@ -289,7 +293,7 @@ module Aspera
|
|
|
289
293
|
# cspell: enable
|
|
290
294
|
|
|
291
295
|
class << self
|
|
292
|
-
#
|
|
296
|
+
# Translate native event name to snake case
|
|
293
297
|
def field_native_to_snake(name)
|
|
294
298
|
case name
|
|
295
299
|
when 'Elapsedusec' then 'elapsed_usec'
|
|
@@ -298,7 +302,7 @@ module Aspera
|
|
|
298
302
|
end
|
|
299
303
|
end
|
|
300
304
|
|
|
301
|
-
#
|
|
305
|
+
# Translate snake case event name to native
|
|
302
306
|
# @param name [String] Field name
|
|
303
307
|
def field_snake_to_native(name)
|
|
304
308
|
field = name.delete('_')
|
|
@@ -320,7 +324,7 @@ module Aspera
|
|
|
320
324
|
end
|
|
321
325
|
|
|
322
326
|
# Build command to send on management port
|
|
323
|
-
# @param data [Hash] e.g. {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
|
327
|
+
# @param data [Hash] keys are snake case: e.g. {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
|
324
328
|
# @return [String] frame to send on management port
|
|
325
329
|
def command_to_stream(data)
|
|
326
330
|
data
|
|
@@ -339,7 +343,7 @@ module Aspera
|
|
|
339
343
|
end
|
|
340
344
|
attr_reader :last_event
|
|
341
345
|
|
|
342
|
-
#
|
|
346
|
+
# Process line of mgt port event
|
|
343
347
|
# @param line [String] line of mgt port event
|
|
344
348
|
# @return [Hash] event hash or nil if event is not yet complete
|
|
345
349
|
def process_line(line)
|
data/lib/aspera/assert.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Aspera
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Assert that a condition is true, else raise exception
|
|
34
|
-
# @param assertion [
|
|
34
|
+
# @param assertion [TrueClass, FalseClass] Must be true
|
|
35
35
|
# @param info [String,nil] Fixed message in case assert fails, else use `block`
|
|
36
36
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
37
37
|
# @param block [Proc] Produces a string that describes the problem for complex messages
|
|
@@ -41,8 +41,8 @@ module Aspera
|
|
|
41
41
|
return if assertion
|
|
42
42
|
message = 'assertion failed'
|
|
43
43
|
info = yield if block_given?
|
|
44
|
-
message = "#{message}: #{info}" if info
|
|
45
|
-
message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
|
|
44
|
+
message = type.eql?(AssertError) ? "#{message}: #{info}" : info if info
|
|
45
|
+
# message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
|
|
46
46
|
report_error(type, message)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -52,11 +52,33 @@ module Aspera
|
|
|
52
52
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
53
53
|
# @param block [Proc] Additional description in front of message
|
|
54
54
|
def assert_type(value, *classes, type: AssertError)
|
|
55
|
-
assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting #{classes.join(', ')}, but have #{value.inspect}"}
|
|
55
|
+
assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting #{classes.join(', ')}, but have (#{value.class})#{value.inspect}"}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Assert that all value of array are of the same specified type
|
|
59
|
+
# @param array [Array] The array to check
|
|
60
|
+
# @param klass [Class] The expected type of elements
|
|
61
|
+
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
62
|
+
# @param block [Proc] Additional description in front of message
|
|
63
|
+
def assert_array_all(array, klass, type: AssertError)
|
|
64
|
+
assert_type(array, Array, type: type)
|
|
65
|
+
assert(array.all?(klass), type: type){"#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}"}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Assert value is Hash, keys have type, and Values have type
|
|
69
|
+
# @param hash [Hash] The hash to check
|
|
70
|
+
# @param key_class [Class] The expected type of keys (or nil)
|
|
71
|
+
# @param value_class [Class] The expected type of values (or nil)
|
|
72
|
+
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
73
|
+
# @param block [Proc] Additional description in front of message
|
|
74
|
+
def assert_hash_all(hash, key_class, value_class, type: AssertError)
|
|
75
|
+
assert_type(hash, Hash, type: type)
|
|
76
|
+
assert_array_all(hash.keys, key_class, type: AssertError){"#{"#{yield}: " if block_given?}keys"} unless key_class.nil?
|
|
77
|
+
assert_array_all(hash.values, value_class, type: AssertError){"#{"#{yield}: " if block_given?}values"} unless value_class.nil?
|
|
56
78
|
end
|
|
57
79
|
|
|
58
80
|
# Assert that value is one of the given values
|
|
59
|
-
# @param value [
|
|
81
|
+
# @param value [Object] Value to check
|
|
60
82
|
# @param values [Array] Accepted values
|
|
61
83
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
62
84
|
# @param block [Proc] Additional description in front of message
|
|
@@ -69,7 +91,7 @@ module Aspera
|
|
|
69
91
|
end
|
|
70
92
|
|
|
71
93
|
# The value is not one of the expected values
|
|
72
|
-
# @param value [
|
|
94
|
+
# @param value [Object] The wrong value
|
|
73
95
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
74
96
|
# @param block [Proc] Additional description in front of message
|
|
75
97
|
def error_unexpected_value(value, type: InternalError)
|
data/lib/aspera/cli/error.rb
CHANGED
|
@@ -6,11 +6,13 @@ module Aspera
|
|
|
6
6
|
class Error < StandardError; end
|
|
7
7
|
# Raised when an unexpected argument is provided.
|
|
8
8
|
class BadArgument < Error; end
|
|
9
|
+
class MissingArgument < Error; end
|
|
9
10
|
class NoSuchElement < Error; end
|
|
10
11
|
|
|
11
12
|
class BadIdentifier < Error
|
|
12
|
-
def initialize(res_type, res_id)
|
|
13
|
-
|
|
13
|
+
def initialize(res_type, res_id, field: 'identifier', count: 0)
|
|
14
|
+
msg = count.eql?(0) ? 'not found' : "found #{count}"
|
|
15
|
+
super("#{res_type} with #{field}=#{res_id}: #{msg}")
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -17,16 +17,8 @@ module Aspera
|
|
|
17
17
|
class ExtendedValue
|
|
18
18
|
include Singleton
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
MARKER_IN_END = '@'
|
|
23
|
-
|
|
24
|
-
# Special handlers stop processing of handlers on right
|
|
25
|
-
# :extend includes processing of other handlers in itself
|
|
26
|
-
# :val keeps the value intact
|
|
27
|
-
SPECIAL_HANDLERS = %i[extend val].freeze
|
|
28
|
-
|
|
29
|
-
private_constant :MARKER_START, :MARKER_END, :MARKER_IN_END, :SPECIAL_HANDLERS
|
|
20
|
+
# First is default
|
|
21
|
+
DEFAULT_DECODERS = %i[none json ruby yaml]
|
|
30
22
|
|
|
31
23
|
class << self
|
|
32
24
|
# Decode comma separated table text
|
|
@@ -45,8 +37,41 @@ module Aspera
|
|
|
45
37
|
return hash_array
|
|
46
38
|
end
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
# JSON Parser, with more information on error location
|
|
41
|
+
# extract a context: 10 chars before and after the error on the given line and display a pointer "^"
|
|
42
|
+
# :reek:UncommunicativeMethodName
|
|
43
|
+
def JSON_parse(value) # rubocop:disable Naming/MethodName
|
|
44
|
+
JSON.parse(value)
|
|
45
|
+
rescue JSON::ParserError => e
|
|
46
|
+
m = /at line (\d+) column (\d+)/.match(e.message)
|
|
47
|
+
raise if m.nil?
|
|
48
|
+
line = m[1].to_i - 1
|
|
49
|
+
column = m[2].to_i - 1
|
|
50
|
+
lines = value.lines
|
|
51
|
+
raise if line >= lines.size
|
|
52
|
+
error_line = lines[line].chomp
|
|
53
|
+
context_col_beg = [column - 10, 0].max
|
|
54
|
+
context_col_end = [column + 10, error_line.length].min
|
|
55
|
+
context = error_line[context_col_beg...context_col_end]
|
|
56
|
+
cursor_pos = column - context_col_beg
|
|
57
|
+
pointer = ' ' * cursor_pos + '^'.blink
|
|
58
|
+
raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The value must be empty
|
|
62
|
+
# @param value [String] The value as parameter
|
|
63
|
+
# @param ext_type [Symbol] The method of extended value
|
|
64
|
+
def assert_no_value(value, ext_type)
|
|
65
|
+
Aspera.assert(value.empty?, type: BadArgument){"no value allowed for extended value type: #{ext_type}"}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def read_stdin(mode)
|
|
69
|
+
case mode
|
|
70
|
+
when '' then $stdin.read
|
|
71
|
+
when 'bin' then $stdin.binmode.read
|
|
72
|
+
when 'chomp' then $stdin.chomp
|
|
73
|
+
else raise BadArgument, "`stdin` supports only: '', 'bin' or 'chomp'"
|
|
74
|
+
end
|
|
50
75
|
end
|
|
51
76
|
end
|
|
52
77
|
|
|
@@ -54,7 +79,8 @@ module Aspera
|
|
|
54
79
|
|
|
55
80
|
def initialize
|
|
56
81
|
# Base handlers
|
|
57
|
-
# Other handlers can be set using
|
|
82
|
+
# Other handlers can be set using `on`
|
|
83
|
+
# e.g. `preset` is reader in config plugin
|
|
58
84
|
@handlers = {
|
|
59
85
|
val: lambda{ |i| i},
|
|
60
86
|
base64: lambda{ |i| Base64.decode64(i)},
|
|
@@ -62,7 +88,7 @@ module Aspera
|
|
|
62
88
|
env: lambda{ |i| ENV.fetch(i, nil)},
|
|
63
89
|
file: lambda{ |i| File.read(File.expand_path(i))},
|
|
64
90
|
uri: lambda{ |i| UriReader.read(i)},
|
|
65
|
-
json: lambda{ |i| JSON_parse(i)},
|
|
91
|
+
json: lambda{ |i| ExtendedValue.JSON_parse(i)},
|
|
66
92
|
lines: lambda{ |i| i.split("\n")},
|
|
67
93
|
list: lambda{ |i| i[1..-1].split(i[0])},
|
|
68
94
|
none: lambda{ |i| ExtendedValue.assert_no_value(i, :none); nil}, # rubocop:disable Style/Semicolon
|
|
@@ -70,67 +96,63 @@ module Aspera
|
|
|
70
96
|
re: lambda{ |i| Regexp.new(i, Regexp::MULTILINE)},
|
|
71
97
|
ruby: lambda{ |i| Environment.secure_eval(i, __FILE__, __LINE__)},
|
|
72
98
|
secret: lambda{ |i| prompt = i.empty? ? 'secret' : i; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
|
|
73
|
-
stdin: lambda{ |i| ExtendedValue.
|
|
74
|
-
stdbin: lambda{ |i| ExtendedValue.assert_no_value(i, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
|
|
99
|
+
stdin: lambda{ |i| ExtendedValue.read_stdin(i)},
|
|
75
100
|
yaml: lambda{ |i| YAML.load(i)},
|
|
76
101
|
zlib: lambda{ |i| Zlib::Inflate.inflate(i)},
|
|
77
|
-
extend: lambda{ |i| ExtendedValue.instance.
|
|
102
|
+
extend: lambda{ |i| ExtendedValue.instance.evaluate_extend(i)}
|
|
78
103
|
}
|
|
104
|
+
@regex_single = nil
|
|
105
|
+
@regex_extend = nil
|
|
79
106
|
@default_decoder = nil
|
|
107
|
+
update_regex
|
|
80
108
|
end
|
|
81
109
|
|
|
82
|
-
# Regex to match an extended value
|
|
83
|
-
def
|
|
84
|
-
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# JSON Parser, with more information on error location
|
|
88
|
-
# :reek:UncommunicativeMethodName
|
|
89
|
-
def JSON_parse(value) # rubocop:disable Naming/MethodName
|
|
90
|
-
JSON.parse(value)
|
|
91
|
-
rescue JSON::ParserError => e
|
|
92
|
-
m = /at line (\d+) column (\d+)/.match(e.message)
|
|
93
|
-
raise if m.nil?
|
|
94
|
-
line = m[1].to_i - 1
|
|
95
|
-
column = m[2].to_i - 1
|
|
96
|
-
lines = value.lines
|
|
97
|
-
raise if line >= lines.size
|
|
98
|
-
error_line = lines[line].chomp
|
|
99
|
-
context_col_beg = [column - 10, 0].max
|
|
100
|
-
context_col_end = [column + 10, error_line.length].min
|
|
101
|
-
context = error_line[context_col_beg...context_col_end]
|
|
102
|
-
cursor_pos = column - context_col_beg
|
|
103
|
-
pointer = ' ' * cursor_pos + '^'.blink
|
|
104
|
-
raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
|
|
110
|
+
# Update the Regex to match an extended value based on @handlers
|
|
111
|
+
def update_regex
|
|
112
|
+
handler_regex = "#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
113
|
+
@regex_single = Regexp.new("^#{handler_regex}(.*)$", Regexp::MULTILINE)
|
|
114
|
+
@regex_extend = Regexp.new("^(.*)#{handler_regex}([^#{MARKER_IN_END}]*)#{MARKER_IN_END}(.*)$", Regexp::MULTILINE)
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
public
|
|
108
118
|
|
|
119
|
+
attr_reader :default_decoder
|
|
120
|
+
|
|
109
121
|
def default_decoder=(value)
|
|
110
122
|
Log.log.debug{"Setting default decoder to (#{value.class}) #{value}"}
|
|
111
|
-
Aspera.
|
|
123
|
+
Aspera.assert_values(value, DEFAULT_DECODERS)
|
|
124
|
+
value = nil if value.eql?(:none)
|
|
112
125
|
@default_decoder = value
|
|
113
126
|
end
|
|
114
127
|
|
|
128
|
+
# List of Extended Value methods
|
|
115
129
|
def modifiers; @handlers.keys; end
|
|
116
130
|
|
|
117
131
|
# Add a new handler
|
|
118
|
-
def
|
|
119
|
-
Log.log.debug{"setting handler for #{name}"}
|
|
132
|
+
def on(name, &block)
|
|
120
133
|
Aspera.assert_type(name, Symbol){'name'}
|
|
121
|
-
|
|
134
|
+
Aspera.assert(block)
|
|
135
|
+
Log.log.debug{"Setting handler for #{name}"}
|
|
136
|
+
@handlers[name] = block
|
|
137
|
+
update_regex
|
|
122
138
|
end
|
|
123
139
|
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# @param
|
|
128
|
-
|
|
140
|
+
# Parses a `String` value to extended value.
|
|
141
|
+
# If it is a String using supported extended value modifiers, then evaluate them.
|
|
142
|
+
# Other value types are returned as is.
|
|
143
|
+
# @param value [String] the value to parse
|
|
144
|
+
# @param context [String] Context in which evaluation is done
|
|
145
|
+
# @param allowed [Array<Class>,NilClass] Expected types
|
|
146
|
+
# @return [Object] Evaluated value
|
|
147
|
+
def evaluate(value, context:, allowed: nil)
|
|
129
148
|
return value unless value.is_a?(String)
|
|
130
|
-
|
|
149
|
+
Aspera.assert_array_all(allowed, Class) unless allowed.nil?
|
|
150
|
+
# use default decoder if not an extended value and expect complex types
|
|
151
|
+
using_default_decoder = allowed&.all?{ |t| DEFAULT_PARSER_TYPES.include?(t)} && !@regex_single.match?(value) && !@default_decoder.nil?
|
|
152
|
+
value = [MARKER_START, @default_decoder, MARKER_END, value].join if using_default_decoder
|
|
131
153
|
# First determine decoders, in reversed order
|
|
132
154
|
handlers_reversed = []
|
|
133
|
-
while (m = value.match(
|
|
155
|
+
while (m = value.match(@regex_single))
|
|
134
156
|
handler = m[1].to_sym
|
|
135
157
|
handlers_reversed.unshift(handler)
|
|
136
158
|
value = m[2]
|
|
@@ -139,27 +161,37 @@ module Aspera
|
|
|
139
161
|
Log.log.trace1{"evaluating: #{handlers_reversed}, value: #{value}"}
|
|
140
162
|
handlers_reversed.each do |handler|
|
|
141
163
|
value = @handlers[handler].call(value)
|
|
164
|
+
rescue => e
|
|
165
|
+
raise BadArgument, "Evaluation of #{handler} for #{context}: #{e.message}"
|
|
142
166
|
end
|
|
143
167
|
return value
|
|
144
168
|
end
|
|
145
169
|
|
|
146
|
-
# Parse string value as extended value
|
|
147
|
-
# Use default decoder if none is specified
|
|
148
|
-
def evaluate_with_default(value)
|
|
149
|
-
value = [MARKER_START, @default_decoder, MARKER_END, value].join if value.is_a?(String) && value.match(/^#{handler_regex_string}.*$/).nil? && !@default_decoder.nil?
|
|
150
|
-
return evaluate(value)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
170
|
# Find inner extended values
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
while (m = value.match(
|
|
171
|
+
# Only used in above lambda
|
|
172
|
+
def evaluate_extend(value)
|
|
173
|
+
while (m = value.match(@regex_extend))
|
|
157
174
|
sub_value = "@#{m[2]}:#{m[3]}"
|
|
158
175
|
Log.log.debug{"evaluating #{sub_value}"}
|
|
159
|
-
value = "#{m[1]}#{evaluate(sub_value)}#{m[4]}"
|
|
176
|
+
value = "#{m[1]}#{evaluate(sub_value, context: 'composite extended value')}#{m[4]}"
|
|
160
177
|
end
|
|
161
178
|
return value
|
|
162
179
|
end
|
|
180
|
+
# marker "@"
|
|
181
|
+
MARKER_START = '@'
|
|
182
|
+
# marker ":"
|
|
183
|
+
MARKER_END = ':'
|
|
184
|
+
# marker "@"
|
|
185
|
+
MARKER_IN_END = '@'
|
|
186
|
+
|
|
187
|
+
# Special handlers stop processing of handlers on right
|
|
188
|
+
# :extend includes processing of other handlers in itself
|
|
189
|
+
# :val keeps the value intact
|
|
190
|
+
SPECIAL_HANDLERS = %i[extend val].freeze
|
|
191
|
+
|
|
192
|
+
# Array and Hash types:
|
|
193
|
+
DEFAULT_PARSER_TYPES = [Array, Hash].freeze
|
|
194
|
+
private_constant :MARKER_START, :MARKER_END, :MARKER_IN_END, :SPECIAL_HANDLERS, :DEFAULT_PARSER_TYPES
|
|
163
195
|
end
|
|
164
196
|
end
|
|
165
197
|
end
|