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,92 +1,91 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# cspell:ignore snid fnid bidi ssync asyncs rund asnodeadmin mkfile mklink asperabrowser asperabrowserurl watchfolders watchfolderd entsrv
|
3
4
|
require 'aspera/cli/basic_auth_plugin'
|
4
|
-
require 'aspera/cli/
|
5
|
+
require 'aspera/cli/sync_actions'
|
6
|
+
require 'aspera/fasp/transfer_spec'
|
5
7
|
require 'aspera/nagios'
|
6
8
|
require 'aspera/hash_ext'
|
7
9
|
require 'aspera/id_generator'
|
8
10
|
require 'aspera/node'
|
9
11
|
require 'aspera/aoc'
|
10
|
-
require 'aspera/
|
11
|
-
require 'aspera/
|
12
|
+
require 'aspera/oauth'
|
13
|
+
require 'aspera/node_simulator'
|
14
|
+
require 'aspera/assert'
|
12
15
|
require 'base64'
|
13
16
|
require 'zlib'
|
14
17
|
|
15
18
|
module Aspera
|
16
19
|
module Cli
|
17
20
|
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
|
-
|
63
21
|
class Node < Aspera::Cli::BasicAuthPlugin
|
22
|
+
include SyncActions
|
64
23
|
class << self
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
24
|
+
@@node_options_declared = false # rubocop:disable Style/ClassVars
|
25
|
+
def application_name
|
26
|
+
'HSTS Node API'
|
27
|
+
end
|
28
|
+
|
29
|
+
def detect(address_or_url)
|
30
|
+
urls = if address_or_url.match?(%r{^[a-z]{1,6}://})
|
31
|
+
[address_or_url]
|
32
|
+
else
|
33
|
+
[
|
34
|
+
"https://#{address_or_url}",
|
35
|
+
"https://#{address_or_url}:9092",
|
36
|
+
"http://#{address_or_url}:9091"
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
urls.each do |base_url|
|
41
|
+
next unless base_url.match?('https?://')
|
42
|
+
api = Rest.new(base_url: base_url)
|
43
|
+
test_endpoint = 'ping'
|
44
|
+
result = api.call(operation: 'GET', subpath: test_endpoint)
|
45
|
+
next unless result[:http].body.eql?('')
|
46
|
+
url_length = -2 - test_endpoint.length
|
47
|
+
return {
|
48
|
+
url: result[:http].uri.to_s[0..url_length]
|
49
|
+
}
|
50
|
+
rescue StandardError => e
|
51
|
+
Log.log.debug{"detect error: #{e}"}
|
70
52
|
end
|
71
53
|
return nil
|
72
54
|
end
|
73
55
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
56
|
+
def wizard(object:, private_key_path: nil, pub_key_pem: nil)
|
57
|
+
options = object.options
|
58
|
+
return {
|
59
|
+
preset_value: {
|
60
|
+
url: options.get_option(:url, mandatory: true),
|
61
|
+
username: options.get_option(:username, mandatory: true),
|
62
|
+
password: options.get_option(:password, mandatory: true)
|
63
|
+
},
|
64
|
+
test_args: 'info'
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def declare_options(options, force: false)
|
69
|
+
return if @@node_options_declared && !force
|
70
|
+
@@node_options_declared = true # rubocop:disable Style/ClassVars
|
71
|
+
options.declare(:validator, 'Identifier of validator (optional for central)')
|
72
|
+
options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
|
73
|
+
options.declare(:sync_name, 'Sync name')
|
74
|
+
options.declare(
|
75
|
+
:default_ports, 'Use standard FASP ports or get from node api (gen4)', values: :bool, default: :yes,
|
76
|
+
handler: {o: Aspera::Node, m: :use_standard_ports})
|
77
|
+
options.declare(:root_id, 'File id of top folder if using bearer tokens')
|
78
|
+
SyncActions.declare_options(options)
|
79
|
+
options.parse_options!
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
84
83
|
# spellchecker: disable
|
85
84
|
# SOAP API call to test central API
|
86
|
-
CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>'\
|
87
|
-
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">'\
|
88
|
-
'<soapenv:Header></soapenv:Header>'\
|
89
|
-
'<soapenv:Body><typ:GetSessionInfoRequest><SessionFilter><SessionStatus>running</SessionStatus></SessionFilter></typ:GetSessionInfoRequest></soapenv:Body>'\
|
85
|
+
CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>' \
|
86
|
+
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">' \
|
87
|
+
'<soapenv:Header></soapenv:Header>' \
|
88
|
+
'<soapenv:Body><typ:GetSessionInfoRequest><SessionFilter><SessionStatus>running</SessionStatus></SessionFilter></typ:GetSessionInfoRequest></soapenv:Body>' \
|
90
89
|
'</soapenv:Envelope>'
|
91
90
|
# spellchecker: enable
|
92
91
|
|
@@ -94,17 +93,17 @@ module Aspera
|
|
94
93
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
95
94
|
|
96
95
|
# actions in execute_command_gen3
|
97
|
-
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download sync]
|
96
|
+
COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download http_node_download sync]
|
98
97
|
|
99
98
|
BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
|
100
99
|
|
101
|
-
SPECIAL_ACTIONS = %i[health events info license].freeze
|
100
|
+
SPECIAL_ACTIONS = %i[health events info slash license].freeze
|
102
101
|
|
103
102
|
# actions available in v3 in gen4
|
104
|
-
V3_IN_V4_ACTIONS = %i[
|
103
|
+
V3_IN_V4_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
105
104
|
|
106
105
|
# actions used commonly when a node is involved
|
107
|
-
COMMON_ACTIONS = %i[
|
106
|
+
COMMON_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
108
107
|
|
109
108
|
private_constant(*%i[CENTRAL_SOAP_API_TEST SEARCH_REMOVE_FIELDS BASE_ACTIONS SPECIAL_ACTIONS V3_IN_V4_ACTIONS COMMON_ACTIONS])
|
110
109
|
|
@@ -114,26 +113,22 @@ module Aspera
|
|
114
113
|
# commands for execute_command_gen4
|
115
114
|
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download show modify permission thumbnail v3].concat(NODE4_READ_ACTIONS).freeze
|
116
115
|
|
117
|
-
COMMANDS_COS = %i[upload download info
|
116
|
+
COMMANDS_COS = %i[upload download info access_keys api_details transfer].freeze
|
118
117
|
COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
|
119
118
|
COMMANDS_FASPEX = COMMON_ACTIONS
|
120
119
|
|
121
|
-
def initialize(env)
|
120
|
+
def initialize(env, api: nil)
|
122
121
|
super(env)
|
123
|
-
|
124
|
-
return if env[:man_only]
|
122
|
+
Node.declare_options(options, force: env[:all_manuals])
|
125
123
|
@api_node =
|
126
|
-
if
|
124
|
+
if !api.nil? || env[:all_manuals]
|
127
125
|
# this can be Aspera::Node or Aspera::Rest (shares)
|
128
|
-
|
129
|
-
elsif options.get_option(:password, mandatory: true)
|
126
|
+
api
|
127
|
+
elsif Oauth.bearer?(options.get_option(:password, mandatory: true))
|
130
128
|
# info is provided like node_info of aoc
|
131
129
|
Aspera::Node.new(params: {
|
132
130
|
base_url: options.get_option(:url, mandatory: true),
|
133
|
-
headers:
|
134
|
-
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, mandatory: true),
|
135
|
-
'Authorization' => options.get_option(:password, mandatory: true)
|
136
|
-
}
|
131
|
+
headers: Aspera::Node.bearer_headers(options.get_option(:password, mandatory: true))
|
137
132
|
})
|
138
133
|
else
|
139
134
|
# this is normal case
|
@@ -147,26 +142,6 @@ module Aspera
|
|
147
142
|
end
|
148
143
|
end
|
149
144
|
|
150
|
-
def c_textify_browse(table_data)
|
151
|
-
return table_data.map do |i|
|
152
|
-
i['permissions'] = i['permissions'].map { |x| x['name'] }.join(',')
|
153
|
-
i
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# key/value is defined in main in hash_table
|
158
|
-
def c_textify_bool_list_result(list, name_list)
|
159
|
-
list.each_index do |i|
|
160
|
-
next unless name_list.include?(list[i]['key'])
|
161
|
-
list[i]['value'].each do |item|
|
162
|
-
list.push({'key' => item['name'], 'value' => item['value']})
|
163
|
-
end
|
164
|
-
list.delete_at(i)
|
165
|
-
# continue at same index because we delete current one
|
166
|
-
redo
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
145
|
# reduce the path from a result on given named column
|
171
146
|
def c_result_remove_prefix_path(result, column, path_prefix)
|
172
147
|
if !path_prefix.nil?
|
@@ -222,7 +197,7 @@ module Aspera
|
|
222
197
|
when :search
|
223
198
|
search_root = get_next_arg_add_prefix(prefix_path, 'search root')
|
224
199
|
parameters = {'path' => search_root}
|
225
|
-
other_options =
|
200
|
+
other_options = query_option
|
226
201
|
parameters.merge!(other_options) unless other_options.nil?
|
227
202
|
resp = @api_node.create('files/search', parameters)
|
228
203
|
result = { type: :object_list, data: resp[:data]['items']}
|
@@ -268,17 +243,40 @@ module Aspera
|
|
268
243
|
# if there is no items
|
269
244
|
case send_result['self']['type']
|
270
245
|
when 'directory', 'container' # directory: node, container: shares
|
271
|
-
result = { data: send_result['items'], type: :object_list
|
246
|
+
result = { data: send_result['items'], type: :object_list }
|
272
247
|
formatter.display_item_count(send_result['item_count'], send_result['total_count'])
|
273
248
|
else # 'file','symbolic_link'
|
274
249
|
result = { data: send_result['self'], type: :single_object}
|
275
|
-
# result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
276
|
-
# raise "unknown type: #{send_result['self']['type']}"
|
277
250
|
end
|
278
251
|
return c_result_remove_prefix_path(result, 'path', prefix_path)
|
279
252
|
when :sync
|
280
|
-
|
281
|
-
|
253
|
+
return execute_sync_action do |sync_direction, local_path, remote_path|
|
254
|
+
# Gen3 API
|
255
|
+
# empty transfer spec for authorization request
|
256
|
+
request_transfer_spec = {
|
257
|
+
type: case sync_direction
|
258
|
+
when :push then :sync_upload
|
259
|
+
when :pull then :sync_download
|
260
|
+
when :bidi then :sync
|
261
|
+
end,
|
262
|
+
paths: [{
|
263
|
+
source: remote_path,
|
264
|
+
destination: local_path
|
265
|
+
}]
|
266
|
+
}
|
267
|
+
# add fixed parameters if any (for COS)
|
268
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
269
|
+
# prepare payload for single request
|
270
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
271
|
+
# only one request, so only one answer
|
272
|
+
transfer_spec = @api_node.create('files/sync_setup', setup_payload)[:data]['transfer_specs'].first['transfer_spec']
|
273
|
+
# API returns null tag... but async does not like it
|
274
|
+
transfer_spec.delete_if{ |_k, v| v.nil? }
|
275
|
+
# delete this part, as the returned value contains only destination, and not sources
|
276
|
+
# transfer_spec.delete('paths') if command.eql?(:upload)
|
277
|
+
Log.log.debug{Log.dump(:ts, transfer_spec)}
|
278
|
+
transfer_spec
|
279
|
+
end
|
282
280
|
when :upload, :download
|
283
281
|
# empty transfer spec for authorization request
|
284
282
|
request_transfer_spec = {}
|
@@ -297,8 +295,16 @@ module Aspera
|
|
297
295
|
# delete this part, as the returned value contains only destination, and not sources
|
298
296
|
transfer_spec.delete('paths') if command.eql?(:upload)
|
299
297
|
return Main.result_transfer(transfer.start(transfer_spec))
|
298
|
+
when :http_node_download
|
299
|
+
remote_path = get_next_arg_add_prefix(prefix_path, 'remote path')
|
300
|
+
file_name = File.basename(remote_path)
|
301
|
+
@api_node.call(
|
302
|
+
operation: 'GET',
|
303
|
+
subpath: "files/#{URI.encode_www_form_component(remote_path)}/contents",
|
304
|
+
save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE), file_name))
|
305
|
+
return Main.result_status("downloaded: #{file_name}")
|
300
306
|
end
|
301
|
-
|
307
|
+
error_unreachable_line
|
302
308
|
end
|
303
309
|
|
304
310
|
# common API to node and Shares
|
@@ -307,20 +313,37 @@ module Aspera
|
|
307
313
|
case command
|
308
314
|
when *COMMANDS_GEN3
|
309
315
|
execute_command_gen3(command, prefix_path)
|
310
|
-
when :
|
311
|
-
ak_command = options.get_next_command([
|
316
|
+
when :access_keys
|
317
|
+
ak_command = options.get_next_command(%i[do set_bearer_key].concat(Plugin::ALL_OPS))
|
312
318
|
case ak_command
|
313
|
-
when *Plugin::ALL_OPS
|
319
|
+
when *Plugin::ALL_OPS
|
320
|
+
return entity_command(ak_command, @api_node, 'access_keys') do |field, value|
|
321
|
+
raise 'only selector: %id:self' unless field.eql?('id') && value.eql?('self')
|
322
|
+
@api_node.read('access_keys/self')[:data]['id']
|
323
|
+
end
|
314
324
|
when :do
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
325
|
+
access_key_id = options.get_next_argument('access key id')
|
326
|
+
root_file_id = options.get_option(:root_id)
|
327
|
+
if root_file_id.nil?
|
328
|
+
ak_info = @api_node.read("access_keys/#{access_key_id}")[:data]
|
329
|
+
# change API credentials if different access key
|
330
|
+
if !access_key_id.eql?('self')
|
331
|
+
@api_node.params[:auth][:username] = ak_info['id']
|
332
|
+
@api_node.params[:auth][:password] = config.lookup_secret(url: @api_node.params[:base_url], username: ak_info['id'], mandatory: true)
|
333
|
+
end
|
334
|
+
root_file_id = ak_info['root_file_id']
|
321
335
|
end
|
322
336
|
command_repo = options.get_next_command(COMMANDS_GEN4)
|
323
|
-
return execute_command_gen4(command_repo,
|
337
|
+
return execute_command_gen4(command_repo, root_file_id)
|
338
|
+
when :set_bearer_key
|
339
|
+
access_key_id = options.get_next_argument('access key id')
|
340
|
+
access_key_id = @api_node.read('access_keys/self')[:data]['id'] if access_key_id.eql?('self')
|
341
|
+
bearer_key_pem = options.get_next_argument('public or private RSA key PEM value', type: String)
|
342
|
+
key = OpenSSL::PKey.read(bearer_key_pem)
|
343
|
+
key = key.public_key if key.private?
|
344
|
+
bearer_key_pem = key.to_pem
|
345
|
+
@api_node.update("access_keys/#{access_key_id}", {token_verification_key: bearer_key_pem})
|
346
|
+
return Main.result_status('public key updated')
|
324
347
|
end
|
325
348
|
when :health
|
326
349
|
nagios = Nagios.new
|
@@ -345,10 +368,13 @@ module Aspera
|
|
345
368
|
return nagios.result
|
346
369
|
when :events
|
347
370
|
events = @api_node.read('events', query_read_delete)[:data]
|
348
|
-
return { type: :object_list, data: events}
|
371
|
+
return { type: :object_list, data: events, fields: ->(f){!f.start_with?('data')} }
|
349
372
|
when :info
|
350
373
|
nd_info = @api_node.read('info')[:data]
|
351
|
-
return { type: :single_object, data: nd_info
|
374
|
+
return { type: :single_object, data: nd_info}
|
375
|
+
when :slash
|
376
|
+
nd_info = @api_node.read('')[:data]
|
377
|
+
return { type: :single_object, data: nd_info}
|
352
378
|
when :license
|
353
379
|
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
354
380
|
node_license = @api_node.read('license')[:data]
|
@@ -364,7 +390,7 @@ module Aspera
|
|
364
390
|
# @return [Hash] api and main file id for given path or id
|
365
391
|
# Allows to specify a file by its path or by its id on the node
|
366
392
|
def apifid_from_next_arg(top_file_id)
|
367
|
-
file_path = instance_identifier(description: 'path or id') do |attribute, value|
|
393
|
+
file_path = instance_identifier(description: 'path or %id:<id>') do |attribute, value|
|
368
394
|
raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
369
395
|
# directly return result for method
|
370
396
|
return {api: @api_node, file_id: value}
|
@@ -380,14 +406,14 @@ module Aspera
|
|
380
406
|
command_legacy = options.get_next_command(V3_IN_V4_ACTIONS)
|
381
407
|
# TODO: shall we support all methods here ? what if there is a link ?
|
382
408
|
apifid = @api_node.resolve_api_fid(top_file_id, '')
|
383
|
-
return Node.new(@agents
|
409
|
+
return Node.new(@agents, api: apifid[:api]).execute_action(command_legacy)
|
384
410
|
when :node_info, :bearer_token_node
|
385
411
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
386
412
|
result = {
|
387
413
|
url: apifid[:api].params[:base_url],
|
388
414
|
root_id: apifid[:file_id]
|
389
415
|
}
|
390
|
-
|
416
|
+
assert_values(apifid[:api].params[:auth][:type], %i[basic oauth2])
|
391
417
|
case apifid[:api].params[:auth][:type]
|
392
418
|
when :basic
|
393
419
|
result[:username] = apifid[:api].params[:auth][:username]
|
@@ -395,10 +421,11 @@ module Aspera
|
|
395
421
|
when :oauth2
|
396
422
|
result[:username] = apifid[:api].params[:headers][Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY]
|
397
423
|
result[:password] = apifid[:api].oauth_token
|
398
|
-
else
|
424
|
+
else error_unreachable_line
|
399
425
|
end
|
400
426
|
return {type: :single_object, data: result} if command_repo.eql?(:node_info)
|
401
|
-
|
427
|
+
# check format of bearer token
|
428
|
+
Oauth.bearer_extract(result[:password])
|
402
429
|
return Main.result_status(result[:password])
|
403
430
|
when :browse
|
404
431
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
@@ -413,7 +440,7 @@ module Aspera
|
|
413
440
|
return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
|
414
441
|
when :find
|
415
442
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
416
|
-
test_block = Aspera::Node.
|
443
|
+
test_block = Aspera::Node.file_matcher_from_argument(options)
|
417
444
|
return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
|
418
445
|
when :mkdir
|
419
446
|
containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
|
@@ -428,15 +455,27 @@ module Aspera
|
|
428
455
|
result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
|
429
456
|
return Main.result_status("renamed to #{newname}")
|
430
457
|
when :delete
|
431
|
-
return do_bulk_operation(
|
432
|
-
raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
|
458
|
+
return do_bulk_operation(command: command_repo, descr: 'path', values: String, id_result: 'path') do |l_path|
|
433
459
|
apifid = @api_node.resolve_api_fid(top_file_id, l_path)
|
434
460
|
result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
|
435
461
|
{'path' => l_path}
|
436
462
|
end
|
437
463
|
when :sync
|
438
|
-
|
439
|
-
|
464
|
+
return execute_sync_action do |sync_direction, _local_path, remote_path|
|
465
|
+
# Gen4 API
|
466
|
+
# direction is push pull, bidi
|
467
|
+
assert_values(sync_direction, %i[push pull bidi])
|
468
|
+
ts_direction = case sync_direction
|
469
|
+
when :push, :bidi then Fasp::TransferSpec::DIRECTION_SEND
|
470
|
+
when :pull then Fasp::TransferSpec::DIRECTION_RECEIVE
|
471
|
+
else error_unreachable_line
|
472
|
+
end
|
473
|
+
# remote is specified by option to_folder
|
474
|
+
apifid = @api_node.resolve_api_fid(top_file_id, remote_path)
|
475
|
+
transfer_spec = apifid[:api].transfer_spec_gen4(apifid[:file_id], ts_direction)
|
476
|
+
Log.log.debug{Log.dump(:ts, transfer_spec)}
|
477
|
+
transfer_spec
|
478
|
+
end
|
440
479
|
when :upload
|
441
480
|
apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
|
442
481
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)))
|
@@ -475,7 +514,7 @@ module Aspera
|
|
475
514
|
source_paths = [{'source' => source_folder.pop}]
|
476
515
|
source_folder = source_folder.join(Aspera::Node::PATH_SEPARATOR)
|
477
516
|
end
|
478
|
-
raise
|
517
|
+
raise Cli::BadArgument, 'one file at a time only in HTTP mode' if source_paths.length > 1
|
479
518
|
file_name = source_paths.first['source']
|
480
519
|
apifid = @api_node.resolve_api_fid(top_file_id, File.join(source_folder, file_name))
|
481
520
|
apifid[:api].call(
|
@@ -499,8 +538,7 @@ module Aspera
|
|
499
538
|
subpath: "files/#{apifid[:file_id]}/preview",
|
500
539
|
headers: {'Accept' => 'image/png'}
|
501
540
|
)
|
502
|
-
|
503
|
-
return Main.result_status(Preview::Terminal.build(result[:http].body, reserved_lines: 3))
|
541
|
+
return Main.result_picture_in_terminal(options, result[:http].body)
|
504
542
|
when :permission
|
505
543
|
apifid = apifid_from_next_arg(top_file_id)
|
506
544
|
command_perm = options.get_next_command(%i[list create delete])
|
@@ -514,10 +552,8 @@ module Aspera
|
|
514
552
|
items = apifid[:api].read('permissions', list_options)[:data]
|
515
553
|
return {type: :object_list, data: items}
|
516
554
|
when :delete
|
517
|
-
|
518
|
-
|
519
|
-
# TODO: notify event ?
|
520
|
-
apifid[:api].delete("permissions/#{perm_id}")
|
555
|
+
return do_bulk_operation(command: command_perm, descr: 'identifier', values: :identifier) do |one_id|
|
556
|
+
apifid[:api].delete("permissions/#{one_id}")
|
521
557
|
# notify application of deletion
|
522
558
|
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app, types: ['permission.deleted']) unless the_app.nil?
|
523
559
|
{'id' => one_id}
|
@@ -535,11 +571,11 @@ module Aspera
|
|
535
571
|
# notify application of creation
|
536
572
|
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app) unless the_app.nil?
|
537
573
|
return { type: :single_object, data: created_data}
|
538
|
-
else
|
574
|
+
else error_unreachable_line
|
539
575
|
end
|
540
|
-
else
|
576
|
+
else error_unreachable_line
|
541
577
|
end # command_repo
|
542
|
-
|
578
|
+
error_unreachable_line
|
543
579
|
end # execute_command_gen4
|
544
580
|
|
545
581
|
# This is older API
|
@@ -549,7 +585,7 @@ module Aspera
|
|
549
585
|
async_name = options.get_option(:sync_name)
|
550
586
|
if async_name.nil?
|
551
587
|
async_id = instance_identifier
|
552
|
-
if async_id.eql?(
|
588
|
+
if async_id.eql?(ExtendedValue::ALL) && %i[show delete].include?(command)
|
553
589
|
async_ids = @api_node.read('async/list')[:data]['sync_ids']
|
554
590
|
else
|
555
591
|
Integer(async_id) # must be integer
|
@@ -572,7 +608,7 @@ module Aspera
|
|
572
608
|
when :show
|
573
609
|
resp = @api_node.create('async/summary', post_data)[:data]['sync_summaries']
|
574
610
|
return Main.result_empty if resp.empty?
|
575
|
-
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if async_id.eql?(
|
611
|
+
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if async_id.eql?(ExtendedValue::ALL)
|
576
612
|
return { type: :single_object, data: resp.first }
|
577
613
|
when :delete
|
578
614
|
resp = @api_node.create('async/delete', post_data)[:data]
|
@@ -589,7 +625,7 @@ module Aspera
|
|
589
625
|
# filename str
|
590
626
|
# skip int
|
591
627
|
# status int
|
592
|
-
filter =
|
628
|
+
filter = query_option
|
593
629
|
post_data.merge!(filter) unless filter.nil?
|
594
630
|
resp = @api_node.create('async/files', post_data)[:data]
|
595
631
|
data = resp['sync_files']
|
@@ -620,6 +656,20 @@ module Aspera
|
|
620
656
|
end
|
621
657
|
end
|
622
658
|
|
659
|
+
# @return [Integer] id of the sync
|
660
|
+
# @raise [Cli::BadArgument] if no such sync, or not by name
|
661
|
+
# @param [String] field name of the field to search
|
662
|
+
# @param [String] value value of the field to search
|
663
|
+
def ssync_lookup(field, value)
|
664
|
+
raise Cli::BadArgument, "Only search by name is supported (#{field})" unless field.eql?('name')
|
665
|
+
@api_node.read('asyncs')[:data]['ids'].each do |id|
|
666
|
+
sync_info = @api_node.read("asyncs/#{id}")[:data]['configuration']
|
667
|
+
# name is unique, so we can return
|
668
|
+
return id if sync_info[field].eql?(value)
|
669
|
+
end
|
670
|
+
raise Cli::BadArgument, "no such sync: #{field}=#{value}"
|
671
|
+
end
|
672
|
+
|
623
673
|
ACTIONS = %i[
|
624
674
|
async
|
625
675
|
ssync
|
@@ -629,7 +679,9 @@ module Aspera
|
|
629
679
|
watch_folder
|
630
680
|
central
|
631
681
|
asperabrowser
|
632
|
-
basic_token
|
682
|
+
basic_token
|
683
|
+
bearer_token
|
684
|
+
simulator].concat(COMMON_ACTIONS).freeze
|
633
685
|
|
634
686
|
def execute_action(command=nil, prefix_path=nil)
|
635
687
|
command ||= options.get_next_command(ACTIONS)
|
@@ -640,17 +692,15 @@ module Aspera
|
|
640
692
|
# newer API
|
641
693
|
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
|
642
694
|
case sync_command
|
643
|
-
when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids')
|
695
|
+
when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids'){|field, value|ssync_lookup(field, value)}
|
644
696
|
else
|
645
|
-
asyncs_id = instance_identifier
|
697
|
+
asyncs_id = instance_identifier {|field, value|ssync_lookup(field, value)}
|
646
698
|
parameters = nil
|
647
699
|
if %i[start stop].include?(sync_command)
|
648
700
|
@api_node.create("asyncs/#{asyncs_id}/#{sync_command}", parameters)
|
649
701
|
return Main.result_status('Done')
|
650
702
|
end
|
651
|
-
if %i[bandwidth counters files].include?(sync_command)
|
652
|
-
parameters = value_or_query(allowed_types: Hash, mandatory: false) || {}
|
653
|
-
end
|
703
|
+
parameters = query_option(default: {}) if %i[bandwidth counters files].include?(sync_command)
|
654
704
|
return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
|
655
705
|
end
|
656
706
|
when :stream
|
@@ -660,13 +710,13 @@ module Aspera
|
|
660
710
|
resp = @api_node.read('ops/transfers', old_query_read_delete)
|
661
711
|
return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
|
662
712
|
when :create
|
663
|
-
resp = @api_node.create('streams', value_create_modify(command: command
|
713
|
+
resp = @api_node.create('streams', value_create_modify(command: command))
|
664
714
|
return { type: :single_object, data: resp[:data] }
|
665
715
|
when :show
|
666
716
|
resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
|
667
717
|
return { type: :other_struct, data: resp[:data] }
|
668
718
|
when :modify
|
669
|
-
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command
|
719
|
+
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command))
|
670
720
|
return { type: :other_struct, data: resp[:data] }
|
671
721
|
when :cancel
|
672
722
|
resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
|
@@ -675,7 +725,7 @@ module Aspera
|
|
675
725
|
raise 'error'
|
676
726
|
end
|
677
727
|
when :transfer
|
678
|
-
command = options.get_next_command(%i[list cancel show modify bandwidth_average])
|
728
|
+
command = options.get_next_command(%i[list cancel show modify bandwidth_average sessions])
|
679
729
|
res_class_path = 'ops/transfers'
|
680
730
|
if %i[cancel show modify].include?(command)
|
681
731
|
one_res_id = instance_identifier
|
@@ -683,15 +733,24 @@ module Aspera
|
|
683
733
|
end
|
684
734
|
case command
|
685
735
|
when :list
|
686
|
-
|
687
|
-
query = query_read_delete
|
688
|
-
raise 'Query must be a Hash' unless query.nil? || query.is_a?(Hash)
|
689
|
-
transfers_data = @api_node.read(res_class_path, query)[:data]
|
736
|
+
transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
|
690
737
|
return {
|
691
738
|
type: :object_list,
|
692
739
|
data: transfers_data,
|
693
740
|
fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
|
694
741
|
}
|
742
|
+
when :sessions
|
743
|
+
transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
|
744
|
+
sessions = transfers_data.map{|t|t['sessions']}.flatten
|
745
|
+
sessions.each do |session|
|
746
|
+
session['start_time'] = Time.at(session['start_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
747
|
+
session['end_time'] = Time.at(session['end_time_usec'] / 1_000_000.0).utc.iso8601(0)
|
748
|
+
end
|
749
|
+
return {
|
750
|
+
type: :object_list,
|
751
|
+
data: sessions,
|
752
|
+
fields: %w[id status start_time end_time target_rate_kbps]
|
753
|
+
}
|
695
754
|
when :cancel
|
696
755
|
resp = @api_node.cancel(one_res_path)
|
697
756
|
return { type: :other_struct, data: resp[:data] }
|
@@ -702,7 +761,7 @@ module Aspera
|
|
702
761
|
resp = @api_node.update(one_res_path, options.get_next_argument('update value', type: Hash))
|
703
762
|
return { type: :other_struct, data: resp[:data] }
|
704
763
|
when :bandwidth_average
|
705
|
-
transfers_data = @api_node.read(res_class_path,
|
764
|
+
transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
|
706
765
|
# collect all key dates
|
707
766
|
bandwidth_period = {}
|
708
767
|
dir_info = %i[avg_kbps sessions].freeze
|
@@ -779,7 +838,7 @@ module Aspera
|
|
779
838
|
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
780
839
|
case command
|
781
840
|
when :create
|
782
|
-
resp = @api_node.create(res_class_path, value_create_modify(command: command
|
841
|
+
resp = @api_node.create(res_class_path, value_create_modify(command: command))
|
783
842
|
return Main.result_status("#{resp[:data]['id']} created")
|
784
843
|
when :list
|
785
844
|
resp = @api_node.read(res_class_path, old_query_read_delete)
|
@@ -787,7 +846,7 @@ module Aspera
|
|
787
846
|
when :show
|
788
847
|
return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
|
789
848
|
when :modify
|
790
|
-
@api_node.update(one_res_path,
|
849
|
+
@api_node.update(one_res_path, query_option(mandatory: true))
|
791
850
|
return Main.result_status("#{one_res_id} updated")
|
792
851
|
when :delete
|
793
852
|
@api_node.delete(one_res_path)
|
@@ -799,7 +858,7 @@ module Aspera
|
|
799
858
|
command = options.get_next_command(%i[session file])
|
800
859
|
validator_id = options.get_option(:validator)
|
801
860
|
validation = {'validator_id' => validator_id} unless validator_id.nil?
|
802
|
-
request_data =
|
861
|
+
request_data = query_option(default: {})
|
803
862
|
case command
|
804
863
|
when :session
|
805
864
|
command = options.get_next_command([:list])
|
@@ -820,7 +879,7 @@ module Aspera
|
|
820
879
|
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
821
880
|
resp = @api_node.create('services/rest/transfers/v1/files', request_data)[:data]
|
822
881
|
resp = JSON.parse(resp) if resp.is_a?(String)
|
823
|
-
Log.dump(:resp, resp)
|
882
|
+
Log.log.debug{Log.dump(:resp, resp)}
|
824
883
|
return { type: :object_list, data: resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path]}
|
825
884
|
when :modify
|
826
885
|
request_data.deep_merge!(validation) unless validation.nil?
|
@@ -839,7 +898,27 @@ module Aspera
|
|
839
898
|
OpenApplication.instance.uri(options.get_option(:asperabrowserurl) + '?goto=' + encoded_params)
|
840
899
|
return Main.result_status('done')
|
841
900
|
when :basic_token
|
842
|
-
return Main.result_status(Rest.
|
901
|
+
return Main.result_status(Rest.basic_token(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true)))
|
902
|
+
when :bearer_token
|
903
|
+
private_key = OpenSSL::PKey::RSA.new(options.get_next_argument('private RSA key PEM value', type: String))
|
904
|
+
token_info = options.get_next_argument('user and group identification', type: Hash)
|
905
|
+
access_key = options.get_option(:username, mandatory: true)
|
906
|
+
return Main.result_status(Aspera::Node.bearer_token(payload: token_info, access_key: access_key, private_key: private_key))
|
907
|
+
when :simulator
|
908
|
+
require 'aspera/node_simulator'
|
909
|
+
parameters = value_create_modify(command: command)
|
910
|
+
parameters = parameters.symbolize_keys
|
911
|
+
raise 'Missing key: url' unless parameters.key?(:url)
|
912
|
+
uri = URI.parse(parameters[:url])
|
913
|
+
server = WebServerSimple.new(uri, certificate: parameters[:certificate])
|
914
|
+
server.mount(uri.path, NodeSimulatorServlet, parameters[:credentials], transfer)
|
915
|
+
# on ctrl-c, tell server main loop to exit
|
916
|
+
trap('INT') { server.shutdown }
|
917
|
+
formatter.display_status("Node Simulator listening on #{uri.port}")
|
918
|
+
Log.log.info{"Listening on #{uri.port}"}
|
919
|
+
# this is blocking until server exits
|
920
|
+
server.start
|
921
|
+
return Main.result_status('Simulator terminated')
|
843
922
|
end # case command
|
844
923
|
raise 'ERROR: shall not reach this line'
|
845
924
|
end # execute_action
|