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