aspera-cli 4.11.0 → 4.12.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/BUGS.md +0 -1
- data/CHANGELOG.md +71 -52
- data/CONTRIBUTING.md +31 -6
- data/README.md +404 -259
- data/bin/asession +2 -2
- data/docs/test_env.conf +1 -0
- data/examples/aoc.rb +2 -2
- data/examples/dascli +11 -11
- data/examples/faspex4.rb +7 -7
- data/examples/node.rb +1 -1
- data/examples/proxy.pac +2 -2
- data/examples/server.rb +3 -3
- data/lib/aspera/aoc.rb +105 -40
- data/lib/aspera/cli/extended_value.rb +4 -4
- data/lib/aspera/cli/{formater.rb → formatter.rb} +7 -7
- data/lib/aspera/cli/listener/progress.rb +1 -1
- data/lib/aspera/cli/listener/progress_multi.rb +2 -2
- data/lib/aspera/cli/main.rb +18 -18
- data/lib/aspera/cli/manager.rb +5 -5
- data/lib/aspera/cli/plugin.rb +23 -20
- data/lib/aspera/cli/plugins/aoc.rb +75 -112
- data/lib/aspera/cli/plugins/ats.rb +6 -6
- data/lib/aspera/cli/plugins/config.rb +84 -83
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +38 -38
- data/lib/aspera/cli/plugins/faspex5.rb +187 -43
- data/lib/aspera/cli/plugins/node.rb +30 -37
- data/lib/aspera/cli/plugins/orchestrator.rb +7 -4
- data/lib/aspera/cli/plugins/preview.rb +10 -9
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/plugins/shares.rb +67 -43
- data/lib/aspera/cli/transfer_agent.rb +16 -16
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/command_line_builder.rb +70 -66
- data/lib/aspera/cos_node.rb +9 -9
- data/lib/aspera/fasp/agent_base.rb +3 -1
- data/lib/aspera/fasp/agent_connect.rb +23 -23
- data/lib/aspera/fasp/agent_direct.rb +13 -14
- data/lib/aspera/fasp/agent_httpgw.rb +20 -19
- data/lib/aspera/fasp/agent_node.rb +13 -15
- data/lib/aspera/fasp/agent_trsdk.rb +1 -1
- data/lib/aspera/fasp/installation.rb +5 -5
- data/lib/aspera/fasp/listener.rb +1 -1
- data/lib/aspera/fasp/parameters.rb +49 -41
- data/lib/aspera/fasp/parameters.yaml +311 -212
- data/lib/aspera/fasp/resume_policy.rb +2 -2
- data/lib/aspera/fasp/transfer_spec.rb +0 -13
- data/lib/aspera/faspex_gw.rb +80 -161
- data/lib/aspera/faspex_postproc.rb +77 -0
- data/lib/aspera/log.rb +7 -7
- data/lib/aspera/nagios.rb +6 -6
- data/lib/aspera/node.rb +24 -19
- data/lib/aspera/oauth.rb +50 -47
- data/lib/aspera/proxy_auto_config.js +22 -22
- data/lib/aspera/proxy_auto_config.rb +3 -3
- data/lib/aspera/rest.rb +12 -10
- data/lib/aspera/rest_error_analyzer.rb +5 -5
- data/lib/aspera/secret_hider.rb +4 -3
- data/lib/aspera/ssh.rb +4 -4
- data/lib/aspera/sync.rb +37 -36
- data/lib/aspera/web_auth.rb +7 -59
- data/lib/aspera/web_server_simple.rb +76 -0
- data.tar.gz.sig +0 -0
- metadata +6 -4
- metadata.gz.sig +0 -0
@@ -1,28 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# spellchecker: ignore workgroups,mypackages
|
4
|
+
|
3
5
|
require 'aspera/cli/basic_auth_plugin'
|
4
6
|
require 'aspera/persistency_action_once'
|
5
7
|
require 'aspera/id_generator'
|
6
8
|
require 'aspera/nagios'
|
9
|
+
require 'aspera/environment'
|
7
10
|
require 'securerandom'
|
11
|
+
require 'ruby-progressbar'
|
12
|
+
require 'tty-spinner'
|
8
13
|
|
9
14
|
module Aspera
|
10
15
|
module Cli
|
11
16
|
module Plugins
|
12
17
|
class Faspex5 < Aspera::Cli::BasicAuthPlugin
|
18
|
+
RECIPIENT_TYPES = %w[user workgroup external_user distribution_list shared_inbox].freeze
|
19
|
+
PACKAGE_TERMINATED = %w[completed failed].freeze
|
20
|
+
API_DETECT = 'api/v5/configuration/ping'
|
13
21
|
class << self
|
14
22
|
def detect(base_url)
|
15
23
|
api = Rest.new(base_url: base_url, redirect_max: 1)
|
16
|
-
result = api.read(
|
24
|
+
result = api.read(API_DETECT)
|
17
25
|
if result[:http].code.start_with?('2') && result[:http].body.strip.empty?
|
18
|
-
return {version: '5'}
|
26
|
+
return {version: '5', url: result[:http].uri.to_s[0..-(API_DETECT.length + 2)]}
|
19
27
|
end
|
20
28
|
return nil
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
24
32
|
TRANSFER_CONNECT = 'connect'
|
25
|
-
private_constant :TRANSFER_CONNECT
|
26
33
|
|
27
34
|
def initialize(env)
|
28
35
|
super(env)
|
@@ -32,42 +39,43 @@ module Aspera
|
|
32
39
|
options.add_opt_list(:auth, [:boot].concat(Oauth::STD_AUTH_TYPES), 'OAuth type of authentication')
|
33
40
|
options.add_opt_simple(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
|
34
41
|
options.add_opt_simple(:passphrase, 'RSA private key passphrase')
|
42
|
+
options.add_opt_simple(:shared_folder, 'Shared folder source for package files')
|
35
43
|
options.set_option(:auth, :jwt)
|
36
44
|
options.parse_options!
|
37
45
|
end
|
38
46
|
|
39
47
|
def set_api
|
40
|
-
@
|
41
|
-
@
|
42
|
-
|
48
|
+
@faspex5_api_base_url = options.get_option(:url, is_type: :mandatory).gsub(%r{/+$}, '')
|
49
|
+
@faspex5_api_auth_url = "#{@faspex5_api_base_url}/auth"
|
50
|
+
faspex5_api_v5_url = "#{@faspex5_api_base_url}/api/v5"
|
43
51
|
case options.get_option(:auth, is_type: :mandatory)
|
44
52
|
when :boot
|
45
53
|
# the password here is the token copied directly from browser in developer mode
|
46
54
|
@api_v5 = Rest.new({
|
47
|
-
base_url:
|
55
|
+
base_url: faspex5_api_v5_url,
|
48
56
|
headers: {'Authorization' => options.get_option(:password, is_type: :mandatory)}
|
49
57
|
})
|
50
58
|
when :web
|
51
59
|
# opens a browser and ask user to auth using web
|
52
60
|
@api_v5 = Rest.new({
|
53
|
-
base_url:
|
61
|
+
base_url: faspex5_api_v5_url,
|
54
62
|
auth: {
|
55
|
-
type:
|
56
|
-
base_url:
|
57
|
-
|
58
|
-
client_id:
|
59
|
-
web:
|
63
|
+
type: :oauth2,
|
64
|
+
base_url: @faspex5_api_auth_url,
|
65
|
+
grant_method: :web,
|
66
|
+
client_id: options.get_option(:client_id, is_type: :mandatory),
|
67
|
+
web: {redirect_uri: options.get_option(:redirect_uri, is_type: :mandatory)}
|
60
68
|
}})
|
61
69
|
when :jwt
|
62
70
|
app_client_id = options.get_option(:client_id, is_type: :mandatory)
|
63
71
|
@api_v5 = Rest.new({
|
64
|
-
base_url:
|
72
|
+
base_url: faspex5_api_v5_url,
|
65
73
|
auth: {
|
66
|
-
type:
|
67
|
-
base_url:
|
68
|
-
|
69
|
-
client_id:
|
70
|
-
jwt:
|
74
|
+
type: :oauth2,
|
75
|
+
base_url: @faspex5_api_auth_url,
|
76
|
+
grant_method: :jwt,
|
77
|
+
client_id: app_client_id,
|
78
|
+
jwt: {
|
71
79
|
payload: {
|
72
80
|
iss: app_client_id, # issuer
|
73
81
|
aud: app_client_id, # audience TODO: ???
|
@@ -81,18 +89,77 @@ module Aspera
|
|
81
89
|
end
|
82
90
|
end
|
83
91
|
|
84
|
-
|
92
|
+
def normalize_recipients(parameters)
|
93
|
+
return unless parameters.key?('recipients')
|
94
|
+
raise 'Field recipients must be an Array' unless parameters['recipients'].is_a?(Array)
|
95
|
+
parameters['recipients'] = parameters['recipients'].map do |recipient_data|
|
96
|
+
# if just a string, assume it is the name
|
97
|
+
if recipient_data.is_a?(String)
|
98
|
+
result = @api_v5.read('contacts', {q: recipient_data, context: 'packages', type: [Rest::ARRAY_PARAMS, *RECIPIENT_TYPES]})[:data]
|
99
|
+
raise "No matching contact for #{recipient_data}" if result.empty?
|
100
|
+
raise "Multiple matching contact for #{recipient_data} : #{result['contacts'].map{|i|i['name']}.join(', ')}" unless 1.eql?(result['total_count'])
|
101
|
+
matched = result['contacts'].first
|
102
|
+
recipient_data = {
|
103
|
+
name: matched['name'],
|
104
|
+
recipient_type: matched['type']
|
105
|
+
}
|
106
|
+
end
|
107
|
+
# result for mapping
|
108
|
+
recipient_data
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def wait_for_complete_upload(id)
|
113
|
+
parameters = options.get_option(:value)
|
114
|
+
spinner = nil
|
115
|
+
progress = nil
|
116
|
+
while true
|
117
|
+
status = @api_v5.read("packages/#{id}/upload_details")[:data]
|
118
|
+
# user asked to not follow
|
119
|
+
break unless parameters
|
120
|
+
if status['upload_status'].eql?('submitted')
|
121
|
+
if spinner.nil?
|
122
|
+
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
123
|
+
spinner.start
|
124
|
+
end
|
125
|
+
spinner.update(title: status['upload_status'])
|
126
|
+
spinner.spin
|
127
|
+
elsif progress.nil?
|
128
|
+
progress = ProgressBar.create(
|
129
|
+
format: '%a %B %p%% %r Mbps %e',
|
130
|
+
rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
|
131
|
+
title: 'progress',
|
132
|
+
total: status['bytes_total'].to_i)
|
133
|
+
else
|
134
|
+
progress.progress = status['bytes_written'].to_i
|
135
|
+
end
|
136
|
+
break if PACKAGE_TERMINATED.include?(status['upload_status'])
|
137
|
+
sleep(0.5)
|
138
|
+
end
|
139
|
+
status['id'] = id
|
140
|
+
return status
|
141
|
+
end
|
142
|
+
|
143
|
+
def lookup_entity(entity_type, property, value)
|
144
|
+
# TODO: what if too many, use paging ?
|
145
|
+
all = @api_v5.read(entity_type)[:data][entity_type]
|
146
|
+
found = all.find{|i|i[property].eql?(value)}
|
147
|
+
raise "No #{entity_type} with #{property} = #{value}" if found.nil?
|
148
|
+
return found
|
149
|
+
end
|
150
|
+
|
151
|
+
ACTIONS = %i[health version user bearer_token package shared_folders admin gateway postprocessing].freeze
|
85
152
|
|
86
153
|
def execute_action
|
87
|
-
set_api
|
88
154
|
command = options.get_next_command(ACTIONS)
|
155
|
+
set_api unless command.eql?(:postprocessing)
|
89
156
|
case command
|
90
157
|
when :version
|
91
158
|
return { type: :single_object, data: @api_v5.read('version')[:data] }
|
92
159
|
when :health
|
93
160
|
nagios = Nagios.new
|
94
161
|
begin
|
95
|
-
result = Rest.new(base_url: @
|
162
|
+
result = Rest.new(base_url: @faspex5_api_base_url).read('health')[:data]
|
96
163
|
result.each do |k, v|
|
97
164
|
nagios.add_ok(k, v.to_s)
|
98
165
|
end
|
@@ -114,7 +181,7 @@ module Aspera
|
|
114
181
|
when :bearer_token
|
115
182
|
return {type: :text, data: @api_v5.oauth_token}
|
116
183
|
when :package
|
117
|
-
command = options.get_next_command(%i[list show send receive])
|
184
|
+
command = options.get_next_command(%i[list show send receive status])
|
118
185
|
case command
|
119
186
|
when :list
|
120
187
|
parameters = options.get_option(:value)
|
@@ -126,20 +193,40 @@ module Aspera
|
|
126
193
|
when :show
|
127
194
|
id = instance_identifier
|
128
195
|
return {type: :single_object, data: @api_v5.read("packages/#{id}")[:data]}
|
196
|
+
when :status
|
197
|
+
status = wait_for_complete_upload(instance_identifier)
|
198
|
+
return {type: :single_object, data: status}
|
129
199
|
when :send
|
130
200
|
parameters = options.get_option(:value, is_type: :mandatory)
|
131
|
-
raise CliBadArgument, '
|
201
|
+
raise CliBadArgument, 'Value must be Hash, refer to API' unless parameters.is_a?(Hash)
|
202
|
+
normalize_recipients(parameters)
|
132
203
|
package = @api_v5.create('packages', parameters)[:data]
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
204
|
+
shared_folder = options.get_option(:shared_folder)
|
205
|
+
if shared_folder.nil?
|
206
|
+
# TODO: option to send from remote source or httpgw
|
207
|
+
transfer_spec = @api_v5.call(
|
208
|
+
operation: 'POST',
|
209
|
+
subpath: "packages/#{package['id']}/transfer_spec/upload",
|
210
|
+
headers: {'Accept' => 'application/json'},
|
211
|
+
url_params: {transfer_type: TRANSFER_CONNECT},
|
212
|
+
json_params: {paths: transfer.source_list}
|
213
|
+
)[:data]
|
214
|
+
transfer_spec.delete('authentication')
|
215
|
+
return Main.result_transfer(transfer.start(transfer_spec))
|
216
|
+
else
|
217
|
+
if !shared_folder.to_i.to_s.eql?(shared_folder)
|
218
|
+
shared_folder = lookup_entity('shared_folders', 'name', shared_folder)['id']
|
219
|
+
end
|
220
|
+
transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
|
221
|
+
# start remote transfer and get first status
|
222
|
+
result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)[:data]
|
223
|
+
result['id'] = package['id']
|
224
|
+
unless result['status'].eql?('completed')
|
225
|
+
formatter.display_status("Package #{package['id']}")
|
226
|
+
result = wait_for_complete_upload(package['id'])
|
227
|
+
end
|
228
|
+
return {type: :single_object, data: result}
|
229
|
+
end
|
143
230
|
when :receive
|
144
231
|
pkg_type = 'received'
|
145
232
|
pack_id = instance_identifier
|
@@ -166,39 +253,67 @@ module Aspera
|
|
166
253
|
package_ids.reject!{|i|skip_ids_data.include?(i)}
|
167
254
|
end
|
168
255
|
result_transfer = []
|
169
|
-
package_ids.each do |
|
256
|
+
package_ids.each do |pkg_id|
|
170
257
|
param_file_list = {}
|
171
258
|
begin
|
172
|
-
param_file_list['paths'] = transfer.
|
259
|
+
param_file_list['paths'] = transfer.source_list
|
173
260
|
rescue Aspera::Cli::CliBadArgument
|
174
261
|
# paths is optional
|
175
262
|
end
|
176
263
|
# TODO: allow from sent as well ?
|
177
264
|
transfer_spec = @api_v5.call(
|
178
265
|
operation: 'POST',
|
179
|
-
subpath: "packages/#{
|
266
|
+
subpath: "packages/#{pkg_id}/transfer_spec/download",
|
180
267
|
headers: {'Accept' => 'application/json'},
|
181
268
|
url_params: {transfer_type: TRANSFER_CONNECT, type: pkg_type},
|
182
269
|
json_params: param_file_list
|
183
270
|
)[:data]
|
184
271
|
transfer_spec.delete('authentication')
|
185
272
|
statuses = transfer.start(transfer_spec)
|
186
|
-
result_transfer.push({'package' =>
|
273
|
+
result_transfer.push({'package' => pkg_id, Main::STATUS_FIELD => statuses})
|
187
274
|
# skip only if all sessions completed
|
188
|
-
skip_ids_data.push(
|
275
|
+
skip_ids_data.push(pkg_id) if TransferAgent.session_status(statuses).eql?(:success)
|
189
276
|
end
|
190
277
|
skip_ids_persistency&.save
|
191
278
|
return Main.result_transfer_multiple(result_transfer)
|
192
279
|
end # case package
|
280
|
+
when :shared_folders
|
281
|
+
all_shared_folders = @api_v5.read('shared_folders')[:data]['shared_folders']
|
282
|
+
case options.get_next_command(%i[list browse])
|
283
|
+
when :list
|
284
|
+
return {type: :object_list, data: all_shared_folders}
|
285
|
+
when :browse
|
286
|
+
shared_folder_id = instance_identifier
|
287
|
+
path = options.get_next_argument('folder path', mandatory: false) || '/'
|
288
|
+
node = all_shared_folders.find{|i|i['id'].eql?(shared_folder_id)}
|
289
|
+
raise "No such shared folder id #{shared_folder_id}" if node.nil?
|
290
|
+
result = @api_v5.call({
|
291
|
+
operation: 'POST',
|
292
|
+
subpath: "nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse",
|
293
|
+
headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
|
294
|
+
json_params: {'path': path, 'filters': {'basenames': []}},
|
295
|
+
url_params: {offset: 0, limit: 100}
|
296
|
+
})[:data]
|
297
|
+
if result.key?('items')
|
298
|
+
return {type: :object_list, data: result['items']}
|
299
|
+
else
|
300
|
+
return {type: :single_object, data: result['self']}
|
301
|
+
end
|
302
|
+
end
|
193
303
|
when :admin
|
194
|
-
case options.get_next_command([
|
304
|
+
case options.get_next_command(%i[resource])
|
195
305
|
when :resource
|
196
|
-
res_type = options.get_next_command(%i[accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs metadata_profiles
|
306
|
+
res_type = options.get_next_command(%i[accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs metadata_profiles
|
307
|
+
email_notifications])
|
197
308
|
res_path = list_key = res_type.to_s
|
309
|
+
id_as_arg = false
|
198
310
|
case res_type
|
199
311
|
when :metadata_profiles
|
200
312
|
res_path = 'configuration/metadata_profiles'
|
201
313
|
list_key = 'profiles'
|
314
|
+
when :email_notifications
|
315
|
+
list_key = false
|
316
|
+
id_as_arg = 'type'
|
202
317
|
end
|
203
318
|
display_fields =
|
204
319
|
case res_type
|
@@ -207,10 +322,39 @@ module Aspera
|
|
207
322
|
end
|
208
323
|
adm_api = @api_v5
|
209
324
|
if res_type.eql?(:oauth_clients)
|
210
|
-
adm_api = Rest.new(@api_v5.params.merge({base_url: @
|
325
|
+
adm_api = Rest.new(@api_v5.params.merge({base_url: @faspex5_api_auth_url}))
|
211
326
|
end
|
212
|
-
return entity_action(adm_api, res_path, item_list_key: list_key, display_fields: display_fields)
|
327
|
+
return entity_action(adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg)
|
213
328
|
end
|
329
|
+
when :gateway
|
330
|
+
require 'aspera/faspex_gw'
|
331
|
+
url = options.get_option(:value, is_type: :mandatory)
|
332
|
+
uri = URI.parse(url)
|
333
|
+
server = WebServerSimple.new(uri)
|
334
|
+
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
335
|
+
trap('INT') { server.shutdown }
|
336
|
+
formatter.display_status("Faspex 4 gateway listening on #{url}")
|
337
|
+
Log.log.info("Listening on #{url}")
|
338
|
+
# this is blocking until server exits
|
339
|
+
server.start
|
340
|
+
return Main.result_status('Gateway terminated')
|
341
|
+
when :postprocessing
|
342
|
+
require 'aspera/faspex_postproc'
|
343
|
+
parameters = options.get_option(:value, is_type: :mandatory)
|
344
|
+
raise 'parameters must be Hash' unless parameters.is_a?(Hash)
|
345
|
+
parameters = parameters.symbolize_keys
|
346
|
+
raise 'Missing key: url' unless parameters.key?(:url)
|
347
|
+
uri = URI.parse(parameters[:url])
|
348
|
+
parameters[:processing] ||= {}
|
349
|
+
parameters[:processing][:root] = uri.path
|
350
|
+
server = WebServerSimple.new(uri, certificate: parameters[:certificate])
|
351
|
+
server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
|
352
|
+
trap('INT') { server.shutdown }
|
353
|
+
formatter.display_status("Faspex 4 post processing listening on #{uri.port}")
|
354
|
+
Log.log.info("Listening on #{uri.port}")
|
355
|
+
# this is blocking until server exits
|
356
|
+
server.start
|
357
|
+
return Main.result_status('Gateway terminated')
|
214
358
|
end # case command
|
215
359
|
end # action
|
216
360
|
end # Faspex5
|
@@ -40,12 +40,14 @@ module Aspera
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
# spellchecker: disable
|
43
44
|
# SOAP API call to test central API
|
44
45
|
CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>'\
|
45
46
|
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">'\
|
46
47
|
'<soapenv:Header></soapenv:Header>'\
|
47
48
|
'<soapenv:Body><typ:GetSessionInfoRequest><SessionFilter><SessionStatus>running</SessionStatus></SessionFilter></typ:GetSessionInfoRequest></soapenv:Body>'\
|
48
49
|
'</soapenv:Envelope>'
|
50
|
+
# spellchecker: enable
|
49
51
|
|
50
52
|
# fields removed in result of search
|
51
53
|
SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
|
@@ -88,8 +90,8 @@ module Aspera
|
|
88
90
|
Aspera::Node.new(params: {
|
89
91
|
base_url: options.get_option(:url, is_type: :mandatory),
|
90
92
|
headers: {
|
91
|
-
Aspera::Node::
|
92
|
-
'Authorization'
|
93
|
+
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options.get_option(:username, is_type: :mandatory),
|
94
|
+
'Authorization' => options.get_option(:password, is_type: :mandatory)
|
93
95
|
}
|
94
96
|
})
|
95
97
|
else
|
@@ -141,10 +143,10 @@ module Aspera
|
|
141
143
|
end
|
142
144
|
|
143
145
|
# translates paths results into CLI result, and removes prefix
|
144
|
-
def c_result_translate_rem_prefix(
|
146
|
+
def c_result_translate_rem_prefix(response, type, success_msg, path_prefix)
|
145
147
|
errors = []
|
146
148
|
resres = { data: [], type: :object_list, fields: [type, 'result']}
|
147
|
-
JSON.parse(
|
149
|
+
JSON.parse(response[:http].body)['paths'].each do |p|
|
148
150
|
result = success_msg
|
149
151
|
if p.key?('error')
|
150
152
|
Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
|
@@ -162,10 +164,10 @@ module Aspera
|
|
162
164
|
|
163
165
|
# get path arguments from command line, and add prefix
|
164
166
|
def get_next_arg_add_prefix(path_prefix, name, number=:single)
|
165
|
-
|
166
|
-
return
|
167
|
-
return File.join(path_prefix,
|
168
|
-
return
|
167
|
+
path_or_list = options.get_next_argument(name, expected: number)
|
168
|
+
return path_or_list if path_prefix.nil?
|
169
|
+
return File.join(path_prefix, path_or_list) if path_or_list.is_a?(String)
|
170
|
+
return path_or_list.map {|p| File.join(path_prefix, p)} if path_or_list.is_a?(Array)
|
169
171
|
raise StandardError, 'expect: nil, String or Array'
|
170
172
|
end
|
171
173
|
|
@@ -185,8 +187,8 @@ module Aspera
|
|
185
187
|
result = { type: :object_list, data: resp[:data]['items']}
|
186
188
|
return Main.result_empty if result[:data].empty?
|
187
189
|
result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
|
188
|
-
|
189
|
-
|
190
|
+
formatter.display_status("Items: #{resp[:data]['item_count']}/#{resp[:data]['total_count']}")
|
191
|
+
formatter.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
|
190
192
|
return c_result_remove_prefix_path(result, 'path', prefix_path)
|
191
193
|
when :space
|
192
194
|
path_list = get_next_arg_add_prefix(prefix_path, 'folder path or ext.val. list')
|
@@ -217,8 +219,7 @@ module Aspera
|
|
217
219
|
resp = @api_node.create('files/rename', { 'paths' => [{ 'path' => path_base, 'source' => path_src, 'destination' => path_dst }] })
|
218
220
|
return c_result_translate_rem_prefix(resp, 'entry', 'moved', prefix_path)
|
219
221
|
when :browse
|
220
|
-
|
221
|
-
query = { path: thepath}
|
222
|
+
query = { path: get_next_arg_add_prefix(prefix_path, 'path')}
|
222
223
|
additional_query = options.get_option(:query)
|
223
224
|
query.merge!(additional_query) unless additional_query.nil?
|
224
225
|
send_result = @api_node.create('files/browse', query)[:data]
|
@@ -227,7 +228,7 @@ module Aspera
|
|
227
228
|
case send_result['self']['type']
|
228
229
|
when 'directory', 'container' # directory: node, container: shares
|
229
230
|
result = { data: send_result['items'], type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
230
|
-
|
231
|
+
formatter.display_status("Items: #{send_result['item_count']}/#{send_result['total_count']}")
|
231
232
|
else # 'file','symbolic_link'
|
232
233
|
result = { data: send_result['self'], type: :single_object}
|
233
234
|
# result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
|
@@ -351,7 +352,7 @@ module Aspera
|
|
351
352
|
case command_perm
|
352
353
|
when :list
|
353
354
|
# generic options : TODO: as arg ? option_url_query
|
354
|
-
list_options ||= {'include' => [
|
355
|
+
list_options ||= {'include' => [Rest::ARRAY_PARAMS, 'access_level', 'permission_count']}
|
355
356
|
# add which one to get
|
356
357
|
list_options['file_id'] = apifid[:file_id]
|
357
358
|
list_options['inherited'] ||= false
|
@@ -392,8 +393,7 @@ module Aspera
|
|
392
393
|
apifid = @api_node.resolve_api_fid(top_file_id, '')
|
393
394
|
return Node.new(@agents.merge(skip_basic_auth_options: true, skip_node_options: true, node_api: apifid[:api])).execute_action(command_legacy)
|
394
395
|
when :node_info, :bearer_token_node
|
395
|
-
|
396
|
-
apifid = @api_node.resolve_api_fid(top_file_id, thepath)
|
396
|
+
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
397
397
|
result = {
|
398
398
|
url: apifid[:api].params[:base_url],
|
399
399
|
root_id: apifid[:file_id]
|
@@ -404,7 +404,7 @@ module Aspera
|
|
404
404
|
result[:username] = apifid[:api].params[:auth][:username]
|
405
405
|
result[:password] = apifid[:api].params[:auth][:password]
|
406
406
|
when :oauth2
|
407
|
-
result[:username] = apifid[:api].params[:headers][Aspera::Node::
|
407
|
+
result[:username] = apifid[:api].params[:headers][Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY]
|
408
408
|
result[:password] = apifid[:api].oauth_token
|
409
409
|
else raise 'unknown'
|
410
410
|
end
|
@@ -412,38 +412,34 @@ module Aspera
|
|
412
412
|
raise 'not bearer token' unless result[:password].start_with?('Bearer ')
|
413
413
|
return Main.result_status(result[:password])
|
414
414
|
when :browse
|
415
|
-
|
416
|
-
apifid = @api_node.resolve_api_fid(top_file_id, thepath)
|
415
|
+
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
417
416
|
file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
|
418
417
|
if file_info['type'].eql?('folder')
|
419
418
|
result = apifid[:api].read("files/#{apifid[:file_id]}/files", options.get_option(:value))
|
420
419
|
items = result[:data]
|
421
|
-
|
420
|
+
formatter.display_status("Items: #{result[:data].length}/#{result[:http]['X-Total-Count']}")
|
422
421
|
else
|
423
422
|
items = [file_info]
|
424
423
|
end
|
425
424
|
return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
|
426
425
|
when :find
|
427
|
-
|
428
|
-
apifid = @api_node.resolve_api_fid(top_file_id, thepath)
|
426
|
+
apifid = @api_node.resolve_api_fid(top_file_id, options.get_next_argument('path'))
|
429
427
|
test_block = Aspera::Node.file_matcher(options.get_option(:value))
|
430
428
|
return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
|
431
429
|
when :mkdir
|
432
|
-
|
433
|
-
containing_folder_path = thepath.split(Aspera::Node::PATH_SEPARATOR)
|
430
|
+
containing_folder_path = options.get_next_argument('path').split(Aspera::Node::PATH_SEPARATOR)
|
434
431
|
new_folder = containing_folder_path.pop
|
435
432
|
apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Aspera::Node::PATH_SEPARATOR))
|
436
433
|
result = apifid[:api].create("files/#{apifid[:file_id]}/files", {name: new_folder, type: :folder})[:data]
|
437
434
|
return Main.result_status("created: #{result['name']} (id=#{result['id']})")
|
438
435
|
when :rename
|
439
|
-
|
436
|
+
file_path = options.get_next_argument('source path')
|
440
437
|
newname = options.get_next_argument('new name')
|
441
|
-
apifid = @api_node.resolve_api_fid(top_file_id,
|
438
|
+
apifid = @api_node.resolve_api_fid(top_file_id, file_path)
|
442
439
|
result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
|
443
|
-
return Main.result_status("renamed #{
|
440
|
+
return Main.result_status("renamed #{file_path} to #{newname}")
|
444
441
|
when :delete
|
445
|
-
|
446
|
-
return do_bulk_operation(thepath, 'deleted', id_result: 'path') do |l_path|
|
442
|
+
return do_bulk_operation(options.get_next_argument('path'), 'deleted', id_result: 'path') do |l_path|
|
447
443
|
raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
|
448
444
|
apifid = @api_node.resolve_api_fid(top_file_id, l_path)
|
449
445
|
result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
|
@@ -517,7 +513,7 @@ module Aspera
|
|
517
513
|
asyncname = options.get_option(:sync_name)
|
518
514
|
if asyncname.nil?
|
519
515
|
asyncid = instance_identifier
|
520
|
-
if asyncid.eql?(
|
516
|
+
if asyncid.eql?(VAL_ALL) && %i[show delete].include?(command)
|
521
517
|
asyncids = @api_node.read('async/list')[:data]['sync_ids']
|
522
518
|
else
|
523
519
|
Integer(asyncid) # must be integer
|
@@ -540,7 +536,7 @@ module Aspera
|
|
540
536
|
when :show
|
541
537
|
resp = @api_node.create('async/summary', pdata)[:data]['sync_summaries']
|
542
538
|
return Main.result_empty if resp.empty?
|
543
|
-
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if asyncid.eql?(
|
539
|
+
return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if asyncid.eql?(VAL_ALL)
|
544
540
|
return { type: :single_object, data: resp.first }
|
545
541
|
when :delete
|
546
542
|
resp = @api_node.create('async/delete', pdata)[:data]
|
@@ -628,16 +624,13 @@ module Aspera
|
|
628
624
|
resp = @api_node.create('streams', options.get_option(:value, is_type: :mandatory))
|
629
625
|
return { type: :single_object, data: resp[:data] }
|
630
626
|
when :show
|
631
|
-
|
632
|
-
resp = @api_node.read('ops/transfers/' + trid)
|
627
|
+
resp = @api_node.read("ops/transfers/#{options.get_next_argument('transfer id')}")
|
633
628
|
return { type: :other_struct, data: resp[:data] }
|
634
629
|
when :modify
|
635
|
-
|
636
|
-
resp = @api_node.update('streams/' + trid, options.get_option(:value, is_type: :mandatory))
|
630
|
+
resp = @api_node.update("streams/#{options.get_next_argument('transfer id')}", options.get_option(:value, is_type: :mandatory))
|
637
631
|
return { type: :other_struct, data: resp[:data] }
|
638
632
|
when :cancel
|
639
|
-
|
640
|
-
resp = @api_node.cancel('streams/' + trid)
|
633
|
+
resp = @api_node.cancel("streams/#{options.get_next_argument('transfer id')}")
|
641
634
|
return { type: :other_struct, data: resp[:data] }
|
642
635
|
else
|
643
636
|
raise 'error'
|
@@ -123,13 +123,16 @@ module Aspera
|
|
123
123
|
case command
|
124
124
|
when :status
|
125
125
|
options = {}
|
126
|
-
options[:id] = wf_id unless wf_id.eql?(
|
126
|
+
options[:id] = wf_id unless wf_id.eql?(VAL_ALL)
|
127
127
|
result = call_ao('workflows_status', options)[:data]
|
128
128
|
return {type: :object_list, data: result['workflows']['workflow']}
|
129
129
|
when :list
|
130
130
|
result = call_ao('workflows_list', id: 0)[:data]
|
131
|
-
return {
|
132
|
-
|
131
|
+
return {
|
132
|
+
type: :object_list,
|
133
|
+
data: result['workflows']['workflow'],
|
134
|
+
fields: %w[id portable_id name published_status published_revision_id latest_revision_id last_modification]
|
135
|
+
}
|
133
136
|
when :details
|
134
137
|
result = call_ao('workflow_details', id: wf_id)[:data]
|
135
138
|
return {type: :object_list, data: result['workflows']['workflow']['statuses']}
|
@@ -160,7 +163,7 @@ fields: %w[id portable_id name published_status published_revision_id latest_rev
|
|
160
163
|
raise "Expects: work_step:result_name format, but got #{expected}" if fields.length != 2
|
161
164
|
call_params['explicit_output_step'] = fields[0]
|
162
165
|
call_params['explicit_output_variable'] = fields[1]
|
163
|
-
#
|
166
|
+
# implicitly, call is synchronous
|
164
167
|
call_params['synchronous'] = true
|
165
168
|
end
|
166
169
|
if call_params['synchronous']
|