aspera-cli 4.14.0 → 4.16.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 (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -1,17 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/cli/plugins/node'
3
+ require 'aspera/cli/basic_auth_plugin'
4
+ require 'aspera/nagios'
5
+ require 'aspera/log'
6
+ require 'aspera/assert'
4
7
  require 'xmlsimple'
5
8
 
6
9
  module Aspera
7
10
  module Cli
8
11
  module Plugins
9
12
  class Orchestrator < Aspera::Cli::BasicAuthPlugin
13
+ class << self
14
+ STANDARD_PATH = '/aspera/orchestrator'
15
+ def detect(address_or_url)
16
+ address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
17
+ urls = [address_or_url]
18
+ urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
19
+ urls.each do |base_url|
20
+ next unless base_url.match?('https?://')
21
+ api = Rest.new(base_url: base_url)
22
+ test_endpoint = 'api/remote_node_ping'
23
+ result = api.read(test_endpoint, {format: :json})
24
+ next unless result[:data]['remote_orchestrator_info']
25
+ url = result[:http].uri.to_s
26
+ return {
27
+ version: result[:data]['remote_orchestrator_info']['orchestrator-version'],
28
+ url: url[0..url.index(test_endpoint) - 2]
29
+ }
30
+ rescue StandardError => e
31
+ Log.log.debug{"detect error: #{e}"}
32
+ end
33
+ return nil
34
+ end
35
+
36
+ def wizard(object:, private_key_path: nil, pub_key_pem: nil)
37
+ options = object.options
38
+ return {
39
+ preset_value: {
40
+ url: options.get_option(:url, mandatory: true),
41
+ username: options.get_option(:username, mandatory: true),
42
+ password: options.get_option(:password, mandatory: true)
43
+ },
44
+ test_args: 'workflow list'
45
+ }
46
+ end
47
+ end
48
+
10
49
  def initialize(env)
11
50
  super(env)
12
- options.declare(:params, 'Start parameters', types: Hash, default: {})
13
- options.declare(:result, "Specify result value as: 'work step:parameter'")
14
- options.declare(:synchronous, 'Work step:parameter expected as result', values: :bool, default: :no)
51
+ options.declare(:result, "Specify result value as: 'work_step:parameter'")
52
+ options.declare(:synchronous, 'Wait for completion', values: :bool, default: :no)
15
53
  options.declare(:ret_style, 'How return type is requested in api', values: %i[header arg ext], default: :arg)
16
54
  options.declare(:auth_style, 'Authentication type', values: %i[arg_pass head_basic apikey], default: :head_basic)
17
55
  options.parse_options!
@@ -59,12 +97,12 @@ module Aspera
59
97
  call_args[:url_params][:format] = format
60
98
  when :ext
61
99
  call_args[:subpath] = "#{call_args[:subpath]}.#{format}"
62
- else raise 'unexpected'
100
+ else error_unexpected_value(call_type)
63
101
  end
64
102
  end
65
103
  result = @api_orch.call(call_args)
66
104
  result[:data] = XmlSimple.xml_in(result[:http].body, opt[:xml_opt] || {'ForceArray' => true}) if format.eql?('xml')
67
- Log.dump(:data, result[:data])
105
+ Log.log.debug{Log.dump(:data, result[:data])}
68
106
  return result
69
107
  end
70
108
 
@@ -74,7 +112,7 @@ module Aspera
74
112
  when :arg_pass
75
113
  rest_params[:auth] = {
76
114
  type: :url,
77
- url_creds: {
115
+ url_query: {
78
116
  'login' => options.get_option(:username, mandatory: true),
79
117
  'password' => options.get_option(:password, mandatory: true) }}
80
118
  when :head_basic
@@ -118,9 +156,9 @@ module Aspera
118
156
  end
119
157
  case command
120
158
  when :status
121
- options = {}
122
- options[:id] = wf_id unless wf_id.eql?(VAL_ALL)
123
- result = call_ao('workflows_status', options)[:data]
159
+ call_opts = {}
160
+ call_opts[:id] = wf_id unless wf_id.eql?(ExtendedValue::ALL)
161
+ result = call_ao('workflows_status', call_opts)[:data]
124
162
  return {type: :object_list, data: result['workflows']['workflow']}
125
163
  when :list
126
164
  result = call_ao('workflows_list', id: 0)[:data]
@@ -145,19 +183,18 @@ module Aspera
145
183
  }
146
184
  call_params = {format: :json}
147
185
  override_accept = nil
148
- # set external parameters if any
149
- # TODO: make not an option, but a parameter
150
- self.options.get_option(:params, mandatory: true).each do |name, value|
186
+ # get external parameters if any
187
+ options.get_next_argument('external_parameters', mandatory: false, type: Hash, default: {}).each do |name, value|
151
188
  call_params["external_parameters[#{name}]"] = value
152
189
  end
153
190
  # synchronous call ?
154
- call_params['synchronous'] = true if self.options.get_option(:synchronous, mandatory: true)
191
+ call_params['synchronous'] = true if options.get_option(:synchronous, mandatory: true)
155
192
  # expected result for synchro call ?
156
- expected = self.options.get_option(:result)
157
- unless expected.nil?
193
+ result_location = options.get_option(:result)
194
+ unless result_location.nil?
158
195
  result[:type] = :status
159
- fields = expected.split(':')
160
- raise "Expects: work_step:result_name format, but got #{expected}" if fields.length != 2
196
+ fields = result_location.split(':')
197
+ raise Cli::BadArgument, "Expects: work_step:result_name : #{result_location}" if fields.length != 2
161
198
  call_params['explicit_output_step'] = fields[0]
162
199
  call_params['explicit_output_variable'] = fields[1]
163
200
  # implicitly, call is synchronous
@@ -170,7 +207,7 @@ module Aspera
170
207
  result[:data] = call_ao('initiate', id: wf_id, args: call_params, accept: override_accept)[:data]
171
208
  return result
172
209
  end # wf command
173
- else raise "ERROR, unknown command: [#{command}]"
210
+ else error_unexpected_value(command)
174
211
  end # case command
175
212
  end # execute_action
176
213
  end # Orchestrator
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore trevents
3
4
  require 'aspera/cli/basic_auth_plugin'
4
5
  require 'aspera/preview/generator'
5
6
  require 'aspera/preview/options'
6
7
  require 'aspera/preview/utils'
7
8
  require 'aspera/preview/file_types'
9
+ require 'aspera/preview/terminal'
8
10
  require 'aspera/fasp/transfer_spec'
9
11
  require 'aspera/persistency_action_once'
10
12
  require 'aspera/node'
11
13
  require 'aspera/hash_ext'
12
14
  require 'aspera/timer_limiter'
13
15
  require 'aspera/id_generator'
14
- require 'date'
16
+ require 'aspera/log'
17
+ require 'aspera/assert'
15
18
  require 'securerandom'
16
19
 
17
20
  module Aspera
@@ -26,8 +29,11 @@ module Aspera
26
29
  PREVIEW_BASENAME = 'preview'
27
30
  # subfolder in system tmp folder
28
31
  TMP_DIR_PREFIX = 'prev_tmp'
32
+ # same value as in aspera.conf
29
33
  DEFAULT_PREVIEWS_FOLDER = 'previews'
34
+ # mark that this is used by a particular access key
30
35
  AK_MARKER_FILE = '.aspera_access_key'
36
+ # URL prefix for local storage
31
37
  PVCL_LOCAL_STORAGE = 'file:///'
32
38
  LOG_LIMITER_SEC = 30.0
33
39
  private_constant :PREV_GEN_TAG,
@@ -53,6 +59,8 @@ module Aspera
53
59
  @gen_options = Aspera::Preview::Options.new
54
60
  # used to trigger periodic processing
55
61
  @periodic = TimerLimiter.new(LOG_LIMITER_SEC)
62
+ # Proc
63
+ @filter_block = nil
56
64
  # link CLI options to gen_info attributes
57
65
  options.declare(
58
66
  :skip_format, 'Skip this preview format (multiple possible)', values: Aspera::Preview::Generator::PREVIEW_FORMATS,
@@ -66,7 +74,7 @@ module Aspera
66
74
  options.declare(:previews_folder, 'Preview folder in storage root', handler: {o: self, m: :option_previews_folder}, default: DEFAULT_PREVIEWS_FOLDER)
67
75
  options.declare(:temp_folder, 'Path to temp folder', default: Dir.tmpdir)
68
76
  options.declare(:skip_folders, 'List of folder to skip', handler: {o: self, m: :option_skip_folders}, default: [])
69
- options.declare(:case, 'Basename of output for for test')
77
+ options.declare(:base, 'Basename of output for for test')
70
78
  options.declare(:scan_path, 'Subpath in folder id to start scan in (default=/)')
71
79
  options.declare(:scan_id, 'Folder id in storage to start scan in, default is access key main folder id')
72
80
  options.declare(:mimemagic, 'Use Mime type detection of gem mimemagic', values: :bool, default: false)
@@ -88,7 +96,7 @@ module Aspera
88
96
  end
89
97
 
90
98
  options.parse_options!
91
- raise 'skip_folder shall be an Array, use @json:[...]' unless @option_skip_folders.is_a?(Array)
99
+ assert_type(@option_skip_folders, Array){'skip_folder'}
92
100
  @tmp_folder = File.join(options.get_option(:temp_folder, mandatory: true), "#{TMP_DIR_PREFIX}.#{SecureRandom.uuid}")
93
101
  FileUtils.mkdir_p(@tmp_folder)
94
102
  Log.log.debug{"tmpdir: #{@tmp_folder}"}
@@ -98,7 +106,7 @@ module Aspera
98
106
  @skip_types = []
99
107
  value.split(',').each do |v|
100
108
  s = v.to_sym
101
- raise "not supported: #{v}" unless Aspera::Preview::FileTypes::CONVERSION_TYPES.include?(s)
109
+ assert_values(s, Aspera::Preview::FileTypes::CONVERSION_TYPES){'skip_types'}
102
110
  @skip_types.push(s)
103
111
  end
104
112
  end
@@ -205,7 +213,7 @@ module Aspera
205
213
  end
206
214
 
207
215
  def do_transfer(direction, folder_id, source_filename, destination='/')
208
- raise 'Internal ERROR' if destination.nil? && direction.eql?(Fasp::TransferSpec::DIRECTION_RECEIVE)
216
+ assert(!(destination.nil? && direction.eql?(Fasp::TransferSpec::DIRECTION_RECEIVE)))
209
217
  t_spec = @api_node.transfer_spec_gen4(folder_id, direction, {
210
218
  'paths' => [{'source' => source_filename}],
211
219
  'tags' => {Fasp::TransferSpec::TAG_RESERVED => {PREV_GEN_TAG => true}}
@@ -232,6 +240,7 @@ module Aspera
232
240
  def get_infos_remote(gen_infos, entry)
233
241
  # store source directly here
234
242
  local_original_filepath = File.join(@tmp_folder, entry['name'])
243
+ # require 'date'
235
244
  # original_mtime=DateTime.parse(entry['modified_time'])
236
245
  # out: where previews are generated
237
246
  local_entry_preview_dir = File.join(@tmp_folder, entry_preview_folder_name(entry))
@@ -255,9 +264,10 @@ module Aspera
255
264
  "#{entry['id']}#{PREVIEW_FOLDER_SUFFIX}"
256
265
  end
257
266
 
258
- def preview_filename(preview_format, filename=nil)
259
- filename ||= PREVIEW_BASENAME
260
- return "#{filename}.#{preview_format}"
267
+ # Generate a file name based on basename and format (extension)
268
+ def preview_filename(preview_format, base_name=nil)
269
+ base_name ||= PREVIEW_BASENAME
270
+ return "#{base_name}.#{preview_format}"
261
271
  end
262
272
 
263
273
  # generate preview files for one folder entry (file) if necessary
@@ -290,10 +300,13 @@ module Aspera
290
300
  next false if gen_info[:preview_newer_than_original]
291
301
  end
292
302
  end
293
- # need generator for further checks
294
- gen_info[:generator] = Aspera::Preview::Generator.new(@gen_options, gen_info[:src], gen_info[:dst], @tmp_folder, entry['content_type'])
295
- # get conversion_type (if known) and check if supported
296
- next false unless gen_info[:generator].supported?
303
+ begin
304
+ # need generator for further checks
305
+ gen_info[:generator] = Aspera::Preview::Generator.new(gen_info[:src], gen_info[:dst], @gen_options, @tmp_folder, entry['content_type'])
306
+ rescue
307
+ # no conversion supported
308
+ next false
309
+ end
297
310
  # shall we skip it ?
298
311
  next false if @skip_types.include?(gen_info[:generator].conversion_type)
299
312
  # ok we need to generate
@@ -307,7 +320,7 @@ module Aspera
307
320
  # download original file to temp folder
308
321
  do_transfer(Fasp::TransferSpec::DIRECTION_RECEIVE, entry['parent_file_id'], entry['name'], @tmp_folder)
309
322
  end
310
- Log.log.info{"source: #{entry['id']}: #{entry['path']})"}
323
+ Log.log.info{"source: #{entry['id']}: #{entry['path']}"}
311
324
  gen_infos.each do |gen_info|
312
325
  gen_info[:generator].generate rescue nil
313
326
  end
@@ -328,24 +341,22 @@ module Aspera
328
341
  end # generate_preview
329
342
 
330
343
  # scan all files in provided folder entry
331
- # @param scan_start subpath to start folder scan inside
332
- def scan_folder_files(top_entry, scan_start=nil)
333
- if !scan_start.nil?
344
+ # @param top_path subpath to start folder scan inside
345
+ def scan_folder_files(top_entry, top_path=nil)
346
+ unless top_path.nil?
334
347
  # canonical path: start with / and ends with /
335
- scan_start = '/' + scan_start.split('/').reject(&:empty?).join('/')
336
- scan_start = "#{scan_start}/" # unless scan_start.end_with?('/')
348
+ top_path = '/' + top_path.split('/').reject(&:empty?).join('/') + '/'
337
349
  end
338
- filter_block = Aspera::Node.file_matcher(value_or_query(allowed_types: String))
339
- Log.log.debug{"scan: #{top_entry} : #{scan_start}".green}
350
+ Log.log.debug{"scan: #{top_entry} : #{top_path}".green}
340
351
  # don't use recursive call, use list instead
341
352
  entries_to_process = [top_entry]
342
353
  until entries_to_process.empty?
343
354
  entry = entries_to_process.shift
344
- # process this entry only if it is within the scan_start
355
+ # process this entry only if it is within the top_path
345
356
  entry_path_with_slash = entry['path']
346
357
  Log.log.info{"processing entry #{entry_path_with_slash}"} if @periodic.trigger?
347
358
  entry_path_with_slash = "#{entry_path_with_slash}/" unless entry_path_with_slash.end_with?('/')
348
- if !scan_start.nil? && !scan_start.start_with?(entry_path_with_slash) && !entry_path_with_slash.start_with?(scan_start)
359
+ if !top_path.nil? && !top_path.start_with?(entry_path_with_slash) && !entry_path_with_slash.start_with?(top_path)
349
360
  Log.log.debug{"#{entry['path']} folder (skip start)".bg_red}
350
361
  next
351
362
  end
@@ -353,7 +364,7 @@ module Aspera
353
364
  begin
354
365
  case entry['type']
355
366
  when 'file'
356
- if filter_block.call(entry)
367
+ if @filter_block.call(entry)
357
368
  generate_preview(entry)
358
369
  else
359
370
  Log.log.debug('skip by filter')
@@ -386,11 +397,11 @@ module Aspera
386
397
  end
387
398
  end
388
399
 
389
- ACTIONS = %i[scan events trevents check test].freeze
400
+ ACTIONS = %i[scan events trevents check test show].freeze
390
401
 
391
402
  def execute_action
392
403
  command = options.get_next_command(ACTIONS)
393
- unless %i[check test].include?(command)
404
+ unless %i[check test show].include?(command)
394
405
  # this will use node api
395
406
  @api_node = Aspera::Node.new(params: basic_auth_params)
396
407
  @transfer_server_address = URI.parse(@api_node.params[:base_url]).host
@@ -408,25 +419,25 @@ module Aspera
408
419
  if @access_remote
409
420
  # NOTE: the filter "name", it's why we take the first one
410
421
  @previews_folder_entry = get_folder_entries(@access_key_self['root_file_id'], {name: @option_previews_folder}).first
411
- raise CliError, "Folder #{@option_previews_folder} does not exist on node. "\
422
+ raise Cli::Error, "Folder #{@option_previews_folder} does not exist on node. " \
412
423
  'Please create it in the storage root, or specify an alternate name.' if @previews_folder_entry.nil?
413
424
  else
414
- raise 'only local storage allowed in this mode' unless @access_key_self['storage']['type'].eql?('local')
425
+ assert(@access_key_self['storage']['type'].eql?('local')){'only local storage allowed in this mode'}
415
426
  @local_storage_root = @access_key_self['storage']['path']
416
427
  # TODO: option to override @local_storage_root='xxx'
417
428
  @local_storage_root = @local_storage_root[PVCL_LOCAL_STORAGE.length..-1] if @local_storage_root.start_with?(PVCL_LOCAL_STORAGE)
418
429
  # TODO: windows could have "C:" ?
419
- raise "not local storage: #{@local_storage_root}" unless @local_storage_root.start_with?('/')
420
- raise CliError, "Local storage root folder #{@local_storage_root} does not exist." unless File.directory?(@local_storage_root)
430
+ assert(@local_storage_root.start_with?('/')){"not local storage: #{@local_storage_root}"}
431
+ assert(File.directory?(@local_storage_root), exception_class: Cli::Error){"Local storage root folder #{@local_storage_root} does not exist."}
421
432
  @local_preview_folder = File.join(@local_storage_root, @option_previews_folder)
422
- raise CliError, "Folder #{@local_preview_folder} does not exist locally. "\
433
+ raise Cli::Error, "Folder #{@local_preview_folder} does not exist locally. " \
423
434
  'Please create it, or specify an alternate name.' unless File.directory?(@local_preview_folder)
424
435
  # protection to avoid clash of file id for two different access keys
425
436
  marker_file = File.join(@local_preview_folder, AK_MARKER_FILE)
426
437
  Log.log.debug{"marker file: #{marker_file}"}
427
438
  if File.exist?(marker_file)
428
439
  ak = File.read(marker_file).chomp
429
- raise "mismatch access key in #{marker_file}: contains #{ak}, using #{@access_key_self['id']}" unless @access_key_self['id'].eql?(ak)
440
+ assert(@access_key_self['id'].eql?(ak)){"mismatch access key in #{marker_file}: contains #{ak}, using #{@access_key_self['id']}"}
430
441
  else
431
442
  File.write(marker_file, @access_key_self['id'])
432
443
  end
@@ -450,9 +461,11 @@ module Aspera
450
461
  else
451
462
  @api_node.read("files/#{scan_id}")[:data]
452
463
  end
464
+ @filter_block = Aspera::Node.file_matcher_from_argument(options)
453
465
  scan_folder_files(folder_info, scan_path)
454
466
  return Main.result_status('scan finished')
455
467
  when :events, :trevents
468
+ @filter_block = Aspera::Node.file_matcher_from_argument(options)
456
469
  iteration_persistency = nil
457
470
  if options.get_option(:once_only, mandatory: true)
458
471
  iteration_persistency = PersistencyActionOnce.new(
@@ -470,15 +483,18 @@ module Aspera
470
483
  return Main.result_status("#{command} finished")
471
484
  when :check
472
485
  return Main.result_status('Tools validated')
473
- when :test
474
- format = options.get_next_argument('format', expected: Aspera::Preview::Generator::PREVIEW_FORMATS)
486
+ when :test, :show
475
487
  source = options.get_next_argument('source file')
476
- dest = preview_filename(format, options.get_option(:case))
477
- g = Aspera::Preview::Generator.new(@gen_options, source, dest, @tmp_folder, nil)
478
- raise "cannot find file type for #{source}" if g.conversion_type.nil?
479
- raise "out format #{format} not supported" unless g.supported?
488
+ format = options.get_next_argument('format', expected: Aspera::Preview::Generator::PREVIEW_FORMATS, default: :png)
489
+ generated_file_path = preview_filename(format, options.get_option(:base))
490
+ g = Aspera::Preview::Generator.new(source, generated_file_path, @gen_options, @tmp_folder, nil)
480
491
  g.generate
481
- return Main.result_status("generated: #{dest}")
492
+ if command.eql?(:show)
493
+ terminal_options = options.get_option(:query, default: {}).symbolize_keys
494
+ Log.log.debug{"preview: #{generated_file_path}"}
495
+ formatter.display_status(Aspera::Preview::Terminal.build(File.read(generated_file_path), **terminal_options))
496
+ end
497
+ return Main.result_status("generated: #{generated_file_path}")
482
498
  else
483
499
  raise 'error'
484
500
  end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore ascmd zmode zuid zgid fasping
3
4
  require 'aspera/cli/basic_auth_plugin'
4
- require 'aspera/cli/plugins/sync'
5
- require 'aspera/ascmd'
5
+ require 'aspera/cli/sync_actions'
6
6
  require 'aspera/fasp/transfer_spec'
7
+ require 'aspera/ascmd'
7
8
  require 'aspera/ssh'
8
9
  require 'aspera/nagios'
10
+ require 'aspera/log'
11
+ require 'aspera/assert'
9
12
  require 'tempfile'
10
13
  require 'open3'
11
14
 
@@ -13,17 +16,8 @@ module Aspera
13
16
  module Cli
14
17
  module Plugins
15
18
  # implement basic remote access with FASP/SSH
16
- class SyncSpecServer
17
- def initialize(transfer_spec)
18
- @transfer_spec = transfer_spec
19
- end
20
-
21
- def transfer_spec(direction, local_path, remote_path)
22
- return @transfer_spec
23
- end
24
- end
25
-
26
19
  class Server < Aspera::Cli::BasicAuthPlugin
20
+ include SyncActions
27
21
  SSH_SCHEME = 'ssh'
28
22
  LOCAL_SCHEME = 'local'
29
23
  HTTPS_SCHEME = 'https'
@@ -49,11 +43,59 @@ module Aspera
49
43
  end
50
44
  end
51
45
 
46
+ class << self
47
+ def application_name
48
+ 'HSTS Fasp/SSH'
49
+ end
50
+
51
+ def detect(address_or_url)
52
+ urls = if address_or_url.match?(%r{^[a-z]{1,6}://})
53
+ [address_or_url]
54
+ else
55
+ [
56
+ "ssh://#{address_or_url}:33001",
57
+ "ssh://#{address_or_url}:22"
58
+ ]
59
+ # wss not practical as it requires a token
60
+ end
61
+
62
+ urls.each do |base_url|
63
+ server_uri = URI.parse(base_url)
64
+ Log.log.debug{"URI=#{server_uri}, host=#{server_uri.hostname}, port=#{server_uri.port}, scheme=#{server_uri.scheme}"}
65
+ next unless server_uri.scheme.eql?(SSH_SCHEME)
66
+ begin
67
+ socket = TCPSocket.new(server_uri.hostname, server_uri.port)
68
+ socket.puts('SSH-2.0-Ascli_0.0')
69
+ version = socket.gets.chomp
70
+ if version.match?(/^SSH-2.0-/)
71
+ return {version: version.gsub(/^SSH-2.0-/, ''), url: base_url}
72
+ end
73
+ rescue StandardError => e
74
+ Log.log.debug{"detect error: #{e}"}
75
+ end
76
+ end
77
+ return nil
78
+ end
79
+
80
+ def wizard(object:, private_key_path: nil, pub_key_pem: nil)
81
+ options = object.options
82
+ return {
83
+ preset_value: {
84
+ url: options.get_option(:url, mandatory: true),
85
+ username: options.get_option(:username, mandatory: true),
86
+ password: options.get_option(:password, mandatory: true)
87
+ },
88
+ test_args: 'files br /'
89
+ }
90
+ end
91
+ end
92
+
52
93
  def initialize(env)
53
94
  super(env)
54
95
  options.declare(:ssh_keys, 'SSH key path list (Array or single)')
55
96
  options.declare(:passphrase, 'SSH private key passphrase')
56
97
  options.declare(:ssh_options, 'SSH options', types: Hash, default: {})
98
+ SyncActions.declare_options(options)
57
99
  options.parse_options!
58
100
  @ssh_opts = options.get_option(:ssh_options).symbolize_keys
59
101
  end
@@ -108,8 +150,9 @@ module Aspera
108
150
  end
109
151
  ssh_key_list = options.get_option(:ssh_keys)
110
152
  if !ssh_key_list.nil?
111
- raise 'Expecting single value or array for ssh_keys' unless ssh_key_list.is_a?(Array) || ssh_key_list.is_a?(String)
112
153
  ssh_key_list = [ssh_key_list] if ssh_key_list.is_a?(String)
154
+ assert_type(ssh_key_list, Array){'ssh_keys'}
155
+ assert(ssh_key_list.all?(String))
113
156
  ssh_key_list.map!{|p|File.expand_path(p)}
114
157
  Log.log.debug{"SSH keys=#{ssh_key_list}"}
115
158
  if !ssh_key_list.empty?
@@ -138,8 +181,8 @@ module Aspera
138
181
  Fasp::TransferSpec.action_to_direction(transfer_spec, command)
139
182
  return Main.result_transfer(transfer.start(transfer_spec))
140
183
  when :sync
141
- sync_plugin = Plugins::Sync.new(@agents, sync_spec: SyncSpecServer.new(transfer_spec))
142
- return sync_plugin.execute_action
184
+ # lets ignore the arguments provided by execute_sync_action, we just give the transfer spec
185
+ return execute_sync_action {transfer_spec}
143
186
  end
144
187
  end
145
188
 
@@ -185,16 +228,16 @@ module Aspera
185
228
  else
186
229
  nagios.add_critical('transfer', statuses.reject{|i|i.eql?(:success)}.first.to_s)
187
230
  end
188
- else raise 'ERROR'
231
+ else error_unexpected_value(command_nagios)
189
232
  end
190
233
  return nagios.result
191
234
  when *TRANSFER_COMMANDS
192
235
  return execute_transfer(command, server_transfer_spec)
193
236
  when *Aspera::AsCmd::OPERATIONS
194
- args = options.get_next_argument('ascmd command arguments', expected: :multiple, mandatory: false)
237
+ command_arguments = options.get_next_argument('ascmd command arguments', expected: :multiple, mandatory: false)
195
238
  ascmd = Aspera::AsCmd.new(ascmd_executor)
196
239
  begin
197
- result = ascmd.send(:execute_single, command, args)
240
+ result = ascmd.send(:execute_single, command, command_arguments)
198
241
  case command
199
242
  when :mkdir, :mv, :cp, :rm
200
243
  return Main.result_success
@@ -206,9 +249,9 @@ module Aspera
206
249
  return {type: :single_object, data: result.stringify_keys}
207
250
  end
208
251
  rescue Aspera::AsCmd::Error => e
209
- raise CliBadArgument, e.extended_message
252
+ raise Cli::BadArgument, e.extended_message
210
253
  end
211
- else raise 'internal error: unexpected action'
254
+ else error_unreachable_line
212
255
  end
213
256
  end # execute_action
214
257
  end # Server