aspera-cli 4.14.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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