aspera-cli 4.20.0 → 4.21.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +29 -3
  4. data/CONTRIBUTING.md +2 -0
  5. data/README.md +571 -375
  6. data/bin/asession +2 -2
  7. data/examples/get_proto_file.rb +1 -1
  8. data/lib/aspera/agent/alpha.rb +10 -16
  9. data/lib/aspera/agent/connect.rb +20 -2
  10. data/lib/aspera/agent/direct.rb +21 -30
  11. data/lib/aspera/agent/node.rb +1 -11
  12. data/lib/aspera/agent/{trsdk.rb → transferd.rb} +13 -34
  13. data/lib/aspera/api/aoc.rb +13 -8
  14. data/lib/aspera/api/node.rb +45 -28
  15. data/lib/aspera/ascp/installation.rb +87 -48
  16. data/lib/aspera/ascp/management.rb +27 -6
  17. data/lib/aspera/cli/formatter.rb +148 -154
  18. data/lib/aspera/cli/info.rb +1 -1
  19. data/lib/aspera/cli/main.rb +12 -0
  20. data/lib/aspera/cli/manager.rb +2 -2
  21. data/lib/aspera/cli/plugin.rb +2 -2
  22. data/lib/aspera/cli/plugins/aoc.rb +28 -18
  23. data/lib/aspera/cli/plugins/config.rb +106 -54
  24. data/lib/aspera/cli/plugins/cos.rb +1 -0
  25. data/lib/aspera/cli/plugins/faspex.rb +4 -2
  26. data/lib/aspera/cli/plugins/faspex5.rb +21 -9
  27. data/lib/aspera/cli/plugins/node.rb +45 -38
  28. data/lib/aspera/cli/transfer_progress.rb +2 -0
  29. data/lib/aspera/cli/version.rb +1 -1
  30. data/lib/aspera/command_line_builder.rb +1 -1
  31. data/lib/aspera/environment.rb +48 -14
  32. data/lib/aspera/node_simulator.rb +230 -112
  33. data/lib/aspera/oauth/base.rb +34 -47
  34. data/lib/aspera/oauth/factory.rb +41 -2
  35. data/lib/aspera/oauth/jwt.rb +4 -1
  36. data/lib/aspera/persistency_action_once.rb +1 -1
  37. data/lib/aspera/persistency_folder.rb +20 -2
  38. data/lib/aspera/preview/generator.rb +1 -1
  39. data/lib/aspera/preview/utils.rb +8 -3
  40. data/lib/aspera/products/alpha.rb +30 -0
  41. data/lib/aspera/products/connect.rb +48 -0
  42. data/lib/aspera/products/other.rb +82 -0
  43. data/lib/aspera/products/transferd.rb +54 -0
  44. data/lib/aspera/rest.rb +18 -13
  45. data/lib/aspera/secret_hider.rb +2 -2
  46. data/lib/aspera/ssh.rb +31 -24
  47. data/lib/aspera/transfer/parameters.rb +2 -1
  48. data/lib/aspera/transfer/spec.yaml +22 -20
  49. data/lib/aspera/transfer/sync.rb +1 -5
  50. data/lib/aspera/transfer/uri.rb +2 -2
  51. data/lib/transferd_pb.rb +86 -0
  52. data/lib/transferd_services_pb.rb +84 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +38 -21
  55. metadata.gz.sig +0 -0
  56. data/lib/aspera/ascp/products.rb +0 -168
  57. data/lib/transfer_pb.rb +0 -84
  58. data/lib/transfer_services_pb.rb +0 -82
@@ -14,7 +14,6 @@ require 'pp'
14
14
 
15
15
  module Aspera
16
16
  module Cli
17
- CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
18
17
  # This class is used to transform a complex structure into a simple hash
19
18
  class Flattener
20
19
  def initialize(formatter)
@@ -30,17 +29,6 @@ module Aspera
30
29
  return @result
31
30
  end
32
31
 
33
- # Special method for configuration overview
34
- def config_over(something)
35
- @result = []
36
- something.each do |config, preset|
37
- preset.each do |parameter, value|
38
- @result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
39
- end
40
- end
41
- return @result
42
- end
43
-
44
32
  private
45
33
 
46
34
  # Recursive function to flatten any type
@@ -101,9 +89,10 @@ module Aspera
101
89
  DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv image].freeze
102
90
  # user output levels
103
91
  DISPLAY_LEVELS = %i[info data error].freeze
104
- FIELD_VALUE_HEADINGS = %i[key value].freeze
92
+ # column names for single object display in table
93
+ SINGLE_OBJECT_COLUMN_NAMES = %i[field value].freeze
105
94
 
106
- private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR, :FIELD_VALUE_HEADINGS
95
+ private_constant :FIELDS_LESS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES
107
96
  # prefix to display error messages in user messages (terminal)
108
97
  ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
109
98
  WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
@@ -172,12 +161,37 @@ module Aspera
172
161
  @spinner = nil
173
162
  end
174
163
 
175
- # options are: format, output, display, fields, select, table_style, flat_hash, transpose_single
164
+ def declare_options(options)
165
+ default_table_style = if Environment.instance.terminal_supports_unicode?
166
+ {border: :unicode_round}
167
+ else
168
+ {}
169
+ end
170
+ options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
171
+ options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
172
+ options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
173
+ options.declare(
174
+ :fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
175
+ types: [String, Array, Regexp, Proc],
176
+ default: SpecialValues::DEF)
177
+ options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
178
+ options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
179
+ options.declare(:flat_hash, '(Table) Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
180
+ options.declare(
181
+ :multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', values: %i[no yes single],
182
+ handler: {o: self, m: :option_handler}, default: :no)
183
+ options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
184
+ options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
185
+ end
186
+
187
+ # method accessed by option manager
188
+ # options are: format, output, display, fields, select, table_style, flat_hash, multi_single
176
189
  def option_handler(option_symbol, operation, value=nil)
177
190
  Aspera.assert_values(operation, %i[set get])
178
191
  case operation
179
192
  when :set
180
193
  @options[option_symbol] = value
194
+ # special handling of some options
181
195
  case option_symbol
182
196
  when :output
183
197
  $stdout = if value.eql?('-')
@@ -186,7 +200,9 @@ module Aspera
186
200
  File.open(value, 'w')
187
201
  end
188
202
  when :image
203
+ # get list if key arguments of method
189
204
  allowed_options = Preview::Terminal.method(:build).parameters.select{|i|i[0].eql?(:key)}.map{|i|i[1]}
205
+ # check that only supported options are given
190
206
  unknown_options = value.keys.map(&:to_sym) - allowed_options
191
207
  raise "Invalid parameter(s) for option image: #{unknown_options.join(', ')}, use #{allowed_options.join(', ')}" unless unknown_options.empty?
192
208
  end
@@ -196,28 +212,6 @@ module Aspera
196
212
  nil
197
213
  end
198
214
 
199
- def declare_options(options)
200
- default_table_style = if Environment.instance.terminal_supports_unicode?
201
- {border: :unicode_round}
202
- else
203
- {}
204
- end
205
- options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
206
- options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
207
- options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
208
- options.declare(
209
- :fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
210
- types: [String, Array, Regexp, Proc],
211
- default: SpecialValues::DEF)
212
- options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
213
- options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
214
- options.declare(:flat_hash, '(Table) Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
215
- options.declare(:transpose_single, '(Table) Single object fields output vertically', values: :bool, handler: {o: self, m: :option_handler}, default: true)
216
- options.declare(:multi_table, '(Table) Each element of a table are displayed as a table', values: :bool, handler: {o: self, m: :option_handler}, default: false)
217
- options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
218
- options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
219
- end
220
-
221
215
  # main output method
222
216
  # data: for requested data, not displayed if level==error
223
217
  # info: additional info, displayed if level==info
@@ -244,6 +238,116 @@ module Aspera
244
238
  display_status(count_msg)
245
239
  end
246
240
 
241
+ def hide_secrets(data)
242
+ SecretHider.deep_remove_secret(data) unless @options[:show_secrets] || @options[:display].eql?(:data)
243
+ end
244
+
245
+ # this method displays the results, especially the table format
246
+ # @param type [Symbol] type of data
247
+ # @param data [Object] data to display
248
+ # @param total [Integer] total number of items
249
+ # @param fields [Array<String>] list of fields to display
250
+ # @param name [String] name of the column to display
251
+ def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
252
+ Log.log.debug{"display_results: #{type} class=#{data.class}"}
253
+ Log.log.trace1{"display_results:data=#{data}"}
254
+ Aspera.assert_type(type, Symbol){'result must have type'}
255
+ Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
256
+ display_item_count(data.length, total) unless total.nil?
257
+ hide_secrets(data)
258
+ case @options[:format]
259
+ when :text
260
+ display_message(:data, data.to_s)
261
+ when :nagios
262
+ Nagios.process(data)
263
+ when :ruby
264
+ display_message(:data, PP.pp(filter_list_on_fields(data), +''))
265
+ when :json
266
+ display_message(:data, JSON.generate(filter_list_on_fields(data)))
267
+ when :jsonpp
268
+ display_message(:data, JSON.pretty_generate(filter_list_on_fields(data)))
269
+ when :yaml
270
+ display_message(:data, YAML.dump(filter_list_on_fields(data)))
271
+ when :image
272
+ # assume it is an url
273
+ url = data
274
+ case type
275
+ when :single_object, :object_list
276
+ url = [url] if type.eql?(:single_object)
277
+ raise 'image display requires a single result' unless url.length == 1
278
+ fields = compute_fields(url, fields)
279
+ raise 'select a field to display' unless fields.length == 1
280
+ url = url.first
281
+ raise 'no such field' unless url.key?(fields.first)
282
+ url = url[fields.first]
283
+ end
284
+ raise "not url: #{url.class} #{url}" unless url.is_a?(String)
285
+ display_message(:data, status_image(url))
286
+ when :table, :csv
287
+ case type
288
+ when :object_list, :single_object
289
+ obj_list = data
290
+ if type.eql?(:single_object)
291
+ obj_list = [obj_list]
292
+ @options[:multi_single] = :yes
293
+ end
294
+ Aspera.assert_type(obj_list, Array)
295
+ Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
296
+ # :object_list is an array of hash tables, where key=colum name
297
+ obj_list = obj_list.map{|obj|Flattener.new(self).flatten(obj)} if @options[:flat_hash]
298
+ display_table(obj_list, compute_fields(obj_list, fields))
299
+ when :value_list
300
+ # :value_list is a simple array of values, name of column provided in the :name
301
+ display_table(data.map { |i| { name => i } }, [name])
302
+ when :empty # no table
303
+ display_message(:info, special_format('empty'))
304
+ return
305
+ when :nothing # no result expected
306
+ Log.log.debug('no result expected')
307
+ when :status # no table
308
+ # :status displays a simple message
309
+ display_message(:info, data)
310
+ when :text # no table
311
+ # :status displays a simple message
312
+ display_message(:data, data)
313
+ when :other_struct # no table
314
+ # :other_struct is any other type of structure
315
+ display_message(:data, PP.pp(data, +''))
316
+ else
317
+ raise "unknown data type: #{type}"
318
+ end
319
+ else
320
+ raise "not expected: #{@options[:format]}"
321
+ end
322
+ end
323
+
324
+ # @return text suitable to display an image from url
325
+ # @param blob [String] either a URL or image data
326
+ def status_image(blob)
327
+ # check if URL
328
+ begin
329
+ URI.parse(blob)
330
+ url = blob
331
+ unless Environment.instance.url_method.eql?(:text)
332
+ Environment.instance.open_uri(url)
333
+ return ''
334
+ end
335
+ blob = UriReader.read(url)
336
+ rescue URI::InvalidURIError
337
+ nil
338
+ end
339
+ # try base64
340
+ begin
341
+ blob = Base64.strict_decode64(blob)
342
+ rescue
343
+ nil
344
+ end
345
+ return Preview::Terminal.build(blob, **@options[:image].symbolize_keys)
346
+ end
347
+ #==========================================================================================
348
+
349
+ private
350
+
247
351
  def all_fields(data)
248
352
  data.each_with_object({}){|v, m|v.each_key{|c|m[c] = true}}.keys
249
353
  end
@@ -300,7 +404,7 @@ module Aspera
300
404
  return data if @options[:fields].eql?(SpecialValues::DEF)
301
405
  selected_fields = compute_fields(data, @options[:fields])
302
406
  return data.map{|i|i[selected_fields.first]} if selected_fields.length == 1
303
- return data.map{|i|i.select{|k, _|selected_fields.include?(k)}}
407
+ return data.map{|i|i.slice(*selected_fields)}
304
408
  end
305
409
 
306
410
  # filter the list of items on the select option
@@ -314,9 +418,9 @@ module Aspera
314
418
  end
315
419
  end
316
420
 
317
- # this method displays a table
318
- # object_array: array of hash
319
- # fields: list of column names
421
+ # displays a list of objects
422
+ # @param object_array [Array] array of hash
423
+ # @param fields [Array] list of column names
320
424
  def display_table(object_array, fields)
321
425
  Aspera.assert(!fields.nil?){'missing fields parameter'}
322
426
  filter_columns_on_select(object_array)
@@ -330,13 +434,6 @@ module Aspera
330
434
  display_message(:data, object_array.first[fields.first])
331
435
  return
332
436
  end
333
- single_transposed = @options[:transpose_single] && object_array.length == 1
334
- # Special case if only one row (it could be object_list or single_object)
335
- if single_transposed
336
- single = object_array.first
337
- object_array = fields.map { |i| FIELD_VALUE_HEADINGS.zip([i, single[i]]).to_h }
338
- fields = FIELD_VALUE_HEADINGS
339
- end
340
437
  Log.log.debug{Log.dump(:object_array, object_array)}
341
438
  # convert data to string, and keep only display fields
342
439
  final_table_rows = object_array.map { |r| fields.map { |c| r[c].to_s } }
@@ -345,11 +442,12 @@ module Aspera
345
442
  # here : fields : list of column names
346
443
  case @options[:format]
347
444
  when :table
348
- if @options[:multi_table] && !single_transposed
445
+ if @options[:multi_single].eql?(:yes) ||
446
+ (@options[:multi_single].eql?(:single) && final_table_rows.length.eql?(1))
349
447
  final_table_rows.each do |row|
350
448
  Log.log.debug{Log.dump(:row, row)}
351
449
  display_message(:data, Terminal::Table.new(
352
- headings: FIELD_VALUE_HEADINGS,
450
+ headings: SINGLE_OBJECT_COLUMN_NAMES,
353
451
  rows: fields.zip(row),
354
452
  style: @options[:table_style]&.symbolize_keys))
355
453
  end
@@ -366,110 +464,6 @@ module Aspera
366
464
  raise "not expected: #{@options[:format]}"
367
465
  end
368
466
  end
369
-
370
- # @return text suitable to display an image from url
371
- def status_image(blob)
372
- begin
373
- raise URI::InvalidURIError, 'not uri' if !(blob =~ /\A#{URI::RFC2396_PARSER.make_regexp}\z/)
374
- # it's a url
375
- url = blob
376
- unless Environment.instance.url_method.eql?(:text)
377
- Environment.instance.open_uri(url)
378
- return ''
379
- end
380
- # remote_image = Rest.new(base_url: url).read('')
381
- # mime = remote_image[:http]['content-type']
382
- # blob = remote_image[:http].body
383
- # Log.log.warn("Image ? #{remote_image[:http]['content-type']}") unless mime.include?('image/')
384
- blob = UriReader.read(url)
385
- rescue URI::InvalidURIError
386
- nil
387
- end
388
- # try base64
389
- begin
390
- blob = Base64.strict_decode64(blob)
391
- rescue
392
- nil
393
- end
394
- return Preview::Terminal.build(blob, **@options[:image].symbolize_keys)
395
- end
396
-
397
- # this method displays the results, especially the table format
398
- # @param type [Symbol] type of data
399
- # @param data [Object] data to display
400
- # @param total [Integer] total number of items
401
- # @param fields [Array<String>] list of fields to display
402
- # @param name [String] name of the column to display
403
- def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
404
- Log.log.debug{"display_results: #{type} class=#{data.class} data=#{data}"}
405
- Aspera.assert_type(type, Symbol){'result must have type'}
406
- Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
407
- display_item_count(data.length, total) unless total.nil?
408
- SecretHider.deep_remove_secret(data) unless @options[:show_secrets] || @options[:display].eql?(:data)
409
- case @options[:format]
410
- when :text
411
- display_message(:data, data.to_s)
412
- when :nagios
413
- Nagios.process(data)
414
- when :ruby
415
- display_message(:data, PP.pp(filter_list_on_fields(data), +''))
416
- when :json
417
- display_message(:data, JSON.generate(filter_list_on_fields(data)))
418
- when :jsonpp
419
- display_message(:data, JSON.pretty_generate(filter_list_on_fields(data)))
420
- when :yaml
421
- display_message(:data, YAML.dump(filter_list_on_fields(data)))
422
- when :image
423
- # assume it is an url
424
- url = data
425
- case type
426
- when :single_object, :object_list
427
- url = [url] if type.eql?(:single_object)
428
- raise 'image display requires a single result' unless url.length == 1
429
- fields = compute_fields(url, fields)
430
- raise 'select a field to display' unless fields.length == 1
431
- url = url.first
432
- raise 'no such field' unless url.key?(fields.first)
433
- url = url[fields.first]
434
- end
435
- raise "not url: #{url.class} #{url}" unless url.is_a?(String)
436
- display_message(:data, status_image(url))
437
- when :table, :csv
438
- case type
439
- when :config_over
440
- display_table(Flattener.new(self).config_over(data), CONF_OVERVIEW_KEYS)
441
- when :object_list, :single_object
442
- obj_list = data
443
- obj_list = [obj_list] if type.eql?(:single_object)
444
- Aspera.assert_type(obj_list, Array)
445
- Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
446
- # :object_list is an array of hash tables, where key=colum name
447
- obj_list = obj_list.map{|obj|Flattener.new(self).flatten(obj)} if @options[:flat_hash]
448
- display_table(obj_list, compute_fields(obj_list, fields))
449
- when :value_list
450
- # :value_list is a simple array of values, name of column provided in the :name
451
- display_table(data.map { |i| { name => i } }, [name])
452
- when :empty # no table
453
- display_message(:info, special_format('empty'))
454
- return
455
- when :nothing # no result expected
456
- Log.log.debug('no result expected')
457
- when :status # no table
458
- # :status displays a simple message
459
- display_message(:info, data)
460
- when :text # no table
461
- # :status displays a simple message
462
- display_message(:data, data)
463
- when :other_struct # no table
464
- # :other_struct is any other type of structure
465
- display_message(:data, PP.pp(data, +''))
466
- else
467
- raise "unknown data type: #{type}"
468
- end
469
- else
470
- raise "not expected: #{@options[:format]}"
471
- end
472
- end
473
467
  end
474
468
  end
475
469
  end
@@ -12,7 +12,7 @@ module Aspera
12
12
  SRC_URL = 'https://github.com/IBM/aspera-cli'
13
13
  # set this to warn in advance when minimum required ruby version will increase
14
14
  # see also required_ruby_version in gemspec file
15
- RUBY_FUTURE_MINIMUM_VERSION = '3.0'
15
+ RUBY_FUTURE_MINIMUM_VERSION = '3.2'
16
16
  end
17
17
  end
18
18
  end
@@ -63,6 +63,18 @@ module Aspera
63
63
  def result_image(blob, formatter:)
64
64
  return Main.result_status(formatter.status_image(blob))
65
65
  end
66
+
67
+ def result_single_object(data)
68
+ return {type: :single_object, data: data}
69
+ end
70
+
71
+ def result_object_list(data, fields: nil, total: nil)
72
+ return {type: :object_list, data: data, fields: fields, total: nil}
73
+ end
74
+
75
+ def result_value_list(data, name)
76
+ return {type: :value_list, data: data, name: name}
77
+ end
66
78
  end
67
79
 
68
80
  private
@@ -105,13 +105,14 @@ module Aspera
105
105
  # @param descr [String] description for help
106
106
  # @param to_check [Object] value to check
107
107
  # @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
108
+ # @param check_array [bool] set to true if it is a list of values to check
108
109
  def validate_type(what, descr, to_check, type_list, check_array: false)
109
110
  return nil if type_list.nil?
110
111
  Aspera.assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
111
112
  value_list = check_array ? to_check : [to_check]
112
113
  value_list.each do |value|
113
114
  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
+ "#{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
116
  type_list.any?{|t|value.is_a?(t)}
116
117
  end
117
118
  end
@@ -500,7 +501,6 @@ module Aspera
500
501
  self.class.multi_choice_assert(false, message, accept_list)
501
502
  end
502
503
  end
503
- result = nil
504
504
  sensitive = option && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
505
505
  default_prompt = "#{what}: #{descr}"
506
506
  # ask interactively
@@ -220,12 +220,12 @@ module Aspera
220
220
  query = options.get_option(:query)
221
221
  # dup default, as it could be frozen
222
222
  query = default.dup if query.nil?
223
- Log.log.debug{"Query=#{query}".bg_red}
223
+ Log.log.debug{"query_read_delete=#{query}".bg_red}
224
224
  begin
225
225
  # check it is suitable
226
226
  URI.encode_www_form(query) unless query.nil?
227
227
  rescue StandardError => e
228
- raise Cli::BadArgument, "Query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
228
+ raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
229
229
  end
230
230
  return query
231
231
  end
@@ -66,14 +66,14 @@ module Aspera
66
66
  base_url = "#{base_url}.#{Api::AoC::SAAS_DOMAIN_PROD}" unless base_url.include?('.')
67
67
  # AoC is only https
68
68
  return nil unless base_url.start_with?('https://')
69
- res_http = Rest.new(base_url: base_url, redirect_max: 10).call(operation: 'GET')[:http]
70
- # Any AoC is on this domain
71
- return nil unless res_http.uri.host.end_with?(Api::AoC::SAAS_DOMAIN_PROD)
72
- Log.log.debug{"AoC Main page: #{res_http.body.include?(Api::AoC::PRODUCT_NAME)}"}
73
- base_url = res_http.uri.to_s if res_http.uri.path.include?('/public')
69
+ res_http = Rest.new(base_url: base_url, redirect_max: 0).call(operation: 'GET', subpath: 'auth/ping', return_error: true)[:http]
70
+ return nil if res_http['Location'].nil?
71
+ redirect_uri = URI.parse(res_http['Location'])
72
+ od = Api::AoC.split_org_domain(URI.parse(base_url))
73
+ return nil unless redirect_uri.path.end_with?("oauth2/#{od[:organization]}/login")
74
74
  # either in standard domain, or product name in page
75
75
  return {
76
- version: 'SaaS',
76
+ version: Api::AoC.saas_url?(base_url) ? 'SaaS' : 'Self-managed',
77
77
  url: base_url
78
78
  }
79
79
  end
@@ -92,8 +92,6 @@ module Aspera
92
92
  # set vars to look like object
93
93
  options = object.options
94
94
  formatter = object.formatter
95
- options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: true)
96
- options.parse_options!
97
95
  instance_url = options.get_option(:url, mandatory: true)
98
96
  pub_link_info = Api::AoC.link_info(instance_url)
99
97
  if !pub_link_info[:token].nil?
@@ -108,6 +106,8 @@ module Aspera
108
106
  test_args: 'organization'
109
107
  }
110
108
  end
109
+ options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: Api::AoC.saas_url?(instance_url))
110
+ options.parse_options!
111
111
  # make username mandatory for jwt, this triggers interactive input
112
112
  wiz_username = options.get_option(:username, mandatory: true)
113
113
  raise "Username shall be an email in AoC: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
@@ -133,15 +133,15 @@ module Aspera
133
133
  formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
134
134
  formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
135
135
  formatter.display_status('Check or create in integration:')
136
- formatter.display_status("- name: #{@info[:name]}")
136
+ formatter.display_status('- name: cli')
137
137
  formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
138
138
  formatter.display_status('- origin: localhost')
139
139
  formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
140
140
  end
141
- Environment.instance.open_uri("#{instance_url}/admin/api-clients")
141
+ Environment.instance.open_uri("#{instance_url}/admin/integrations/api-clients")
142
142
  options.get_option(:client_id, mandatory: true)
143
143
  options.get_option(:client_secret, mandatory: true)
144
- use_browser_authentication = true
144
+ # use_browser_authentication = true
145
145
  end
146
146
  if use_browser_authentication
147
147
  formatter.display_status('We will use web authentication to bootstrap.')
@@ -282,13 +282,17 @@ module Aspera
282
282
  end
283
283
 
284
284
  # list all entities, given additional, default and user's queries
285
+ # @param resource_class_path path to query on API
286
+ # @param fields fields to display
287
+ # @param base_query a query applied always
288
+ # @param default_query default query unless overriden by user
285
289
  def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
286
290
  Aspera.assert_type(base_query, Hash)
287
291
  Aspera.assert_type(default_query, Hash)
288
292
  user_query = query_read_delete(default: default_query)
289
293
  # caller may add specific modifications or checks
290
294
  yield(user_query) if block_given?
291
- return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query)))
295
+ return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query).compact))
292
296
  end
293
297
 
294
298
  def resolve_dropbox_name_default_ws_id(query)
@@ -411,8 +415,8 @@ module Aspera
411
415
  when :operation then default_fields = nil
412
416
  when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
413
417
  when :user then default_fields.push('name', 'email')
414
- when :group_membership then default_fields.push(*%w[group_id member_type member_id])
415
- when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
418
+ when :group_membership then default_fields.push('group_id', 'member_type', 'member_id')
419
+ when :workspace_membership then default_fields.push('workspace_id', 'member_type', 'member_id')
416
420
  end
417
421
  return result_list(resource_class_path, fields: default_fields, default_query: default_query)
418
422
  when :show
@@ -801,6 +805,12 @@ module Aspera
801
805
  else
802
806
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
803
807
  end
808
+ file_list =
809
+ begin
810
+ transfer.source_list.map{|i|{'source'=>i}}
811
+ rescue Cli::BadArgument
812
+ [{'source' => '.'}]
813
+ end
804
814
  # list here
805
815
  result_transfer = []
806
816
  formatter.display_status("found #{ids_to_download.length} package(s).")
@@ -816,7 +826,7 @@ module Aspera
816
826
  package_node_api.transfer_spec_gen4(
817
827
  package_info['contents_file_id'],
818
828
  Transfer::Spec::DIRECTION_RECEIVE,
819
- {'paths'=> [{'source' => '.'}]}),
829
+ {'paths'=> file_list}),
820
830
  rest_token: package_node_api)
821
831
  result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
822
832
  # update skip list only if all transfer sessions completed
@@ -837,14 +847,14 @@ module Aspera
837
847
  resolve_dropbox_name_default_ws_id(query)
838
848
  end
839
849
  when :delete
840
- return do_bulk_operation(command: package_command, descr: 'identifier', values: identifier) do |id|
850
+ return do_bulk_operation(command: package_command, descr: 'identifier', values: instance_identifier) do |id|
841
851
  Aspera.assert_values(id.class, [String, Integer]){'identifier'}
842
852
  aoc_api.delete("packages/#{id}")
843
853
  end
844
854
  when *Node::NODE4_READ_ACTIONS
845
855
  package_id = instance_identifier
846
856
  package_info = aoc_api.read("packages/#{package_id}")
847
- return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: Api::Node::SCOPE_USER)
857
+ return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['contents_file_id'], scope: Api::Node::SCOPE_USER)
848
858
  end
849
859
  when :files
850
860
  command_repo = options.get_next_command([:short_link].concat(NODE4_EXT_COMMANDS))
@@ -891,7 +901,7 @@ module Aspera
891
901
  when :automation
892
902
  Log.log.warn('BETA: work under progress')
893
903
  # automation api is not in the same place
894
- automation_api = Rest.new(**aoc_api.params.merge(base_url: aoc_api.base_url.gsub('/api/', '/automation/')))
904
+ automation_api = Rest.new(**aoc_api.params, base_url: aoc_api.base_url.gsub('/api/', '/automation/'))
895
905
  command_automation = options.get_next_command(%i[workflows instances])
896
906
  case command_automation
897
907
  when :instances