aspera-cli 4.18.0 → 4.19.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 +23 -0
- data/CONTRIBUTING.md +5 -12
- data/README.md +152 -84
- data/examples/build_exec +85 -0
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +4 -4
- data/lib/aspera/agent/base.rb +2 -0
- data/lib/aspera/agent/connect.rb +3 -4
- data/lib/aspera/agent/direct.rb +108 -104
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/api/aoc.rb +2 -2
- data/lib/aspera/api/httpgw.rb +95 -57
- data/lib/aspera/api/node.rb +110 -77
- data/lib/aspera/ascp/installation.rb +47 -32
- data/lib/aspera/ascp/management.rb +4 -1
- data/lib/aspera/ascp/products.rb +2 -8
- data/lib/aspera/cli/extended_value.rb +27 -14
- data/lib/aspera/cli/formatter.rb +35 -28
- data/lib/aspera/cli/main.rb +11 -11
- data/lib/aspera/cli/manager.rb +109 -94
- data/lib/aspera/cli/plugin.rb +4 -7
- data/lib/aspera/cli/plugin_factory.rb +10 -1
- data/lib/aspera/cli/plugins/aoc.rb +15 -14
- data/lib/aspera/cli/plugins/config.rb +35 -29
- data/lib/aspera/cli/plugins/faspex.rb +5 -4
- data/lib/aspera/cli/plugins/faspex5.rb +16 -13
- data/lib/aspera/cli/plugins/node.rb +50 -41
- data/lib/aspera/cli/plugins/orchestrator.rb +3 -2
- data/lib/aspera/cli/plugins/preview.rb +1 -1
- data/lib/aspera/cli/plugins/server.rb +2 -2
- data/lib/aspera/cli/plugins/shares.rb +11 -7
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +73 -32
- data/lib/aspera/cli/transfer_agent.rb +3 -2
- data/lib/aspera/cli/transfer_progress.rb +1 -1
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +100 -7
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -0
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -0
- data/lib/aspera/oauth/web.rb +7 -6
- data/lib/aspera/rest.rb +46 -15
- data/lib/aspera/secret_hider.rb +3 -2
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +7 -5
- data/lib/aspera/transfer/parameters.rb +27 -19
- data/lib/aspera/transfer/spec.rb +8 -10
- data/lib/aspera/transfer/sync.rb +52 -47
- data/lib/aspera/web_auth.rb +0 -1
- data/lib/aspera/web_server_simple.rb +24 -13
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
- data/examples/rubyc +0 -24
- data/lib/aspera/open_application.rb +0 -69
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Aspera
|
|
|
20
20
|
@method = method_name
|
|
21
21
|
@option_name = option_name
|
|
22
22
|
@has_writer = @object.respond_to?(writer_method)
|
|
23
|
-
Log.log.
|
|
23
|
+
Log.log.trace1{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
|
|
24
24
|
Aspera.assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -55,12 +55,13 @@ module Aspera
|
|
|
55
55
|
# option name separator in code (symbol)
|
|
56
56
|
OPTION_SEP_SYMBOL = '_'
|
|
57
57
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
|
58
|
-
TYPE_INTEGER = [Integer].freeze
|
|
59
58
|
OPTION_VALUE_SEPARATOR = '='
|
|
60
59
|
OPTION_PREFIX = '--'
|
|
61
60
|
OPTIONS_STOP = '--'
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
DEFAULT_PARSER_TYPES = [Array, Hash].freeze
|
|
63
|
+
|
|
64
|
+
private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER
|
|
64
65
|
|
|
65
66
|
class << self
|
|
66
67
|
def enum_to_bool(enum)
|
|
@@ -86,9 +87,9 @@ module Aspera
|
|
|
86
87
|
|
|
87
88
|
# Generates error message with list of allowed values
|
|
88
89
|
# @param error_msg [String] error message
|
|
89
|
-
# @param
|
|
90
|
-
def multi_choice_assert(assertion, error_msg,
|
|
91
|
-
raise Cli::BadArgument, [error_msg, 'Use:'].concat(
|
|
90
|
+
# @param accept_list [Array] list of allowed values
|
|
91
|
+
def multi_choice_assert(assertion, error_msg, accept_list)
|
|
92
|
+
raise Cli::BadArgument, [error_msg, 'Use:'].concat(accept_list.map{|c|"- #{c}"}.sort).join("\n") unless assertion
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
# change option name with dash to name with underscore
|
|
@@ -102,14 +103,17 @@ module Aspera
|
|
|
102
103
|
|
|
103
104
|
# @param what [Symbol] :option or :argument
|
|
104
105
|
# @param descr [String] description for help
|
|
105
|
-
# @param
|
|
106
|
+
# @param to_check [Object] value to check
|
|
106
107
|
# @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
|
|
107
|
-
def validate_type(what, descr,
|
|
108
|
+
def validate_type(what, descr, to_check, type_list, check_array: false)
|
|
108
109
|
return nil if type_list.nil?
|
|
109
110
|
Aspera.assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
value_list = check_array ? to_check : [to_check]
|
|
112
|
+
value_list.each do |value|
|
|
113
|
+
raise Cli::BadArgument,
|
|
114
|
+
"#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of ' : ''}#{type_list.map(&:name).join(',')}" unless \
|
|
115
|
+
type_list.any?{|t|value.is_a?(t)}
|
|
116
|
+
end
|
|
113
117
|
end
|
|
114
118
|
end
|
|
115
119
|
|
|
@@ -117,7 +121,7 @@ module Aspera
|
|
|
117
121
|
attr_accessor :ask_missing_mandatory, :ask_missing_optional
|
|
118
122
|
attr_writer :fail_on_missing_mandatory
|
|
119
123
|
|
|
120
|
-
def initialize(program_name)
|
|
124
|
+
def initialize(program_name, argv = nil)
|
|
121
125
|
# command line values *not* starting with '-'
|
|
122
126
|
@unprocessed_cmd_line_arguments = []
|
|
123
127
|
# command line values starting with '-'
|
|
@@ -149,14 +153,7 @@ module Aspera
|
|
|
149
153
|
Log.log.debug{"env=#{@option_pairs_env}".red}
|
|
150
154
|
@unprocessed_cmd_line_options = []
|
|
151
155
|
@unprocessed_cmd_line_arguments = []
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def parse_command_line(argv)
|
|
155
|
-
@parser.separator('')
|
|
156
|
-
@parser.separator('OPTIONS: global')
|
|
157
|
-
declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
|
|
158
|
-
declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
|
|
159
|
-
parse_options!
|
|
156
|
+
return if argv.nil?
|
|
160
157
|
process_options = true
|
|
161
158
|
until argv.empty?
|
|
162
159
|
value = argv.shift
|
|
@@ -174,65 +171,63 @@ module Aspera
|
|
|
174
171
|
end
|
|
175
172
|
@initial_cli_options = @unprocessed_cmd_line_options.dup.freeze
|
|
176
173
|
Log.log.debug{"add_cmd_line_options:commands/arguments=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red}
|
|
174
|
+
@parser.separator('')
|
|
175
|
+
@parser.separator('OPTIONS: global')
|
|
176
|
+
declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
|
|
177
|
+
declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
|
|
178
|
+
declare(:struct_parser, 'Default parser when expected value is a struct', values: %i[json ruby])
|
|
179
|
+
# do not parse options yet, let's wait for option `-h` to be overriden
|
|
177
180
|
end
|
|
178
181
|
|
|
179
182
|
# @param descr [String] description for help
|
|
180
|
-
# @param expected is
|
|
181
|
-
# - Array of allowed value (single value)
|
|
182
|
-
# - :multiple for remaining values
|
|
183
|
-
# - :single for a single unconstrained value
|
|
184
|
-
# - :integer for a single integer value
|
|
185
183
|
# @param mandatory [Boolean] if true, raise error if option not set
|
|
186
|
-
# @param
|
|
184
|
+
# @param multiple [Boolean] if true, return remaining arguments
|
|
185
|
+
# @param accept_list [Array] list of allowed values (Symbol)
|
|
186
|
+
# @param validation [Class, Array] accepted value type(s) or list of Symbols
|
|
187
187
|
# @param aliases [Hash] map of aliases: key = alias, value = real value
|
|
188
188
|
# @param default [Object] default value
|
|
189
|
-
# @return value, list or nil
|
|
190
|
-
def get_next_argument(descr,
|
|
191
|
-
Aspera.assert(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Aspera.assert(
|
|
195
|
-
|
|
196
|
-
allowed_types = type
|
|
189
|
+
# @return one value, list or nil (if optional and no default)
|
|
190
|
+
def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: String, aliases: nil, default: nil)
|
|
191
|
+
Aspera.assert(accept_list.nil? || (accept_list.is_a?(Array) && accept_list.all?(Symbol)))
|
|
192
|
+
validation = Symbol if accept_list
|
|
193
|
+
Aspera.assert(validation.nil? || validation.is_a?(Class) || (validation.is_a?(Array) && validation.all?(Class))){'validation must be Class or Array of Class'}
|
|
194
|
+
Aspera.assert(aliases.nil? || (aliases.is_a?(Hash) && aliases.keys.all?(Symbol) && aliases.values.all?(Symbol))){'aliases must be Hash:Symbol: Symbol'}
|
|
195
|
+
allowed_types = validation
|
|
197
196
|
unless allowed_types.nil?
|
|
198
197
|
allowed_types = [allowed_types] unless allowed_types.is_a?(Array)
|
|
199
198
|
descr = "#{descr} (#{allowed_types.join(', ')})"
|
|
200
199
|
end
|
|
201
200
|
result =
|
|
202
201
|
if !@unprocessed_cmd_line_arguments.empty?
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if value.length.eql?(1) && value.first.is_a?(Array)
|
|
211
|
-
value = value.first
|
|
212
|
-
end
|
|
213
|
-
value
|
|
214
|
-
when Array
|
|
215
|
-
allowed_values = [].concat(expected)
|
|
202
|
+
how_many = multiple ? @unprocessed_cmd_line_arguments.length : 1
|
|
203
|
+
values = @unprocessed_cmd_line_arguments.shift(how_many)
|
|
204
|
+
values = values.map{|v|evaluate_extended_value(v, allowed_types)}
|
|
205
|
+
# if expecting list and only one arg of type array : it is the list
|
|
206
|
+
values = values.first if values.length.eql?(1) && values.first.is_a?(Array)
|
|
207
|
+
if accept_list
|
|
208
|
+
allowed_values = [].concat(accept_list)
|
|
216
209
|
allowed_values.concat(aliases.keys) unless aliases.nil?
|
|
217
|
-
self.class.get_from_list(
|
|
218
|
-
else Aspera.error_unexpected_value(expected)
|
|
210
|
+
values = values.map{|v|self.class.get_from_list(v, descr, allowed_values)}
|
|
219
211
|
end
|
|
212
|
+
multiple ? values : values.first
|
|
220
213
|
elsif !default.nil? then default
|
|
221
214
|
# no value provided, either get value interactively, or exception
|
|
222
|
-
elsif mandatory then get_interactive(
|
|
215
|
+
elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list)
|
|
223
216
|
end
|
|
224
|
-
if result.is_a?(String) &&
|
|
217
|
+
if result.is_a?(String) && validation.eql?(Integer)
|
|
225
218
|
int_result = Integer(result, exception: false)
|
|
226
219
|
raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
|
|
227
220
|
result = int_result
|
|
228
221
|
end
|
|
229
222
|
Log.log.debug{"#{descr}=#{result}"}
|
|
230
223
|
result = aliases[result] if aliases&.key?(result)
|
|
231
|
-
|
|
224
|
+
# if value comes from JSON/YAML, it may come as Integer
|
|
225
|
+
result = result.to_s if result.is_a?(Integer) && validation.eql?(String)
|
|
226
|
+
self.class.validate_type(:argument, descr, result, allowed_types, check_array: multiple) unless result.nil? && !mandatory
|
|
232
227
|
return result
|
|
233
228
|
end
|
|
234
229
|
|
|
235
|
-
def get_next_command(command_list, aliases: nil); return get_next_argument('command',
|
|
230
|
+
def get_next_command(command_list, aliases: nil); return get_next_argument('command', accept_list: command_list, aliases: aliases); end
|
|
236
231
|
|
|
237
232
|
# Get an option value by name
|
|
238
233
|
# either return value or calls handler, can return nil
|
|
@@ -261,13 +256,13 @@ module Aspera
|
|
|
261
256
|
raise Cli::BadArgument, "Missing mandatory option: #{option_symbol}" if mandatory
|
|
262
257
|
elsif @ask_missing_optional || mandatory
|
|
263
258
|
# ask_missing_mandatory
|
|
264
|
-
|
|
259
|
+
accept_list = nil
|
|
265
260
|
# print "please enter: #{option_symbol.to_s}"
|
|
266
261
|
if @declared_options.key?(option_symbol) && attributes.key?(:values)
|
|
267
|
-
|
|
262
|
+
accept_list = attributes[:values]
|
|
268
263
|
end
|
|
269
|
-
result = get_interactive(
|
|
270
|
-
set_option(option_symbol, result, 'interactive')
|
|
264
|
+
result = get_interactive(option_symbol.to_s, option: true, accept_list: accept_list)
|
|
265
|
+
set_option(option_symbol, result, where: 'interactive')
|
|
271
266
|
end
|
|
272
267
|
end
|
|
273
268
|
self.class.validate_type(:option, option_symbol, result, attributes[:types]) unless result.nil? && !mandatory
|
|
@@ -275,12 +270,16 @@ module Aspera
|
|
|
275
270
|
end
|
|
276
271
|
|
|
277
272
|
# set an option value by name, either store value or call handler
|
|
278
|
-
|
|
273
|
+
# @param option_symbol [Symbol] option name
|
|
274
|
+
# @param value [String] value to set
|
|
275
|
+
# @param where [String] where the value comes from
|
|
276
|
+
# @param expect [Class, Array] expected value type(s)
|
|
277
|
+
def set_option(option_symbol, value, where: 'code override')
|
|
279
278
|
Aspera.assert_type(option_symbol, Symbol)
|
|
280
279
|
raise Cli::BadArgument, "Unknown option: #{option_symbol}" unless @declared_options.key?(option_symbol)
|
|
281
280
|
attributes = @declared_options[option_symbol]
|
|
282
281
|
Log.log.warn("#{option_symbol}: Option is deprecated: #{attributes[:deprecation]}") if attributes[:deprecation]
|
|
283
|
-
value =
|
|
282
|
+
value = evaluate_extended_value(value, attributes[:types])
|
|
284
283
|
value = Manager.enum_to_bool(value) if attributes[:values].eql?(BOOLEAN_VALUES)
|
|
285
284
|
Log.log.debug{"(#{attributes[:read_write]}/#{where}) set #{option_symbol}=#{value}"}
|
|
286
285
|
self.class.validate_type(:option, option_symbol, value, attributes[:types])
|
|
@@ -329,21 +328,21 @@ module Aspera
|
|
|
329
328
|
if opt[:read_write].eql?(:accessor)
|
|
330
329
|
Aspera.assert_type(handler, Hash)
|
|
331
330
|
Aspera.assert(handler.keys.sort.eql?(%i[m o]))
|
|
332
|
-
Log.log.
|
|
331
|
+
Log.log.trace1{"set attr obj: #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
|
|
333
332
|
opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
|
|
334
333
|
end
|
|
335
|
-
set_option(option_symbol, default, 'default') unless default.nil?
|
|
334
|
+
set_option(option_symbol, default, where: 'default') unless default.nil?
|
|
336
335
|
on_args = [description]
|
|
337
336
|
case values
|
|
338
337
|
when nil
|
|
339
338
|
on_args.push(symbol_to_option(option_symbol, 'VALUE'))
|
|
340
339
|
on_args.push("-#{short}VALUE") unless short.nil?
|
|
341
340
|
on_args.push(coerce) unless coerce.nil?
|
|
342
|
-
@parser.on(*on_args) { |v| set_option(option_symbol, v, SOURCE_USER) }
|
|
341
|
+
@parser.on(*on_args) { |v| set_option(option_symbol, v, where: SOURCE_USER) }
|
|
343
342
|
when Array, :bool
|
|
344
343
|
if values.eql?(:bool)
|
|
345
344
|
values = BOOLEAN_VALUES
|
|
346
|
-
set_option(option_symbol, Manager.enum_to_bool(default), 'default') unless default.nil?
|
|
345
|
+
set_option(option_symbol, Manager.enum_to_bool(default), where: 'default') unless default.nil?
|
|
347
346
|
end
|
|
348
347
|
# this option value must be a symbol
|
|
349
348
|
opt[:values] = values
|
|
@@ -355,7 +354,7 @@ module Aspera
|
|
|
355
354
|
on_args[0] = "#{description}: #{help_values}"
|
|
356
355
|
on_args.push(symbol_to_option(option_symbol, 'ENUM'))
|
|
357
356
|
on_args.push(values)
|
|
358
|
-
@parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), SOURCE_USER)}
|
|
357
|
+
@parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), where: SOURCE_USER)}
|
|
359
358
|
when :date
|
|
360
359
|
on_args.push(symbol_to_option(option_symbol, 'DATE'))
|
|
361
360
|
@parser.on(*on_args) do |v|
|
|
@@ -364,7 +363,7 @@ module Aspera
|
|
|
364
363
|
when /^-([0-9]+)h/ then Manager.time_to_string(Time.now - (Regexp.last_match(1).to_i * 3600))
|
|
365
364
|
else v
|
|
366
365
|
end
|
|
367
|
-
set_option(option_symbol, time_string, SOURCE_USER)
|
|
366
|
+
set_option(option_symbol, time_string, where: SOURCE_USER)
|
|
368
367
|
end
|
|
369
368
|
when :none
|
|
370
369
|
Aspera.assert(!block.nil?){"missing block for #{option_symbol}"}
|
|
@@ -373,7 +372,7 @@ module Aspera
|
|
|
373
372
|
@parser.on(*on_args, &block)
|
|
374
373
|
else Aspera.error_unexpected_value(values)
|
|
375
374
|
end
|
|
376
|
-
Log.log.
|
|
375
|
+
Log.log.trace1{"on_args=#{on_args}"}
|
|
377
376
|
end
|
|
378
377
|
|
|
379
378
|
# Adds each of the keys of specified hash as an option
|
|
@@ -401,6 +400,7 @@ module Aspera
|
|
|
401
400
|
end
|
|
402
401
|
|
|
403
402
|
# get all original options on command line used to generate a config in config file
|
|
403
|
+
# @return [Hash] options as taken from config file and command line just before command execution
|
|
404
404
|
def unprocessed_options_with_value
|
|
405
405
|
result = {}
|
|
406
406
|
@initial_cli_options.each do |option_value|
|
|
@@ -435,7 +435,7 @@ module Aspera
|
|
|
435
435
|
|
|
436
436
|
# removes already known options from the list
|
|
437
437
|
def parse_options!
|
|
438
|
-
Log.log.
|
|
438
|
+
Log.log.trace1('parse_options!'.red)
|
|
439
439
|
# first conf file, then env var
|
|
440
440
|
consume_option_pairs(@option_pairs_batch, 'set')
|
|
441
441
|
consume_option_pairs(@option_pairs_env, 'env')
|
|
@@ -443,21 +443,21 @@ module Aspera
|
|
|
443
443
|
unknown_options = []
|
|
444
444
|
begin
|
|
445
445
|
# remove known options one by one, exception if unknown
|
|
446
|
-
Log.log.
|
|
446
|
+
Log.log.trace1('before parse'.red)
|
|
447
447
|
@parser.parse!(@unprocessed_cmd_line_options)
|
|
448
|
-
Log.log.
|
|
448
|
+
Log.log.trace1('After parse'.red)
|
|
449
449
|
rescue OptionParser::InvalidOption => e
|
|
450
|
-
Log.log.
|
|
450
|
+
Log.log.trace1{"InvalidOption #{e}".red}
|
|
451
451
|
# save for later processing
|
|
452
452
|
unknown_options.push(e.args.first)
|
|
453
453
|
retry
|
|
454
454
|
end
|
|
455
|
-
Log.log.
|
|
455
|
+
Log.log.trace1{"remains: #{unknown_options}"}
|
|
456
456
|
# set unprocessed options for next time
|
|
457
457
|
@unprocessed_cmd_line_options = unknown_options
|
|
458
458
|
end
|
|
459
459
|
|
|
460
|
-
def prompt_user_input(prompt, sensitive)
|
|
460
|
+
def prompt_user_input(prompt, sensitive: false)
|
|
461
461
|
return $stdin.getpass("#{prompt}> ") if sensitive
|
|
462
462
|
print("#{prompt}> ")
|
|
463
463
|
line = $stdin.gets
|
|
@@ -471,7 +471,7 @@ module Aspera
|
|
|
471
471
|
# @return [Symbol] selected symbol
|
|
472
472
|
def prompt_user_input_in_list(prompt, sym_list)
|
|
473
473
|
loop do
|
|
474
|
-
input = prompt_user_input(prompt
|
|
474
|
+
input = prompt_user_input(prompt).to_sym
|
|
475
475
|
if sym_list.any?{|a|a.eql?(input)}
|
|
476
476
|
return input
|
|
477
477
|
else
|
|
@@ -480,34 +480,49 @@ module Aspera
|
|
|
480
480
|
end
|
|
481
481
|
end
|
|
482
482
|
|
|
483
|
-
|
|
483
|
+
# Prompt user for input in a list of symbols
|
|
484
|
+
# @param descr [String] description for help
|
|
485
|
+
# @param option [Boolean] true if command line option
|
|
486
|
+
# @param multiple [Boolean] true if multiple values expected
|
|
487
|
+
# @param accept_list [Array] list of expected values
|
|
488
|
+
def get_interactive(descr, option: false, multiple: false, accept_list: nil)
|
|
489
|
+
what = option ? 'option' : 'argument'
|
|
484
490
|
if !@ask_missing_mandatory
|
|
485
|
-
|
|
486
|
-
|
|
491
|
+
message = "missing #{what}: #{descr}"
|
|
492
|
+
if accept_list.nil?
|
|
493
|
+
raise Cli::BadArgument, message
|
|
494
|
+
else
|
|
495
|
+
self.class.multi_choice_assert(false, message, accept_list)
|
|
496
|
+
end
|
|
487
497
|
end
|
|
488
498
|
result = nil
|
|
489
|
-
sensitive =
|
|
490
|
-
default_prompt = "#{
|
|
499
|
+
sensitive = option && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
|
|
500
|
+
default_prompt = "#{what}: #{descr}"
|
|
491
501
|
# ask interactively
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
result
|
|
503
|
-
else # one fixed
|
|
504
|
-
result = self.class.get_from_list(prompt_user_input("#{expected.join(' ')}\n#{default_prompt}", sensitive), descr, expected)
|
|
502
|
+
result = []
|
|
503
|
+
puts(' (one per line, end with empty line)') if multiple
|
|
504
|
+
loop do
|
|
505
|
+
prompt = default_prompt
|
|
506
|
+
prompt = "#{accept_list.join(' ')}\n#{default_prompt}" if accept_list
|
|
507
|
+
entry = prompt_user_input(prompt, sensitive: sensitive)
|
|
508
|
+
break if entry.empty? && multiple
|
|
509
|
+
entry = ExtendedValue.instance.evaluate(entry)
|
|
510
|
+
entry = self.class.get_from_list(entry, descr, accept_list) if accept_list
|
|
511
|
+
return entry unless multiple
|
|
512
|
+
result.push(entry)
|
|
505
513
|
end
|
|
506
514
|
return result
|
|
507
515
|
end
|
|
508
516
|
|
|
509
517
|
private
|
|
510
518
|
|
|
519
|
+
def evaluate_extended_value(value, types)
|
|
520
|
+
if DEFAULT_PARSER_TYPES.include?(types) || (types.is_a?(Array) && types.all?{|t|DEFAULT_PARSER_TYPES.include?(t)})
|
|
521
|
+
return ExtendedValue.instance.evaluate_with_default(value)
|
|
522
|
+
end
|
|
523
|
+
return ExtendedValue.instance.evaluate(value)
|
|
524
|
+
end
|
|
525
|
+
|
|
511
526
|
# generate command line option from option symbol
|
|
512
527
|
def symbol_to_option(symbol, opt_val = nil)
|
|
513
528
|
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
|
@@ -523,7 +538,7 @@ module Aspera
|
|
|
523
538
|
# @param unprocessed_options [Array] list of options to apply (key_sym,value)
|
|
524
539
|
# @param where [String] where the options come from
|
|
525
540
|
def consume_option_pairs(unprocessed_options, where)
|
|
526
|
-
Log.log.
|
|
541
|
+
Log.log.trace1{"consume_option_pairs: #{where}"}
|
|
527
542
|
options_to_set = {}
|
|
528
543
|
unprocessed_options.each do |k, v|
|
|
529
544
|
if @declared_options.key?(k)
|
|
@@ -533,11 +548,11 @@ module Aspera
|
|
|
533
548
|
end
|
|
534
549
|
options_to_set[k] = v
|
|
535
550
|
else
|
|
536
|
-
Log.log.
|
|
551
|
+
Log.log.trace1{"unprocessed: #{k}: #{v}"}
|
|
537
552
|
end
|
|
538
553
|
end
|
|
539
554
|
options_to_set.each do |k, v|
|
|
540
|
-
set_option(k, v, where)
|
|
555
|
+
set_option(k, v, where: where)
|
|
541
556
|
# keep only unprocessed values for next parse
|
|
542
557
|
unprocessed_options.delete(k)
|
|
543
558
|
end
|
data/lib/aspera/cli/plugin.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'aspera/assert'
|
|
|
5
5
|
|
|
6
6
|
module Aspera
|
|
7
7
|
module Cli
|
|
8
|
-
#
|
|
8
|
+
# Base class for plugins
|
|
9
9
|
class Plugin
|
|
10
10
|
# operations without id
|
|
11
11
|
GLOBAL_OPS = %i[create list].freeze
|
|
@@ -29,7 +29,6 @@ module Aspera
|
|
|
29
29
|
:value, 'Value for create, update, list filter', types: Hash,
|
|
30
30
|
deprecation: '(4.14) Use positional value for create/modify or option: query for list/delete')
|
|
31
31
|
options.declare(:property, 'Name of property to set (modify operation)')
|
|
32
|
-
options.declare(:id, 'Resource identifier', deprecation: "(4.14) Use positional identifier after verb (#{INSTANCE_OPS.join(',')})")
|
|
33
32
|
options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
|
|
34
33
|
options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
|
|
35
34
|
end
|
|
@@ -54,8 +53,8 @@ module Aspera
|
|
|
54
53
|
options.parser.separator('OPTIONS:')
|
|
55
54
|
end
|
|
56
55
|
|
|
56
|
+
# @return a hash of instance variables
|
|
57
57
|
def init_params
|
|
58
|
-
# return a hash of instance variables
|
|
59
58
|
INIT_PARAMS.map{|p| [p, instance_variable_get("@#{p}".to_sym)]}.to_h
|
|
60
59
|
end
|
|
61
60
|
|
|
@@ -66,9 +65,7 @@ module Aspera
|
|
|
66
65
|
# @return [String, Array] identifier or list of ids
|
|
67
66
|
def instance_identifier(description: 'identifier', as_option: nil, &block)
|
|
68
67
|
if as_option.nil?
|
|
69
|
-
|
|
70
|
-
res_id = options.get_option(:id)
|
|
71
|
-
res_id = options.get_next_argument(description) if res_id.nil?
|
|
68
|
+
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
72
69
|
else
|
|
73
70
|
res_id = options.get_option(as_option)
|
|
74
71
|
end
|
|
@@ -255,7 +252,7 @@ module Aspera
|
|
|
255
252
|
Log.log.warn("option `value` is deprecated. Use positional parameter for #{command}") unless value.nil?
|
|
256
253
|
value = options.get_next_argument(
|
|
257
254
|
"parameters for #{command}#{description.nil? ? '' : " (#{description})"}", mandatory: default.nil?,
|
|
258
|
-
|
|
255
|
+
validation: bulk ? Array : type) if value.nil?
|
|
259
256
|
value = default if value.nil?
|
|
260
257
|
unless type.nil?
|
|
261
258
|
type = [type] unless type.is_a?(Array)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'singleton'
|
|
4
4
|
module Aspera
|
|
5
5
|
module Cli
|
|
6
|
-
#
|
|
6
|
+
# Instantiate plugin from well-known locations
|
|
7
7
|
class PluginFactory
|
|
8
8
|
include Singleton
|
|
9
9
|
|
|
@@ -19,14 +19,17 @@ module Aspera
|
|
|
19
19
|
@plugins = {}
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# @return list of registered plugins
|
|
22
23
|
def plugin_list
|
|
23
24
|
@plugins.keys
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
# @return path to source file of plugin
|
|
26
28
|
def plugin_source(plugin_name_sym)
|
|
27
29
|
@plugins[plugin_name_sym][:source]
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
# add a folder to the list of folders to look for plugins
|
|
30
33
|
def add_lookup_folder(folder)
|
|
31
34
|
@lookup_folders.unshift(folder)
|
|
32
35
|
end
|
|
@@ -43,6 +46,7 @@ module Aspera
|
|
|
43
46
|
end
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
# @return Class object for plugin
|
|
46
50
|
def plugin_class(plugin_name_sym)
|
|
47
51
|
raise "ERROR: plugin not found: #{plugin_name_sym}" unless @plugins.key?(plugin_name_sym)
|
|
48
52
|
require @plugins[plugin_name_sym][:require_stanza]
|
|
@@ -50,6 +54,9 @@ module Aspera
|
|
|
50
54
|
return Object.const_get("#{Module.nesting[1]}::#{PLUGINS_MODULE}::#{plugin_name_sym.to_s.capitalize}")
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
# Create specified plugin
|
|
58
|
+
# @param plugin_name_sym [Symbol] name of plugin
|
|
59
|
+
# @param args [Hash] arguments to pass to plugin constructor
|
|
53
60
|
def create(plugin_name_sym, **args)
|
|
54
61
|
# TODO: check that ancestor is Plugin?
|
|
55
62
|
plugin_class(plugin_name_sym).new(**args)
|
|
@@ -57,6 +64,8 @@ module Aspera
|
|
|
57
64
|
|
|
58
65
|
private
|
|
59
66
|
|
|
67
|
+
# add plugin information to list
|
|
68
|
+
# @param path [String] path to plugin source file
|
|
60
69
|
def add_plugin_info(path)
|
|
61
70
|
raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
|
|
62
71
|
plugin_symbol = File.basename(path, RUBY_FILE_EXT).to_sym
|
|
@@ -4,6 +4,7 @@ require 'aspera/cli/plugins/node'
|
|
|
4
4
|
require 'aspera/cli/plugins/ats'
|
|
5
5
|
require 'aspera/cli/basic_auth_plugin'
|
|
6
6
|
require 'aspera/cli/transfer_agent'
|
|
7
|
+
require 'aspera/cli/special_values'
|
|
7
8
|
require 'aspera/agent/node'
|
|
8
9
|
require 'aspera/transfer/spec'
|
|
9
10
|
require 'aspera/api/aoc'
|
|
@@ -123,7 +124,7 @@ module Aspera
|
|
|
123
124
|
formatter.display_status(pub_key_pem)
|
|
124
125
|
if !options.get_option(:test_mode)
|
|
125
126
|
formatter.display_status('Once updated or validated, press enter.')
|
|
126
|
-
|
|
127
|
+
Environment.instance.open_uri(instance_url)
|
|
127
128
|
$stdin.gets
|
|
128
129
|
end
|
|
129
130
|
else
|
|
@@ -137,7 +138,7 @@ module Aspera
|
|
|
137
138
|
formatter.display_status('- origin: localhost')
|
|
138
139
|
formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
|
|
139
140
|
end
|
|
140
|
-
|
|
141
|
+
Environment.instance.open_uri("#{instance_url}/admin/api-clients")
|
|
141
142
|
options.get_option(:client_id, mandatory: true)
|
|
142
143
|
options.get_option(:client_secret, mandatory: true)
|
|
143
144
|
use_browser_authentication = true
|
|
@@ -319,8 +320,8 @@ module Aspera
|
|
|
319
320
|
# client side is agent
|
|
320
321
|
# server side is transfer server
|
|
321
322
|
# in same workspace
|
|
322
|
-
push_pull = options.get_next_argument('direction',
|
|
323
|
-
source_folder = options.get_next_argument('folder of source files',
|
|
323
|
+
push_pull = options.get_next_argument('direction', accept_list: %i[push pull])
|
|
324
|
+
source_folder = options.get_next_argument('folder of source files', validation: String)
|
|
324
325
|
case push_pull
|
|
325
326
|
when :push
|
|
326
327
|
client_direction = Transfer::Spec::DIRECTION_SEND
|
|
@@ -415,7 +416,7 @@ module Aspera
|
|
|
415
416
|
fields = object.keys.reject{|k|k.eql?('certificate')}
|
|
416
417
|
return { type: :single_object, data: object, fields: fields }
|
|
417
418
|
when :modify
|
|
418
|
-
changes = options.get_next_argument('properties',
|
|
419
|
+
changes = options.get_next_argument('properties', validation: Hash)
|
|
419
420
|
return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
|
|
420
421
|
aoc_api.update("#{resource_class_path}/#{one_id}", changes)
|
|
421
422
|
{'id' => one_id}
|
|
@@ -427,7 +428,7 @@ module Aspera
|
|
|
427
428
|
end
|
|
428
429
|
when :set_pub_key
|
|
429
430
|
# special : reads private and generate public
|
|
430
|
-
the_private_key = options.get_next_argument('private_key PEM value',
|
|
431
|
+
the_private_key = options.get_next_argument('private_key PEM value', validation: String)
|
|
431
432
|
the_public_key = OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
|
|
432
433
|
aoc_api.update(resource_instance_path, {jwt_grant_enabled: true, public_key: the_public_key})
|
|
433
434
|
return Main.result_success
|
|
@@ -449,7 +450,7 @@ module Aspera
|
|
|
449
450
|
case command_admin
|
|
450
451
|
when :resource
|
|
451
452
|
Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
|
|
452
|
-
return execute_resource_action(options.get_next_argument('resource',
|
|
453
|
+
return execute_resource_action(options.get_next_argument('resource', accept_list: ADMIN_OBJECTS))
|
|
453
454
|
when *ADMIN_OBJECTS
|
|
454
455
|
return execute_resource_action(command_admin)
|
|
455
456
|
when :auth_providers
|
|
@@ -534,7 +535,7 @@ module Aspera
|
|
|
534
535
|
return {type: :object_list, data: events}
|
|
535
536
|
when :transfers
|
|
536
537
|
event_type = command_analytics.to_s
|
|
537
|
-
filter_resource = options.get_next_argument('resource',
|
|
538
|
+
filter_resource = options.get_next_argument('resource', accept_list: %i[organizations users nodes])
|
|
538
539
|
filter_id = options.get_next_argument('identifier', mandatory: false) ||
|
|
539
540
|
case filter_resource
|
|
540
541
|
when :organizations then aoc_api.current_user_info['organization_id']
|
|
@@ -622,7 +623,7 @@ module Aspera
|
|
|
622
623
|
when :show
|
|
623
624
|
return { type: :single_object, data: aoc_api.current_user_info(exception: true) }
|
|
624
625
|
when :modify
|
|
625
|
-
aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties',
|
|
626
|
+
aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties', validation: Hash))
|
|
626
627
|
return Main.result_status('modified')
|
|
627
628
|
end
|
|
628
629
|
when :preferences
|
|
@@ -631,7 +632,7 @@ module Aspera
|
|
|
631
632
|
when :show
|
|
632
633
|
return { type: :single_object, data: aoc_api.read(user_preferences_res)[:data] }
|
|
633
634
|
when :modify
|
|
634
|
-
aoc_api.update(user_preferences_res, options.get_next_argument('properties',
|
|
635
|
+
aoc_api.update(user_preferences_res, options.get_next_argument('properties', validation: Hash))
|
|
635
636
|
return Main.result_status('modified')
|
|
636
637
|
end
|
|
637
638
|
end
|
|
@@ -690,13 +691,13 @@ module Aspera
|
|
|
690
691
|
].concat(aoc_api.additional_persistence_ids)))
|
|
691
692
|
end
|
|
692
693
|
case ids_to_download
|
|
693
|
-
when
|
|
694
|
+
when SpecialValues::ALL, SpecialValues::INIT
|
|
694
695
|
query = query_read_delete(default: PACKAGE_RECEIVED_BASE_QUERY)
|
|
695
696
|
Aspera.assert_type(query, Hash){'query'}
|
|
696
697
|
resolve_dropbox_name_default_ws_id(query)
|
|
697
698
|
# remove from list the ones already downloaded
|
|
698
699
|
all_ids = api_read_all('packages', query)[:data].map{|e|e['id']}
|
|
699
|
-
if ids_to_download.eql?(
|
|
700
|
+
if ids_to_download.eql?(SpecialValues::INIT)
|
|
700
701
|
Aspera.assert(skip_ids_persistency){'Only with option once_only'}
|
|
701
702
|
skip_ids_persistency.data.clear.concat(all_ids)
|
|
702
703
|
skip_ids_persistency.save
|
|
@@ -758,9 +759,9 @@ module Aspera
|
|
|
758
759
|
when *NODE4_EXT_COMMANDS
|
|
759
760
|
return execute_nodegen4_command(command_repo, aoc_api.context[:home_node_id], file_id: aoc_api.context[:home_file_id], scope: Api::Node::SCOPE_USER)
|
|
760
761
|
when :short_link
|
|
761
|
-
link_type = options.get_next_argument('link type',
|
|
762
|
+
link_type = options.get_next_argument('link type', accept_list: %i[public private])
|
|
762
763
|
short_link_command = options.get_next_command(%i[create delete list])
|
|
763
|
-
folder_dest = options.get_next_argument('path',
|
|
764
|
+
folder_dest = options.get_next_argument('path', validation: String)
|
|
764
765
|
home_node_api = aoc_api.node_api_from(
|
|
765
766
|
node_id: aoc_api.context[:home_node_id],
|
|
766
767
|
workspace_id: aoc_api.context[:workspace_id],
|