aspera-cli 4.13.0 → 4.14.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 +28 -5
- data/CONTRIBUTING.md +17 -1
- data/README.md +782 -401
- data/examples/dascli +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +21 -32
- data/lib/aspera/ascmd.rb +1 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
- data/lib/aspera/cli/formatter.rb +17 -25
- data/lib/aspera/cli/main.rb +21 -27
- data/lib/aspera/cli/manager.rb +128 -114
- data/lib/aspera/cli/plugin.rb +87 -38
- data/lib/aspera/cli/plugins/alee.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +216 -102
- data/lib/aspera/cli/plugins/ats.rb +16 -18
- data/lib/aspera/cli/plugins/bss.rb +3 -3
- data/lib/aspera/cli/plugins/config.rb +177 -367
- data/lib/aspera/cli/plugins/console.rb +4 -6
- data/lib/aspera/cli/plugins/cos.rb +12 -13
- data/lib/aspera/cli/plugins/faspex.rb +17 -18
- data/lib/aspera/cli/plugins/faspex5.rb +332 -216
- data/lib/aspera/cli/plugins/node.rb +171 -142
- data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
- data/lib/aspera/cli/plugins/preview.rb +38 -60
- data/lib/aspera/cli/plugins/server.rb +22 -15
- data/lib/aspera/cli/plugins/shares.rb +24 -33
- data/lib/aspera/cli/plugins/sync.rb +3 -3
- data/lib/aspera/cli/transfer_agent.rb +29 -26
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +9 -7
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +7 -3
- data/lib/aspera/fasp/agent_connect.rb +5 -0
- data/lib/aspera/fasp/agent_direct.rb +5 -5
- data/lib/aspera/fasp/agent_httpgw.rb +138 -60
- data/lib/aspera/fasp/agent_trsdk.rb +2 -0
- data/lib/aspera/fasp/error_info.rb +2 -0
- data/lib/aspera/fasp/installation.rb +18 -19
- data/lib/aspera/fasp/parameters.rb +18 -17
- data/lib/aspera/fasp/parameters.yaml +2 -1
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +6 -5
- data/lib/aspera/fasp/uri.rb +23 -21
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/hash_ext.rb +12 -2
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node.rb +62 -80
- data/lib/aspera/oauth.rb +1 -1
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/preview/terminal.rb +61 -15
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +37 -0
- data/lib/aspera/secret_hider.rb +6 -1
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/sync.rb +2 -0
- data.tar.gz.sig +0 -0
- metadata +3 -4
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -186
- data/lib/aspera/data/7 +0 -0
data/lib/aspera/cli/manager.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'aspera/colors'
|
4
4
|
require 'aspera/log'
|
5
|
+
require 'aspera/secret_hider'
|
5
6
|
require 'aspera/cli/extended_value'
|
6
7
|
require 'optparse'
|
7
8
|
require 'io/console'
|
@@ -103,7 +104,7 @@ module Aspera
|
|
103
104
|
@unprocessed_cmd_line_options = []
|
104
105
|
# a copy of all initial options
|
105
106
|
@initial_cli_options = []
|
106
|
-
# option description: key = option symbol, value=hash, :
|
107
|
+
# option description: key = option symbol, value=hash, :read_write, :accessor, :value, :accepted
|
107
108
|
@declared_options = {}
|
108
109
|
# do we ask missing options and arguments to user ?
|
109
110
|
@ask_missing_mandatory = false # STDIN.isatty
|
@@ -130,10 +131,8 @@ module Aspera
|
|
130
131
|
unless argv.nil?
|
131
132
|
@parser.separator('')
|
132
133
|
@parser.separator('OPTIONS: global')
|
133
|
-
|
134
|
-
|
135
|
-
add_opt_boolean(:interactive, 'use interactive input of missing params')
|
136
|
-
add_opt_boolean(:ask_options, 'ask even optional options')
|
134
|
+
declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
|
135
|
+
declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
|
137
136
|
parse_options!
|
138
137
|
process_options = true
|
139
138
|
until argv.empty?
|
@@ -153,21 +152,20 @@ module Aspera
|
|
153
152
|
Log.log.debug{"add_cmd_line_options:commands/args=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red}
|
154
153
|
end
|
155
154
|
|
156
|
-
def get_next_command(command_list); return get_next_argument('command', expected: command_list); end
|
157
|
-
|
158
155
|
# @param expected is
|
159
156
|
# - Array of allowed value (single value)
|
160
157
|
# - :multiple for remaining values
|
161
158
|
# - :single for a single unconstrained value
|
162
159
|
# @param mandatory true/false
|
163
160
|
# @param type expected class for result
|
161
|
+
# @param aliases list of aliases for the value
|
164
162
|
# @return value, list or nil
|
165
|
-
def get_next_argument(descr, expected: :single, mandatory: true, type: nil)
|
163
|
+
def get_next_argument(descr, expected: :single, mandatory: true, type: nil, aliases: nil, default: nil)
|
166
164
|
unless type.nil?
|
167
165
|
raise 'internal: type must be a Class' unless type.is_a?(Class)
|
168
166
|
descr = "#{descr} (#{type})"
|
169
167
|
end
|
170
|
-
result =
|
168
|
+
result = default
|
171
169
|
if !@unprocessed_cmd_line_arguments.empty?
|
172
170
|
# there are values
|
173
171
|
case expected
|
@@ -179,66 +177,37 @@ module Aspera
|
|
179
177
|
if result.length.eql?(1) && result.first.is_a?(Array)
|
180
178
|
result = result.first
|
181
179
|
end
|
180
|
+
when Array
|
181
|
+
allowed_values = [].concat(expected)
|
182
|
+
allowed_values.concat(aliases.keys) unless aliases.nil?
|
183
|
+
raise "internal error: only symbols allowed: #{allowed_values}" unless allowed_values.all?(Symbol)
|
184
|
+
result = self.class.get_from_list(@unprocessed_cmd_line_arguments.shift, descr, allowed_values)
|
182
185
|
else
|
183
|
-
|
186
|
+
raise 'internal error'
|
184
187
|
end
|
185
188
|
elsif mandatory
|
186
189
|
# no value provided
|
187
190
|
result = get_interactive(:argument, descr, expected: expected)
|
188
191
|
end
|
189
192
|
Log.log.debug{"#{descr}=#{result}"}
|
193
|
+
result = aliases[result] if !aliases.nil? && aliases.key?(result)
|
190
194
|
raise "argument shall be #{type.name}" unless type.nil? || result.is_a?(type)
|
191
195
|
return result
|
192
196
|
end
|
193
197
|
|
194
|
-
|
195
|
-
def declare_option(option_symbol, type)
|
196
|
-
Log.log.debug{"declare_option: #{option_symbol}: #{type}: skip=#{@declared_options.key?(option_symbol)}".green}
|
197
|
-
if @declared_options.key?(option_symbol)
|
198
|
-
raise "INTERNAL ERROR: option #{option_symbol} already declared. only accessor can be re-declared and ignored" \
|
199
|
-
unless @declared_options[option_symbol][:type].eql?(:accessor)
|
200
|
-
return
|
201
|
-
end
|
202
|
-
@declared_options[option_symbol] = {type: type}
|
203
|
-
# by default passwords and secrets are sensitive, else specify when declaring the option
|
204
|
-
@declared_options[option_symbol][:sensitive] = true if !%w[password secret key].select{|i| option_symbol.to_s.end_with?(i)}.empty?
|
205
|
-
end
|
206
|
-
|
207
|
-
# define option with handler
|
208
|
-
def set_obj_attr(option_symbol, object, attr_symb, default_value=nil)
|
209
|
-
Log.log.debug{"set attr obj #{option_symbol} (#{object},#{attr_symb})"}
|
210
|
-
declare_option(option_symbol, :accessor)
|
211
|
-
@declared_options[option_symbol][:accessor] = AttrAccessor.new(object, attr_symb)
|
212
|
-
set_option(option_symbol, default_value, 'default obj attr') if !default_value.nil?
|
213
|
-
end
|
214
|
-
|
215
|
-
# set an option value by name, either store value or call handler
|
216
|
-
def set_option(option_symbol, value, where='default')
|
217
|
-
if !@declared_options.key?(option_symbol)
|
218
|
-
Log.log.debug{"set unknown option: #{option_symbol}"}
|
219
|
-
raise 'ERROR: cannot set undeclared option'
|
220
|
-
# declare_option(option_symbol)
|
221
|
-
end
|
222
|
-
value = ExtendedValue.instance.evaluate(value)
|
223
|
-
value = Manager.enum_to_bool(value) if @declared_options[option_symbol][:values].eql?(BOOLEAN_VALUES)
|
224
|
-
Log.log.debug{"(#{@declared_options[option_symbol][:type]}/#{where}) set #{option_symbol}=#{value}"}
|
225
|
-
case @declared_options[option_symbol][:type]
|
226
|
-
when :accessor
|
227
|
-
@declared_options[option_symbol][:accessor].value = value
|
228
|
-
when :value
|
229
|
-
@declared_options[option_symbol][:value] = value
|
230
|
-
else # nil or other
|
231
|
-
raise 'error'
|
232
|
-
end
|
233
|
-
end
|
198
|
+
def get_next_command(command_list, aliases: nil); return get_next_argument('command', expected: command_list, aliases: aliases); end
|
234
199
|
|
235
200
|
# Get an option value by name
|
236
|
-
# either return value or
|
201
|
+
# either return value or calls handler, can return nil
|
237
202
|
# ask interactively if requested/required
|
238
|
-
|
203
|
+
# @param mandatory [Boolean] if true, raise error if option not set
|
204
|
+
# @param allowed_types [Array] list of allowed types
|
205
|
+
def get_option(option_symbol, mandatory: false, allowed_types: nil)
|
206
|
+
allowed_types = [allowed_types] if allowed_types.is_a?(Class)
|
207
|
+
raise 'Internal Error: allowed_types must be an Array of Class or a Class' unless allowed_types.nil? || allowed_types.is_a?(Array)
|
239
208
|
result = nil
|
240
209
|
if @declared_options.key?(option_symbol)
|
241
|
-
case @declared_options[option_symbol][:
|
210
|
+
case @declared_options[option_symbol][:read_write]
|
242
211
|
when :accessor
|
243
212
|
result = @declared_options[option_symbol][:accessor].value
|
244
213
|
when :value
|
@@ -246,15 +215,15 @@ module Aspera
|
|
246
215
|
else
|
247
216
|
raise 'unknown type'
|
248
217
|
end
|
249
|
-
Log.log.debug{"(#{@declared_options[option_symbol][:
|
218
|
+
Log.log.debug{"(#{@declared_options[option_symbol][:read_write]}) get #{option_symbol}=#{result}"}
|
250
219
|
end
|
251
220
|
# do not fail for manual generation if option mandatory but not set
|
252
|
-
result = '' if result.nil? &&
|
221
|
+
result = '' if result.nil? && mandatory && !@fail_on_missing_mandatory
|
253
222
|
# Log.log.debug{"interactive=#{@ask_missing_mandatory}"}
|
254
223
|
if result.nil?
|
255
224
|
if !@ask_missing_mandatory
|
256
|
-
raise CliBadArgument, "Missing mandatory option: #{option_symbol}" if
|
257
|
-
elsif @ask_missing_optional ||
|
225
|
+
raise CliBadArgument, "Missing mandatory option: #{option_symbol}" if mandatory
|
226
|
+
elsif @ask_missing_optional || mandatory
|
258
227
|
# ask_missing_mandatory
|
259
228
|
expected = :single
|
260
229
|
# print "please enter: #{option_symbol.to_s}"
|
@@ -265,72 +234,117 @@ module Aspera
|
|
265
234
|
set_option(option_symbol, result, 'interactive')
|
266
235
|
end
|
267
236
|
end
|
237
|
+
raise "option #{option_symbol} is #{result.class} but must be one of #{allowed_types}" unless \
|
238
|
+
!mandatory || allowed_types.nil? || allowed_types.any? { |t|result.is_a?(t)}
|
268
239
|
return result
|
269
240
|
end
|
270
241
|
|
271
|
-
#
|
272
|
-
def
|
273
|
-
|
274
|
-
|
275
|
-
#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
#
|
285
|
-
|
286
|
-
value = get_option(option_symbol)
|
287
|
-
help_values = values.map{|i|i.eql?(value) ? highlight_current(i) : i}.join(', ')
|
288
|
-
if values.eql?(BOOLEAN_VALUES)
|
289
|
-
help_values = BOOLEAN_SIMPLE.map{|i|(i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
|
242
|
+
# set an option value by name, either store value or call handler
|
243
|
+
def set_option(option_symbol, value, where='code override')
|
244
|
+
raise CliBadArgument, "Unknown option: #{option_symbol}" unless @declared_options.key?(option_symbol)
|
245
|
+
attributes = @declared_options[option_symbol]
|
246
|
+
Log.log.warn("#{option_symbol}: Option is deprecated: #{attributes[:deprecation]}") if attributes[:deprecation]
|
247
|
+
value = ExtendedValue.instance.evaluate(value)
|
248
|
+
value = Manager.enum_to_bool(value) if attributes[:values].eql?(BOOLEAN_VALUES)
|
249
|
+
Log.log.debug{"(#{attributes[:read_write]}/#{where}) set #{option_symbol}=#{value}"}
|
250
|
+
case attributes[:read_write]
|
251
|
+
when :accessor
|
252
|
+
attributes[:accessor].value = value
|
253
|
+
when :value
|
254
|
+
attributes[:value] = value
|
255
|
+
else # nil or other
|
256
|
+
raise 'error'
|
290
257
|
end
|
291
|
-
on_args.push(values)
|
292
|
-
on_args.push("#{help}: #{help_values}")
|
293
|
-
Log.log.debug{"on_args=#{on_args}"}
|
294
|
-
@parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, help, values), 'cmdline')}
|
295
258
|
end
|
296
259
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
#
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
260
|
+
# declare an option
|
261
|
+
# @param option_symbol [Symbol] option name
|
262
|
+
# @param description [String] description for help
|
263
|
+
# @param handler [Hash] handler for option value: keys: o (object) and m (method)
|
264
|
+
# @param default [Object] default value
|
265
|
+
# @param values [nil, Array, :bool, :date, :none] list of allowed values, :bool for true/false, :date for dates, :none for on/off switch
|
266
|
+
# @param short [String] short option name
|
267
|
+
# @param coerce [Class] one of the coerce types accepted par option parser
|
268
|
+
# @param types [Class, Array] accepted value type(s)
|
269
|
+
# @param block [Proc] block to execute when option is found
|
270
|
+
def declare(option_symbol, description, handler: nil, default: nil, values: nil, short: nil, coerce: nil, types: nil, deprecation: nil, &block)
|
271
|
+
raise "INTERNAL ERROR: #{option_symbol} already declared" if @declared_options.key?(option_symbol)
|
272
|
+
raise "INTERNAL ERROR: #{option_symbol} ends with dot" unless description[-1] != '.'
|
273
|
+
raise "INTERNAL ERROR: #{option_symbol} does not start with capital" unless description[0] == description[0].upcase
|
274
|
+
raise "INTERNAL ERROR: #{option_symbol} shall use :types" if description.downcase.include?('hash') || description.downcase.include?('extended value')
|
275
|
+
opt = @declared_options[option_symbol] = {
|
276
|
+
read_write: handler.nil? ? :value : :accessor,
|
277
|
+
# by default passwords and secrets are sensitive, else specify when declaring the option
|
278
|
+
sensitive: SecretHider.secret?(option_symbol, '')
|
279
|
+
}
|
280
|
+
if !types.nil?
|
281
|
+
types = [types] unless types.is_a?(Array)
|
282
|
+
raise "INTERNAL ERROR: types must be classes: #{types}" unless types.all?(Class)
|
283
|
+
opt[:types] = types
|
284
|
+
description = "#{description} (#{types.map(&:name).join(', ')})"
|
285
|
+
end
|
286
|
+
if deprecation
|
287
|
+
opt[:deprecation] = deprecation
|
288
|
+
description = "#{description} (#{'deprecated'.blue}: #{deprecation})"
|
289
|
+
end
|
290
|
+
Log.log.debug{"declare: #{option_symbol}: #{opt[:read_write]}".green}
|
291
|
+
if opt[:read_write].eql?(:accessor)
|
292
|
+
raise 'internal error' unless handler.is_a?(Hash)
|
293
|
+
raise 'internal error' unless handler.keys.sort.eql?(%i[m o])
|
294
|
+
Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
|
295
|
+
opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m])
|
296
|
+
end
|
297
|
+
set_option(option_symbol, default, 'default') unless default.nil?
|
298
|
+
on_args = [description]
|
299
|
+
case values
|
300
|
+
when nil
|
301
|
+
on_args.push(symbol_to_option(option_symbol, 'VALUE'))
|
302
|
+
on_args.push("-#{short}VALUE") unless short.nil?
|
303
|
+
on_args.push(coerce) unless coerce.nil?
|
304
|
+
@parser.on(*on_args) { |v| set_option(option_symbol, v, 'cmdline') }
|
305
|
+
when Array, :bool
|
306
|
+
if values.eql?(:bool)
|
307
|
+
values = BOOLEAN_VALUES
|
308
|
+
set_option(option_symbol, Manager.enum_to_bool(default), 'default') unless default.nil?
|
309
|
+
end
|
310
|
+
# this option value must be a symbol
|
311
|
+
opt[:values] = values
|
312
|
+
value = get_option(option_symbol)
|
313
|
+
help_values = values.map{|i|i.eql?(value) ? highlight_current(i) : i}.join(', ')
|
314
|
+
if values.eql?(BOOLEAN_VALUES)
|
315
|
+
help_values = BOOLEAN_SIMPLE.map{|i|(i.eql?(:yes) && value) || (i.eql?(:no) && !value) ? highlight_current(i) : i}.join(', ')
|
316
|
+
end
|
317
|
+
on_args[0] = "#{description}: #{help_values}"
|
318
|
+
on_args.push(symbol_to_option(option_symbol, 'ENUM'))
|
319
|
+
on_args.push(values)
|
320
|
+
@parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), 'cmdline')}
|
321
|
+
when :date
|
322
|
+
on_args.push(symbol_to_option(option_symbol, 'DATE'))
|
323
|
+
@parser.on(*on_args) do |v|
|
324
|
+
time_string = case v
|
325
|
+
when 'now' then Manager.time_to_string(Time.now)
|
326
|
+
when /^-([0-9]+)h/ then Manager.time_to_string(Time.now - (Regexp.last_match(1).to_i * 3600))
|
327
|
+
else v
|
328
|
+
end
|
329
|
+
set_option(option_symbol, time_string, 'cmdline')
|
324
330
|
end
|
331
|
+
when :none
|
332
|
+
raise "internal error: missing block for #{option_symbol}" if block.nil?
|
333
|
+
on_args.push(symbol_to_option(option_symbol, nil))
|
334
|
+
on_args.push("-#{short}") if short.is_a?(String)
|
335
|
+
@parser.on(*on_args, &block)
|
336
|
+
else raise 'internal error'
|
325
337
|
end
|
338
|
+
Log.log.debug{"on_args=#{on_args}"}
|
326
339
|
end
|
327
340
|
|
328
|
-
#
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
341
|
+
# Adds each of the keys of specified hash as an option
|
342
|
+
# @param preset_hash [Hash] hash of options to add
|
343
|
+
def add_option_preset(preset_hash, op: :push)
|
344
|
+
Log.log.debug{"add_option_preset=#{preset_hash}"}
|
345
|
+
raise "internal error: default expects Hash: #{preset_hash.class}" unless preset_hash.is_a?(Hash)
|
346
|
+
# incremental override
|
347
|
+
preset_hash.each{|k, v|@unprocessed_defaults.send(op, [k.to_sym, v])}
|
334
348
|
end
|
335
349
|
|
336
350
|
# check if there were unprocessed values to generate error
|
@@ -346,7 +360,7 @@ module Aspera
|
|
346
360
|
return result
|
347
361
|
end
|
348
362
|
|
349
|
-
# get all original options
|
363
|
+
# get all original options on command line used to generate a config in config file
|
350
364
|
def get_options_table(remove_from_remaining: true)
|
351
365
|
result = {}
|
352
366
|
@initial_cli_options.each do |optionval|
|
data/lib/aspera/cli/plugin.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aspera/cli/extended_value'
|
4
|
+
|
3
5
|
module Aspera
|
4
6
|
module Cli
|
5
7
|
# base class for plugins modules
|
@@ -8,13 +10,16 @@ module Aspera
|
|
8
10
|
GLOBAL_OPS = %i[create list].freeze
|
9
11
|
# operations with id
|
10
12
|
INSTANCE_OPS = %i[modify delete show].freeze
|
13
|
+
# all standard operations
|
11
14
|
ALL_OPS = [GLOBAL_OPS, INSTANCE_OPS].flatten.freeze
|
12
|
-
# max number of items for list command
|
15
|
+
# special query parameter: max number of items for list command
|
13
16
|
MAX_ITEMS = 'max'
|
14
|
-
# max number of pages for list command
|
17
|
+
# special query parameter: max number of pages for list command
|
15
18
|
MAX_PAGES = 'pmax'
|
16
19
|
# used when all resources are selected
|
17
20
|
VAL_ALL = 'ALL'
|
21
|
+
# special identifier format: look for this name to find where supported
|
22
|
+
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/.freeze
|
18
23
|
|
19
24
|
# global for inherited classes
|
20
25
|
@@options_created = false # rubocop:disable Style/ClassVars
|
@@ -24,46 +29,54 @@ module Aspera
|
|
24
29
|
# env.each_key {|k| raise "wrong agent key #{k}" unless AGENTS.include?(k)}
|
25
30
|
@agents = env
|
26
31
|
# check presence in descendant of mandatory method and constant
|
27
|
-
raise StandardError, "
|
32
|
+
raise StandardError, "Missing method 'execute_action' in #{self.class}" unless respond_to?(:execute_action)
|
28
33
|
raise StandardError, 'ACTIONS shall be redefined by subclass' unless self.class.constants.include?(:ACTIONS)
|
29
34
|
options.parser.separator('')
|
30
35
|
options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
|
31
36
|
options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
|
32
37
|
options.parser.separator('OPTIONS:')
|
33
38
|
return if @@options_created
|
34
|
-
options.
|
35
|
-
options.
|
36
|
-
|
37
|
-
|
38
|
-
options.
|
39
|
-
options.
|
40
|
-
options.
|
41
|
-
options.
|
39
|
+
options.declare(:query, 'Additional filter for for some commands (list/delete)', types: Hash)
|
40
|
+
options.declare(
|
41
|
+
:value, 'Value for create, update, list filter', types: Hash,
|
42
|
+
deprecation: 'Use positional value for create/modify or option: query for list/delete')
|
43
|
+
options.declare(:property, 'Name of property to set (modify operation)')
|
44
|
+
options.declare(:id, 'Resource identifier', deprecation: "Use identifier after verb (#{INSTANCE_OPS.join(',')})")
|
45
|
+
options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
|
46
|
+
options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
|
42
47
|
options.parse_options!
|
43
48
|
@@options_created = true # rubocop:disable Style/ClassVars
|
44
49
|
end
|
45
50
|
|
46
|
-
# must be called AFTER the instance action
|
47
|
-
|
51
|
+
# must be called AFTER the instance action, ... folder browse <call instance_identifier>
|
52
|
+
# @param description [String] description of the identifier
|
53
|
+
# @param block [Proc] block to search for identifier based on attribute value
|
54
|
+
# @return [String] identifier
|
55
|
+
def instance_identifier(description: 'identifier', &block)
|
48
56
|
res_id = options.get_option(:id)
|
49
|
-
res_id = options.get_next_argument(
|
57
|
+
res_id = options.get_next_argument(description) if res_id.nil?
|
58
|
+
# cab be an Array
|
59
|
+
if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
|
60
|
+
if block
|
61
|
+
res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
|
62
|
+
else
|
63
|
+
raise CliBadArgument, "Percent syntax for #{description} not supported in this context"
|
64
|
+
end
|
65
|
+
end
|
50
66
|
return res_id
|
51
67
|
end
|
52
68
|
|
53
|
-
# TODO
|
54
|
-
# def get_next_id_command(instance_ops: INSTANCE_OPS,global_ops: GLOBAL_OPS)
|
55
|
-
# return get_next_argument('command',expected: command_list)
|
56
|
-
# end
|
57
|
-
|
58
69
|
# For create and delete operations: execute one actin or multiple if bulk is yes
|
59
|
-
# @param
|
70
|
+
# @param single_or_array [Object] single hash, or array of hash for bulk
|
60
71
|
# @param success_msg deleted or created
|
72
|
+
# @param id_result [String] key in result hash to use as identifier
|
73
|
+
# @param fields [Array] fields to display
|
61
74
|
def do_bulk_operation(single_or_array, success_msg, id_result: 'id', fields: :default)
|
62
75
|
raise 'programming error: missing block' unless block_given?
|
63
76
|
params = options.get_option(:bulk) ? single_or_array : [single_or_array]
|
64
77
|
raise 'expecting Array for bulk operation' unless params.is_a?(Array)
|
65
78
|
Log.log.warn('Empty list given for bulk operation') if params.empty?
|
66
|
-
Log.dump(:
|
79
|
+
Log.dump(:bulk_operation, params)
|
67
80
|
result_list = []
|
68
81
|
params.each do |param|
|
69
82
|
# init for delete
|
@@ -96,11 +109,14 @@ module Aspera
|
|
96
109
|
# @param id_default [String] default identifier to use for existing entity commands (show, modify)
|
97
110
|
# @param item_list_key [String] result is in a sub key of the json
|
98
111
|
# @param id_as_arg [String] if set, the id is provided as url argument ?<id_as_arg>=<id>
|
112
|
+
# @param is_singleton [Boolean] if true, res_class_path is the full path to the resource
|
99
113
|
# @return result suitable for CLI result
|
100
|
-
def entity_command(command, rest_api, res_class_path, display_fields: nil, id_default: nil, item_list_key: false, id_as_arg: false)
|
101
|
-
if
|
114
|
+
def entity_command(command, rest_api, res_class_path, display_fields: nil, id_default: nil, item_list_key: false, id_as_arg: false, is_singleton: false, &block)
|
115
|
+
if is_singleton
|
116
|
+
one_res_path = res_class_path
|
117
|
+
elsif INSTANCE_OPS.include?(command)
|
102
118
|
begin
|
103
|
-
one_res_id = instance_identifier
|
119
|
+
one_res_id = instance_identifier(&block)
|
104
120
|
rescue StandardError => e
|
105
121
|
raise e if id_default.nil?
|
106
122
|
one_res_id = id_default
|
@@ -108,29 +124,24 @@ module Aspera
|
|
108
124
|
one_res_path = "#{res_class_path}/#{one_res_id}"
|
109
125
|
one_res_path = "#{res_class_path}?#{id_as_arg}=#{one_res_id}" if id_as_arg
|
110
126
|
end
|
111
|
-
|
112
|
-
if %i[create modify].include?(command)
|
113
|
-
parameters = options.get_option(:value, is_type: :mandatory)
|
114
|
-
end
|
115
|
-
# parameters optional for list
|
116
|
-
if %i[list delete].include?(command)
|
117
|
-
parameters = options.get_option(:value)
|
118
|
-
end
|
127
|
+
|
119
128
|
case command
|
120
129
|
when :create
|
121
|
-
|
130
|
+
raise 'cannot create singleton' if is_singleton
|
131
|
+
return do_bulk_operation(value_create_modify(command: command, type: :bulk_hash), 'created', fields: display_fields) do |params|
|
122
132
|
raise 'expecting Hash' unless params.is_a?(Hash)
|
123
133
|
rest_api.create(res_class_path, params)[:data]
|
124
134
|
end
|
125
135
|
when :delete
|
136
|
+
raise 'cannot delete singleton' if is_singleton
|
126
137
|
return do_bulk_operation(one_res_id, 'deleted') do |one_id|
|
127
|
-
rest_api.delete("#{res_class_path}/#{one_id}",
|
138
|
+
rest_api.delete("#{res_class_path}/#{one_id}", old_query_read_delete)
|
128
139
|
{'id' => one_id}
|
129
140
|
end
|
130
141
|
when :show
|
131
142
|
return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
|
132
143
|
when :list
|
133
|
-
resp = rest_api.read(res_class_path,
|
144
|
+
resp = rest_api.read(res_class_path, old_query_read_delete)
|
134
145
|
data = resp[:data]
|
135
146
|
# TODO: not generic : which application is this for ?
|
136
147
|
if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
|
@@ -153,6 +164,7 @@ module Aspera
|
|
153
164
|
raise "An error occurred: unexpected result type for list: #{data.class}"
|
154
165
|
end
|
155
166
|
when :modify
|
167
|
+
parameters = value_create_modify(command: command, type: Hash)
|
156
168
|
property = options.get_option(:property)
|
157
169
|
parameters = {property => parameters} unless property.nil?
|
158
170
|
rest_api.update(one_res_path, parameters)
|
@@ -169,8 +181,8 @@ module Aspera
|
|
169
181
|
return entity_command(command, rest_api, res_class_path, **opts)
|
170
182
|
end
|
171
183
|
|
172
|
-
# query for list
|
173
|
-
def
|
184
|
+
# query parameters in URL suitable for REST list/GET and delete/DELETE
|
185
|
+
def query_read_delete(default: nil)
|
174
186
|
query = options.get_option(:query)
|
175
187
|
# dup default, as it could be frozen
|
176
188
|
query = default.dup if query.nil?
|
@@ -179,11 +191,48 @@ module Aspera
|
|
179
191
|
# check it is suitable
|
180
192
|
URI.encode_www_form(query) unless query.nil?
|
181
193
|
rescue StandardError => e
|
182
|
-
raise CliBadArgument, "
|
194
|
+
raise CliBadArgument, "Query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
|
183
195
|
end
|
184
196
|
return query
|
185
197
|
end
|
186
198
|
|
199
|
+
# TODO: when deprecation of `value` is completed: remove this method, replace with query_read_delete
|
200
|
+
def old_query_read_delete
|
201
|
+
query = options.get_option(:value) # legacy, deprecated, remove, one day...
|
202
|
+
query = query_read_delete if query.nil?
|
203
|
+
return query
|
204
|
+
end
|
205
|
+
|
206
|
+
# TODO: when deprecation of `value` is completed: remove this method, replace with options.get_option(:query)
|
207
|
+
def value_or_query(mandatory: false, allowed_types: nil)
|
208
|
+
value = options.get_option(:value, mandatory: false, allowed_types: allowed_types)
|
209
|
+
value = options.get_option(:query, mandatory: mandatory, allowed_types: allowed_types) if value.nil?
|
210
|
+
return value
|
211
|
+
end
|
212
|
+
|
213
|
+
# Retrieves an extended value from command line, used for creation or modification of entities
|
214
|
+
# @param default [Object] default value if not provided
|
215
|
+
# @param command [String] command name for error message
|
216
|
+
# @param type [Class] expected type of value
|
217
|
+
# TODO: when deprecation of `value` is completed: remove line with :value
|
218
|
+
def value_create_modify(default: nil, command: 'command', type: nil)
|
219
|
+
value = options.get_option(:value)
|
220
|
+
value = options.get_next_argument("parameters for #{command}", mandatory: default.nil?) if value.nil?
|
221
|
+
value = default if value.nil?
|
222
|
+
if type.nil?
|
223
|
+
# nothing to do
|
224
|
+
elsif type.is_a?(Class)
|
225
|
+
raise CliBadArgument, "Value must be a #{type}" unless value.is_a?(type)
|
226
|
+
elsif type.is_a?(Array)
|
227
|
+
raise CliBadArgument, "Value must be one of #{type.join(', ')}" unless type.any?{|t| value.is_a?(t)}
|
228
|
+
elsif type.eql?(:bulk_hash)
|
229
|
+
raise CliBadArgument, 'Value must be a Hash or Array of Hash' unless value.is_a?(Hash) || (value.is_a?(Array) && value.all?(Hash))
|
230
|
+
else
|
231
|
+
raise "Internal error: #{type}"
|
232
|
+
end
|
233
|
+
return value
|
234
|
+
end
|
235
|
+
|
187
236
|
# shortcuts helpers for plugin environment
|
188
237
|
%i[options transfer config formatter persistency].each do |name|
|
189
238
|
define_method(name){@agents[name]}
|
@@ -13,8 +13,8 @@ module Aspera
|
|
13
13
|
command = options.get_next_command(ACTIONS)
|
14
14
|
case command
|
15
15
|
when :entitlement
|
16
|
-
entitlement_id = options.get_option(:username,
|
17
|
-
customer_id = options.get_option(:password,
|
16
|
+
entitlement_id = options.get_option(:username, mandatory: true)
|
17
|
+
customer_id = options.get_option(:password, mandatory: true)
|
18
18
|
api_metering = AoC.metering_api(entitlement_id, customer_id)
|
19
19
|
return {type: :single_object, data: api_metering.read('entitlement')[:data]}
|
20
20
|
end
|