aspera-cli 4.14.0 → 4.15.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 +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
|