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.
- 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
|