aspera-cli 4.15.0 → 4.17.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 +29 -3
- data/CHANGELOG.md +375 -280
- data/CONTRIBUTING.md +71 -18
- data/README.md +1978 -1656
- data/bin/ascli +13 -31
- data/bin/asession +32 -22
- data/examples/dascli +2 -2
- data/lib/aspera/agent/alpha.rb +117 -0
- data/lib/aspera/agent/base.rb +61 -0
- data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
- data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
- data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
- data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
- data/lib/aspera/agent/trsdk.rb +188 -0
- data/lib/aspera/api/aoc.rb +586 -0
- data/lib/aspera/api/ats.rb +46 -0
- data/lib/aspera/api/cos_node.rb +95 -0
- data/lib/aspera/api/node.rb +344 -0
- data/lib/aspera/ascmd.rb +47 -14
- data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
- data/lib/aspera/{fasp → ascp}/management.rb +14 -14
- data/lib/aspera/{fasp → ascp}/products.rb +1 -1
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formatter.rb +27 -14
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/main.rb +49 -29
- data/lib/aspera/cli/manager.rb +46 -36
- data/lib/aspera/cli/plugin.rb +34 -20
- data/lib/aspera/cli/plugin_factory.rb +61 -0
- data/lib/aspera/cli/plugins/alee.rb +7 -7
- data/lib/aspera/cli/plugins/aoc.rb +168 -132
- data/lib/aspera/cli/plugins/ats.rb +33 -33
- data/lib/aspera/cli/plugins/bss.rb +3 -4
- data/lib/aspera/cli/plugins/config.rb +250 -272
- data/lib/aspera/cli/plugins/console.rb +8 -6
- data/lib/aspera/cli/plugins/cos.rb +20 -19
- data/lib/aspera/cli/plugins/faspex.rb +71 -60
- data/lib/aspera/cli/plugins/faspex5.rb +212 -133
- data/lib/aspera/cli/plugins/node.rb +83 -75
- data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
- data/lib/aspera/cli/plugins/preview.rb +33 -31
- data/lib/aspera/cli/plugins/server.rb +33 -32
- data/lib/aspera/cli/plugins/shares.rb +39 -33
- data/lib/aspera/cli/sync_actions.rb +9 -9
- data/lib/aspera/cli/transfer_agent.rb +45 -25
- data/lib/aspera/cli/transfer_progress.rb +2 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +5 -0
- data/lib/aspera/command_line_builder.rb +16 -14
- data/lib/aspera/coverage.rb +21 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +5 -4
- data/lib/aspera/faspex_gw.rb +13 -11
- data/lib/aspera/faspex_postproc.rb +6 -5
- data/lib/aspera/id_generator.rb +4 -2
- data/lib/aspera/json_rpc.rb +10 -8
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +29 -22
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node_simulator.rb +213 -0
- data/lib/aspera/oauth/base.rb +143 -0
- data/lib/aspera/oauth/factory.rb +124 -0
- data/lib/aspera/oauth/generic.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +51 -0
- data/lib/aspera/oauth/url_json.rb +31 -0
- data/lib/aspera/oauth/web.rb +50 -0
- data/lib/aspera/oauth.rb +5 -328
- data/lib/aspera/open_application.rb +7 -7
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/preview/file_types.rb +53 -267
- data/lib/aspera/preview/generator.rb +7 -5
- data/lib/aspera/preview/terminal.rb +17 -7
- data/lib/aspera/preview/utils.rb +8 -7
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +187 -140
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +5 -3
- data/lib/aspera/resumer.rb +77 -0
- data/lib/aspera/secret_hider.rb +5 -2
- data/lib/aspera/ssh.rb +15 -8
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/{fasp → transfer}/error.rb +3 -3
- data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
- data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
- data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
- data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
- data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
- data/lib/aspera/transfer/sync.rb +273 -0
- data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
- data/lib/aspera/web_server_simple.rb +12 -3
- data.tar.gz.sig +0 -0
- metadata +92 -68
- metadata.gz.sig +0 -0
- data/lib/aspera/aoc.rb +0 -606
- data/lib/aspera/ats_api.rb +0 -47
- data/lib/aspera/cos_node.rb +0 -93
- data/lib/aspera/fasp/agent_aspera.rb +0 -126
- data/lib/aspera/fasp/agent_base.rb +0 -48
- data/lib/aspera/fasp/agent_trsdk.rb +0 -146
- data/lib/aspera/fasp/resume_policy.rb +0 -77
- data/lib/aspera/node.rb +0 -338
- data/lib/aspera/sync.rb +0 -219
|
@@ -3,57 +3,73 @@
|
|
|
3
3
|
# spellchecker: ignore workgroups mypackages passcode
|
|
4
4
|
|
|
5
5
|
require 'aspera/cli/basic_auth_plugin'
|
|
6
|
+
require 'aspera/cli/extended_value'
|
|
6
7
|
require 'aspera/persistency_action_once'
|
|
7
8
|
require 'aspera/id_generator'
|
|
8
9
|
require 'aspera/nagios'
|
|
9
10
|
require 'aspera/environment'
|
|
11
|
+
require 'aspera/assert'
|
|
10
12
|
require 'securerandom'
|
|
11
13
|
require 'tty-spinner'
|
|
12
14
|
|
|
13
15
|
module Aspera
|
|
14
16
|
module Cli
|
|
15
17
|
module Plugins
|
|
16
|
-
class Faspex5 <
|
|
18
|
+
class Faspex5 < Cli::BasicAuthPlugin
|
|
17
19
|
RECIPIENT_TYPES = %w[user workgroup external_user distribution_list shared_inbox].freeze
|
|
18
20
|
PACKAGE_TERMINATED = %w[completed failed].freeze
|
|
19
|
-
API_DETECT = 'api/v5/configuration/ping'
|
|
20
21
|
# list of supported mailbox types (to list packages)
|
|
21
22
|
API_LIST_MAILBOX_TYPES = %w[inbox inbox_history inbox_all inbox_all_history outbox outbox_history pending pending_history all].freeze
|
|
22
|
-
PACKAGE_ALL_INIT = 'INIT'
|
|
23
23
|
PACKAGE_SEND_FROM_REMOTE_SOURCE = 'remote_source'
|
|
24
24
|
# Faspex API v5: get transfer spec for connect
|
|
25
25
|
TRANSFER_CONNECT = 'connect'
|
|
26
26
|
ADMIN_RESOURCES = %i[
|
|
27
27
|
accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
|
|
28
|
-
metadata_profiles email_notifications
|
|
28
|
+
metadata_profiles email_notifications alternate_addresses
|
|
29
29
|
].freeze
|
|
30
|
+
# states for jobs not in final state
|
|
30
31
|
JOB_RUNNING = %w[queued working].freeze
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
PATH_STANDARD_ROOT = '/aspera/faspex'
|
|
33
|
+
PATH_API_V5 = 'api/v5'
|
|
34
|
+
# endpoint for authentication API
|
|
35
|
+
PATH_AUTH = 'auth'
|
|
36
|
+
PATH_HEALTH = 'configuration/ping'
|
|
37
|
+
PER_PAGE_DEFAULT = 100
|
|
38
|
+
# OAuth methods supported
|
|
39
|
+
STD_AUTH_TYPES = %i[web jwt boot].freeze
|
|
40
|
+
HEADER_ITERATION_TOKEN = 'X-Aspera-Next-Iteration-Token'
|
|
41
|
+
private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED PATH_HEALTH API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE PER_PAGE_DEFAULT
|
|
42
|
+
STD_AUTH_TYPES])
|
|
33
43
|
class << self
|
|
34
44
|
def application_name
|
|
35
45
|
'Faspex'
|
|
36
46
|
end
|
|
37
47
|
|
|
38
48
|
def detect(address_or_url)
|
|
49
|
+
# add scheme if missing
|
|
39
50
|
address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
|
|
40
51
|
urls = [address_or_url]
|
|
41
|
-
urls.push("#{address_or_url}#{
|
|
42
|
-
|
|
52
|
+
urls.push("#{address_or_url}#{PATH_STANDARD_ROOT}") unless address_or_url.end_with?(PATH_STANDARD_ROOT)
|
|
53
|
+
error = nil
|
|
43
54
|
urls.each do |base_url|
|
|
55
|
+
# Faspex is always HTTPS
|
|
44
56
|
next unless base_url.start_with?('https://')
|
|
45
57
|
api = Rest.new(base_url: base_url, redirect_max: 1)
|
|
46
|
-
|
|
58
|
+
path_api_detect = "#{PATH_API_V5}/#{PATH_HEALTH}"
|
|
59
|
+
result = api.read(path_api_detect)
|
|
47
60
|
next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
|
|
48
|
-
|
|
61
|
+
# end is at -1, and substract 1 for "/"
|
|
62
|
+
url_length = -2 - path_api_detect.length
|
|
49
63
|
# take redirect if any
|
|
50
64
|
return {
|
|
51
65
|
version: result[:http]['x-ibm-aspera'] || '5',
|
|
52
66
|
url: result[:http].uri.to_s[0..url_length]
|
|
53
67
|
}
|
|
54
68
|
rescue StandardError => e
|
|
69
|
+
error = e
|
|
55
70
|
Log.log.debug{"detect error: #{e}"}
|
|
56
71
|
end
|
|
72
|
+
raise error if error
|
|
57
73
|
return nil
|
|
58
74
|
end
|
|
59
75
|
|
|
@@ -89,31 +105,25 @@ module Aspera
|
|
|
89
105
|
}
|
|
90
106
|
end
|
|
91
107
|
|
|
108
|
+
# @return true if the URL is a public link
|
|
92
109
|
def public_link?(url)
|
|
93
|
-
url.include?('
|
|
110
|
+
url.include?('?context=')
|
|
94
111
|
end
|
|
95
112
|
end
|
|
96
113
|
|
|
97
|
-
def initialize(env)
|
|
98
|
-
super
|
|
114
|
+
def initialize(**env)
|
|
115
|
+
super
|
|
99
116
|
options.declare(:client_id, 'OAuth client identifier')
|
|
100
117
|
options.declare(:client_secret, 'OAuth client secret')
|
|
101
118
|
options.declare(:redirect_uri, 'OAuth redirect URI for web authentication')
|
|
102
|
-
options.declare(:auth, 'OAuth type of authentication', values:
|
|
119
|
+
options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
|
|
103
120
|
options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
|
|
104
121
|
options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
|
|
105
|
-
options.declare(:box, "Package inbox, either shared inbox name or one of #{API_LIST_MAILBOX_TYPES} or #{ExtendedValue::ALL}", default: 'inbox')
|
|
122
|
+
options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{ExtendedValue::ALL}", default: 'inbox')
|
|
106
123
|
options.declare(:shared_folder, 'Send package with files from shared folder')
|
|
107
124
|
options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
|
|
108
125
|
options.parse_options!
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def api_url
|
|
112
|
-
return "#{@faspex5_api_base_url}/api/v5"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def auth_api_url
|
|
116
|
-
return "#{@faspex5_api_base_url}/auth"
|
|
126
|
+
@pub_link_context = nil
|
|
117
127
|
end
|
|
118
128
|
|
|
119
129
|
def set_api
|
|
@@ -122,60 +132,63 @@ module Aspera
|
|
|
122
132
|
auth_type = self.class.public_link?(@faspex5_api_base_url) ? :public_link : options.get_option(:auth, mandatory: true)
|
|
123
133
|
case auth_type
|
|
124
134
|
when :public_link
|
|
135
|
+
# resolve any redirect
|
|
136
|
+
@faspex5_api_base_url = Rest.new(base_url: @faspex5_api_base_url, redirect_max: 3).read('')[:http].uri.to_s
|
|
125
137
|
encoded_context = Rest.decode_query(URI.parse(@faspex5_api_base_url).query)['context']
|
|
126
138
|
raise 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
|
|
139
|
+
# public link information (allowed usage)
|
|
127
140
|
@pub_link_context = JSON.parse(Base64.decode64(encoded_context))
|
|
128
141
|
Log.log.trace1{Log.dump(:@pub_link_context, @pub_link_context)}
|
|
129
142
|
# ok, we have the additional parameters, get the base url
|
|
130
143
|
@faspex5_api_base_url = @faspex5_api_base_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
|
|
131
|
-
@api_v5 = Rest.new(
|
|
132
|
-
base_url:
|
|
144
|
+
@api_v5 = Rest.new(
|
|
145
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
|
|
133
146
|
headers: {'Passcode' => @pub_link_context['passcode']}
|
|
134
|
-
|
|
147
|
+
)
|
|
135
148
|
when :boot
|
|
136
149
|
# the password here is the token copied directly from browser in developer mode
|
|
137
|
-
@api_v5 = Rest.new(
|
|
138
|
-
base_url:
|
|
150
|
+
@api_v5 = Rest.new(
|
|
151
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
|
|
139
152
|
headers: {'Authorization' => options.get_option(:password, mandatory: true)}
|
|
140
|
-
|
|
153
|
+
)
|
|
141
154
|
when :web
|
|
142
155
|
# opens a browser and ask user to auth using web
|
|
143
|
-
@api_v5 = Rest.new(
|
|
144
|
-
base_url:
|
|
156
|
+
@api_v5 = Rest.new(
|
|
157
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
|
|
145
158
|
auth: {
|
|
146
159
|
type: :oauth2,
|
|
147
|
-
base_url:
|
|
160
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
|
|
148
161
|
grant_method: :web,
|
|
149
162
|
client_id: options.get_option(:client_id, mandatory: true),
|
|
150
|
-
|
|
151
|
-
}
|
|
163
|
+
redirect_uri: options.get_option(:redirect_uri, mandatory: true)
|
|
164
|
+
})
|
|
152
165
|
when :jwt
|
|
153
166
|
app_client_id = options.get_option(:client_id, mandatory: true)
|
|
154
|
-
@api_v5 = Rest.new(
|
|
155
|
-
base_url:
|
|
167
|
+
@api_v5 = Rest.new(
|
|
168
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
|
|
156
169
|
auth: {
|
|
157
|
-
type:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
client_id:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}})
|
|
171
|
-
else raise 'Unexpected case for option: auth'
|
|
170
|
+
type: :oauth2,
|
|
171
|
+
grant_method: :jwt,
|
|
172
|
+
base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
|
|
173
|
+
client_id: app_client_id,
|
|
174
|
+
payload: {
|
|
175
|
+
iss: app_client_id, # issuer
|
|
176
|
+
aud: app_client_id, # audience (this field is not clear...)
|
|
177
|
+
sub: "user:#{options.get_option(:username, mandatory: true)}" # subject is a user
|
|
178
|
+
},
|
|
179
|
+
private_key_obj: OpenSSL::PKey::RSA.new(options.get_option(:private_key, mandatory: true), options.get_option(:passphrase)),
|
|
180
|
+
headers: {typ: 'JWT'}
|
|
181
|
+
})
|
|
182
|
+
else Aspera.error_unexpected_value(auth_type)
|
|
172
183
|
end
|
|
184
|
+
# in case user wants to use HTTPGW tell transfer agent how to get address
|
|
185
|
+
transfer.httpgw_url_cb = lambda { @api_v5.read('account')[:data]['gateway_url'] }
|
|
173
186
|
end
|
|
174
187
|
|
|
175
188
|
# if recipient is just an email, then convert to expected API hash : name and type
|
|
176
189
|
def normalize_recipients(parameters)
|
|
177
190
|
return unless parameters.key?('recipients')
|
|
178
|
-
|
|
191
|
+
Aspera.assert_type(parameters['recipients'], Array){'recipients'}
|
|
179
192
|
recipient_types = RECIPIENT_TYPES
|
|
180
193
|
if parameters.key?('recipient_types')
|
|
181
194
|
recipient_types = parameters['recipient_types']
|
|
@@ -183,7 +196,7 @@ module Aspera
|
|
|
183
196
|
recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
|
|
184
197
|
end
|
|
185
198
|
parameters['recipients'].map! do |recipient_data|
|
|
186
|
-
# if just a string,
|
|
199
|
+
# if just a string, make a general lookup and build expected name/type hash
|
|
187
200
|
if recipient_data.is_a?(String)
|
|
188
201
|
matched = @api_v5.lookup_by_name('contacts', recipient_data, {context: 'packages', type: Rest.array_params(recipient_types)})
|
|
189
202
|
recipient_data = {
|
|
@@ -236,27 +249,25 @@ module Aspera
|
|
|
236
249
|
spinner.spin
|
|
237
250
|
sleep(0.5)
|
|
238
251
|
end
|
|
239
|
-
|
|
252
|
+
Aspera.error_unreachable_line
|
|
240
253
|
end
|
|
241
254
|
|
|
242
|
-
#
|
|
255
|
+
# Get a (full or partial) list of all entities of a given type
|
|
243
256
|
# @param type [String] the type of entity to list (just a name)
|
|
244
257
|
# @param query [Hash,nil] additional query parameters
|
|
245
|
-
# @param
|
|
258
|
+
# @param real_path [String] real path if it's n ot just the type
|
|
246
259
|
# @param item_list_key [String] key in the result to get the list of items
|
|
247
|
-
def list_entities(type:,
|
|
248
|
-
query = {} if query.nil?
|
|
260
|
+
def list_entities(type:, real_path: nil, query: {}, item_list_key: nil)
|
|
249
261
|
type = type.to_s if type.is_a?(Symbol)
|
|
262
|
+
Aspera.assert_type(type, String)
|
|
250
263
|
item_list_key = type if item_list_key.nil?
|
|
251
|
-
|
|
252
|
-
full_path = type
|
|
253
|
-
full_path = "#{path}/#{full_path}" unless path.nil? || path.empty?
|
|
264
|
+
full_path = real_path.nil? ? type : real_path
|
|
254
265
|
result = []
|
|
255
266
|
offset = 0
|
|
256
267
|
max_items = query.delete(MAX_ITEMS)
|
|
257
268
|
remain_pages = query.delete(MAX_PAGES)
|
|
258
269
|
# merge default parameters, by default 100 per page
|
|
259
|
-
query = {'limit'=>
|
|
270
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
260
271
|
loop do
|
|
261
272
|
query['offset'] = offset
|
|
262
273
|
page_result = @api_v5.read(full_path, query)[:data]
|
|
@@ -275,33 +286,36 @@ module Aspera
|
|
|
275
286
|
end
|
|
276
287
|
|
|
277
288
|
# lookup an entity id from its name
|
|
278
|
-
def lookup_entity_by_field(type:, value:, field: 'name', query: :default,
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
def lookup_entity_by_field(type:, value:, field: 'name', query: :default, real_path: nil, item_list_key: nil)
|
|
290
|
+
if query.eql?(:default)
|
|
291
|
+
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
292
|
+
query = {'q'=> value}
|
|
293
|
+
end
|
|
294
|
+
found = list_entities(type: type, real_path: real_path, query: query, item_list_key: item_list_key).select{|i|i[field].eql?(value)}
|
|
281
295
|
case found.length
|
|
282
296
|
when 0 then raise "No #{type} with #{field} = #{value}"
|
|
283
297
|
when 1 then return found.first
|
|
284
|
-
else raise "Found #{found.length} #{
|
|
298
|
+
else raise "Found #{found.length} #{real_path} with #{field} = #{value}"
|
|
285
299
|
end
|
|
286
300
|
end
|
|
287
301
|
|
|
288
302
|
# list all packages with optional filter
|
|
289
|
-
def list_packages_with_filter
|
|
303
|
+
def list_packages_with_filter(query: {})
|
|
290
304
|
filter = options.get_next_argument('filter', mandatory: false, type: Proc, default: ->(_x){true})
|
|
291
305
|
# translate box name to API prefix (with ending slash)
|
|
292
306
|
box = options.get_option(:box)
|
|
293
|
-
|
|
307
|
+
real_path =
|
|
294
308
|
case box
|
|
295
|
-
when ExtendedValue::ALL then '' # only admin can list all packages globally
|
|
296
|
-
when *API_LIST_MAILBOX_TYPES then box
|
|
309
|
+
when ExtendedValue::ALL then 'packages' # only admin can list all packages globally
|
|
310
|
+
when *API_LIST_MAILBOX_TYPES then "#{box}/packages"
|
|
297
311
|
else
|
|
298
312
|
group_type = options.get_option(:group_type)
|
|
299
|
-
"#{group_type}/#{lookup_entity_by_field(type: group_type, value: box)['id']}"
|
|
313
|
+
"#{group_type}/#{lookup_entity_by_field(type: group_type, value: box)['id']}/packages"
|
|
300
314
|
end
|
|
301
315
|
return list_entities(
|
|
302
316
|
type: 'packages',
|
|
303
|
-
query: query_read_delete(default:
|
|
304
|
-
|
|
317
|
+
query: query_read_delete(default: query),
|
|
318
|
+
real_path: real_path).select(&filter)
|
|
305
319
|
end
|
|
306
320
|
|
|
307
321
|
def package_receive(package_ids)
|
|
@@ -310,30 +324,37 @@ module Aspera
|
|
|
310
324
|
if options.get_option(:once_only, mandatory: true)
|
|
311
325
|
# read ids from persistency
|
|
312
326
|
skip_ids_persistency = PersistencyActionOnce.new(
|
|
313
|
-
manager:
|
|
327
|
+
manager: persistency,
|
|
314
328
|
data: [],
|
|
315
329
|
id: IdGenerator.from_list([
|
|
316
330
|
'faspex_recv',
|
|
317
331
|
options.get_option(:url, mandatory: true),
|
|
318
|
-
options.get_option(:username, mandatory: true)
|
|
332
|
+
options.get_option(:username, mandatory: true),
|
|
333
|
+
options.get_option(:box, mandatory: true)
|
|
334
|
+
]))
|
|
319
335
|
end
|
|
336
|
+
packages = []
|
|
320
337
|
case package_ids
|
|
321
|
-
when
|
|
322
|
-
|
|
338
|
+
when ExtendedValue::INIT
|
|
339
|
+
Aspera.assert(skip_ids_persistency){'Only with option once_only'}
|
|
323
340
|
skip_ids_persistency.data.clear.concat(list_packages_with_filter.map{|p|p['id']})
|
|
324
341
|
skip_ids_persistency.save
|
|
325
342
|
return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
|
326
343
|
when ExtendedValue::ALL
|
|
327
344
|
# TODO: if packages have same name, they will overwrite ?
|
|
328
|
-
|
|
329
|
-
Log.log.
|
|
330
|
-
Log.log.
|
|
331
|
-
|
|
332
|
-
Log.log.
|
|
345
|
+
packages = list_packages_with_filter(query: {'status' => 'completed'})
|
|
346
|
+
Log.log.trace1{Log.dump(:package_ids, packages.map{|p|p['id']})}
|
|
347
|
+
Log.log.trace1{Log.dump(:skip_ids, skip_ids_persistency.data)}
|
|
348
|
+
packages.reject!{|p|skip_ids_persistency.data.include?(p['id'])} if skip_ids_persistency
|
|
349
|
+
Log.log.trace1{Log.dump(:package_ids, packages.map{|p|p['id']})}
|
|
350
|
+
else
|
|
351
|
+
# a single id was provided, or a list of ids
|
|
352
|
+
package_ids = [package_ids] unless package_ids.is_a?(Array)
|
|
353
|
+
Aspera.assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
|
|
354
|
+
Aspera.assert(package_ids.all?(String)){'Package id shall be String'}
|
|
355
|
+
# packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")[:data]}
|
|
356
|
+
packages = package_ids.map{|pkg_id|{'id'=>pkg_id}}
|
|
333
357
|
end
|
|
334
|
-
# a single id was provided
|
|
335
|
-
# TODO: check package_ids is a list of strings
|
|
336
|
-
package_ids = [package_ids] if package_ids.is_a?(String)
|
|
337
358
|
result_transfer = []
|
|
338
359
|
param_file_list = {}
|
|
339
360
|
begin
|
|
@@ -352,7 +373,8 @@ module Aspera
|
|
|
352
373
|
else # shared inbox / workgroup
|
|
353
374
|
download_params[:recipient_workgroup_id] = lookup_entity_by_field(type: options.get_option(:group_type), value: box)['id']
|
|
354
375
|
end
|
|
355
|
-
|
|
376
|
+
packages.each do |package|
|
|
377
|
+
pkg_id = package['id']
|
|
356
378
|
formatter.display_status("Receiving package #{pkg_id}")
|
|
357
379
|
# TODO: allow from sent as well ?
|
|
358
380
|
transfer_spec = @api_v5.call(
|
|
@@ -375,6 +397,45 @@ module Aspera
|
|
|
375
397
|
return Main.result_transfer_multiple(result_transfer)
|
|
376
398
|
end
|
|
377
399
|
|
|
400
|
+
# browse a folder
|
|
401
|
+
# @param browse_endpoint [String] the endpoint to browse
|
|
402
|
+
def browse_folder(browse_endpoint)
|
|
403
|
+
path = options.get_next_argument('folder path', mandatory: false, default: '/')
|
|
404
|
+
query = query_read_delete(default: {})
|
|
405
|
+
query['filters'] = {} unless query.key?('filters')
|
|
406
|
+
filters = query.delete('filters')
|
|
407
|
+
filters['basenames'] = [] unless filters.key?('basenames')
|
|
408
|
+
Aspera.assert_type(filters, Hash){'filters'}
|
|
409
|
+
max_items = query.delete('max')
|
|
410
|
+
recursive = query.delete('recursive')
|
|
411
|
+
all_items = []
|
|
412
|
+
folders_to_process = [path]
|
|
413
|
+
until folders_to_process.empty?
|
|
414
|
+
path = folders_to_process.shift
|
|
415
|
+
query.delete('iteration_token')
|
|
416
|
+
loop do
|
|
417
|
+
response = @api_v5.call(
|
|
418
|
+
operation: 'POST',
|
|
419
|
+
subpath: browse_endpoint,
|
|
420
|
+
headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
|
|
421
|
+
url_params: query,
|
|
422
|
+
json_params: {'path' => path, 'filters' => filters})
|
|
423
|
+
all_items.concat(response[:data]['items'])
|
|
424
|
+
if recursive
|
|
425
|
+
folders_to_process.concat(response[:data]['items'].select{|i|i['type'].eql?('directory')}.map{|i|i['path']})
|
|
426
|
+
end
|
|
427
|
+
if !max_items.nil? && (all_items.count >= max_items)
|
|
428
|
+
all_items = all_items.slice(0, max_items) if all_items.count > max_items
|
|
429
|
+
break
|
|
430
|
+
end
|
|
431
|
+
iteration_token = response[:http][HEADER_ITERATION_TOKEN]
|
|
432
|
+
break if iteration_token.nil? || iteration_token.empty?
|
|
433
|
+
query['iteration_token'] = iteration_token
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
return {type: :object_list, data: all_items}
|
|
437
|
+
end
|
|
438
|
+
|
|
378
439
|
def package_action
|
|
379
440
|
command = options.get_next_command(%i[show browse status delete receive send list])
|
|
380
441
|
package_id =
|
|
@@ -385,35 +446,34 @@ module Aspera
|
|
|
385
446
|
when :show
|
|
386
447
|
return {type: :single_object, data: @api_v5.read("packages/#{package_id}")[:data]}
|
|
387
448
|
when :browse
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
result = @api_v5.call({
|
|
396
|
-
operation: 'POST',
|
|
397
|
-
subpath: "packages/#{package_id}/files/received",
|
|
398
|
-
headers: {'Accept' => 'application/json'},
|
|
399
|
-
url_params: params,
|
|
400
|
-
json_params: {'path' => path, 'filters' => {'basenames'=>[]}}})[:data]
|
|
401
|
-
formatter.display_item_count(result['item_count'], result['total_count'])
|
|
402
|
-
return {type: :object_list, data: result['items']}
|
|
449
|
+
location = case options.get_option(:box)
|
|
450
|
+
when 'inbox' then 'received'
|
|
451
|
+
when 'outbox' then 'sent'
|
|
452
|
+
else raise 'Browse only available for inbox and outbox'
|
|
453
|
+
end
|
|
454
|
+
return browse_folder("packages/#{package_id}/files/#{location}")
|
|
403
455
|
when :status
|
|
404
456
|
status = wait_package_status(package_id, status_list: nil)
|
|
405
457
|
return {type: :single_object, data: status}
|
|
406
458
|
when :delete
|
|
407
459
|
ids = package_id
|
|
408
460
|
ids = [ids] unless ids.is_a?(Array)
|
|
409
|
-
|
|
461
|
+
Aspera.assert_type(ids, Array){'Package identifier'}
|
|
462
|
+
Aspera.assert(ids.all?(String)){'Package id shall be String'}
|
|
410
463
|
# API returns 204, empty on success
|
|
411
|
-
@api_v5.call(
|
|
464
|
+
@api_v5.call(operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids})
|
|
412
465
|
return Main.result_status('Package(s) deleted')
|
|
413
466
|
when :receive
|
|
414
467
|
return package_receive(package_id)
|
|
415
468
|
when :send
|
|
416
469
|
parameters = value_create_modify(command: command)
|
|
470
|
+
# autofill recipient for public url
|
|
471
|
+
if @pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
|
|
472
|
+
parameters['recipients'] = [{
|
|
473
|
+
name: @pub_link_context['name'],
|
|
474
|
+
recipient_type: @pub_link_context['recipient_type']
|
|
475
|
+
}]
|
|
476
|
+
end
|
|
417
477
|
normalize_recipients(parameters)
|
|
418
478
|
package = @api_v5.create('packages', parameters)[:data]
|
|
419
479
|
shared_folder = options.get_option(:shared_folder)
|
|
@@ -453,7 +513,7 @@ module Aspera
|
|
|
453
513
|
end # case package
|
|
454
514
|
end
|
|
455
515
|
|
|
456
|
-
ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing].freeze
|
|
516
|
+
ACTIONS = %i[health version user bearer_token packages shared_folders admin gateway postprocessing invitations].freeze
|
|
457
517
|
|
|
458
518
|
def execute_action
|
|
459
519
|
command = options.get_next_command(ACTIONS)
|
|
@@ -473,7 +533,9 @@ module Aspera
|
|
|
473
533
|
end
|
|
474
534
|
return nagios.result
|
|
475
535
|
when :user
|
|
476
|
-
case options.get_next_command(%i[profile])
|
|
536
|
+
case options.get_next_command(%i[account profile])
|
|
537
|
+
when :account
|
|
538
|
+
return { type: :single_object, data: @api_v5.read('account')[:data] }
|
|
477
539
|
when :profile
|
|
478
540
|
case options.get_next_command(%i[show modify])
|
|
479
541
|
when :show
|
|
@@ -499,21 +561,9 @@ module Aspera
|
|
|
499
561
|
raise "multiple matches for #{field} = #{value}" if matches.length > 1
|
|
500
562
|
matches.first['id']
|
|
501
563
|
end
|
|
502
|
-
path = options.get_next_argument('folder path', mandatory: false) || '/'
|
|
503
564
|
node = all_shared_folders.find{|i|i['id'].eql?(shared_folder_id)}
|
|
504
565
|
raise "No such shared folder id #{shared_folder_id}" if node.nil?
|
|
505
|
-
|
|
506
|
-
operation: 'POST',
|
|
507
|
-
subpath: "nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse",
|
|
508
|
-
headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
|
|
509
|
-
json_params: {'path': path, 'filters': {'basenames': []}},
|
|
510
|
-
url_params: {offset: 0, limit: 100}
|
|
511
|
-
})[:data]
|
|
512
|
-
if result.key?('items')
|
|
513
|
-
return {type: :object_list, data: result['items']}
|
|
514
|
-
else
|
|
515
|
-
return {type: :single_object, data: result['self']}
|
|
516
|
-
end
|
|
566
|
+
return browse_folder("nodes/#{node['node_id']}/shared_folders/#{shared_folder_id}/browse")
|
|
517
567
|
end
|
|
518
568
|
when :admin
|
|
519
569
|
case options.get_next_command(%i[resource smtp].freeze)
|
|
@@ -523,11 +573,14 @@ module Aspera
|
|
|
523
573
|
id_as_arg = false
|
|
524
574
|
display_fields = nil
|
|
525
575
|
adm_api = @api_v5
|
|
576
|
+
special_query = :default
|
|
526
577
|
available_commands = [].concat(Plugin::ALL_OPS)
|
|
527
578
|
case res_type
|
|
528
579
|
when :metadata_profiles
|
|
529
580
|
res_path = 'configuration/metadata_profiles'
|
|
530
581
|
list_key = 'profiles'
|
|
582
|
+
when :alternate_addresses
|
|
583
|
+
res_path = 'configuration/alternate_addresses'
|
|
531
584
|
when :email_notifications
|
|
532
585
|
list_key = false
|
|
533
586
|
id_as_arg = 'type'
|
|
@@ -535,14 +588,31 @@ module Aspera
|
|
|
535
588
|
display_fields = Formatter.all_but('user_profile_data_attributes')
|
|
536
589
|
when :oauth_clients
|
|
537
590
|
display_fields = Formatter.all_but('public_key')
|
|
538
|
-
adm_api = Rest.new(
|
|
591
|
+
adm_api = Rest.new(**@api_v5.params.merge(base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}"))
|
|
539
592
|
when :shared_inboxes, :workgroups
|
|
540
593
|
available_commands.push(:members, :saml_groups, :invite_external_collaborator)
|
|
594
|
+
special_query = {'all': true}
|
|
595
|
+
when :nodes
|
|
596
|
+
available_commands.push(:shared_folders, :browse)
|
|
541
597
|
end
|
|
542
598
|
res_command = options.get_next_command(available_commands)
|
|
543
599
|
case res_command
|
|
544
600
|
when *Plugin::ALL_OPS
|
|
545
|
-
return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg)
|
|
601
|
+
return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg) do |field, value|
|
|
602
|
+
lookup_entity_by_field(
|
|
603
|
+
type: res_type, real_path: res_path, field: field, value: value, query: special_query)['id']
|
|
604
|
+
end
|
|
605
|
+
when :shared_folders
|
|
606
|
+
node_id = instance_identifier do |field, value|
|
|
607
|
+
lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']
|
|
608
|
+
end
|
|
609
|
+
sh_path = "#{res_path}/#{node_id}/shared_folders"
|
|
610
|
+
return entity_action(adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
|
|
611
|
+
lookup_entity_by_field(
|
|
612
|
+
type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
|
|
613
|
+
end
|
|
614
|
+
when :browse
|
|
615
|
+
return browse_folder("#{res_path}/#{instance_identifier}/browse")
|
|
546
616
|
when :invite_external_collaborator
|
|
547
617
|
shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
|
548
618
|
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
|
@@ -550,7 +620,9 @@ module Aspera
|
|
|
550
620
|
res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
|
|
551
621
|
result = adm_api.create(res_path, creation_payload)[:data]
|
|
552
622
|
formatter.display_status(result['message'])
|
|
553
|
-
result = lookup_entity_by_field(
|
|
623
|
+
result = lookup_entity_by_field(
|
|
624
|
+
type: 'members', real_path: "#{res_type}/#{shared_inbox_id}/members", value: creation_payload['email_address'],
|
|
625
|
+
query: {})
|
|
554
626
|
return {type: :single_object, data: result}
|
|
555
627
|
when :members, :saml_groups
|
|
556
628
|
res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
|
@@ -605,34 +677,41 @@ module Aspera
|
|
|
605
677
|
return { type: :single_object, data: result }
|
|
606
678
|
end
|
|
607
679
|
end
|
|
680
|
+
when :invitations
|
|
681
|
+
invitation_endpoint = 'invitations'
|
|
682
|
+
invitation_command = options.get_next_command(%i[resend].concat(Plugin::ALL_OPS))
|
|
683
|
+
case invitation_command
|
|
684
|
+
when :create
|
|
685
|
+
return do_bulk_operation(command: invitation_command, descr: 'data') do |params|
|
|
686
|
+
invitation_endpoint = params.key?('recipient_name') ? 'public_invitations' : 'invitations'
|
|
687
|
+
@api_v5.create(invitation_endpoint, params)[:data]
|
|
688
|
+
end
|
|
689
|
+
when :resend
|
|
690
|
+
@api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend")
|
|
691
|
+
return Main.result_status('Invitation resent')
|
|
692
|
+
else
|
|
693
|
+
return entity_command(
|
|
694
|
+
invitation_command, @api_v5, invitation_endpoint, item_list_key: invitation_endpoint,
|
|
695
|
+
display_fields: %w[id public recipient_type recipient_name email_address])
|
|
696
|
+
end
|
|
608
697
|
when :gateway
|
|
609
698
|
require 'aspera/faspex_gw'
|
|
610
|
-
url = value_create_modify(command: command, type: String)
|
|
699
|
+
url = value_create_modify(command: command, description: 'listening url (e.g. https://localhost:12345)', type: String)
|
|
611
700
|
uri = URI.parse(url)
|
|
612
701
|
server = WebServerSimple.new(uri)
|
|
613
702
|
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
|
614
|
-
# on ctrl-c, tell server main loop to exit
|
|
615
|
-
trap('INT') { server.shutdown }
|
|
616
|
-
formatter.display_status("Gateway for Faspex 4-style API listening on #{url}")
|
|
617
|
-
Log.log.info("Listening on #{url}")
|
|
618
|
-
# this is blocking until server exits
|
|
619
703
|
server.start
|
|
620
704
|
return Main.result_status('Gateway terminated')
|
|
621
705
|
when :postprocessing
|
|
622
706
|
require 'aspera/faspex_postproc' # cspell:disable-line
|
|
623
707
|
parameters = value_create_modify(command: command)
|
|
624
708
|
parameters = parameters.symbolize_keys
|
|
625
|
-
|
|
709
|
+
Aspera.assert(parameters.key?(:url)){'Missing key: url'}
|
|
626
710
|
uri = URI.parse(parameters[:url])
|
|
627
711
|
parameters[:processing] ||= {}
|
|
628
712
|
parameters[:processing][:root] = uri.path
|
|
629
713
|
server = WebServerSimple.new(uri, certificate: parameters[:certificate])
|
|
630
714
|
server.mount(uri.path, Faspex4PostProcServlet, parameters[:processing])
|
|
631
|
-
# on ctrl-c, tell server main loop to exit
|
|
632
|
-
trap('INT') { server.shutdown }
|
|
633
|
-
formatter.display_status("Web-hook for Faspex 4-style post processing listening on #{uri.port}")
|
|
634
|
-
Log.log.info("Listening on #{uri.port}")
|
|
635
|
-
# this is blocking until server exits
|
|
636
715
|
server.start
|
|
637
716
|
return Main.result_status('Gateway terminated')
|
|
638
717
|
end # case command
|