aspera-cli 4.15.0 → 4.16.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/BUGS.md +29 -3
- data/CHANGELOG.md +292 -228
- data/CONTRIBUTING.md +69 -18
- data/README.md +1102 -952
- data/bin/ascli +13 -31
- data/bin/asession +3 -1
- data/examples/dascli +2 -2
- data/lib/aspera/aoc.rb +28 -33
- data/lib/aspera/ascmd.rb +3 -6
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formatter.rb +26 -13
- data/lib/aspera/cli/hints.rb +4 -3
- data/lib/aspera/cli/main.rb +16 -3
- data/lib/aspera/cli/manager.rb +45 -36
- data/lib/aspera/cli/plugin.rb +20 -13
- data/lib/aspera/cli/plugins/aoc.rb +103 -73
- data/lib/aspera/cli/plugins/ats.rb +4 -3
- data/lib/aspera/cli/plugins/config.rb +114 -119
- data/lib/aspera/cli/plugins/cos.rb +2 -2
- data/lib/aspera/cli/plugins/faspex.rb +23 -19
- data/lib/aspera/cli/plugins/faspex5.rb +75 -43
- data/lib/aspera/cli/plugins/node.rb +28 -15
- data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
- data/lib/aspera/cli/plugins/preview.rb +9 -7
- data/lib/aspera/cli/plugins/server.rb +6 -3
- data/lib/aspera/cli/plugins/shares.rb +30 -26
- data/lib/aspera/cli/sync_actions.rb +9 -9
- data/lib/aspera/cli/transfer_agent.rb +21 -14
- data/lib/aspera/cli/transfer_progress.rb +2 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +13 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +4 -2
- data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
- data/lib/aspera/fasp/agent_base.rb +17 -7
- data/lib/aspera/fasp/agent_direct.rb +88 -84
- data/lib/aspera/fasp/agent_httpgw.rb +4 -3
- data/lib/aspera/fasp/agent_node.rb +3 -2
- data/lib/aspera/fasp/agent_trsdk.rb +79 -37
- data/lib/aspera/fasp/installation.rb +51 -12
- data/lib/aspera/fasp/management.rb +11 -6
- data/lib/aspera/fasp/parameters.rb +53 -47
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +2 -2
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +6 -5
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +10 -8
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/log.rb +4 -3
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +17 -16
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +22 -19
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/preview/file_types.rb +53 -267
- data/lib/aspera/preview/generator.rb +7 -5
- data/lib/aspera/preview/terminal.rb +14 -5
- data/lib/aspera/preview/utils.rb +8 -7
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +29 -13
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +2 -0
- data/lib/aspera/secret_hider.rb +5 -2
- data/lib/aspera/ssh.rb +10 -8
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/web_server_simple.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +96 -45
- metadata.gz.sig +0 -0
- data/lib/aspera/sync.rb +0 -219
data/lib/aspera/cli/manager.rb
CHANGED
@@ -5,6 +5,7 @@ require 'aspera/cli/error'
|
|
5
5
|
require 'aspera/colors'
|
6
6
|
require 'aspera/secret_hider'
|
7
7
|
require 'aspera/log'
|
8
|
+
require 'aspera/assert'
|
8
9
|
require 'io/console'
|
9
10
|
require 'optparse'
|
10
11
|
|
@@ -20,7 +21,7 @@ module Aspera
|
|
20
21
|
@option_name = option_name
|
21
22
|
@has_writer = @object.respond_to?(writer_method)
|
22
23
|
Log.log.debug{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
|
23
|
-
|
24
|
+
assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
|
24
25
|
end
|
25
26
|
|
26
27
|
def value
|
@@ -54,12 +55,13 @@ module Aspera
|
|
54
55
|
# option name separator in code (symbol)
|
55
56
|
OPTION_SEP_SYMBOL = '_'
|
56
57
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
58
|
+
TYPE_INTEGER = [Integer].freeze
|
57
59
|
|
58
|
-
private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER
|
60
|
+
private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER, :TYPE_INTEGER
|
59
61
|
|
60
62
|
class << self
|
61
63
|
def enum_to_bool(enum)
|
62
|
-
|
64
|
+
assert_values(enum, BOOLEAN_VALUES){'boolean'}
|
63
65
|
return TRUE_VALUES.include?(enum)
|
64
66
|
end
|
65
67
|
|
@@ -73,14 +75,17 @@ module Aspera
|
|
73
75
|
matching_exact = allowed_values.select{|i| i.to_s.eql?(short_value)}
|
74
76
|
return matching_exact.first if matching_exact.length == 1
|
75
77
|
matching = allowed_values.select{|i| i.to_s.start_with?(short_value)}
|
76
|
-
|
77
|
-
|
78
|
+
multi_choice_assert(!matching.empty?,"unknown value for #{descr}: #{short_value}", allowed_values)
|
79
|
+
multi_choice_assert(matching.length.eql?(1),"ambiguous shortcut for #{descr}: #{short_value}", matching)
|
78
80
|
return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
|
79
81
|
return matching.first
|
80
82
|
end
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
+
# Generates error message with list of allowed values
|
85
|
+
# @param error_msg [String] error message
|
86
|
+
# @param choices [Array] list of allowed values
|
87
|
+
def multi_choice_assert(assertion,error_msg, choices)
|
88
|
+
raise Cli::BadArgument, [error_msg, 'Use:'].concat(choices.map{|c|"- #{c}"}.sort).join("\n") unless assertion
|
84
89
|
end
|
85
90
|
|
86
91
|
# change option name with dash to name with underscore
|
@@ -92,9 +97,13 @@ module Aspera
|
|
92
97
|
return "--#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}"
|
93
98
|
end
|
94
99
|
|
100
|
+
# @param what [Symbol] :option or :argument
|
101
|
+
# @param descr [String] description for help
|
102
|
+
# @param value [Object] value to check
|
103
|
+
# @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
|
95
104
|
def validate_type(what, descr, value, type_list)
|
96
105
|
return nil if type_list.nil?
|
97
|
-
|
106
|
+
assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
|
98
107
|
raise Cli::BadArgument,
|
99
108
|
"#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of ' : ''}#{type_list.map(&:name).join(',')}" unless \
|
100
109
|
type_list.any?{|t|value.is_a?(t)}
|
@@ -172,10 +181,14 @@ module Aspera
|
|
172
181
|
# @param default [Object] default value
|
173
182
|
# @return value, list or nil
|
174
183
|
def get_next_argument(descr, expected: :single, mandatory: true, type: nil, aliases: nil, default: nil)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
184
|
+
assert(%i[single multiple].include?(expected) || (expected.is_a?(Array) && expected.all?(Symbol))){'expected must be single, multiple, or array of symbol'}
|
185
|
+
assert(type.nil? || type.is_a?(Class) || (type.is_a?(Array) && type.all?(Class))){'type must be Class or Array of Class'}
|
186
|
+
assert(aliases.nil? || (aliases.is_a?(Hash) && aliases.keys.all?(Symbol) && aliases.values.all?(Symbol))){'aliases must be Hash'}
|
187
|
+
allowed_types = type
|
188
|
+
unless allowed_types.nil?
|
189
|
+
allowed_types = [allowed_types] unless allowed_types.is_a?(Array)
|
190
|
+
descr = "#{descr} (#{allowed_types.join(', ')})"
|
191
|
+
Log.log.debug{">>>> #{descr}=#{allowed_types}"}
|
179
192
|
end
|
180
193
|
result =
|
181
194
|
if !@unprocessed_cmd_line_arguments.empty?
|
@@ -193,23 +206,21 @@ module Aspera
|
|
193
206
|
when Array
|
194
207
|
allowed_values = [].concat(expected)
|
195
208
|
allowed_values.concat(aliases.keys) unless aliases.nil?
|
196
|
-
raise "internal error: only symbols allowed: #{allowed_values}" unless allowed_values.all?(Symbol)
|
197
209
|
self.class.get_from_list(@unprocessed_cmd_line_arguments.shift, descr, allowed_values)
|
198
|
-
else
|
199
|
-
raise 'Internal error: expected: must be single, multiple, or value array'
|
210
|
+
else error_unexpected_value(expected)
|
200
211
|
end
|
201
212
|
elsif !default.nil? then default
|
202
213
|
# no value provided, either get value interactively, or exception
|
203
214
|
elsif mandatory then get_interactive(:argument, descr, expected: expected)
|
204
215
|
end
|
205
|
-
if result.is_a?(String) &&
|
206
|
-
|
207
|
-
|
208
|
-
|
216
|
+
if result.is_a?(String) && allowed_types.eql?(TYPE_INTEGER)
|
217
|
+
int_result = Integer(result, exception: false)
|
218
|
+
raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
|
219
|
+
result = int_result
|
209
220
|
end
|
210
221
|
Log.log.debug{"#{descr}=#{result}"}
|
211
|
-
result = aliases[result] if
|
212
|
-
self.class.validate_type(:argument, descr, result,
|
222
|
+
result = aliases[result] if aliases&.key?(result)
|
223
|
+
self.class.validate_type(:argument, descr, result, allowed_types) unless result.nil? && !mandatory
|
213
224
|
return result
|
214
225
|
end
|
215
226
|
|
@@ -221,7 +232,7 @@ module Aspera
|
|
221
232
|
# @param mandatory [Boolean] if true, raise error if option not set
|
222
233
|
def get_option(option_symbol, mandatory: false, default: nil)
|
223
234
|
attributes = @declared_options[option_symbol]
|
224
|
-
|
235
|
+
assert(attributes){"option not declared: #{option_symbol}"}
|
225
236
|
result = nil
|
226
237
|
case attributes[:read_write]
|
227
238
|
when :accessor
|
@@ -284,12 +295,10 @@ module Aspera
|
|
284
295
|
# @param types [Class, Array] accepted value type(s)
|
285
296
|
# @param block [Proc] block to execute when option is found
|
286
297
|
def declare(option_symbol, description, handler: nil, default: nil, values: nil, short: nil, coerce: nil, types: nil, deprecation: nil, &block)
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
raise "INTERNAL ERROR: #{option_symbol} description does not start with capital" unless description[0] == description[0].upcase
|
292
|
-
raise "INTERNAL ERROR: #{option_symbol} shall use :types" if ['hash', 'extended value'].any?{|s|description.downcase.include?(s) }
|
298
|
+
assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
|
299
|
+
assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
|
300
|
+
assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with capital"}
|
301
|
+
assert(!['hash', 'extended value'].any?{|s|description.downcase.include?(s) }){"#{option_symbol} shall use :types"}
|
293
302
|
opt = @declared_options[option_symbol] = {
|
294
303
|
read_write: handler.nil? ? :value : :accessor,
|
295
304
|
# by default passwords and secrets are sensitive, else specify when declaring the option
|
@@ -297,7 +306,7 @@ module Aspera
|
|
297
306
|
}
|
298
307
|
if !types.nil?
|
299
308
|
types = [types] unless types.is_a?(Array)
|
300
|
-
|
309
|
+
assert(types.all?(Class)){"types must be Array of Class: #{types}"}
|
301
310
|
opt[:types] = types
|
302
311
|
description = "#{description} (#{types.map(&:name).join(', ')})"
|
303
312
|
end
|
@@ -307,8 +316,8 @@ module Aspera
|
|
307
316
|
end
|
308
317
|
Log.log.debug{"declare: #{option_symbol}: #{opt[:read_write]}".green}
|
309
318
|
if opt[:read_write].eql?(:accessor)
|
310
|
-
|
311
|
-
|
319
|
+
assert_type(handler, Hash)
|
320
|
+
assert(handler.keys.sort.eql?(%i[m o]))
|
312
321
|
Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
|
313
322
|
opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
|
314
323
|
end
|
@@ -347,11 +356,11 @@ module Aspera
|
|
347
356
|
set_option(option_symbol, time_string, SOURCE_USER)
|
348
357
|
end
|
349
358
|
when :none
|
350
|
-
|
359
|
+
assert(!block.nil?){"missing block for #{option_symbol}"}
|
351
360
|
on_args.push(symbol_to_option(option_symbol, nil))
|
352
361
|
on_args.push("-#{short}") if short.is_a?(String)
|
353
362
|
@parser.on(*on_args, &block)
|
354
|
-
else
|
363
|
+
else error_unexpected_value(values)
|
355
364
|
end
|
356
365
|
Log.log.debug{"on_args=#{on_args}"}
|
357
366
|
end
|
@@ -359,8 +368,8 @@ module Aspera
|
|
359
368
|
# Adds each of the keys of specified hash as an option
|
360
369
|
# @param preset_hash [Hash] hash of options to add
|
361
370
|
def add_option_preset(preset_hash, op: :push)
|
371
|
+
assert_type(preset_hash, Hash)
|
362
372
|
Log.log.debug{"add_option_preset=#{preset_hash}"}
|
363
|
-
raise "internal error: default expects Hash: #{preset_hash.class}" unless preset_hash.is_a?(Hash)
|
364
373
|
# incremental override
|
365
374
|
preset_hash.each{|k, v|@unprocessed_defaults.send(op, [k.to_sym, v])}
|
366
375
|
end
|
@@ -460,8 +469,8 @@ module Aspera
|
|
460
469
|
|
461
470
|
def get_interactive(type, descr, expected: :single)
|
462
471
|
if !@ask_missing_mandatory
|
463
|
-
raise Cli::BadArgument,
|
464
|
-
|
472
|
+
raise Cli::BadArgument, "missing argument (#{expected}): #{descr}" unless expected.is_a?(Array)
|
473
|
+
self.class.multi_choice_assert(false,"missing: #{descr}", expected)
|
465
474
|
end
|
466
475
|
result = nil
|
467
476
|
sensitive = type.eql?(:option) && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
|
data/lib/aspera/cli/plugin.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/cli/extended_value'
|
4
|
+
require 'aspera/assert'
|
4
5
|
|
5
6
|
module Aspera
|
6
7
|
module Cli
|
@@ -33,11 +34,11 @@ module Aspera
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def initialize(env)
|
36
|
-
|
37
|
+
assert_type(env, Hash)
|
37
38
|
@agents = env
|
38
39
|
# check presence in descendant of mandatory method and constant
|
39
|
-
|
40
|
-
|
40
|
+
assert(respond_to?(:execute_action)){"Missing method 'execute_action' in #{self.class}"}
|
41
|
+
assert(self.class.constants.include?(:ACTIONS)){'ACTIONS shall be redefined by subclass'}
|
41
42
|
# manual header for all plugins
|
42
43
|
options.parser.separator('')
|
43
44
|
options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
|
@@ -75,6 +76,7 @@ module Aspera
|
|
75
76
|
# @param id_result [String] key in result hash to use as identifier
|
76
77
|
# @param fields [Array] fields to display
|
77
78
|
def do_bulk_operation(command:, descr:, values: Hash, id_result: 'id', fields: :default)
|
79
|
+
assert(block_given?){'missing block'}
|
78
80
|
is_bulk = options.get_option(:bulk)
|
79
81
|
case values
|
80
82
|
when :identifier
|
@@ -82,7 +84,6 @@ module Aspera
|
|
82
84
|
when Class
|
83
85
|
values = value_create_modify(command: command, type: values, bulk: is_bulk)
|
84
86
|
end
|
85
|
-
raise 'Internal error: missing block' unless block_given?
|
86
87
|
# if not bulk, there is a single value
|
87
88
|
params = is_bulk ? values : [values]
|
88
89
|
Log.log.warn('Empty list given for bulk operation') if params.empty?
|
@@ -147,6 +148,7 @@ module Aspera
|
|
147
148
|
return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
|
148
149
|
when :list
|
149
150
|
resp = rest_api.read(res_class_path, old_query_read_delete)
|
151
|
+
return Main.result_empty if resp[:http].code == '204'
|
150
152
|
data = resp[:data]
|
151
153
|
# TODO: not generic : which application is this for ?
|
152
154
|
if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
|
@@ -180,10 +182,10 @@ module Aspera
|
|
180
182
|
end
|
181
183
|
|
182
184
|
# implement generic rest operations on given resource path
|
183
|
-
def entity_action(rest_api, res_class_path, **opts)
|
185
|
+
def entity_action(rest_api, res_class_path, **opts, &block)
|
184
186
|
# res_name=res_class_path.gsub(%r{^.*/},'').gsub(%r{s$},'').gsub('_',' ')
|
185
187
|
command = options.get_next_command(ALL_OPS)
|
186
|
-
return entity_command(command, rest_api, res_class_path, **opts)
|
188
|
+
return entity_command(command, rest_api, res_class_path, **opts, &block)
|
187
189
|
end
|
188
190
|
|
189
191
|
# query parameters in URL suitable for REST list/GET and delete/DELETE
|
@@ -223,22 +225,27 @@ module Aspera
|
|
223
225
|
|
224
226
|
# Retrieves an extended value from command line, used for creation or modification of entities
|
225
227
|
# @param command [Symbol] command name for error message
|
226
|
-
# @param type [Class] expected type of value, either a Class, an Array of Class
|
228
|
+
# @param type [Class] expected type of value, either a Class, an Array of Class
|
229
|
+
# @param bulk [Boolean] if true, value must be an Array of <type>
|
227
230
|
# @param default [Object] default value if not provided
|
228
231
|
# TODO: when deprecation of `value` is completed: remove line with :value
|
229
|
-
def value_create_modify(command:, type: Hash, bulk: false, default: nil)
|
232
|
+
def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
|
230
233
|
value = options.get_option(:value)
|
231
234
|
Log.log.warn("option `value` is deprecated. Use positional parameter for #{command}") unless value.nil?
|
232
|
-
value = options.get_next_argument(
|
235
|
+
value = options.get_next_argument(
|
236
|
+
"parameters for #{command}#{description.nil? ? '' : " (#{description})"}", mandatory: default.nil?,
|
237
|
+
type: bulk ? Array : type) if value.nil?
|
233
238
|
value = default if value.nil?
|
234
239
|
unless type.nil?
|
235
240
|
type = [type] unless type.is_a?(Array)
|
236
|
-
|
241
|
+
assert(type.all?(Class)){"check types must be a Class, not #{type.map(&:class).join(',')}"}
|
237
242
|
if bulk
|
238
|
-
|
239
|
-
|
243
|
+
assert_type(value, Array, exception_class: Cli::BadArgument)
|
244
|
+
value.each do |v|
|
245
|
+
assert_values(v.class, type, exception_class: Cli::BadArgument)
|
246
|
+
end
|
240
247
|
else
|
241
|
-
|
248
|
+
assert_values(value.class, type, exception_class: Cli::BadArgument)
|
242
249
|
end
|
243
250
|
end
|
244
251
|
return value
|