aspera-cli 4.24.2 → 4.25.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1070 -758
  4. data/CONTRIBUTING.md +130 -115
  5. data/README.md +961 -623
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/factory.rb +9 -6
  8. data/lib/aspera/agent/transferd.rb +8 -8
  9. data/lib/aspera/api/aoc.rb +104 -67
  10. data/lib/aspera/api/ats.rb +1 -0
  11. data/lib/aspera/api/cos_node.rb +3 -2
  12. data/lib/aspera/api/faspex.rb +17 -10
  13. data/lib/aspera/api/node.rb +10 -12
  14. data/lib/aspera/ascmd.rb +2 -3
  15. data/lib/aspera/ascp/installation.rb +60 -46
  16. data/lib/aspera/ascp/management.rb +9 -5
  17. data/lib/aspera/assert.rb +28 -6
  18. data/lib/aspera/cli/error.rb +4 -2
  19. data/lib/aspera/cli/extended_value.rb +94 -62
  20. data/lib/aspera/cli/formatter.rb +44 -58
  21. data/lib/aspera/cli/main.rb +21 -14
  22. data/lib/aspera/cli/manager.rb +317 -250
  23. data/lib/aspera/cli/plugins/alee.rb +3 -3
  24. data/lib/aspera/cli/plugins/aoc.rb +139 -78
  25. data/lib/aspera/cli/plugins/ats.rb +30 -36
  26. data/lib/aspera/cli/plugins/base.rb +68 -55
  27. data/lib/aspera/cli/plugins/config.rb +90 -100
  28. data/lib/aspera/cli/plugins/console.rb +15 -9
  29. data/lib/aspera/cli/plugins/cos.rb +1 -1
  30. data/lib/aspera/cli/plugins/faspex.rb +39 -30
  31. data/lib/aspera/cli/plugins/faspex5.rb +57 -52
  32. data/lib/aspera/cli/plugins/faspio.rb +10 -7
  33. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  34. data/lib/aspera/cli/plugins/node.rb +140 -125
  35. data/lib/aspera/cli/plugins/oauth.rb +13 -12
  36. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  37. data/lib/aspera/cli/plugins/preview.rb +28 -48
  38. data/lib/aspera/cli/plugins/server.rb +9 -10
  39. data/lib/aspera/cli/plugins/shares.rb +77 -43
  40. data/lib/aspera/cli/sync_actions.rb +49 -38
  41. data/lib/aspera/cli/transfer_agent.rb +16 -35
  42. data/lib/aspera/cli/version.rb +1 -1
  43. data/lib/aspera/cli/wizard.rb +8 -5
  44. data/lib/aspera/command_line_builder.rb +24 -21
  45. data/lib/aspera/coverage.rb +6 -2
  46. data/lib/aspera/dot_container.rb +108 -0
  47. data/lib/aspera/environment.rb +71 -84
  48. data/lib/aspera/faspex_gw.rb +1 -1
  49. data/lib/aspera/faspex_postproc.rb +1 -1
  50. data/lib/aspera/id_generator.rb +7 -10
  51. data/lib/aspera/keychain/factory.rb +1 -2
  52. data/lib/aspera/keychain/macos_security.rb +2 -2
  53. data/lib/aspera/log.rb +2 -1
  54. data/lib/aspera/markdown.rb +31 -0
  55. data/lib/aspera/nagios.rb +6 -5
  56. data/lib/aspera/oauth/base.rb +41 -64
  57. data/lib/aspera/oauth/factory.rb +6 -7
  58. data/lib/aspera/oauth/generic.rb +1 -1
  59. data/lib/aspera/oauth/jwt.rb +1 -1
  60. data/lib/aspera/oauth/url_json.rb +6 -4
  61. data/lib/aspera/oauth/web.rb +2 -2
  62. data/lib/aspera/preview/file_types.rb +24 -38
  63. data/lib/aspera/preview/terminal.rb +95 -29
  64. data/lib/aspera/preview/utils.rb +6 -5
  65. data/lib/aspera/products/connect.rb +3 -3
  66. data/lib/aspera/rest.rb +54 -39
  67. data/lib/aspera/rest_error_analyzer.rb +4 -4
  68. data/lib/aspera/ssh.rb +10 -6
  69. data/lib/aspera/ssl.rb +41 -0
  70. data/lib/aspera/sync/conf.schema.yaml +184 -36
  71. data/lib/aspera/sync/database.rb +2 -1
  72. data/lib/aspera/sync/operations.rb +128 -72
  73. data/lib/aspera/transfer/parameters.rb +9 -10
  74. data/lib/aspera/transfer/spec.rb +2 -3
  75. data/lib/aspera/transfer/spec.schema.yaml +52 -22
  76. data/lib/aspera/transfer/spec_doc.rb +20 -30
  77. data/lib/aspera/uri_reader.rb +18 -4
  78. data/lib/transferd_pb.rb +2 -2
  79. data.tar.gz.sig +0 -0
  80. metadata +34 -6
  81. metadata.gz.sig +0 -0
@@ -7,6 +7,8 @@ require 'aspera/secret_hider'
7
7
  require 'aspera/environment'
8
8
  require 'aspera/log'
9
9
  require 'aspera/assert'
10
+ require 'aspera/markdown'
11
+ require 'aspera/dot_container'
10
12
  require 'terminal-table'
11
13
  require 'tty-spinner'
12
14
  require 'yaml'
@@ -63,16 +65,32 @@ module Aspera
63
65
  end
64
66
  end
65
67
 
68
+ # Give Markdown String, or matched data, return formatted string for terminal
66
69
  # used by spec_doc
67
- def keyword_highlight(value)
68
- value.bold
70
+ # @param match [MatchData,String]
71
+ def markdown_text(match)
72
+ if match.is_a?(String)
73
+ match = Markdown::FORMATS.match(match)
74
+ Aspera.assert(match)
75
+ end
76
+ Aspera.assert_type(match, MatchData)
77
+ if match[:entity]
78
+ Aspera.assert_values(match[:entity], 'bsol')
79
+ '\\'
80
+ elsif match[:bold]
81
+ match[:bold].to_s.blue
82
+ elsif match[:code]
83
+ match[:code].to_s.bold
84
+ else
85
+ Aspera.error_unexpected_value(match.to_s)
86
+ end
69
87
  end
70
88
 
71
- # replace empty values with a readable version on terminal
72
- def enhance_display_values_hash(input_hash)
73
- stack = [input_hash]
74
- until stack.empty?
75
- current = stack.pop
89
+ # Replace special values with a readable version on terminal
90
+ def replace_specific_for_terminal(input_hash)
91
+ hash_to_process = [input_hash]
92
+ until hash_to_process.empty?
93
+ current = hash_to_process.pop
76
94
  current.each do |key, value|
77
95
  case value
78
96
  when NilClass
@@ -84,54 +102,24 @@ module Aspera
84
102
  when Array
85
103
  if value.empty?
86
104
  current[key] = special_format('empty list')
105
+ elsif value.all?(String)
106
+ current[key] = value.join(',')
87
107
  else
88
108
  value.each do |item|
89
- stack.push(item) if item.is_a?(Hash)
109
+ hash_to_process.push(item) if item.is_a?(Hash)
90
110
  end
91
111
  end
92
112
  when Hash
93
113
  if value.empty?
94
114
  current[key] = special_format('empty dict')
95
115
  else
96
- stack.push(value)
116
+ hash_to_process.push(value)
97
117
  end
98
118
  end
99
119
  end
100
120
  end
101
121
  end
102
122
 
103
- # Flatten a Hash into single level hash
104
- def flatten_hash(input)
105
- Aspera.assert_type(input, Hash)
106
- return input if input.empty?
107
- flat = {}
108
- stack = [[nil, input]]
109
- until stack.empty?
110
- prefix, current = stack.pop
111
- if current.respond_to?(:empty?) && current.empty?
112
- flat[prefix] = current
113
- next
114
- end
115
- case current
116
- when Hash
117
- current.reverse_each{ |k, v| stack.push([[prefix, k].compact.join('.'), v])}
118
- when Array
119
- if current.all?(String)
120
- flat[prefix] = current.join("\n")
121
- elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
122
- flat[prefix] = current.map{ |i| i['name']}.join(', ')
123
- elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
124
- stack.push([prefix, current.each_with_object({}){ |i, h| h[i['name']] = i['value']}])
125
- else
126
- current.each_with_index.reverse_each{ |v, k| stack.push([[prefix, k].compact.join('.'), v])}
127
- end
128
- else
129
- flat[prefix] = current
130
- end
131
- end
132
- flat
133
- end
134
-
135
123
  def all_but(list)
136
124
  list = [list] unless list.is_a?(Array)
137
125
  return list.map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
@@ -166,23 +154,23 @@ module Aspera
166
154
  else
167
155
  {}
168
156
  end
169
- options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
170
- options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
171
- options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
157
+ options.declare(:format, 'Output format', allowed: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
158
+ options.declare(:output, 'Destination for results', handler: {o: self, m: :option_handler})
159
+ options.declare(:display, 'Output only some information', allowed: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
172
160
  options.declare(
173
161
  :fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
174
- types: [String, Array, Regexp, Proc],
162
+ allowed: [String, Array, Regexp, Proc],
175
163
  default: SpecialValues::DEF
176
164
  )
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)
165
+ options.declare(:select, 'Select only some items in lists: column, value', allowed: [Hash, Proc], handler: {o: self, m: :option_handler})
166
+ options.declare(:table_style, '(Table) Display style', allowed: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
167
+ options.declare(:flat_hash, '(Table) Display deep values as additional keys', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_handler}, default: true)
180
168
  options.declare(
181
- :multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', values: %i[no yes single],
169
+ :multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', allowed: %i[no yes single],
182
170
  handler: {o: self, m: :option_handler}, default: :no
183
171
  )
184
- options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
185
- options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
172
+ options.declare(:show_secrets, 'Show secrets on command output', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_handler}, default: false)
173
+ options.declare(:image, 'Options for image display', allowed: Hash, handler: {o: self, m: :option_handler}, default: {})
186
174
  end
187
175
 
188
176
  # method accessed by option manager
@@ -321,14 +309,13 @@ module Aspera
321
309
  if data.empty?
322
310
  display_message(:data, self.class.special_format('empty dict'))
323
311
  else
324
- data = self.class.flatten_hash(data) if @options[:flat_hash]
312
+ data = DotContainer.new(data).to_dotted if @options[:flat_hash]
325
313
  display_table([data], compute_fields([data], fields), single: true)
326
314
  end
327
315
  when :object_list
328
316
  # :object_list is an Array of Hash, where key=column name
329
- Aspera.assert_type(data, Array)
330
- Aspera.assert(data.all?(Hash)){"expecting Array of Hash: #{data.inspect}"}
331
- data = data.map{ |obj| self.class.flatten_hash(obj)} if @options[:flat_hash]
317
+ Aspera.assert_array_all(data, Hash){'result'}
318
+ data = data.map{ |obj| DotContainer.new(obj).to_dotted} if @options[:flat_hash]
332
319
  display_table(data, compute_fields(data, fields), single: type.eql?(:single_object))
333
320
  when :value_list
334
321
  # :value_list is a simple array of values, name of column provided in `name`
@@ -406,8 +393,7 @@ module Aspera
406
393
  def filter_list_on_fields(data)
407
394
  # by default, keep all data intact
408
395
  return data if @options[:fields].eql?(SpecialValues::DEF) && @options[:select].nil?
409
- Aspera.assert_type(data, Array){'Filtering fields or select requires result is an Array of Hash'}
410
- Aspera.assert(data.all?(Hash)){'Filtering fields or select requires result is an Array of Hash'}
396
+ Aspera.assert_array_all(data, Hash){'filter or select'}
411
397
  filter_columns_on_select(data)
412
398
  return data if @options[:fields].eql?(SpecialValues::DEF)
413
399
  selected_fields = compute_fields(data, @options[:fields])
@@ -441,7 +427,7 @@ module Aspera
441
427
  return
442
428
  end
443
429
  filter_columns_on_select(object_array)
444
- object_array.each{ |i| self.class.enhance_display_values_hash(i)}
430
+ object_array.each{ |i| self.class.replace_specific_for_terminal(i)}
445
431
  # if table has only one element, and only one field, display the value
446
432
  if object_array.length == 1 && fields.length == 1
447
433
  Log.log.debug("display_table: single element, field: #{fields.first}")
@@ -214,6 +214,7 @@ module Aspera
214
214
  rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
215
215
  rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
216
216
  rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
217
+ rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing', usage: true}
217
218
  rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
218
219
  rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
219
220
  rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
@@ -228,6 +229,7 @@ module Aspera
228
229
  unless exception_info.nil?
229
230
  Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
230
231
  @context.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
232
+ Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
231
233
  @context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
232
234
  # Is that a known error condition with proposal for remediation ?
233
235
  Hints.hint_for(exception_info[:e], @context.formatter)
@@ -287,7 +289,6 @@ module Aspera
287
289
  @context.options = Manager.new(Info::CMD_NAME, @argv)
288
290
  # Formatter adds options
289
291
  @context.formatter.declare_options(@context.options)
290
- ExtendedValue.instance.default_decoder = @context.options.get_option(:struct_parser)
291
292
  # Compare $0 with expected name
292
293
  current_prog_name = File.basename($PROGRAM_NAME)
293
294
  @context.formatter.display_message(
@@ -349,28 +350,34 @@ module Aspera
349
350
  # Define header for manual
350
351
  def declare_global_options
351
352
  Log.log.debug('declare_global_options')
352
- @context.options.declare(:help, 'Show this message', values: :none, short: 'h'){@option_help = true}
353
- @context.options.declare(:bash_comp, 'Generate bash completion for command', values: :none){@bash_completion = true}
354
- @context.options.declare(:show_config, 'Display parameters used for the provided action', values: :none){@option_show_config = true}
355
- @context.options.declare(:version, 'Display version', values: :none, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
353
+ @context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
354
+ @context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
355
+ @context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
356
+ @context.options.declare(:version, 'Display version', allowed: Allowed::TYPES_NONE, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
356
357
  @context.options.declare(
357
358
  :ui, 'Method to start browser',
358
- values: USER_INTERFACES,
359
+ allowed: USER_INTERFACES,
359
360
  handler: {o: Environment.instance, m: :url_method}
360
361
  )
361
362
  @context.options.declare(
362
363
  :invalid_characters, 'Replacement character and invalid filename characters',
363
364
  handler: {o: Environment.instance, m: :file_illegal_characters}
364
365
  )
365
- @context.options.declare(:log_level, 'Log level', values: Log::LEVELS, handler: {o: Log.instance, m: :level})
366
- @context.options.declare(:log_format, 'Log formatter', types: [Proc, Logger::Formatter, String], handler: {o: Log.instance, m: :formatter})
367
- @context.options.declare(:logger, 'Logging method', values: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
368
- @context.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', coerce: Integer, types: Integer)
369
- @context.options.declare(:once_only, 'Process only new items (some commands)', values: :bool, default: false)
370
- @context.options.declare(:log_secrets, 'Show passwords in logs', values: :bool, handler: {o: SecretHider.instance, m: :log_secrets})
371
- @context.options.declare(:clean_temp, 'Cleanup temporary files on exit', values: :bool, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
366
+ @context.options.declare(:log_level, 'Log level', allowed: Log::LEVELS, handler: {o: Log.instance, m: :level})
367
+ @context.options.declare(:log_format, 'Log formatter', allowed: [Proc, Logger::Formatter, String], handler: {o: Log.instance, m: :formatter})
368
+ @context.options.declare(:logger, 'Logging method', allowed: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
369
+ @context.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', allowed: Allowed::TYPES_INTEGER)
370
+ @context.options.declare(:once_only, 'Process only new items (some commands)', allowed: Allowed::TYPES_BOOLEAN, default: false)
371
+ @context.options.declare(:log_secrets, 'Show passwords in logs', allowed: Allowed::TYPES_BOOLEAN, handler: {o: SecretHider.instance, m: :log_secrets})
372
+ @context.options.declare(:clean_temp, 'Cleanup temporary files on exit', allowed: Allowed::TYPES_BOOLEAN, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
372
373
  @context.options.declare(:temp_folder, 'Temporary folder', handler: {o: TempFileManager.instance, m: :global_temp})
373
- @context.options.declare(:pid_file, 'Write process identifier to file, delete on exit', types: String)
374
+ @context.options.declare(:pid_file, 'Write process identifier to file, delete on exit')
375
+ @context.options.declare(
376
+ :parser, 'Default parser for structured parameters and options',
377
+ handler: {o: ExtendedValue.instance, m: :default_decoder},
378
+ allowed: ExtendedValue::DEFAULT_DECODERS,
379
+ default: ExtendedValue::DEFAULT_DECODERS.first
380
+ )
374
381
  # Parse declared options
375
382
  @context.options.parse_options!
376
383
  end