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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
@@ -1,17 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'aspera/cli/
|
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(:
|
13
|
-
options.declare(:
|
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
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
result = call_ao('workflows_status',
|
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
|
-
#
|
149
|
-
|
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
|
191
|
+
call_params['synchronous'] = true if options.get_option(:synchronous, mandatory: true)
|
155
192
|
# expected result for synchro call ?
|
156
|
-
|
157
|
-
unless
|
193
|
+
result_location = options.get_option(:result)
|
194
|
+
unless result_location.nil?
|
158
195
|
result[:type] = :status
|
159
|
-
fields =
|
160
|
-
raise "Expects: work_step:result_name
|
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
|
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 '
|
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(:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
332
|
-
def scan_folder_files(top_entry,
|
333
|
-
|
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
|
-
|
336
|
-
scan_start = "#{scan_start}/" # unless scan_start.end_with?('/')
|
348
|
+
top_path = '/' + top_path.split('/').reject(&:empty?).join('/') + '/'
|
337
349
|
end
|
338
|
-
|
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
|
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 !
|
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
|
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
|
-
|
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
|
-
|
420
|
-
|
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
|
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
|
-
|
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
|
-
|
477
|
-
|
478
|
-
|
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
|
-
|
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/
|
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
|
-
|
142
|
-
return
|
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
|
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
|
-
|
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,
|
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
|
252
|
+
raise Cli::BadArgument, e.extended_message
|
210
253
|
end
|
211
|
-
else
|
254
|
+
else error_unreachable_line
|
212
255
|
end
|
213
256
|
end # execute_action
|
214
257
|
end # Server
|