aspera-cli 4.17.0 → 4.18.1
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 +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
|