aspera-cli 4.17.0 → 4.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -4
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +15 -1
- data/README.md +711 -432
- data/bin/ascli +5 -0
- data/bin/asession +2 -2
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +10 -8
- data/lib/aspera/agent/base.rb +9 -6
- data/lib/aspera/agent/connect.rb +7 -8
- data/lib/aspera/agent/direct.rb +56 -37
- data/lib/aspera/agent/httpgw.rb +23 -324
- data/lib/aspera/agent/node.rb +19 -20
- data/lib/aspera/agent/trsdk.rb +19 -20
- data/lib/aspera/api/aoc.rb +17 -14
- data/lib/aspera/api/cos_node.rb +4 -4
- data/lib/aspera/api/httpgw.rb +342 -0
- data/lib/aspera/api/node.rb +135 -89
- data/lib/aspera/ascmd.rb +4 -3
- data/lib/aspera/ascp/installation.rb +15 -7
- data/lib/aspera/ascp/management.rb +2 -2
- data/lib/aspera/ascp/products.rb +1 -1
- data/lib/aspera/cli/basic_auth_plugin.rb +5 -9
- data/lib/aspera/cli/extended_value.rb +35 -16
- data/lib/aspera/cli/formatter.rb +161 -70
- data/lib/aspera/cli/hints.rb +18 -0
- data/lib/aspera/cli/main.rb +32 -39
- data/lib/aspera/cli/manager.rb +151 -119
- data/lib/aspera/cli/plugin.rb +27 -21
- data/lib/aspera/cli/plugin_factory.rb +31 -20
- data/lib/aspera/cli/plugins/alee.rb +14 -2
- data/lib/aspera/cli/plugins/aoc.rb +152 -141
- data/lib/aspera/cli/plugins/ats.rb +1 -1
- data/lib/aspera/cli/plugins/config.rb +72 -65
- data/lib/aspera/cli/plugins/console.rb +8 -5
- data/lib/aspera/cli/plugins/faspex.rb +32 -23
- data/lib/aspera/cli/plugins/faspex5.rb +232 -156
- data/lib/aspera/cli/plugins/faspio.rb +85 -0
- data/lib/aspera/cli/plugins/httpgw.rb +55 -0
- data/lib/aspera/cli/plugins/node.rb +129 -64
- data/lib/aspera/cli/plugins/orchestrator.rb +33 -30
- data/lib/aspera/cli/plugins/preview.rb +7 -3
- data/lib/aspera/cli/plugins/server.rb +6 -6
- data/lib/aspera/cli/plugins/shares.rb +16 -14
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +10 -10
- data/lib/aspera/cli/transfer_agent.rb +7 -6
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +70 -9
- data/lib/aspera/faspex_gw.rb +5 -4
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/log.rb +6 -3
- data/lib/aspera/node_simulator.rb +2 -2
- data/lib/aspera/oauth/base.rb +31 -19
- data/lib/aspera/oauth/factory.rb +12 -13
- data/lib/aspera/oauth/generic.rb +1 -0
- data/lib/aspera/oauth/jwt.rb +18 -15
- data/lib/aspera/oauth/url_json.rb +8 -6
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/options.rb +3 -3
- data/lib/aspera/preview/terminal.rb +4 -4
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +5 -1
- data/lib/aspera/rest.rb +105 -88
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +2 -2
- data/lib/aspera/rest_errors_aspera.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +2 -4
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +39 -36
- data/lib/aspera/transfer/spec.rb +2 -0
- data/lib/aspera/transfer/sync.rb +2 -1
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +5 -4
- data/lib/aspera/web_auth.rb +1 -1
- data/lib/aspera/web_server_simple.rb +4 -3
- data.tar.gz.sig +0 -0
- metadata +7 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/bss.rb +0 -71
- data/lib/aspera/open_application.rb +0 -71
data/lib/aspera/cli/manager.rb
CHANGED
@@ -55,9 +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
|
-
|
58
|
+
OPTION_VALUE_SEPARATOR = '='
|
59
|
+
OPTION_PREFIX = '--'
|
60
|
+
OPTIONS_STOP = '--'
|
59
61
|
|
60
|
-
|
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
|
61
65
|
|
62
66
|
class << self
|
63
67
|
def enum_to_bool(enum)
|
@@ -83,9 +87,9 @@ module Aspera
|
|
83
87
|
|
84
88
|
# Generates error message with list of allowed values
|
85
89
|
# @param error_msg [String] error message
|
86
|
-
# @param
|
87
|
-
def multi_choice_assert(assertion, error_msg,
|
88
|
-
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
|
89
93
|
end
|
90
94
|
|
91
95
|
# change option name with dash to name with underscore
|
@@ -94,19 +98,22 @@ module Aspera
|
|
94
98
|
end
|
95
99
|
|
96
100
|
def option_name_to_line(name)
|
97
|
-
return "
|
101
|
+
return "#{OPTION_PREFIX}#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}"
|
98
102
|
end
|
99
103
|
|
100
104
|
# @param what [Symbol] :option or :argument
|
101
105
|
# @param descr [String] description for help
|
102
|
-
# @param
|
106
|
+
# @param to_check [Object] value to check
|
103
107
|
# @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
|
104
|
-
def validate_type(what, descr,
|
108
|
+
def validate_type(what, descr, to_check, type_list, check_array: false)
|
105
109
|
return nil if type_list.nil?
|
106
110
|
Aspera.assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
110
117
|
end
|
111
118
|
end
|
112
119
|
|
@@ -114,23 +121,25 @@ module Aspera
|
|
114
121
|
attr_accessor :ask_missing_mandatory, :ask_missing_optional
|
115
122
|
attr_writer :fail_on_missing_mandatory
|
116
123
|
|
117
|
-
def initialize(program_name)
|
118
|
-
# command line values not starting with '-'
|
124
|
+
def initialize(program_name, argv = nil)
|
125
|
+
# command line values *not* starting with '-'
|
119
126
|
@unprocessed_cmd_line_arguments = []
|
120
127
|
# command line values starting with '-'
|
121
128
|
@unprocessed_cmd_line_options = []
|
122
129
|
# a copy of all initial options
|
123
130
|
@initial_cli_options = []
|
124
|
-
# option description: key = option symbol, value=
|
131
|
+
# option description: key = option symbol, value=Hash, :read_write, :accessor, :value, :accepted
|
125
132
|
@declared_options = {}
|
126
133
|
# do we ask missing options and arguments to user ?
|
127
134
|
@ask_missing_mandatory = false # STDIN.isatty
|
128
135
|
# ask optional options if not provided and in interactive
|
129
136
|
@ask_missing_optional = false
|
130
137
|
@fail_on_missing_mandatory = true
|
131
|
-
#
|
132
|
-
|
133
|
-
|
138
|
+
# Array of [key(sym), value]
|
139
|
+
# those must be set before parse
|
140
|
+
# parse consumes those defined only
|
141
|
+
@option_pairs_batch = {}
|
142
|
+
@option_pairs_env = {}
|
134
143
|
# NOTE: was initially inherited but it is preferred to have specific methods
|
135
144
|
@parser = OptionParser.new
|
136
145
|
@parser.program_name = program_name
|
@@ -138,100 +147,94 @@ module Aspera
|
|
138
147
|
env_prefix = program_name.upcase + OPTION_SEP_SYMBOL
|
139
148
|
ENV.each do |k, v|
|
140
149
|
if k.start_with?(env_prefix)
|
141
|
-
@
|
150
|
+
@option_pairs_env[k[env_prefix.length..-1].downcase.to_sym] = v
|
142
151
|
end
|
143
152
|
end
|
144
|
-
Log.log.debug{"env=#{@
|
153
|
+
Log.log.debug{"env=#{@option_pairs_env}".red}
|
145
154
|
@unprocessed_cmd_line_options = []
|
146
155
|
@unprocessed_cmd_line_arguments = []
|
147
|
-
|
148
|
-
|
149
|
-
def parse_command_line(argv)
|
150
|
-
@parser.separator('')
|
151
|
-
@parser.separator('OPTIONS: global')
|
152
|
-
declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
|
153
|
-
declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
|
154
|
-
parse_options!
|
156
|
+
return if argv.nil?
|
155
157
|
process_options = true
|
156
158
|
until argv.empty?
|
157
159
|
value = argv.shift
|
158
160
|
if process_options && value.start_with?('-')
|
159
|
-
|
161
|
+
Log.log.trace1{"opt: #{value}"}
|
162
|
+
if value.eql?(OPTIONS_STOP)
|
160
163
|
process_options = false
|
161
164
|
else
|
162
165
|
@unprocessed_cmd_line_options.push(value)
|
163
166
|
end
|
164
167
|
else
|
168
|
+
Log.log.trace1{"arg: #{value}"}
|
165
169
|
@unprocessed_cmd_line_arguments.push(value)
|
166
170
|
end
|
167
171
|
end
|
168
|
-
@initial_cli_options = @unprocessed_cmd_line_options.dup
|
172
|
+
@initial_cli_options = @unprocessed_cmd_line_options.dup.freeze
|
169
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
|
170
180
|
end
|
171
181
|
|
172
182
|
# @param descr [String] description for help
|
173
|
-
# @param expected is
|
174
|
-
# - Array of allowed value (single value)
|
175
|
-
# - :multiple for remaining values
|
176
|
-
# - :single for a single unconstrained value
|
177
|
-
# - :integer for a single integer value
|
178
183
|
# @param mandatory [Boolean] if true, raise error if option not set
|
179
|
-
# @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
|
180
187
|
# @param aliases [Hash] map of aliases: key = alias, value = real value
|
181
188
|
# @param default [Object] default value
|
182
|
-
# @return value, list or nil
|
183
|
-
def get_next_argument(descr,
|
184
|
-
Aspera.assert(
|
185
|
-
|
186
|
-
|
187
|
-
Aspera.assert(
|
188
|
-
|
189
|
-
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
|
190
196
|
unless allowed_types.nil?
|
191
197
|
allowed_types = [allowed_types] unless allowed_types.is_a?(Array)
|
192
198
|
descr = "#{descr} (#{allowed_types.join(', ')})"
|
193
199
|
end
|
194
200
|
result =
|
195
201
|
if !@unprocessed_cmd_line_arguments.empty?
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
if value.length.eql?(1) && value.first.is_a?(Array)
|
204
|
-
value = value.first
|
205
|
-
end
|
206
|
-
value
|
207
|
-
when Array
|
208
|
-
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)
|
209
209
|
allowed_values.concat(aliases.keys) unless aliases.nil?
|
210
|
-
self.class.get_from_list(
|
211
|
-
else Aspera.error_unexpected_value(expected)
|
210
|
+
values = values.map{|v|self.class.get_from_list(v, descr, allowed_values)}
|
212
211
|
end
|
212
|
+
multiple ? values : values.first
|
213
213
|
elsif !default.nil? then default
|
214
214
|
# no value provided, either get value interactively, or exception
|
215
|
-
elsif mandatory then get_interactive(
|
215
|
+
elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list)
|
216
216
|
end
|
217
|
-
if result.is_a?(String) &&
|
217
|
+
if result.is_a?(String) && validation.eql?(Integer)
|
218
218
|
int_result = Integer(result, exception: false)
|
219
219
|
raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
|
220
220
|
result = int_result
|
221
221
|
end
|
222
222
|
Log.log.debug{"#{descr}=#{result}"}
|
223
223
|
result = aliases[result] if aliases&.key?(result)
|
224
|
-
|
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
|
225
227
|
return result
|
226
228
|
end
|
227
229
|
|
228
|
-
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
|
229
231
|
|
230
232
|
# Get an option value by name
|
231
233
|
# either return value or calls handler, can return nil
|
232
234
|
# ask interactively if requested/required
|
233
235
|
# @param mandatory [Boolean] if true, raise error if option not set
|
234
236
|
def get_option(option_symbol, mandatory: false, default: nil)
|
237
|
+
Aspera.assert_type(option_symbol, Symbol)
|
235
238
|
attributes = @declared_options[option_symbol]
|
236
239
|
Aspera.assert(attributes){"option not declared: #{option_symbol}"}
|
237
240
|
result = nil
|
@@ -253,13 +256,13 @@ module Aspera
|
|
253
256
|
raise Cli::BadArgument, "Missing mandatory option: #{option_symbol}" if mandatory
|
254
257
|
elsif @ask_missing_optional || mandatory
|
255
258
|
# ask_missing_mandatory
|
256
|
-
|
259
|
+
accept_list = nil
|
257
260
|
# print "please enter: #{option_symbol.to_s}"
|
258
261
|
if @declared_options.key?(option_symbol) && attributes.key?(:values)
|
259
|
-
|
262
|
+
accept_list = attributes[:values]
|
260
263
|
end
|
261
|
-
result = get_interactive(
|
262
|
-
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')
|
263
266
|
end
|
264
267
|
end
|
265
268
|
self.class.validate_type(:option, option_symbol, result, attributes[:types]) unless result.nil? && !mandatory
|
@@ -267,11 +270,16 @@ module Aspera
|
|
267
270
|
end
|
268
271
|
|
269
272
|
# set an option value by name, either store value or call handler
|
270
|
-
|
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')
|
278
|
+
Aspera.assert_type(option_symbol, Symbol)
|
271
279
|
raise Cli::BadArgument, "Unknown option: #{option_symbol}" unless @declared_options.key?(option_symbol)
|
272
280
|
attributes = @declared_options[option_symbol]
|
273
281
|
Log.log.warn("#{option_symbol}: Option is deprecated: #{attributes[:deprecation]}") if attributes[:deprecation]
|
274
|
-
value =
|
282
|
+
value = evaluate_extended_value(value, attributes[:types])
|
275
283
|
value = Manager.enum_to_bool(value) if attributes[:values].eql?(BOOLEAN_VALUES)
|
276
284
|
Log.log.debug{"(#{attributes[:read_write]}/#{where}) set #{option_symbol}=#{value}"}
|
277
285
|
self.class.validate_type(:option, option_symbol, value, attributes[:types])
|
@@ -292,10 +300,11 @@ module Aspera
|
|
292
300
|
# @param default [Object] default value
|
293
301
|
# @param values [nil, Array, :bool, :date, :none] list of allowed values, :bool for true/false, :date for dates, :none for on/off switch
|
294
302
|
# @param short [String] short option name
|
295
|
-
# @param coerce [Class] one of the coerce types accepted
|
303
|
+
# @param coerce [Class] one of the coerce types accepted by option parser
|
296
304
|
# @param types [Class, Array] accepted value type(s)
|
297
305
|
# @param block [Proc] block to execute when option is found
|
298
306
|
def declare(option_symbol, description, handler: nil, default: nil, values: nil, short: nil, coerce: nil, types: nil, deprecation: nil, &block)
|
307
|
+
Aspera.assert_type(option_symbol, Symbol)
|
299
308
|
Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
|
300
309
|
Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
|
301
310
|
Aspera.assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with capital"}
|
@@ -307,7 +316,7 @@ module Aspera
|
|
307
316
|
}
|
308
317
|
if !types.nil?
|
309
318
|
types = [types] unless types.is_a?(Array)
|
310
|
-
Aspera.assert(types.all?(Class)){"types must be Array of Class: #{types}"}
|
319
|
+
Aspera.assert(types.all?(Class)){"types must be (Array of) Class: #{types}"}
|
311
320
|
opt[:types] = types
|
312
321
|
description = "#{description} (#{types.map(&:name).join(', ')})"
|
313
322
|
end
|
@@ -322,18 +331,18 @@ module Aspera
|
|
322
331
|
Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
|
323
332
|
opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
|
324
333
|
end
|
325
|
-
set_option(option_symbol, default, 'default') unless default.nil?
|
334
|
+
set_option(option_symbol, default, where: 'default') unless default.nil?
|
326
335
|
on_args = [description]
|
327
336
|
case values
|
328
337
|
when nil
|
329
338
|
on_args.push(symbol_to_option(option_symbol, 'VALUE'))
|
330
339
|
on_args.push("-#{short}VALUE") unless short.nil?
|
331
340
|
on_args.push(coerce) unless coerce.nil?
|
332
|
-
@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) }
|
333
342
|
when Array, :bool
|
334
343
|
if values.eql?(:bool)
|
335
344
|
values = BOOLEAN_VALUES
|
336
|
-
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?
|
337
346
|
end
|
338
347
|
# this option value must be a symbol
|
339
348
|
opt[:values] = values
|
@@ -345,7 +354,7 @@ module Aspera
|
|
345
354
|
on_args[0] = "#{description}: #{help_values}"
|
346
355
|
on_args.push(symbol_to_option(option_symbol, 'ENUM'))
|
347
356
|
on_args.push(values)
|
348
|
-
@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)}
|
349
358
|
when :date
|
350
359
|
on_args.push(symbol_to_option(option_symbol, 'DATE'))
|
351
360
|
@parser.on(*on_args) do |v|
|
@@ -354,11 +363,11 @@ module Aspera
|
|
354
363
|
when /^-([0-9]+)h/ then Manager.time_to_string(Time.now - (Regexp.last_match(1).to_i * 3600))
|
355
364
|
else v
|
356
365
|
end
|
357
|
-
set_option(option_symbol, time_string, SOURCE_USER)
|
366
|
+
set_option(option_symbol, time_string, where: SOURCE_USER)
|
358
367
|
end
|
359
368
|
when :none
|
360
369
|
Aspera.assert(!block.nil?){"missing block for #{option_symbol}"}
|
361
|
-
on_args.push(symbol_to_option(option_symbol
|
370
|
+
on_args.push(symbol_to_option(option_symbol))
|
362
371
|
on_args.push("-#{short}") if short.is_a?(String)
|
363
372
|
@parser.on(*on_args, &block)
|
364
373
|
else Aspera.error_unexpected_value(values)
|
@@ -368,11 +377,13 @@ module Aspera
|
|
368
377
|
|
369
378
|
# Adds each of the keys of specified hash as an option
|
370
379
|
# @param preset_hash [Hash] hash of options to add
|
371
|
-
def add_option_preset(preset_hash,
|
380
|
+
def add_option_preset(preset_hash, where, override: true)
|
372
381
|
Aspera.assert_type(preset_hash, Hash)
|
373
|
-
Log.log.debug{"add_option_preset
|
374
|
-
|
375
|
-
|
382
|
+
Log.log.debug{"add_option_preset: #{preset_hash}, #{where}, #{override}"}
|
383
|
+
preset_hash.each do |k, v|
|
384
|
+
option_symbol = k.to_sym
|
385
|
+
@option_pairs_batch[option_symbol] = v if override || !@option_pairs_batch.key?(option_symbol)
|
386
|
+
end
|
376
387
|
end
|
377
388
|
|
378
389
|
# check if there were unprocessed values to generate error
|
@@ -389,20 +400,21 @@ module Aspera
|
|
389
400
|
end
|
390
401
|
|
391
402
|
# get all original options on command line used to generate a config in config file
|
392
|
-
|
403
|
+
# @return [Hash] options as taken from config file and command line just before command execution
|
404
|
+
def unprocessed_options_with_value
|
393
405
|
result = {}
|
394
406
|
@initial_cli_options.each do |option_value|
|
395
407
|
case option_value
|
396
|
-
when
|
408
|
+
when /^#{OPTION_PREFIX}([^=]+)$/o
|
397
409
|
# ignore
|
398
|
-
when
|
410
|
+
when /^#{OPTION_PREFIX}([^=]+)=(.*)$/o
|
399
411
|
name = Regexp.last_match(1)
|
400
412
|
value = Regexp.last_match(2)
|
401
413
|
name.gsub!(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
|
402
414
|
value = ExtendedValue.instance.evaluate(value)
|
403
415
|
Log.log.debug{"option #{name}=#{value}"}
|
404
416
|
result[name] = value
|
405
|
-
@unprocessed_cmd_line_options.delete(option_value)
|
417
|
+
@unprocessed_cmd_line_options.delete(option_value)
|
406
418
|
else
|
407
419
|
raise Cli::BadArgument, "wrong option format: #{option_value}"
|
408
420
|
end
|
@@ -425,8 +437,8 @@ module Aspera
|
|
425
437
|
def parse_options!
|
426
438
|
Log.log.debug('parse_options!'.red)
|
427
439
|
# first conf file, then env var
|
428
|
-
|
429
|
-
|
440
|
+
consume_option_pairs(@option_pairs_batch, 'set')
|
441
|
+
consume_option_pairs(@option_pairs_env, 'env')
|
430
442
|
# command line override
|
431
443
|
unknown_options = []
|
432
444
|
begin
|
@@ -445,7 +457,7 @@ module Aspera
|
|
445
457
|
@unprocessed_cmd_line_options = unknown_options
|
446
458
|
end
|
447
459
|
|
448
|
-
def prompt_user_input(prompt, sensitive)
|
460
|
+
def prompt_user_input(prompt, sensitive: false)
|
449
461
|
return $stdin.getpass("#{prompt}> ") if sensitive
|
450
462
|
print("#{prompt}> ")
|
451
463
|
line = $stdin.gets
|
@@ -459,7 +471,7 @@ module Aspera
|
|
459
471
|
# @return [Symbol] selected symbol
|
460
472
|
def prompt_user_input_in_list(prompt, sym_list)
|
461
473
|
loop do
|
462
|
-
input = prompt_user_input(prompt
|
474
|
+
input = prompt_user_input(prompt).to_sym
|
463
475
|
if sym_list.any?{|a|a.eql?(input)}
|
464
476
|
return input
|
465
477
|
else
|
@@ -468,38 +480,53 @@ module Aspera
|
|
468
480
|
end
|
469
481
|
end
|
470
482
|
|
471
|
-
|
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'
|
472
490
|
if !@ask_missing_mandatory
|
473
|
-
|
474
|
-
|
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
|
475
497
|
end
|
476
498
|
result = nil
|
477
|
-
sensitive =
|
478
|
-
default_prompt = "#{
|
499
|
+
sensitive = option && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
|
500
|
+
default_prompt = "#{what}: #{descr}"
|
479
501
|
# ask interactively
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
result
|
491
|
-
else # one fixed
|
492
|
-
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)
|
493
513
|
end
|
494
514
|
return result
|
495
515
|
end
|
496
516
|
|
497
517
|
private
|
498
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
|
+
|
499
526
|
# generate command line option from option symbol
|
500
|
-
def symbol_to_option(symbol, opt_val)
|
501
|
-
result =
|
502
|
-
result = result
|
527
|
+
def symbol_to_option(symbol, opt_val = nil)
|
528
|
+
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
529
|
+
result = [result, OPTION_VALUE_SEPARATOR, opt_val].join unless opt_val.nil?
|
503
530
|
return result
|
504
531
|
end
|
505
532
|
|
@@ -507,23 +534,28 @@ module Aspera
|
|
507
534
|
$stdout.isatty ? value.to_s.red.bold : "[#{value}]"
|
508
535
|
end
|
509
536
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
537
|
+
# try to evaluate options set ib batch
|
538
|
+
# @param unprocessed_options [Array] list of options to apply (key_sym,value)
|
539
|
+
# @param where [String] where the options come from
|
540
|
+
def consume_option_pairs(unprocessed_options, where)
|
541
|
+
Log.log.debug{"consume_option_pairs: #{where}"}
|
542
|
+
options_to_set = {}
|
543
|
+
unprocessed_options.each do |k, v|
|
514
544
|
if @declared_options.key?(k)
|
515
545
|
# constrained parameters as string are revert to symbol
|
516
546
|
if @declared_options[k].key?(:values) && v.is_a?(String)
|
517
|
-
v = self.class.get_from_list(v, k
|
547
|
+
v = self.class.get_from_list(v, "#{k} in #{where}", @declared_options[k][:values])
|
518
548
|
end
|
519
|
-
|
549
|
+
options_to_set[k] = v
|
520
550
|
else
|
521
|
-
unprocessed
|
551
|
+
Log.log.debug{"unprocessed: #{k}: #{v}"}
|
522
552
|
end
|
523
553
|
end
|
524
|
-
|
525
|
-
|
526
|
-
|
554
|
+
options_to_set.each do |k, v|
|
555
|
+
set_option(k, v, where: where)
|
556
|
+
# keep only unprocessed values for next parse
|
557
|
+
unprocessed_options.delete(k)
|
558
|
+
end
|
527
559
|
end
|
528
560
|
end
|
529
561
|
end
|
data/lib/aspera/cli/plugin.rb
CHANGED
@@ -19,6 +19,7 @@ module Aspera
|
|
19
19
|
MAX_PAGES = 'pmax'
|
20
20
|
# special identifier format: look for this name to find where supported
|
21
21
|
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/.freeze
|
22
|
+
# instance variables, also constructor parameters
|
22
23
|
INIT_PARAMS = %i[options transfer config formatter persistency only_manual].freeze
|
23
24
|
|
24
25
|
class << self
|
@@ -28,7 +29,6 @@ module Aspera
|
|
28
29
|
:value, 'Value for create, update, list filter', types: Hash,
|
29
30
|
deprecation: '(4.14) Use positional value for create/modify or option: query for list/delete')
|
30
31
|
options.declare(:property, 'Name of property to set (modify operation)')
|
31
|
-
options.declare(:id, 'Resource identifier', deprecation: "(4.14) Use positional identifier after verb (#{INSTANCE_OPS.join(',')})")
|
32
32
|
options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
|
33
33
|
options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
|
34
34
|
end
|
@@ -62,15 +62,14 @@ module Aspera
|
|
62
62
|
# @param description [String] description of the identifier
|
63
63
|
# @param as_option [Symbol] option name to use if identifier is an option
|
64
64
|
# @param block [Proc] block to search for identifier based on attribute value
|
65
|
-
# @return [String] identifier
|
65
|
+
# @return [String, Array] identifier or list of ids
|
66
66
|
def instance_identifier(description: 'identifier', as_option: nil, &block)
|
67
67
|
if as_option.nil?
|
68
|
-
res_id = options.get_option(:
|
69
|
-
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?
|
70
69
|
else
|
71
70
|
res_id = options.get_option(as_option)
|
72
71
|
end
|
73
|
-
#
|
72
|
+
# can be an Array
|
74
73
|
if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
|
75
74
|
if block
|
76
75
|
res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
|
@@ -108,7 +107,9 @@ module Aspera
|
|
108
107
|
# execute custom code
|
109
108
|
res = yield(param)
|
110
109
|
# if block returns a hash, let's use this (create)
|
111
|
-
result = res if
|
110
|
+
result = res if res.is_a?(Hash)
|
111
|
+
# TODO: remove when faspio gw api fixes this
|
112
|
+
result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
|
112
113
|
# create -> created
|
113
114
|
result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
|
114
115
|
rescue StandardError => e
|
@@ -133,9 +134,16 @@ module Aspera
|
|
133
134
|
# @param item_list_key [String] result is in a sub key of the json
|
134
135
|
# @param id_as_arg [String] if set, the id is provided as url argument ?<id_as_arg>=<id>
|
135
136
|
# @param is_singleton [Boolean] if true, res_class_path is the full path to the resource
|
137
|
+
# @param delete_style [String] if set, the delete operation by array in payload
|
136
138
|
# @param block [Proc] block to search for identifier based on attribute value
|
137
139
|
# @return result suitable for CLI result
|
138
|
-
def entity_command(command, rest_api, res_class_path,
|
140
|
+
def entity_command(command, rest_api, res_class_path,
|
141
|
+
display_fields: nil,
|
142
|
+
item_list_key: false,
|
143
|
+
id_as_arg: false,
|
144
|
+
is_singleton: false,
|
145
|
+
delete_style: nil,
|
146
|
+
&block)
|
139
147
|
if is_singleton
|
140
148
|
one_res_path = res_class_path
|
141
149
|
elsif INSTANCE_OPS.include?(command)
|
@@ -152,14 +160,20 @@ module Aspera
|
|
152
160
|
end
|
153
161
|
when :delete
|
154
162
|
raise 'cannot delete singleton' if is_singleton
|
163
|
+
if !delete_style.nil?
|
164
|
+
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
165
|
+
Aspera.assert_type(one_res_id, Array, exception_class: Cli::BadArgument)
|
166
|
+
rest_api.call(operation: 'DELETE', subpath: res_class_path, headers: {'Accept' => 'application/json'}, body: {delete_style => one_res_id}, body_type: :json)
|
167
|
+
return Main.result_status('deleted')
|
168
|
+
end
|
155
169
|
return do_bulk_operation(command: command, descr: 'identifier', values: one_res_id) do |one_id|
|
156
|
-
rest_api.delete("#{res_class_path}/#{one_id}",
|
170
|
+
rest_api.delete("#{res_class_path}/#{one_id}", query_read_delete)
|
157
171
|
{'id' => one_id}
|
158
172
|
end
|
159
173
|
when :show
|
160
174
|
return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
|
161
175
|
when :list
|
162
|
-
resp = rest_api.read(res_class_path,
|
176
|
+
resp = rest_api.read(res_class_path, query_read_delete)
|
163
177
|
return Main.result_empty if resp[:http].code == '204'
|
164
178
|
data = resp[:data]
|
165
179
|
# TODO: not generic : which application is this for ?
|
@@ -215,14 +229,6 @@ module Aspera
|
|
215
229
|
return query
|
216
230
|
end
|
217
231
|
|
218
|
-
# TODO: when deprecation of `value` is completed: remove this method, replace with query_read_delete
|
219
|
-
# deprecation: 4.14
|
220
|
-
def old_query_read_delete
|
221
|
-
query = options.get_option(:value) # legacy, deprecated, remove, one day...
|
222
|
-
query = query_read_delete if query.nil?
|
223
|
-
return query
|
224
|
-
end
|
225
|
-
|
226
232
|
# TODO: when deprecation of `value` is completed: remove this method, replace with options.get_option(:query)
|
227
233
|
# deprecation: 4.14
|
228
234
|
def query_option(mandatory: false, default: nil)
|
@@ -246,7 +252,7 @@ module Aspera
|
|
246
252
|
Log.log.warn("option `value` is deprecated. Use positional parameter for #{command}") unless value.nil?
|
247
253
|
value = options.get_next_argument(
|
248
254
|
"parameters for #{command}#{description.nil? ? '' : " (#{description})"}", mandatory: default.nil?,
|
249
|
-
|
255
|
+
validation: bulk ? Array : type) if value.nil?
|
250
256
|
value = default if value.nil?
|
251
257
|
unless type.nil?
|
252
258
|
type = [type] unless type.is_a?(Array)
|
@@ -262,6 +268,6 @@ module Aspera
|
|
262
268
|
end
|
263
269
|
return value
|
264
270
|
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|