aspera-cli 4.12.0 → 4.14.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/CHANGELOG.md +45 -5
- data/CONTRIBUTING.md +113 -22
- data/README.md +1289 -754
- data/bin/ascli +3 -3
- data/examples/dascli +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +63 -74
- data/lib/aspera/ascmd.rb +5 -3
- data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
- data/lib/aspera/cli/extended_value.rb +24 -37
- data/lib/aspera/cli/formatter.rb +23 -25
- data/lib/aspera/cli/info.rb +2 -4
- data/lib/aspera/cli/main.rb +27 -27
- data/lib/aspera/cli/manager.rb +143 -120
- data/lib/aspera/cli/plugin.rb +88 -43
- data/lib/aspera/cli/plugins/alee.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +235 -104
- data/lib/aspera/cli/plugins/ats.rb +16 -18
- data/lib/aspera/cli/plugins/bss.rb +3 -3
- data/lib/aspera/cli/plugins/config.rb +190 -373
- data/lib/aspera/cli/plugins/console.rb +4 -6
- data/lib/aspera/cli/plugins/cos.rb +12 -13
- data/lib/aspera/cli/plugins/faspex.rb +21 -21
- data/lib/aspera/cli/plugins/faspex5.rb +399 -150
- data/lib/aspera/cli/plugins/node.rb +260 -174
- data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
- data/lib/aspera/cli/plugins/preview.rb +40 -62
- data/lib/aspera/cli/plugins/server.rb +33 -16
- data/lib/aspera/cli/plugins/shares.rb +24 -33
- data/lib/aspera/cli/plugins/sync.rb +6 -6
- data/lib/aspera/cli/transfer_agent.rb +47 -30
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +9 -7
- data/lib/aspera/command_line_builder.rb +2 -1
- data/lib/aspera/cos_node.rb +1 -1
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +7 -3
- data/lib/aspera/fasp/agent_connect.rb +6 -1
- data/lib/aspera/fasp/agent_direct.rb +17 -17
- data/lib/aspera/fasp/agent_httpgw.rb +138 -60
- data/lib/aspera/fasp/agent_node.rb +14 -4
- data/lib/aspera/fasp/agent_trsdk.rb +2 -0
- data/lib/aspera/fasp/error_info.rb +2 -0
- data/lib/aspera/fasp/installation.rb +19 -19
- data/lib/aspera/fasp/parameters.rb +29 -20
- data/lib/aspera/fasp/parameters.yaml +5 -2
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +8 -5
- data/lib/aspera/fasp/uri.rb +23 -21
- data/lib/aspera/faspex_gw.rb +1 -0
- data/lib/aspera/faspex_postproc.rb +3 -3
- data/lib/aspera/hash_ext.rb +12 -2
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node.rb +73 -84
- data/lib/aspera/oauth.rb +4 -3
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/preview/file_types.rb +8 -6
- data/lib/aspera/preview/generator.rb +23 -11
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +80 -0
- data/lib/aspera/preview/utils.rb +11 -11
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +42 -4
- data/lib/aspera/rest_call_error.rb +3 -1
- data/lib/aspera/secret_hider.rb +10 -5
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/sync.rb +41 -33
- data/lib/aspera/web_server_simple.rb +22 -18
- data.tar.gz.sig +0 -0
- metadata +40 -48
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -179
- data/examples/aoc.rb +0 -30
- data/examples/faspex4.rb +0 -94
- data/examples/node.rb +0 -96
- data/examples/server.rb +0 -93
- data/lib/aspera/data/7 +0 -0
@@ -7,6 +7,7 @@ require 'aspera/hash_ext'
|
|
7
7
|
require 'aspera/id_generator'
|
8
8
|
require 'aspera/node'
|
9
9
|
require 'aspera/aoc'
|
10
|
+
require 'aspera/sync'
|
10
11
|
require 'aspera/fasp/transfer_spec'
|
11
12
|
require 'base64'
|
12
13
|
require 'zlib'
|
@@ -14,6 +15,51 @@ require 'zlib'
|
|
14
15
|
module Aspera
|
15
16
|
module Cli
|
16
17
|
module Plugins
|
18
|
+
class SyncSpecGen3
|
19
|
+
def initialize(api_node)
|
20
|
+
@api_node = api_node
|
21
|
+
end
|
22
|
+
|
23
|
+
def transfer_spec(direction, local_path, remote_path)
|
24
|
+
# empty transfer spec for authorization request
|
25
|
+
direction_sym = direction.to_sym
|
26
|
+
request_transfer_spec = {
|
27
|
+
type: Aspera::Sync::DIRECTION_TO_REQUEST_TYPE[direction_sym],
|
28
|
+
paths: {
|
29
|
+
source: remote_path,
|
30
|
+
destination: local_path
|
31
|
+
}
|
32
|
+
}
|
33
|
+
# add fixed parameters if any (for COS)
|
34
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
35
|
+
# prepare payload for single request
|
36
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
37
|
+
# only one request, so only one answer
|
38
|
+
transfer_spec = @api_node.create('files/sync_setup', setup_payload)[:data]['transfer_specs'].first['transfer_spec']
|
39
|
+
# API returns null tag... but async does not like it
|
40
|
+
transfer_spec.delete_if{ |_k, v| v.nil? }
|
41
|
+
# delete this part, as the returned value contains only destination, and not sources
|
42
|
+
# transfer_spec.delete('paths') if command.eql?(:upload)
|
43
|
+
Log.dump(:ts, transfer_spec)
|
44
|
+
return transfer_spec
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class SyncSpecGen4
|
49
|
+
def initialize(api_node, top_file_id)
|
50
|
+
@api_node = api_node
|
51
|
+
@top_file_id = top_file_id
|
52
|
+
end
|
53
|
+
|
54
|
+
def transfer_spec(direction, local_path, remote_path)
|
55
|
+
# remote is specified by option to_folder
|
56
|
+
apifid = @api_node.resolve_api_fid(@top_file_id, remote_path)
|
57
|
+
transfer_spec = apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)
|
58
|
+
Log.dump(:ts, transfer_spec)
|
59
|
+
return transfer_spec
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
17
63
|
class Node < Aspera::Cli::BasicAuthPlugin
|
18
64
|
class << self
|
19
65
|
def detect(base_url)
|
@@ -26,15 +72,10 @@ module Aspera
|
|
26
72
|
end
|
27
73
|
|
28
74
|
def register_node_options(env)
|
29
|
-
env[:options].
|
30
|
-
env[:options].
|
31
|
-
env[:options].
|
32
|
-
env[:options].
|
33
|
-
env[:options].add_opt_list(:token_type, %i[aspera basic hybrid], 'Type of token used for transfers')
|
34
|
-
env[:options].add_opt_boolean(:default_ports, 'use standard FASP ports or get from node api (gen4)')
|
35
|
-
env[:options].set_option(:asperabrowserurl, 'https://asperabrowser.mybluemix.net')
|
36
|
-
env[:options].set_option(:token_type, :aspera)
|
37
|
-
env[:options].set_option(:default_ports, :yes)
|
75
|
+
env[:options].declare(:validator, 'Identifier of validator (optional for central)')
|
76
|
+
env[:options].declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
|
77
|
+
env[:options].declare(:sync_name, 'Sync name')
|
78
|
+
env[:options].declare(:default_ports, 'Use standard FASP ports or get from node api (gen4)', values: :bool, default: :yes)
|
38
79
|
env[:options].parse_options!
|
39
80
|
Aspera::Node.use_standard_ports = env[:options].get_option(:default_ports)
|
40
81
|
end
|
@@ -53,7 +94,7 @@ module Aspera
|
|
53
94
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
54
95
|
|
55
96
|
# actions in execute_command_gen3
|
56
|
-
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download]
|
97
|
+
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download sync]
|
57
98
|
|
58
99
|
BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
|
59
100
|
|
@@ -71,7 +112,7 @@ module Aspera
|
|
71
112
|
NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
|
72
113
|
|
73
114
|
# commands for execute_command_gen4
|
74
|
-
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download
|
115
|
+
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download show modify permission thumbnail v3].concat(NODE4_READ_ACTIONS).freeze
|
75
116
|
|
76
117
|
COMMANDS_COS = %i[upload download info access_key api_details transfer].freeze
|
77
118
|
COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
|
@@ -85,23 +126,23 @@ module Aspera
|
|
85
126
|
if env.key?(:node_api)
|
86
127
|
# this can be Aspera::Node or Aspera::Rest (shares)
|
87
128
|
env[:node_api]
|
88
|
-
elsif options.get_option(:password,
|
129
|
+
elsif options.get_option(:password, mandatory: true).start_with?('Bearer ')
|
89
130
|
# info is provided like node_info of aoc
|
90
131
|
Aspera::Node.new(params: {
|
91
|
-
base_url: options.get_option(:url,
|
132
|
+
base_url: options.get_option(:url, mandatory: true),
|
92
133
|
headers: {
|
93
|
-
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username,
|
94
|
-
'Authorization' => options.get_option(:password,
|
134
|
+
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, mandatory: true),
|
135
|
+
'Authorization' => options.get_option(:password, mandatory: true)
|
95
136
|
}
|
96
137
|
})
|
97
138
|
else
|
98
139
|
# this is normal case
|
99
140
|
Aspera::Node.new(params: {
|
100
|
-
base_url: options.get_option(:url,
|
141
|
+
base_url: options.get_option(:url, mandatory: true),
|
101
142
|
auth: {
|
102
143
|
type: :basic,
|
103
|
-
username: options.get_option(:username,
|
104
|
-
password: options.get_option(:password,
|
144
|
+
username: options.get_option(:username, mandatory: true),
|
145
|
+
password: options.get_option(:password, mandatory: true)
|
105
146
|
}})
|
106
147
|
end
|
107
148
|
end
|
@@ -145,7 +186,7 @@ module Aspera
|
|
145
186
|
# translates paths results into CLI result, and removes prefix
|
146
187
|
def c_result_translate_rem_prefix(response, type, success_msg, path_prefix)
|
147
188
|
errors = []
|
148
|
-
|
189
|
+
final_result = { data: [], type: :object_list, fields: [type, 'result']}
|
149
190
|
JSON.parse(response[:http].body)['paths'].each do |p|
|
150
191
|
result = success_msg
|
151
192
|
if p.key?('error')
|
@@ -153,13 +194,13 @@ module Aspera
|
|
153
194
|
result = 'ERROR: ' + p['error']['user_message']
|
154
195
|
errors.push([p['path'], p['error']['user_message']])
|
155
196
|
end
|
156
|
-
|
197
|
+
final_result[:data].push({type => p['path'], 'result' => result})
|
157
198
|
end
|
158
199
|
# one error make all fail
|
159
200
|
unless errors.empty?
|
160
201
|
raise errors.map{|i|"#{i.first}: #{i.last}"}.join(', ')
|
161
202
|
end
|
162
|
-
return c_result_remove_prefix_path(
|
203
|
+
return c_result_remove_prefix_path(final_result, type, path_prefix)
|
163
204
|
end
|
164
205
|
|
165
206
|
# get path arguments from command line, and add prefix
|
@@ -181,13 +222,13 @@ module Aspera
|
|
181
222
|
when :search
|
182
223
|
search_root = get_next_arg_add_prefix(prefix_path, 'search root')
|
183
224
|
parameters = {'path' => search_root}
|
184
|
-
other_options =
|
225
|
+
other_options = value_or_query(allowed_types: Hash)
|
185
226
|
parameters.merge!(other_options) unless other_options.nil?
|
186
227
|
resp = @api_node.create('files/search', parameters)
|
187
228
|
result = { type: :object_list, data: resp[:data]['items']}
|
188
229
|
return Main.result_empty if result[:data].empty?
|
189
230
|
result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
|
190
|
-
formatter.
|
231
|
+
formatter.display_item_count(resp[:data]['item_count'], resp[:data]['total_count'])
|
191
232
|
formatter.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
|
192
233
|
return c_result_remove_prefix_path(result, 'path', prefix_path)
|
193
234
|
when :space
|
@@ -228,44 +269,33 @@ module Aspera
|
|
228
269
|
case send_result['self']['type']
|
229
270
|
when 'directory', 'container' # directory: node, container: shares
|
230
271
|
result = { data: send_result['items'], type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
231
|
-
formatter.
|
272
|
+
formatter.display_item_count(send_result['item_count'], send_result['total_count'])
|
232
273
|
else # 'file','symbolic_link'
|
233
274
|
result = { data: send_result['self'], type: :single_object}
|
234
275
|
# result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
235
276
|
# raise "unknown type: #{send_result['self']['type']}"
|
236
277
|
end
|
237
278
|
return c_result_remove_prefix_path(result, 'path', prefix_path)
|
279
|
+
when :sync
|
280
|
+
node_sync = SyncSpecGen3.new(@api_node)
|
281
|
+
return Plugins::Sync.new(@agents, sync_spec: node_sync).execute_action
|
238
282
|
when :upload, :download
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
# set requested paths depending on direction
|
247
|
-
request_transfer_spec[:paths] = command.eql?(:download) ? transfer.ts_source_paths : [{ destination: transfer.destination_folder('send') }]
|
248
|
-
# add fixed parameters if any (for COS)
|
249
|
-
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
250
|
-
# prepare payload for single request
|
251
|
-
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
252
|
-
# only one request, so only one answer
|
253
|
-
transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
|
254
|
-
# delete this part, as the returned value contains only destination, and not sources
|
255
|
-
transfer_spec.delete('paths') if command.eql?(:upload)
|
256
|
-
when :basic
|
257
|
-
raise 'shall have auth' unless @api_node.params[:auth].is_a?(Hash)
|
258
|
-
raise 'shall be basic auth' unless @api_node.params[:auth][:type].eql?(:basic)
|
259
|
-
transfer_spec = {}.merge(Aspera::Fasp::TransferSpec::AK_TSPEC_BASE)
|
260
|
-
transfer_spec['remote_host'] = URI.parse(@api_node.params[:base_url]).host
|
261
|
-
Fasp::TransferSpec.action_to_direction(transfer_spec, command)
|
262
|
-
transfer_spec['destination_root'] = transfer.destination_folder(transfer_spec['direction'])
|
263
|
-
@api_node.add_tspec_info(transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
264
|
-
else raise "ERROR: token_type #{tt}"
|
265
|
-
end
|
266
|
-
if %i[basic hybrid].include?(token_type)
|
267
|
-
@api_node.ts_basic_token(transfer_spec)
|
283
|
+
# empty transfer spec for authorization request
|
284
|
+
request_transfer_spec = {}
|
285
|
+
# set requested paths depending on direction
|
286
|
+
request_transfer_spec[:paths] = if command.eql?(:download)
|
287
|
+
transfer.ts_source_paths
|
288
|
+
else
|
289
|
+
[{ destination: transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND) }]
|
268
290
|
end
|
291
|
+
# add fixed parameters if any (for COS)
|
292
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
293
|
+
# prepare payload for single request
|
294
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
295
|
+
# only one request, so only one answer
|
296
|
+
transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
|
297
|
+
# delete this part, as the returned value contains only destination, and not sources
|
298
|
+
transfer_spec.delete('paths') if command.eql?(:upload)
|
269
299
|
return Main.result_transfer(transfer.start(transfer_spec))
|
270
300
|
end
|
271
301
|
raise 'INTERNAL ERROR'
|
@@ -314,7 +344,7 @@ module Aspera
|
|
314
344
|
end
|
315
345
|
return nagios.result
|
316
346
|
when :events
|
317
|
-
events = @api_node.read('events',
|
347
|
+
events = @api_node.read('events', query_read_delete)[:data]
|
318
348
|
return { type: :object_list, data: events}
|
319
349
|
when :info
|
320
350
|
nd_info = @api_node.read('info')[:data]
|
@@ -331,57 +361,16 @@ module Aspera
|
|
331
361
|
end
|
332
362
|
end
|
333
363
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
end
|
342
|
-
case command_node_file
|
343
|
-
when :show
|
344
|
-
items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
345
|
-
return {type: :single_object, data: items}
|
346
|
-
when :modify
|
347
|
-
update_param = options.get_next_argument('update data', type: Hash)
|
348
|
-
apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
|
349
|
-
return Main.result_status('Done')
|
350
|
-
when :permission
|
351
|
-
command_perm = options.get_next_command(%i[list create delete])
|
352
|
-
case command_perm
|
353
|
-
when :list
|
354
|
-
# generic options : TODO: as arg ? option_url_query
|
355
|
-
list_options ||= {'include' => [Rest::ARRAY_PARAMS, 'access_level', 'permission_count']}
|
356
|
-
# add which one to get
|
357
|
-
list_options['file_id'] = apifid[:file_id]
|
358
|
-
list_options['inherited'] ||= false
|
359
|
-
items = apifid[:api].read('permissions', list_options)[:data]
|
360
|
-
return {type: :object_list, data: items}
|
361
|
-
when :delete
|
362
|
-
perm_id = instance_identifier
|
363
|
-
return do_bulk_operation(perm_id, 'deleted') do |one_id|
|
364
|
-
# TODO: notify event ?
|
365
|
-
apifid[:api].delete("permissions/#{perm_id}")
|
366
|
-
{'id' => one_id}
|
367
|
-
end
|
368
|
-
when :create
|
369
|
-
create_param = options.get_next_argument('creation data', type: Hash)
|
370
|
-
raise 'no file_id' if create_param.key?('file_id')
|
371
|
-
create_param['file_id'] = apifid[:file_id]
|
372
|
-
create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
373
|
-
# add application specific tags (AoC)
|
374
|
-
the_app = apifid[:api].app_info
|
375
|
-
the_app[:api].permissions_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
|
376
|
-
# create permission
|
377
|
-
created_data = apifid[:api].create('permissions', create_param)[:data]
|
378
|
-
# bnotify application of creation
|
379
|
-
the_app[:api].permissions_create_event(created_data: created_data, app_info: the_app) unless the_app.nil?
|
380
|
-
return { type: :single_object, data: created_data}
|
381
|
-
else raise "internal error:shall not reach here (#{command_perm})"
|
382
|
-
end
|
383
|
-
else raise "internal error:shall not reach here (#{command_node_file})"
|
364
|
+
# @return [Hash] api and main file id for given path or id
|
365
|
+
# Allows to specify a file by its path or by its id on the node
|
366
|
+
def apifid_from_next_arg(top_file_id)
|
367
|
+
file_path = instance_identifier(description: 'path or id') do |attribute, value|
|
368
|
+
raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
369
|
+
# directly return result for method
|
370
|
+
return {api: @api_node, file_id: value}
|
384
371
|
end
|
372
|
+
# there was no selector, so it is a path
|
373
|
+
return @api_node.resolve_api_fid(top_file_id, file_path)
|
385
374
|
end
|
386
375
|
|
387
376
|
def execute_command_gen4(command_repo, top_file_id)
|
@@ -415,16 +404,16 @@ module Aspera
|
|
415
404
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
416
405
|
file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
417
406
|
if file_info['type'].eql?('folder')
|
418
|
-
result = apifid[:api].read("files/#{apifid[:file_id]}/files",
|
407
|
+
result = apifid[:api].read("files/#{apifid[:file_id]}/files", old_query_read_delete)
|
419
408
|
items = result[:data]
|
420
|
-
formatter.
|
409
|
+
formatter.display_item_count(result[:data].length, result[:http]['X-Total-Count'])
|
421
410
|
else
|
422
411
|
items = [file_info]
|
423
412
|
end
|
424
413
|
return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
|
425
414
|
when :find
|
426
415
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
427
|
-
test_block = Aspera::Node.file_matcher(
|
416
|
+
test_block = Aspera::Node.file_matcher(value_or_query(allowed_types: String))
|
428
417
|
return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
|
429
418
|
when :mkdir
|
430
419
|
containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
|
@@ -434,10 +423,10 @@ module Aspera
|
|
434
423
|
return Main.result_status("created: #{result['name']} (id=#{result['id']})")
|
435
424
|
when :rename
|
436
425
|
file_path = options.get_next_argument('source path')
|
437
|
-
newname = options.get_next_argument('new name')
|
438
426
|
apifid = @api_node.resolve_api_fid(top_file_id, file_path)
|
427
|
+
newname = options.get_next_argument('new name')
|
439
428
|
result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
|
440
|
-
return Main.result_status("renamed
|
429
|
+
return Main.result_status("renamed to #{newname}")
|
441
430
|
when :delete
|
442
431
|
return do_bulk_operation(options.get_next_argument('path'), 'deleted', id_result: 'path') do |l_path|
|
443
432
|
raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
|
@@ -446,12 +435,8 @@ module Aspera
|
|
446
435
|
{'path' => l_path}
|
447
436
|
end
|
448
437
|
when :sync
|
449
|
-
|
450
|
-
|
451
|
-
transfer_spec = apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)
|
452
|
-
Log.dump(:ts, transfer_spec)
|
453
|
-
sync_plugin = Plugins::Sync.new(@agents, transfer_spec: transfer_spec)
|
454
|
-
return sync_plugin.execute_action
|
438
|
+
node_sync = SyncSpecGen4.new(@api_node, top_file_id)
|
439
|
+
return Plugins::Sync.new(@agents, sync_spec: node_sync).execute_action
|
455
440
|
when :upload
|
456
441
|
apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
|
457
442
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)))
|
@@ -498,9 +483,60 @@ module Aspera
|
|
498
483
|
subpath: "files/#{apifid[:file_id]}/content",
|
499
484
|
save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE), file_name))
|
500
485
|
return Main.result_status("downloaded: #{file_name}")
|
501
|
-
when :
|
502
|
-
|
503
|
-
|
486
|
+
when :show
|
487
|
+
apifid = apifid_from_next_arg(top_file_id)
|
488
|
+
items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
489
|
+
return {type: :single_object, data: items}
|
490
|
+
when :modify
|
491
|
+
apifid = apifid_from_next_arg(top_file_id)
|
492
|
+
update_param = options.get_next_argument('update data', type: Hash)
|
493
|
+
apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
|
494
|
+
return Main.result_status('Done')
|
495
|
+
when :thumbnail
|
496
|
+
apifid = apifid_from_next_arg(top_file_id)
|
497
|
+
result = apifid[:api].call(
|
498
|
+
operation: 'GET',
|
499
|
+
subpath: "files/#{apifid[:file_id]}/preview",
|
500
|
+
headers: {'Accept' => 'image/png'}
|
501
|
+
)
|
502
|
+
require 'aspera/preview/terminal'
|
503
|
+
return Main.result_status(Preview::Terminal.build(result[:http].body, reserved_lines: 3))
|
504
|
+
when :permission
|
505
|
+
apifid = apifid_from_next_arg(top_file_id)
|
506
|
+
command_perm = options.get_next_command(%i[list create delete])
|
507
|
+
case command_perm
|
508
|
+
when :list
|
509
|
+
# generic options : TODO: as arg ? query_read_delete
|
510
|
+
list_options ||= {'include' => Rest.array_params(%w[access_level permission_count])}
|
511
|
+
# add which one to get
|
512
|
+
list_options['file_id'] = apifid[:file_id]
|
513
|
+
list_options['inherited'] ||= false
|
514
|
+
items = apifid[:api].read('permissions', list_options)[:data]
|
515
|
+
return {type: :object_list, data: items}
|
516
|
+
when :delete
|
517
|
+
perm_id = instance_identifier
|
518
|
+
return do_bulk_operation(perm_id, 'deleted') do |one_id|
|
519
|
+
# TODO: notify event ?
|
520
|
+
apifid[:api].delete("permissions/#{perm_id}")
|
521
|
+
# notify application of deletion
|
522
|
+
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app, types: ['permission.deleted']) unless the_app.nil?
|
523
|
+
{'id' => one_id}
|
524
|
+
end
|
525
|
+
when :create
|
526
|
+
create_param = options.get_next_argument('creation data', type: Hash)
|
527
|
+
raise 'no file_id' if create_param.key?('file_id')
|
528
|
+
create_param['file_id'] = apifid[:file_id]
|
529
|
+
create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
530
|
+
# add application specific tags (AoC)
|
531
|
+
the_app = apifid[:api].app_info
|
532
|
+
the_app[:api].permissions_set_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
|
533
|
+
# create permission
|
534
|
+
created_data = apifid[:api].create('permissions', create_param)[:data]
|
535
|
+
# notify application of creation
|
536
|
+
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app) unless the_app.nil?
|
537
|
+
return { type: :single_object, data: created_data}
|
538
|
+
else raise "internal error:shall not reach here (#{command_perm})"
|
539
|
+
end
|
504
540
|
else raise "INTERNAL ERROR: no case for #{command_repo}"
|
505
541
|
end # command_repo
|
506
542
|
# raise 'INTERNAL ERROR: missing return'
|
@@ -510,65 +546,65 @@ module Aspera
|
|
510
546
|
def execute_async
|
511
547
|
command = options.get_next_command(%i[list delete files show counters bandwidth])
|
512
548
|
unless command.eql?(:list)
|
513
|
-
|
514
|
-
if
|
515
|
-
|
516
|
-
if
|
517
|
-
|
549
|
+
async_name = options.get_option(:sync_name)
|
550
|
+
if async_name.nil?
|
551
|
+
async_id = instance_identifier
|
552
|
+
if async_id.eql?(VAL_ALL) && %i[show delete].include?(command)
|
553
|
+
async_ids = @api_node.read('async/list')[:data]['sync_ids']
|
518
554
|
else
|
519
|
-
Integer(
|
520
|
-
|
555
|
+
Integer(async_id) # must be integer
|
556
|
+
async_ids = [async_id]
|
521
557
|
end
|
522
558
|
else
|
523
|
-
|
524
|
-
summaries = @api_node.create('async/summary', {'syncs' =>
|
525
|
-
selected = summaries.find{|s|s['name'].eql?(
|
526
|
-
raise "no such sync: #{
|
527
|
-
|
528
|
-
|
559
|
+
async_ids = @api_node.read('async/list')[:data]['sync_ids']
|
560
|
+
summaries = @api_node.create('async/summary', {'syncs' => async_ids})[:data]['sync_summaries']
|
561
|
+
selected = summaries.find{|s|s['name'].eql?(async_name)}
|
562
|
+
raise "no such sync: #{async_name}" if selected.nil?
|
563
|
+
async_id = selected['snid']
|
564
|
+
async_ids = [async_id]
|
529
565
|
end
|
530
|
-
|
566
|
+
post_data = {'syncs' => async_ids}
|
531
567
|
end
|
532
568
|
case command
|
533
569
|
when :list
|
534
570
|
resp = @api_node.read('async/list')[:data]['sync_ids']
|
535
571
|
return { type: :value_list, data: resp, name: 'id' }
|
536
572
|
when :show
|
537
|
-
resp = @api_node.create('async/summary',
|
573
|
+
resp = @api_node.create('async/summary', post_data)[:data]['sync_summaries']
|
538
574
|
return Main.result_empty if resp.empty?
|
539
|
-
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if
|
575
|
+
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if async_id.eql?(VAL_ALL)
|
540
576
|
return { type: :single_object, data: resp.first }
|
541
577
|
when :delete
|
542
|
-
resp = @api_node.create('async/delete',
|
578
|
+
resp = @api_node.create('async/delete', post_data)[:data]
|
543
579
|
return { type: :single_object, data: resp, name: 'id' }
|
544
580
|
when :bandwidth
|
545
|
-
|
546
|
-
resp = @api_node.create('async/bandwidth',
|
581
|
+
post_data['seconds'] = 100 # TODO: as parameter with --value
|
582
|
+
resp = @api_node.create('async/bandwidth', post_data)[:data]
|
547
583
|
data = resp['bandwidth_data']
|
548
584
|
return Main.result_empty if data.empty?
|
549
|
-
data = data.first[
|
585
|
+
data = data.first[async_id]['data']
|
550
586
|
return { type: :object_list, data: data, name: 'id' }
|
551
587
|
when :files
|
552
588
|
# count int
|
553
589
|
# filename str
|
554
590
|
# skip int
|
555
591
|
# status int
|
556
|
-
filter =
|
557
|
-
|
558
|
-
resp = @api_node.create('async/files',
|
592
|
+
filter = value_or_query(allowed_types: Hash)
|
593
|
+
post_data.merge!(filter) unless filter.nil?
|
594
|
+
resp = @api_node.create('async/files', post_data)[:data]
|
559
595
|
data = resp['sync_files']
|
560
|
-
data = data.first[
|
596
|
+
data = data.first[async_id] unless data.empty?
|
561
597
|
iteration_data = []
|
562
598
|
skip_ids_persistency = nil
|
563
|
-
if options.get_option(:once_only,
|
599
|
+
if options.get_option(:once_only, mandatory: true)
|
564
600
|
skip_ids_persistency = PersistencyActionOnce.new(
|
565
601
|
manager: @agents[:persistency],
|
566
602
|
data: iteration_data,
|
567
603
|
id: IdGenerator.from_list([
|
568
604
|
'sync_files',
|
569
|
-
options.get_option(:url,
|
570
|
-
options.get_option(:username,
|
571
|
-
|
605
|
+
options.get_option(:url, mandatory: true),
|
606
|
+
options.get_option(:username, mandatory: true),
|
607
|
+
async_id]))
|
572
608
|
unless iteration_data.first.nil?
|
573
609
|
data.select!{|l| l['fnid'].to_i > iteration_data.first}
|
574
610
|
end
|
@@ -578,7 +614,7 @@ module Aspera
|
|
578
614
|
skip_ids_persistency&.save
|
579
615
|
return { type: :object_list, data: data, name: 'id' }
|
580
616
|
when :counters
|
581
|
-
resp = @api_node.create('async/counters',
|
617
|
+
resp = @api_node.create('async/counters', post_data)[:data]['sync_counters'].first[async_id].last
|
582
618
|
return Main.result_empty if resp.nil?
|
583
619
|
return { type: :single_object, data: resp }
|
584
620
|
end
|
@@ -586,7 +622,7 @@ module Aspera
|
|
586
622
|
|
587
623
|
ACTIONS = %i[
|
588
624
|
async
|
589
|
-
|
625
|
+
ssync
|
590
626
|
stream
|
591
627
|
transfer
|
592
628
|
service
|
@@ -599,18 +635,21 @@ module Aspera
|
|
599
635
|
command ||= options.get_next_command(ACTIONS)
|
600
636
|
case command
|
601
637
|
when *COMMON_ACTIONS then return execute_simple_common(command, prefix_path)
|
602
|
-
when :async then return execute_async
|
603
|
-
when :
|
604
|
-
# newer
|
605
|
-
sync_command = options.get_next_command(%i[bandwidth counters files
|
638
|
+
when :async then return execute_async # former API
|
639
|
+
when :ssync
|
640
|
+
# newer API
|
641
|
+
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
|
606
642
|
case sync_command
|
607
643
|
when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids')
|
608
644
|
else
|
609
|
-
parameters = options.get_option(:value)
|
610
645
|
asyncs_id = instance_identifier
|
646
|
+
parameters = nil
|
611
647
|
if %i[start stop].include?(sync_command)
|
612
648
|
@api_node.create("asyncs/#{asyncs_id}/#{sync_command}", parameters)
|
613
|
-
return Main.result_status('
|
649
|
+
return Main.result_status('Done')
|
650
|
+
end
|
651
|
+
if %i[bandwidth counters files].include?(sync_command)
|
652
|
+
parameters = value_or_query(allowed_types: Hash, mandatory: false) || {}
|
614
653
|
end
|
615
654
|
return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
|
616
655
|
end
|
@@ -618,16 +657,16 @@ module Aspera
|
|
618
657
|
command = options.get_next_command(%i[list create show modify cancel])
|
619
658
|
case command
|
620
659
|
when :list
|
621
|
-
resp = @api_node.read('ops/transfers',
|
660
|
+
resp = @api_node.read('ops/transfers', old_query_read_delete)
|
622
661
|
return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
|
623
662
|
when :create
|
624
|
-
resp = @api_node.create('streams',
|
663
|
+
resp = @api_node.create('streams', value_create_modify(command: command, type: Hash))
|
625
664
|
return { type: :single_object, data: resp[:data] }
|
626
665
|
when :show
|
627
666
|
resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
|
628
667
|
return { type: :other_struct, data: resp[:data] }
|
629
668
|
when :modify
|
630
|
-
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}",
|
669
|
+
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command, type: Hash))
|
631
670
|
return { type: :other_struct, data: resp[:data] }
|
632
671
|
when :cancel
|
633
672
|
resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
|
@@ -636,21 +675,21 @@ module Aspera
|
|
636
675
|
raise 'error'
|
637
676
|
end
|
638
677
|
when :transfer
|
639
|
-
command = options.get_next_command(%i[list cancel show])
|
678
|
+
command = options.get_next_command(%i[list cancel show modify bandwidth_average])
|
640
679
|
res_class_path = 'ops/transfers'
|
641
|
-
if %i[cancel show].include?(command)
|
680
|
+
if %i[cancel show modify].include?(command)
|
642
681
|
one_res_id = instance_identifier
|
643
682
|
one_res_path = "#{res_class_path}/#{one_res_id}"
|
644
683
|
end
|
645
684
|
case command
|
646
685
|
when :list
|
647
686
|
# could use ? subpath: 'transfers'
|
648
|
-
query =
|
687
|
+
query = query_read_delete
|
649
688
|
raise 'Query must be a Hash' unless query.nil? || query.is_a?(Hash)
|
650
|
-
|
689
|
+
transfers_data = @api_node.read(res_class_path, query)[:data]
|
651
690
|
return {
|
652
691
|
type: :object_list,
|
653
|
-
data:
|
692
|
+
data: transfers_data,
|
654
693
|
fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
|
655
694
|
}
|
656
695
|
when :cancel
|
@@ -659,13 +698,61 @@ module Aspera
|
|
659
698
|
when :show
|
660
699
|
resp = @api_node.read(one_res_path)
|
661
700
|
return { type: :other_struct, data: resp[:data] }
|
701
|
+
when :modify
|
702
|
+
resp = @api_node.update(one_res_path, options.get_next_argument('update value', type: Hash))
|
703
|
+
return { type: :other_struct, data: resp[:data] }
|
704
|
+
when :bandwidth_average
|
705
|
+
transfers_data = @api_node.read(res_class_path, query)[:data]
|
706
|
+
# collect all key dates
|
707
|
+
bandwidth_period = {}
|
708
|
+
dir_info = %i[avg_kbps sessions].freeze
|
709
|
+
transfers_data.each do |transfer|
|
710
|
+
session = transfer
|
711
|
+
# transfer['sessions'].each do |session|
|
712
|
+
next if session['avg_rate_kbps'].zero?
|
713
|
+
bandwidth_period[session['start_time_usec']] = 0
|
714
|
+
bandwidth_period[session['end_time_usec']] = 0
|
715
|
+
# end
|
716
|
+
end
|
717
|
+
result = []
|
718
|
+
# all dates sorted numerically
|
719
|
+
all_dates = bandwidth_period.keys.sort
|
720
|
+
all_dates.each_with_index do |start_date, index|
|
721
|
+
end_date = all_dates[index + 1]
|
722
|
+
# do not process last one
|
723
|
+
break if end_date.nil?
|
724
|
+
# init data for this period
|
725
|
+
period_bandwidth = Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).each_with_object({}) do |direction, h|
|
726
|
+
h[direction] = dir_info.each_with_object({}) do |k2, h2|
|
727
|
+
h2[k2] = 0
|
728
|
+
end
|
729
|
+
end
|
730
|
+
# find all transfers that were active at this time
|
731
|
+
transfers_data.each do |transfer|
|
732
|
+
session = transfer
|
733
|
+
# transfer['sessions'].each do |session|
|
734
|
+
# skip if not information for this period
|
735
|
+
next if session['avg_rate_kbps'].zero?
|
736
|
+
# skip if not in this period
|
737
|
+
next if session['start_time_usec'] >= end_date || session['end_time_usec'] <= start_date
|
738
|
+
info = period_bandwidth[transfer['start_spec']['direction'].to_sym]
|
739
|
+
info[:avg_kbps] += session['avg_rate_kbps']
|
740
|
+
info[:sessions] += 1
|
741
|
+
# end
|
742
|
+
end
|
743
|
+
next if Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).all? do |dir|
|
744
|
+
period_bandwidth[dir][:sessions].zero?
|
745
|
+
end
|
746
|
+
result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
|
747
|
+
end
|
748
|
+
return { type: :object_list, data: result }
|
662
749
|
else
|
663
750
|
raise 'error'
|
664
751
|
end
|
665
752
|
when :service
|
666
753
|
command = options.get_next_command(%i[list create delete])
|
667
754
|
if [:delete].include?(command)
|
668
|
-
|
755
|
+
service_id = instance_identifier
|
669
756
|
end
|
670
757
|
case command
|
671
758
|
when :list
|
@@ -677,8 +764,8 @@ module Aspera
|
|
677
764
|
resp = @api_node.create('rund/services', params)
|
678
765
|
return Main.result_status("#{resp[:data]['id']} created")
|
679
766
|
when :delete
|
680
|
-
@api_node.delete("rund/services/#{
|
681
|
-
return Main.result_status("#{
|
767
|
+
@api_node.delete("rund/services/#{service_id}")
|
768
|
+
return Main.result_status("#{service_id} deleted")
|
682
769
|
end
|
683
770
|
when :watch_folder
|
684
771
|
res_class_path = 'v3/watchfolders'
|
@@ -692,15 +779,15 @@ module Aspera
|
|
692
779
|
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
693
780
|
case command
|
694
781
|
when :create
|
695
|
-
resp = @api_node.create(res_class_path,
|
782
|
+
resp = @api_node.create(res_class_path, value_create_modify(command: command, type: Hash))
|
696
783
|
return Main.result_status("#{resp[:data]['id']} created")
|
697
784
|
when :list
|
698
|
-
resp = @api_node.read(res_class_path,
|
785
|
+
resp = @api_node.read(res_class_path, old_query_read_delete)
|
699
786
|
return { type: :value_list, data: resp[:data]['ids'], name: 'id' }
|
700
787
|
when :show
|
701
788
|
return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
|
702
789
|
when :modify
|
703
|
-
@api_node.update(one_res_path,
|
790
|
+
@api_node.update(one_res_path, value_or_query(mandatory: true, allowed_types: Hash))
|
704
791
|
return Main.result_status("#{one_res_id} updated")
|
705
792
|
when :delete
|
706
793
|
@api_node.delete(one_res_path)
|
@@ -712,8 +799,7 @@ module Aspera
|
|
712
799
|
command = options.get_next_command(%i[session file])
|
713
800
|
validator_id = options.get_option(:validator)
|
714
801
|
validation = {'validator_id' => validator_id} unless validator_id.nil?
|
715
|
-
request_data =
|
716
|
-
request_data ||= {}
|
802
|
+
request_data = value_create_modify(default: {}, type: Hash)
|
717
803
|
case command
|
718
804
|
when :session
|
719
805
|
command = options.get_next_command([:list])
|
@@ -744,16 +830,16 @@ module Aspera
|
|
744
830
|
end
|
745
831
|
when :asperabrowser
|
746
832
|
browse_params = {
|
747
|
-
'nodeUser' => options.get_option(:username,
|
748
|
-
'nodePW' => options.get_option(:password,
|
749
|
-
'nodeURL' => options.get_option(:url,
|
833
|
+
'nodeUser' => options.get_option(:username, mandatory: true),
|
834
|
+
'nodePW' => options.get_option(:password, mandatory: true),
|
835
|
+
'nodeURL' => options.get_option(:url, mandatory: true)
|
750
836
|
}
|
751
837
|
# encode parameters so that it looks good in url
|
752
838
|
encoded_params = Base64.strict_encode64(Zlib::Deflate.deflate(JSON.generate(browse_params))).gsub(/=+$/, '').tr('+/', '-_').reverse
|
753
839
|
OpenApplication.instance.uri(options.get_option(:asperabrowserurl) + '?goto=' + encoded_params)
|
754
840
|
return Main.result_status('done')
|
755
841
|
when :basic_token
|
756
|
-
return Main.result_status(Rest.basic_creds(options.get_option(:username,
|
842
|
+
return Main.result_status(Rest.basic_creds(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true)))
|
757
843
|
end # case command
|
758
844
|
raise 'ERROR: shall not reach this line'
|
759
845
|
end # execute_action
|