aspera-cli 4.13.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 +81 -7
- data/CONTRIBUTING.md +22 -6
- data/README.md +2038 -1080
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/dascli +1 -1
- data/examples/proxy.pac +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +219 -159
- data/lib/aspera/ascmd.rb +25 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -179
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +104 -156
- data/lib/aspera/cli/manager.rb +259 -209
- data/lib/aspera/cli/plugin.rb +123 -63
- data/lib/aspera/cli/plugins/alee.rb +2 -3
- data/lib/aspera/cli/plugins/aoc.rb +341 -261
- data/lib/aspera/cli/plugins/ats.rb +22 -21
- data/lib/aspera/cli/plugins/bss.rb +5 -5
- data/lib/aspera/cli/plugins/config.rb +578 -627
- data/lib/aspera/cli/plugins/console.rb +44 -6
- data/lib/aspera/cli/plugins/cos.rb +15 -17
- data/lib/aspera/cli/plugins/faspex.rb +114 -100
- data/lib/aspera/cli/plugins/faspex5.rb +411 -264
- data/lib/aspera/cli/plugins/node.rb +354 -259
- data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
- data/lib/aspera/cli/plugins/preview.rb +82 -90
- data/lib/aspera/cli/plugins/server.rb +79 -32
- data/lib/aspera/cli/plugins/shares.rb +55 -42
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +66 -73
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +12 -8
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +24 -9
- 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 +25 -21
- data/lib/aspera/fasp/agent_direct.rb +89 -103
- data/lib/aspera/fasp/agent_httpgw.rb +231 -149
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -32
- data/lib/aspera/fasp/error_info.rb +4 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +53 -195
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +71 -37
- data/lib/aspera/fasp/parameters.yaml +76 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +7 -6
- data/lib/aspera/fasp/uri.rb +26 -24
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +14 -4
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +58 -16
- data/lib/aspera/node.rb +157 -92
- data/lib/aspera/oauth.rb +37 -19
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +1 -1
- 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 +73 -16
- data/lib/aspera/preview/utils.rb +21 -28
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +136 -68
- 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 +18 -15
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/sync.rb +127 -119
- 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 +34 -17
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -186
- 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/data/7 +0 -0
- data/lib/aspera/fasp/listener.rb +0 -13
@@ -1,97 +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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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!
|
86
79
|
end
|
87
80
|
end
|
88
81
|
|
89
82
|
# spellchecker: disable
|
90
83
|
# SOAP API call to test central API
|
91
|
-
CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>'\
|
92
|
-
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">'\
|
93
|
-
'<soapenv:Header></soapenv:Header>'\
|
94
|
-
'<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>' \
|
95
88
|
'</soapenv:Envelope>'
|
96
89
|
# spellchecker: enable
|
97
90
|
|
@@ -99,17 +92,17 @@ module Aspera
|
|
99
92
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
100
93
|
|
101
94
|
# actions in execute_command_gen3
|
102
|
-
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]
|
103
96
|
|
104
97
|
BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
|
105
98
|
|
106
|
-
SPECIAL_ACTIONS = %i[health events info license].freeze
|
99
|
+
SPECIAL_ACTIONS = %i[health events info slash license].freeze
|
107
100
|
|
108
101
|
# actions available in v3 in gen4
|
109
|
-
V3_IN_V4_ACTIONS = %i[
|
102
|
+
V3_IN_V4_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
110
103
|
|
111
104
|
# actions used commonly when a node is involved
|
112
|
-
COMMON_ACTIONS = %i[
|
105
|
+
COMMON_ACTIONS = %i[access_keys].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
|
113
106
|
|
114
107
|
private_constant(*%i[CENTRAL_SOAP_API_TEST SEARCH_REMOVE_FIELDS BASE_ACTIONS SPECIAL_ACTIONS V3_IN_V4_ACTIONS COMMON_ACTIONS])
|
115
108
|
|
@@ -117,61 +110,37 @@ module Aspera
|
|
117
110
|
NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
|
118
111
|
|
119
112
|
# commands for execute_command_gen4
|
120
|
-
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download
|
113
|
+
COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download show modify permission thumbnail v3].concat(NODE4_READ_ACTIONS).freeze
|
121
114
|
|
122
|
-
COMMANDS_COS = %i[upload download info
|
115
|
+
COMMANDS_COS = %i[upload download info access_keys api_details transfer].freeze
|
123
116
|
COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
|
124
117
|
COMMANDS_FASPEX = COMMON_ACTIONS
|
125
118
|
|
126
|
-
def initialize(env)
|
119
|
+
def initialize(env, api: nil)
|
127
120
|
super(env)
|
128
|
-
|
129
|
-
return if env[:man_only]
|
121
|
+
Node.declare_options(options, force: env[:all_manuals])
|
130
122
|
@api_node =
|
131
|
-
if
|
123
|
+
if !api.nil? || env[:all_manuals]
|
132
124
|
# this can be Aspera::Node or Aspera::Rest (shares)
|
133
|
-
|
134
|
-
elsif options.get_option(:password,
|
125
|
+
api
|
126
|
+
elsif Oauth.bearer?(options.get_option(:password, mandatory: true))
|
135
127
|
# info is provided like node_info of aoc
|
136
128
|
Aspera::Node.new(params: {
|
137
|
-
base_url: options.get_option(:url,
|
138
|
-
headers:
|
139
|
-
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, is_type: :mandatory),
|
140
|
-
'Authorization' => options.get_option(:password, is_type: :mandatory)
|
141
|
-
}
|
129
|
+
base_url: options.get_option(:url, mandatory: true),
|
130
|
+
headers: Aspera::Node.bearer_headers(options.get_option(:password, mandatory: true))
|
142
131
|
})
|
143
132
|
else
|
144
133
|
# this is normal case
|
145
134
|
Aspera::Node.new(params: {
|
146
|
-
base_url: options.get_option(:url,
|
135
|
+
base_url: options.get_option(:url, mandatory: true),
|
147
136
|
auth: {
|
148
137
|
type: :basic,
|
149
|
-
username: options.get_option(:username,
|
150
|
-
password: options.get_option(:password,
|
138
|
+
username: options.get_option(:username, mandatory: true),
|
139
|
+
password: options.get_option(:password, mandatory: true)
|
151
140
|
}})
|
152
141
|
end
|
153
142
|
end
|
154
143
|
|
155
|
-
def c_textify_browse(table_data)
|
156
|
-
return table_data.map do |i|
|
157
|
-
i['permissions'] = i['permissions'].map { |x| x['name'] }.join(',')
|
158
|
-
i
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# key/value is defined in main in hash_table
|
163
|
-
def c_textify_bool_list_result(list, name_list)
|
164
|
-
list.each_index do |i|
|
165
|
-
next unless name_list.include?(list[i]['key'])
|
166
|
-
list[i]['value'].each do |item|
|
167
|
-
list.push({'key' => item['name'], 'value' => item['value']})
|
168
|
-
end
|
169
|
-
list.delete_at(i)
|
170
|
-
# continue at same index because we delete current one
|
171
|
-
redo
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
144
|
# reduce the path from a result on given named column
|
176
145
|
def c_result_remove_prefix_path(result, column, path_prefix)
|
177
146
|
if !path_prefix.nil?
|
@@ -227,7 +196,7 @@ module Aspera
|
|
227
196
|
when :search
|
228
197
|
search_root = get_next_arg_add_prefix(prefix_path, 'search root')
|
229
198
|
parameters = {'path' => search_root}
|
230
|
-
other_options =
|
199
|
+
other_options = query_option
|
231
200
|
parameters.merge!(other_options) unless other_options.nil?
|
232
201
|
resp = @api_node.create('files/search', parameters)
|
233
202
|
result = { type: :object_list, data: resp[:data]['items']}
|
@@ -273,53 +242,66 @@ module Aspera
|
|
273
242
|
# if there is no items
|
274
243
|
case send_result['self']['type']
|
275
244
|
when 'directory', 'container' # directory: node, container: shares
|
276
|
-
result = { data: send_result['items'], type: :object_list
|
245
|
+
result = { data: send_result['items'], type: :object_list }
|
277
246
|
formatter.display_item_count(send_result['item_count'], send_result['total_count'])
|
278
247
|
else # 'file','symbolic_link'
|
279
248
|
result = { data: send_result['self'], type: :single_object}
|
280
|
-
# result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
281
|
-
# raise "unknown type: #{send_result['self']['type']}"
|
282
249
|
end
|
283
250
|
return c_result_remove_prefix_path(result, 'path', prefix_path)
|
284
251
|
when :sync
|
285
|
-
|
286
|
-
|
287
|
-
when :upload, :download
|
288
|
-
token_type = options.get_option(:token_type)
|
289
|
-
# nil if Shares 1.x
|
290
|
-
token_type = :aspera if token_type.nil?
|
291
|
-
case token_type
|
292
|
-
when :aspera, :hybrid
|
252
|
+
return execute_sync_action do |sync_direction, local_path, remote_path|
|
253
|
+
# Gen3 API
|
293
254
|
# empty transfer spec for authorization request
|
294
|
-
request_transfer_spec = {
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
+
}
|
301
266
|
# add fixed parameters if any (for COS)
|
302
267
|
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
303
268
|
# prepare payload for single request
|
304
269
|
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
305
270
|
# only one request, so only one answer
|
306
|
-
transfer_spec = @api_node.create(
|
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? }
|
307
274
|
# delete this part, as the returned value contains only destination, and not sources
|
308
|
-
transfer_spec.delete('paths') if command.eql?(:upload)
|
309
|
-
|
310
|
-
|
311
|
-
raise 'shall be basic auth' unless @api_node.params[:auth][:type].eql?(:basic)
|
312
|
-
transfer_spec = {}.merge(Aspera::Fasp::TransferSpec::AK_TSPEC_BASE)
|
313
|
-
transfer_spec['remote_host'] = URI.parse(@api_node.params[:base_url]).host
|
314
|
-
Fasp::TransferSpec.action_to_direction(transfer_spec, command)
|
315
|
-
transfer_spec['destination_root'] = transfer.destination_folder(transfer_spec['direction'])
|
316
|
-
@api_node.add_tspec_info(transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
317
|
-
else raise "ERROR: token_type #{tt}"
|
275
|
+
# transfer_spec.delete('paths') if command.eql?(:upload)
|
276
|
+
Log.log.debug{Log.dump(:ts, transfer_spec)}
|
277
|
+
transfer_spec
|
318
278
|
end
|
319
|
-
|
320
|
-
|
279
|
+
when :upload, :download
|
280
|
+
# empty transfer spec for authorization request
|
281
|
+
request_transfer_spec = {}
|
282
|
+
# set requested paths depending on direction
|
283
|
+
request_transfer_spec[:paths] = if command.eql?(:download)
|
284
|
+
transfer.ts_source_paths
|
285
|
+
else
|
286
|
+
[{ destination: transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND) }]
|
321
287
|
end
|
288
|
+
# add fixed parameters if any (for COS)
|
289
|
+
@api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
|
290
|
+
# prepare payload for single request
|
291
|
+
setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
|
292
|
+
# only one request, so only one answer
|
293
|
+
transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
|
294
|
+
# delete this part, as the returned value contains only destination, and not sources
|
295
|
+
transfer_spec.delete('paths') if command.eql?(:upload)
|
322
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}")
|
323
305
|
end
|
324
306
|
raise 'INTERNAL ERROR'
|
325
307
|
end
|
@@ -330,20 +312,37 @@ module Aspera
|
|
330
312
|
case command
|
331
313
|
when *COMMANDS_GEN3
|
332
314
|
execute_command_gen3(command, prefix_path)
|
333
|
-
when :
|
334
|
-
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))
|
335
317
|
case ak_command
|
336
|
-
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
|
337
323
|
when :do
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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']
|
344
334
|
end
|
345
335
|
command_repo = options.get_next_command(COMMANDS_GEN4)
|
346
|
-
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')
|
347
346
|
end
|
348
347
|
when :health
|
349
348
|
nagios = Nagios.new
|
@@ -367,11 +366,14 @@ module Aspera
|
|
367
366
|
end
|
368
367
|
return nagios.result
|
369
368
|
when :events
|
370
|
-
events = @api_node.read('events',
|
371
|
-
return { type: :object_list, data: events}
|
369
|
+
events = @api_node.read('events', query_read_delete)[:data]
|
370
|
+
return { type: :object_list, data: events, fields: ->(f){!f.start_with?('data')} }
|
372
371
|
when :info
|
373
372
|
nd_info = @api_node.read('info')[:data]
|
374
|
-
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}
|
375
377
|
when :license
|
376
378
|
# requires: asnodeadmin -mu <node user> --acl-add=internal --internal
|
377
379
|
node_license = @api_node.read('license')[:data]
|
@@ -383,66 +385,17 @@ module Aspera
|
|
383
385
|
return { type: :single_object, data: @api_node.params }
|
384
386
|
end
|
385
387
|
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
end
|
395
|
-
case command_node_file
|
396
|
-
when :show
|
397
|
-
items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
398
|
-
return {type: :single_object, data: items}
|
399
|
-
when :modify
|
400
|
-
update_param = options.get_next_argument('update data', type: Hash)
|
401
|
-
apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
|
402
|
-
return Main.result_status('Done')
|
403
|
-
when :thumbnail
|
404
|
-
result = apifid[:api].call(
|
405
|
-
operation: 'GET',
|
406
|
-
subpath: "files/#{apifid[:file_id]}/preview",
|
407
|
-
headers: {'Accept' => 'image/png'}
|
408
|
-
)
|
409
|
-
require 'aspera/preview/terminal'
|
410
|
-
return Main.result_status(Preview::Terminal.build(result[:http].body, reserved_lines: 3))
|
411
|
-
when :permission
|
412
|
-
command_perm = options.get_next_command(%i[list create delete])
|
413
|
-
case command_perm
|
414
|
-
when :list
|
415
|
-
# generic options : TODO: as arg ? option_url_query
|
416
|
-
list_options ||= {'include' => [Rest::ARRAY_PARAMS, 'access_level', 'permission_count']}
|
417
|
-
# add which one to get
|
418
|
-
list_options['file_id'] = apifid[:file_id]
|
419
|
-
list_options['inherited'] ||= false
|
420
|
-
items = apifid[:api].read('permissions', list_options)[:data]
|
421
|
-
return {type: :object_list, data: items}
|
422
|
-
when :delete
|
423
|
-
perm_id = instance_identifier
|
424
|
-
return do_bulk_operation(perm_id, 'deleted') do |one_id|
|
425
|
-
# TODO: notify event ?
|
426
|
-
apifid[:api].delete("permissions/#{perm_id}")
|
427
|
-
{'id' => one_id}
|
428
|
-
end
|
429
|
-
when :create
|
430
|
-
create_param = options.get_next_argument('creation data', type: Hash)
|
431
|
-
raise 'no file_id' if create_param.key?('file_id')
|
432
|
-
create_param['file_id'] = apifid[:file_id]
|
433
|
-
create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
434
|
-
# add application specific tags (AoC)
|
435
|
-
the_app = apifid[:api].app_info
|
436
|
-
the_app[:api].permissions_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
|
437
|
-
# create permission
|
438
|
-
created_data = apifid[:api].create('permissions', create_param)[:data]
|
439
|
-
# notify application of creation
|
440
|
-
the_app[:api].permissions_create_event(created_data: created_data, app_info: the_app) unless the_app.nil?
|
441
|
-
return { type: :single_object, data: created_data}
|
442
|
-
else raise "internal error:shall not reach here (#{command_perm})"
|
443
|
-
end
|
444
|
-
else raise "internal error:shall not reach here (#{command_node_file})"
|
388
|
+
|
389
|
+
# @return [Hash] api and main file id for given path or id
|
390
|
+
# Allows to specify a file by its path or by its id on the node
|
391
|
+
def apifid_from_next_arg(top_file_id)
|
392
|
+
file_path = instance_identifier(description: 'path or %id:<id>') do |attribute, value|
|
393
|
+
raise 'Only selection "id" is supported (file id)' unless attribute.eql?('id')
|
394
|
+
# directly return result for method
|
395
|
+
return {api: @api_node, file_id: value}
|
445
396
|
end
|
397
|
+
# there was no selector, so it is a path
|
398
|
+
return @api_node.resolve_api_fid(top_file_id, file_path)
|
446
399
|
end
|
447
400
|
|
448
401
|
def execute_command_gen4(command_repo, top_file_id)
|
@@ -452,7 +405,7 @@ module Aspera
|
|
452
405
|
command_legacy = options.get_next_command(V3_IN_V4_ACTIONS)
|
453
406
|
# TODO: shall we support all methods here ? what if there is a link ?
|
454
407
|
apifid = @api_node.resolve_api_fid(top_file_id, '')
|
455
|
-
return Node.new(@agents
|
408
|
+
return Node.new(@agents, api: apifid[:api]).execute_action(command_legacy)
|
456
409
|
when :node_info, :bearer_token_node
|
457
410
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
458
411
|
result = {
|
@@ -467,16 +420,17 @@ module Aspera
|
|
467
420
|
when :oauth2
|
468
421
|
result[:username] = apifid[:api].params[:headers][Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY]
|
469
422
|
result[:password] = apifid[:api].oauth_token
|
470
|
-
else raise 'unknown'
|
423
|
+
else raise 'internal error: unknown auth type'
|
471
424
|
end
|
472
425
|
return {type: :single_object, data: result} if command_repo.eql?(:node_info)
|
473
|
-
|
426
|
+
# check format of bearer token
|
427
|
+
Oauth.bearer_extract(result[:password])
|
474
428
|
return Main.result_status(result[:password])
|
475
429
|
when :browse
|
476
430
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
477
431
|
file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
478
432
|
if file_info['type'].eql?('folder')
|
479
|
-
result = apifid[:api].read("files/#{apifid[:file_id]}/files",
|
433
|
+
result = apifid[:api].read("files/#{apifid[:file_id]}/files", old_query_read_delete)
|
480
434
|
items = result[:data]
|
481
435
|
formatter.display_item_count(result[:data].length, result[:http]['X-Total-Count'])
|
482
436
|
else
|
@@ -485,7 +439,7 @@ module Aspera
|
|
485
439
|
return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
|
486
440
|
when :find
|
487
441
|
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
488
|
-
test_block = Aspera::Node.
|
442
|
+
test_block = Aspera::Node.file_matcher_from_argument(options)
|
489
443
|
return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
|
490
444
|
when :mkdir
|
491
445
|
containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
|
@@ -495,20 +449,31 @@ module Aspera
|
|
495
449
|
return Main.result_status("created: #{result['name']} (id=#{result['id']})")
|
496
450
|
when :rename
|
497
451
|
file_path = options.get_next_argument('source path')
|
498
|
-
newname = options.get_next_argument('new name')
|
499
452
|
apifid = @api_node.resolve_api_fid(top_file_id, file_path)
|
453
|
+
newname = options.get_next_argument('new name')
|
500
454
|
result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
|
501
|
-
return Main.result_status("renamed
|
455
|
+
return Main.result_status("renamed to #{newname}")
|
502
456
|
when :delete
|
503
|
-
return do_bulk_operation(
|
504
|
-
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|
|
505
458
|
apifid = @api_node.resolve_api_fid(top_file_id, l_path)
|
506
459
|
result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
|
507
460
|
{'path' => l_path}
|
508
461
|
end
|
509
462
|
when :sync
|
510
|
-
|
511
|
-
|
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
|
512
477
|
when :upload
|
513
478
|
apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
|
514
479
|
return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)))
|
@@ -547,7 +512,7 @@ module Aspera
|
|
547
512
|
source_paths = [{'source' => source_folder.pop}]
|
548
513
|
source_folder = source_folder.join(Aspera::Node::PATH_SEPARATOR)
|
549
514
|
end
|
550
|
-
raise
|
515
|
+
raise Cli::BadArgument, 'one file at a time only in HTTP mode' if source_paths.length > 1
|
551
516
|
file_name = source_paths.first['source']
|
552
517
|
apifid = @api_node.resolve_api_fid(top_file_id, File.join(source_folder, file_name))
|
553
518
|
apifid[:api].call(
|
@@ -555,9 +520,62 @@ module Aspera
|
|
555
520
|
subpath: "files/#{apifid[:file_id]}/content",
|
556
521
|
save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE), file_name))
|
557
522
|
return Main.result_status("downloaded: #{file_name}")
|
558
|
-
when :
|
559
|
-
|
560
|
-
|
523
|
+
when :show
|
524
|
+
apifid = apifid_from_next_arg(top_file_id)
|
525
|
+
items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
526
|
+
return {type: :single_object, data: items}
|
527
|
+
when :modify
|
528
|
+
apifid = apifid_from_next_arg(top_file_id)
|
529
|
+
update_param = options.get_next_argument('update data', type: Hash)
|
530
|
+
apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
|
531
|
+
return Main.result_status('Done')
|
532
|
+
when :thumbnail
|
533
|
+
apifid = apifid_from_next_arg(top_file_id)
|
534
|
+
result = apifid[:api].call(
|
535
|
+
operation: 'GET',
|
536
|
+
subpath: "files/#{apifid[:file_id]}/preview",
|
537
|
+
headers: {'Accept' => 'image/png'}
|
538
|
+
)
|
539
|
+
require 'aspera/preview/terminal'
|
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))
|
545
|
+
when :permission
|
546
|
+
apifid = apifid_from_next_arg(top_file_id)
|
547
|
+
command_perm = options.get_next_command(%i[list create delete])
|
548
|
+
case command_perm
|
549
|
+
when :list
|
550
|
+
# generic options : TODO: as arg ? query_read_delete
|
551
|
+
list_options ||= {'include' => Rest.array_params(%w[access_level permission_count])}
|
552
|
+
# add which one to get
|
553
|
+
list_options['file_id'] = apifid[:file_id]
|
554
|
+
list_options['inherited'] ||= false
|
555
|
+
items = apifid[:api].read('permissions', list_options)[:data]
|
556
|
+
return {type: :object_list, data: items}
|
557
|
+
when :delete
|
558
|
+
return do_bulk_operation(command: command_perm, descr: 'identifier', values: :identifier) do |one_id|
|
559
|
+
apifid[:api].delete("permissions/#{one_id}")
|
560
|
+
# notify application of deletion
|
561
|
+
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app, types: ['permission.deleted']) unless the_app.nil?
|
562
|
+
{'id' => one_id}
|
563
|
+
end
|
564
|
+
when :create
|
565
|
+
create_param = options.get_next_argument('creation data', type: Hash)
|
566
|
+
raise 'no file_id' if create_param.key?('file_id')
|
567
|
+
create_param['file_id'] = apifid[:file_id]
|
568
|
+
create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
|
569
|
+
# add application specific tags (AoC)
|
570
|
+
the_app = apifid[:api].app_info
|
571
|
+
the_app[:api].permissions_set_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
|
572
|
+
# create permission
|
573
|
+
created_data = apifid[:api].create('permissions', create_param)[:data]
|
574
|
+
# notify application of creation
|
575
|
+
the_app[:api].permissions_send_event(created_data: created_data, app_info: the_app) unless the_app.nil?
|
576
|
+
return { type: :single_object, data: created_data}
|
577
|
+
else raise "internal error:shall not reach here (#{command_perm})"
|
578
|
+
end
|
561
579
|
else raise "INTERNAL ERROR: no case for #{command_repo}"
|
562
580
|
end # command_repo
|
563
581
|
# raise 'INTERNAL ERROR: missing return'
|
@@ -570,7 +588,7 @@ module Aspera
|
|
570
588
|
async_name = options.get_option(:sync_name)
|
571
589
|
if async_name.nil?
|
572
590
|
async_id = instance_identifier
|
573
|
-
if async_id.eql?(
|
591
|
+
if async_id.eql?(ExtendedValue::ALL) && %i[show delete].include?(command)
|
574
592
|
async_ids = @api_node.read('async/list')[:data]['sync_ids']
|
575
593
|
else
|
576
594
|
Integer(async_id) # must be integer
|
@@ -593,7 +611,7 @@ module Aspera
|
|
593
611
|
when :show
|
594
612
|
resp = @api_node.create('async/summary', post_data)[:data]['sync_summaries']
|
595
613
|
return Main.result_empty if resp.empty?
|
596
|
-
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)
|
597
615
|
return { type: :single_object, data: resp.first }
|
598
616
|
when :delete
|
599
617
|
resp = @api_node.create('async/delete', post_data)[:data]
|
@@ -610,21 +628,21 @@ module Aspera
|
|
610
628
|
# filename str
|
611
629
|
# skip int
|
612
630
|
# status int
|
613
|
-
filter =
|
631
|
+
filter = query_option
|
614
632
|
post_data.merge!(filter) unless filter.nil?
|
615
633
|
resp = @api_node.create('async/files', post_data)[:data]
|
616
634
|
data = resp['sync_files']
|
617
635
|
data = data.first[async_id] unless data.empty?
|
618
636
|
iteration_data = []
|
619
637
|
skip_ids_persistency = nil
|
620
|
-
if options.get_option(:once_only,
|
638
|
+
if options.get_option(:once_only, mandatory: true)
|
621
639
|
skip_ids_persistency = PersistencyActionOnce.new(
|
622
640
|
manager: @agents[:persistency],
|
623
641
|
data: iteration_data,
|
624
642
|
id: IdGenerator.from_list([
|
625
643
|
'sync_files',
|
626
|
-
options.get_option(:url,
|
627
|
-
options.get_option(:username,
|
644
|
+
options.get_option(:url, mandatory: true),
|
645
|
+
options.get_option(:username, mandatory: true),
|
628
646
|
async_id]))
|
629
647
|
unless iteration_data.first.nil?
|
630
648
|
data.select!{|l| l['fnid'].to_i > iteration_data.first}
|
@@ -641,6 +659,20 @@ module Aspera
|
|
641
659
|
end
|
642
660
|
end
|
643
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
|
+
|
644
676
|
ACTIONS = %i[
|
645
677
|
async
|
646
678
|
ssync
|
@@ -650,7 +682,8 @@ module Aspera
|
|
650
682
|
watch_folder
|
651
683
|
central
|
652
684
|
asperabrowser
|
653
|
-
basic_token
|
685
|
+
basic_token
|
686
|
+
bearer_token].concat(COMMON_ACTIONS).freeze
|
654
687
|
|
655
688
|
def execute_action(command=nil, prefix_path=nil)
|
656
689
|
command ||= options.get_next_command(ACTIONS)
|
@@ -659,32 +692,33 @@ module Aspera
|
|
659
692
|
when :async then return execute_async # former API
|
660
693
|
when :ssync
|
661
694
|
# newer API
|
662
|
-
sync_command = options.get_next_command(%i[bandwidth counters files
|
695
|
+
sync_command = options.get_next_command(%i[start stop bandwidth counters files state summary].concat(Plugin::ALL_OPS) - %i[modify])
|
663
696
|
case sync_command
|
664
|
-
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)}
|
665
698
|
else
|
666
|
-
|
667
|
-
|
699
|
+
asyncs_id = instance_identifier {|field, value|ssync_lookup(field, value)}
|
700
|
+
parameters = nil
|
668
701
|
if %i[start stop].include?(sync_command)
|
669
702
|
@api_node.create("asyncs/#{asyncs_id}/#{sync_command}", parameters)
|
670
|
-
return Main.result_status('
|
703
|
+
return Main.result_status('Done')
|
671
704
|
end
|
705
|
+
parameters = query_option(default: {}) if %i[bandwidth counters files].include?(sync_command)
|
672
706
|
return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
|
673
707
|
end
|
674
708
|
when :stream
|
675
709
|
command = options.get_next_command(%i[list create show modify cancel])
|
676
710
|
case command
|
677
711
|
when :list
|
678
|
-
resp = @api_node.read('ops/transfers',
|
712
|
+
resp = @api_node.read('ops/transfers', old_query_read_delete)
|
679
713
|
return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
|
680
714
|
when :create
|
681
|
-
resp = @api_node.create('streams',
|
715
|
+
resp = @api_node.create('streams', value_create_modify(command: command))
|
682
716
|
return { type: :single_object, data: resp[:data] }
|
683
717
|
when :show
|
684
718
|
resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
|
685
719
|
return { type: :other_struct, data: resp[:data] }
|
686
720
|
when :modify
|
687
|
-
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}",
|
721
|
+
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", value_create_modify(command: command))
|
688
722
|
return { type: :other_struct, data: resp[:data] }
|
689
723
|
when :cancel
|
690
724
|
resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
|
@@ -693,29 +727,86 @@ module Aspera
|
|
693
727
|
raise 'error'
|
694
728
|
end
|
695
729
|
when :transfer
|
696
|
-
command = options.get_next_command(%i[list cancel show])
|
730
|
+
command = options.get_next_command(%i[list cancel show modify bandwidth_average sessions])
|
697
731
|
res_class_path = 'ops/transfers'
|
698
|
-
if %i[cancel show].include?(command)
|
732
|
+
if %i[cancel show modify].include?(command)
|
699
733
|
one_res_id = instance_identifier
|
700
734
|
one_res_path = "#{res_class_path}/#{one_res_id}"
|
701
735
|
end
|
702
736
|
case command
|
703
737
|
when :list
|
704
|
-
|
705
|
-
query = options.get_option(:value) || options.get_option(:query)
|
706
|
-
raise 'Query must be a Hash' unless query.nil? || query.is_a?(Hash)
|
707
|
-
resp = @api_node.read(res_class_path, query)
|
738
|
+
transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
|
708
739
|
return {
|
709
740
|
type: :object_list,
|
710
|
-
data:
|
741
|
+
data: transfers_data,
|
711
742
|
fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path]
|
712
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
|
+
}
|
713
756
|
when :cancel
|
714
757
|
resp = @api_node.cancel(one_res_path)
|
715
758
|
return { type: :other_struct, data: resp[:data] }
|
716
759
|
when :show
|
717
760
|
resp = @api_node.read(one_res_path)
|
718
761
|
return { type: :other_struct, data: resp[:data] }
|
762
|
+
when :modify
|
763
|
+
resp = @api_node.update(one_res_path, options.get_next_argument('update value', type: Hash))
|
764
|
+
return { type: :other_struct, data: resp[:data] }
|
765
|
+
when :bandwidth_average
|
766
|
+
transfers_data = @api_node.read(res_class_path, query_read_delete)[:data]
|
767
|
+
# collect all key dates
|
768
|
+
bandwidth_period = {}
|
769
|
+
dir_info = %i[avg_kbps sessions].freeze
|
770
|
+
transfers_data.each do |transfer|
|
771
|
+
session = transfer
|
772
|
+
# transfer['sessions'].each do |session|
|
773
|
+
next if session['avg_rate_kbps'].zero?
|
774
|
+
bandwidth_period[session['start_time_usec']] = 0
|
775
|
+
bandwidth_period[session['end_time_usec']] = 0
|
776
|
+
# end
|
777
|
+
end
|
778
|
+
result = []
|
779
|
+
# all dates sorted numerically
|
780
|
+
all_dates = bandwidth_period.keys.sort
|
781
|
+
all_dates.each_with_index do |start_date, index|
|
782
|
+
end_date = all_dates[index + 1]
|
783
|
+
# do not process last one
|
784
|
+
break if end_date.nil?
|
785
|
+
# init data for this period
|
786
|
+
period_bandwidth = Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).each_with_object({}) do |direction, h|
|
787
|
+
h[direction] = dir_info.each_with_object({}) do |k2, h2|
|
788
|
+
h2[k2] = 0
|
789
|
+
end
|
790
|
+
end
|
791
|
+
# find all transfers that were active at this time
|
792
|
+
transfers_data.each do |transfer|
|
793
|
+
session = transfer
|
794
|
+
# transfer['sessions'].each do |session|
|
795
|
+
# skip if not information for this period
|
796
|
+
next if session['avg_rate_kbps'].zero?
|
797
|
+
# skip if not in this period
|
798
|
+
next if session['start_time_usec'] >= end_date || session['end_time_usec'] <= start_date
|
799
|
+
info = period_bandwidth[transfer['start_spec']['direction'].to_sym]
|
800
|
+
info[:avg_kbps] += session['avg_rate_kbps']
|
801
|
+
info[:sessions] += 1
|
802
|
+
# end
|
803
|
+
end
|
804
|
+
next if Fasp::TransferSpec::DIRECTION_ENUM_VALUES.map(&:to_sym).all? do |dir|
|
805
|
+
period_bandwidth[dir][:sessions].zero?
|
806
|
+
end
|
807
|
+
result.push({start: Time.at(start_date / 1_000_000), end: Time.at(end_date / 1_000_000)}.merge(period_bandwidth))
|
808
|
+
end
|
809
|
+
return { type: :object_list, data: result }
|
719
810
|
else
|
720
811
|
raise 'error'
|
721
812
|
end
|
@@ -749,15 +840,15 @@ module Aspera
|
|
749
840
|
@api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
|
750
841
|
case command
|
751
842
|
when :create
|
752
|
-
resp = @api_node.create(res_class_path,
|
843
|
+
resp = @api_node.create(res_class_path, value_create_modify(command: command))
|
753
844
|
return Main.result_status("#{resp[:data]['id']} created")
|
754
845
|
when :list
|
755
|
-
resp = @api_node.read(res_class_path,
|
846
|
+
resp = @api_node.read(res_class_path, old_query_read_delete)
|
756
847
|
return { type: :value_list, data: resp[:data]['ids'], name: 'id' }
|
757
848
|
when :show
|
758
849
|
return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
|
759
850
|
when :modify
|
760
|
-
@api_node.update(one_res_path,
|
851
|
+
@api_node.update(one_res_path, query_option(mandatory: true))
|
761
852
|
return Main.result_status("#{one_res_id} updated")
|
762
853
|
when :delete
|
763
854
|
@api_node.delete(one_res_path)
|
@@ -769,8 +860,7 @@ module Aspera
|
|
769
860
|
command = options.get_next_command(%i[session file])
|
770
861
|
validator_id = options.get_option(:validator)
|
771
862
|
validation = {'validator_id' => validator_id} unless validator_id.nil?
|
772
|
-
request_data =
|
773
|
-
request_data ||= {}
|
863
|
+
request_data = query_option(default: {})
|
774
864
|
case command
|
775
865
|
when :session
|
776
866
|
command = options.get_next_command([:list])
|
@@ -791,7 +881,7 @@ module Aspera
|
|
791
881
|
request_data.deep_merge!({'validation' => validation}) unless validation.nil?
|
792
882
|
resp = @api_node.create('services/rest/transfers/v1/files', request_data)[:data]
|
793
883
|
resp = JSON.parse(resp) if resp.is_a?(String)
|
794
|
-
Log.dump(:resp, resp)
|
884
|
+
Log.log.debug{Log.dump(:resp, resp)}
|
795
885
|
return { type: :object_list, data: resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path]}
|
796
886
|
when :modify
|
797
887
|
request_data.deep_merge!(validation) unless validation.nil?
|
@@ -801,16 +891,21 @@ module Aspera
|
|
801
891
|
end
|
802
892
|
when :asperabrowser
|
803
893
|
browse_params = {
|
804
|
-
'nodeUser' => options.get_option(:username,
|
805
|
-
'nodePW' => options.get_option(:password,
|
806
|
-
'nodeURL' => options.get_option(:url,
|
894
|
+
'nodeUser' => options.get_option(:username, mandatory: true),
|
895
|
+
'nodePW' => options.get_option(:password, mandatory: true),
|
896
|
+
'nodeURL' => options.get_option(:url, mandatory: true)
|
807
897
|
}
|
808
898
|
# encode parameters so that it looks good in url
|
809
899
|
encoded_params = Base64.strict_encode64(Zlib::Deflate.deflate(JSON.generate(browse_params))).gsub(/=+$/, '').tr('+/', '-_').reverse
|
810
900
|
OpenApplication.instance.uri(options.get_option(:asperabrowserurl) + '?goto=' + encoded_params)
|
811
901
|
return Main.result_status('done')
|
812
902
|
when :basic_token
|
813
|
-
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))
|
814
909
|
end # case command
|
815
910
|
raise 'ERROR: shall not reach this line'
|
816
911
|
end # execute_action
|