aspera-cli 4.26.2 → 4.27.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 +281 -21
- data/CONTRIBUTING.md +98 -7
- data/bin/ascli +3 -3
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +26 -24
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/agent/node.rb +32 -0
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -16
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +15 -14
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +26 -23
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +21 -19
- data/lib/aspera/ascp/installation.rb +37 -38
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +16 -3
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +2 -1
- data/lib/aspera/cli/formatter.rb +41 -27
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +70 -19
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1166 -853
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +457 -100
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +428 -754
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +294 -257
- data/lib/aspera/cli/plugins/faspex5.rb +585 -357
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +998 -794
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
- data/lib/aspera/cli/plugins/preview.rb +180 -139
- data/lib/aspera/cli/plugins/server.rb +124 -79
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +13 -12
- data/lib/aspera/cli/result.rb +61 -11
- data/lib/aspera/cli/runner.rb +275 -141
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +83 -59
- data/lib/aspera/cli/terminal_formatter.rb +1 -1
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +3 -3
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +21 -20
- data/lib/aspera/command_line_builder.rb +22 -22
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +4 -4
- data/lib/aspera/environment.rb +18 -11
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +3 -3
- data/lib/aspera/keychain/macos_security.rb +3 -3
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +17 -3
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +5 -5
- data/lib/aspera/oauth/boot.rb +2 -2
- data/lib/aspera/oauth/factory.rb +7 -5
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_action_once.rb +8 -8
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +3 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +7 -6
- data/lib/aspera/rest.rb +10 -36
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +3 -3
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +59 -10
- data/lib/aspera/schema/registry.rb +39 -6
- data/lib/aspera/secret_hider.rb +7 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/operations.rb +10 -13
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +23 -21
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +6 -6
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +25 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/json_rpc.rb +0 -52
|
@@ -6,6 +6,7 @@ require 'aspera/cli/plugins/node'
|
|
|
6
6
|
require 'aspera/cli/extended_value'
|
|
7
7
|
require 'aspera/cli/special_values'
|
|
8
8
|
require 'aspera/cli/transfer_agent'
|
|
9
|
+
require 'aspera/transfer/result'
|
|
9
10
|
require 'aspera/transfer/uri'
|
|
10
11
|
require 'aspera/transfer/spec'
|
|
11
12
|
require 'aspera/persistency_action_once'
|
|
@@ -22,6 +23,7 @@ module Aspera
|
|
|
22
23
|
module Cli
|
|
23
24
|
module Plugins
|
|
24
25
|
class Faspex < BasicAuth
|
|
26
|
+
application_name 'Faspex v4'
|
|
25
27
|
# required hash key for source in config
|
|
26
28
|
KEY_NODE = 'node' # value must be hash with url, username, password
|
|
27
29
|
KEY_PATH = 'path' # value must be same sub-path as in Faspex's node
|
|
@@ -94,7 +96,7 @@ module Aspera
|
|
|
94
96
|
# get Transfer::Uri::SCHEME URI from entry in xml, and fix problems.
|
|
95
97
|
def get_fasp_uri_from_entry(entry, raise_no_link: true)
|
|
96
98
|
unless entry.key?('link')
|
|
97
|
-
|
|
99
|
+
Aspera.assert(!raise_no_link, type: Cli::BadArgument){'package has no link (deleted?)'}
|
|
98
100
|
return
|
|
99
101
|
end
|
|
100
102
|
result = entry['link'].find{ |e| e['rel'].eql?('package')}['href']
|
|
@@ -123,16 +125,17 @@ module Aspera
|
|
|
123
125
|
}
|
|
124
126
|
end
|
|
125
127
|
|
|
128
|
+
option :link, description: 'Public link for specific operation'
|
|
129
|
+
option :delivery_info, description: 'Package delivery information', allowed: Hash
|
|
130
|
+
option :remote_source, description: 'Remote source for package send (id or %name:)'
|
|
131
|
+
option :storage, description: 'Faspex local storage definition (for browsing source)'
|
|
132
|
+
option :recipient, description: 'Use if recipient is a dropbox (with *)'
|
|
133
|
+
option :box, description: 'Package box', allowed: ATOM_MAILBOXES, default: :inbox
|
|
134
|
+
|
|
126
135
|
def initialize(**_)
|
|
127
136
|
super
|
|
128
137
|
@api_v3 = nil
|
|
129
138
|
@api_v4 = nil
|
|
130
|
-
options.declare(:link, 'Public link for specific operation')
|
|
131
|
-
options.declare(:delivery_info, 'Package delivery information', allowed: Hash)
|
|
132
|
-
options.declare(:remote_source, 'Remote source for package send (id or %name:)')
|
|
133
|
-
options.declare(:storage, 'Faspex local storage definition (for browsing source)')
|
|
134
|
-
options.declare(:recipient, 'Use if recipient is a dropbox (with *)')
|
|
135
|
-
options.declare(:box, 'Package box', allowed: ATOM_MAILBOXES, default: :inbox)
|
|
136
139
|
options.parse_options!
|
|
137
140
|
end
|
|
138
141
|
|
|
@@ -242,7 +245,7 @@ module Aspera
|
|
|
242
245
|
delivery_info = package_create_params['delivery']
|
|
243
246
|
# pub link user
|
|
244
247
|
link_data = self.class.get_link_data(public_link_url)
|
|
245
|
-
|
|
248
|
+
Aspera.assert_values(link_data[:subpath], ['external/submissions/new', 'external/dropbox_submissions/new'], type: Cli::BadArgument){'pub link subpath'}
|
|
246
249
|
create_path = link_data[:subpath].split('/')[0..-2].join('/')
|
|
247
250
|
package_create_params[:passcode] = link_data[:query]['passcode']
|
|
248
251
|
delivery_info[:transfer_type] = 'connect'
|
|
@@ -273,265 +276,299 @@ module Aspera
|
|
|
273
276
|
return package_creation_data.first
|
|
274
277
|
end
|
|
275
278
|
|
|
276
|
-
|
|
279
|
+
# --- DSL ---
|
|
277
280
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
command :health, description: 'Check Faspex 4 API health'
|
|
282
|
+
command :package, description: 'Manage packages'
|
|
283
|
+
command :source, description: 'Manage sources'
|
|
284
|
+
command :me, description: 'Show current user information', action: ->{Result::SingleObject.new(api_v3.read('me'))}
|
|
285
|
+
command :dropbox, description: 'Manage dropboxes'
|
|
286
|
+
command :v4, description: 'Faspex v4 admin commands'
|
|
287
|
+
command :address_book, description: 'Show address book'
|
|
288
|
+
command :login_methods, description: 'Show login methods'
|
|
289
|
+
|
|
290
|
+
commands_under(:package) do
|
|
291
|
+
command :send, description: 'Send a package'
|
|
292
|
+
command :receive, description: 'Receive a package', aliases: [:recv]
|
|
293
|
+
command :list, description: 'List packages', action: ->{Result::ObjectList.new(mailbox_filtered_entries, fields: [PACKAGE_MATCH_FIELD, 'title', 'items'])}
|
|
294
|
+
command :show, description: 'Show a package'
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
commands_under(:source) do
|
|
298
|
+
command :list, description: 'List sources', action: ->{Result::ObjectList.new(api_v3.read('source_shares')['items'])}
|
|
299
|
+
command :info, description: 'Show source info', setup: :setup_source_selected, action: ->(source_info:){Result::SingleObject.new(source_info)}
|
|
300
|
+
command :node, description: 'Execute node commands on source', setup: :setup_source_selected
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
commands_under(%i[source node]) do
|
|
304
|
+
Node::COMMANDS_FASPEX.each do |cmd|
|
|
305
|
+
command(cmd, description: "Node #{cmd} command", setup: :setup_source_node)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
commands_under(:dropbox) do
|
|
310
|
+
command :list, description: 'List dropboxes'
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
commands_under(:v4) do
|
|
314
|
+
entity_command :dropbox, description: 'Manage v4 dropboxes', api: :api_v4, entity: 'admin/dropboxes', display_fields: %w[id e_wg_name e_wg_desc created_at]
|
|
315
|
+
entity_command :dmembership, description: 'Manage dropbox memberships', api: :api_v4, entity: 'dropbox_memberships'
|
|
316
|
+
entity_command :workgroup, api: :api_v4, entity: 'admin/workgroups', display_fields: %w[id e_wg_name e_wg_desc created_at]
|
|
317
|
+
entity_command :wmembership, description: 'Manage workgroup memberships', api: :api_v4, entity: 'workgroup_memberships'
|
|
318
|
+
entity_command :user, api: :api_v4, entity: 'users', display_fields: %w[id name first_name last_name]
|
|
319
|
+
entity_command :metadata_profile, description: 'Manage metadata profiles', api: :api_v4, entity: 'metadata_profiles'
|
|
320
|
+
command :package, description: 'List packages for a box'
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
commands_under(%i[v4 package]) do
|
|
324
|
+
command :users, description: 'List packages for users'
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# --- handlers ---
|
|
328
|
+
|
|
329
|
+
def action_health
|
|
330
|
+
nagios = Nagios.new
|
|
331
|
+
begin
|
|
332
|
+
api_v3.read('me')
|
|
333
|
+
nagios.add_ok('faspex api', 'accessible')
|
|
334
|
+
rescue StandardError => e
|
|
335
|
+
nagios.add_critical('faspex api', e.to_s)
|
|
336
|
+
end
|
|
337
|
+
Result::ObjectList.new(nagios.status_list)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# package sub-handlers
|
|
341
|
+
|
|
342
|
+
def action_package_show
|
|
343
|
+
delivery_id = options.instance_identifier
|
|
344
|
+
Result::SingleObject.new(mailbox_filtered_entries(stop_at_id: delivery_id).find{ |p| p[PACKAGE_MATCH_FIELD].eql?(delivery_id)})
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def action_package_send
|
|
348
|
+
delivery_info = options.get_option(:delivery_info, mandatory: true)
|
|
349
|
+
Aspera.assert_type(delivery_info, Hash, type: Cli::BadArgument){'delivery_info'}
|
|
350
|
+
# actual parameter to faspex API
|
|
351
|
+
package_create_params = {'delivery' => delivery_info}
|
|
352
|
+
public_link_url = options.get_option(:link)
|
|
353
|
+
if public_link_url.nil?
|
|
354
|
+
# authenticated user
|
|
355
|
+
delivery_info['sources'] ||= [{'paths' => []}]
|
|
356
|
+
first_source = delivery_info['sources'].first
|
|
357
|
+
first_source['paths'].concat(transfer.source_list)
|
|
358
|
+
source_id = options.get_option(:remote_source)
|
|
359
|
+
if source_id && (m = Parser.percent_selector(source_id))
|
|
360
|
+
Aspera.assert_values(m[:field], ['name'], type: Cli::BadArgument){'selector field'}
|
|
361
|
+
source_list = api_v3.read('source_shares')['items']
|
|
362
|
+
source_id = self.class.get_source_id_by_name(m[:value], source_list)
|
|
288
363
|
end
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
delivery_id = options.instance_identifier
|
|
295
|
-
return Result::SingleObject.new(mailbox_filtered_entries(stop_at_id: delivery_id).find{ |p| p[PACKAGE_MATCH_FIELD].eql?(delivery_id)})
|
|
296
|
-
when :list
|
|
297
|
-
return Result::ObjectList.new(mailbox_filtered_entries, fields: [PACKAGE_MATCH_FIELD, 'title', 'items'])
|
|
298
|
-
when :send
|
|
299
|
-
delivery_info = options.get_option(:delivery_info, mandatory: true)
|
|
300
|
-
Aspera.assert_type(delivery_info, Hash, type: Cli::BadArgument){'delivery_info'}
|
|
301
|
-
# actual parameter to faspex API
|
|
302
|
-
package_create_params = {'delivery' => delivery_info}
|
|
303
|
-
public_link_url = options.get_option(:link)
|
|
304
|
-
if public_link_url.nil?
|
|
305
|
-
# authenticated user
|
|
306
|
-
delivery_info['sources'] ||= [{'paths' => []}]
|
|
307
|
-
first_source = delivery_info['sources'].first
|
|
308
|
-
first_source['paths'].concat(transfer.source_list)
|
|
309
|
-
source_id = options.get_option(:remote_source)
|
|
310
|
-
if source_id && (m = Manager.percent_selector(source_id))
|
|
311
|
-
Aspera.assert_values(m[:field], ['name'], type: Cli::BadArgument){'selector field'}
|
|
312
|
-
source_list = api_v3.read('source_shares')['items']
|
|
313
|
-
source_id = self.class.get_source_id_by_name(m[:value], source_list)
|
|
314
|
-
end
|
|
315
|
-
first_source['id'] = source_id.to_i unless source_id.nil?
|
|
316
|
-
pkg_created = api_v3.create('send', package_create_params)
|
|
317
|
-
if first_source.key?('id')
|
|
318
|
-
# no transfer spec if remote source: handled by faspex
|
|
319
|
-
return Result::ValueList.new([pkg_created['links']['status']], name: 'link')
|
|
320
|
-
end
|
|
321
|
-
raise Cli::BadArgument, 'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
|
|
322
|
-
transfer_spec = pkg_created['xfer_sessions'].first
|
|
323
|
-
# use source from cmd line, this one only contains destination (already in dest root)
|
|
324
|
-
transfer_spec.delete('paths')
|
|
325
|
-
else # public link
|
|
326
|
-
transfer_spec = send_public_link_to_ts(public_link_url, package_create_params)
|
|
327
|
-
end
|
|
328
|
-
# Log.dump(:transfer_spec,transfer_spec)
|
|
329
|
-
return Runner.result_transfer(transfer.start(transfer_spec))
|
|
330
|
-
when :receive
|
|
331
|
-
link_url = options.get_option(:link)
|
|
332
|
-
# list of faspex ID/URI to download
|
|
333
|
-
pkg_id_uri = nil
|
|
334
|
-
skip_ids_data = []
|
|
335
|
-
skip_ids_persistency = nil
|
|
336
|
-
case link_url
|
|
337
|
-
when nil # usual case: no link
|
|
338
|
-
if options.get_option(:once_only, mandatory: true)
|
|
339
|
-
skip_ids_persistency = PersistencyActionOnce.new(
|
|
340
|
-
manager: persistency,
|
|
341
|
-
data: skip_ids_data,
|
|
342
|
-
id: IdGenerator.from_list(
|
|
343
|
-
'faspex_recv',
|
|
344
|
-
options.get_option(:url, mandatory: true),
|
|
345
|
-
options.get_option(:username, mandatory: true),
|
|
346
|
-
options.get_option(:box, mandatory: true).to_s
|
|
347
|
-
)
|
|
348
|
-
)
|
|
349
|
-
end
|
|
350
|
-
# get command line parameters
|
|
351
|
-
delivery_id = options.instance_identifier
|
|
352
|
-
Aspera.assert(!delivery_id.empty?, 'empty id')
|
|
353
|
-
recipient = options.get_option(:recipient)
|
|
354
|
-
if delivery_id.eql?(SpecialValues::ALL)
|
|
355
|
-
pkg_id_uri = mailbox_filtered_entries.map{ |i| {id: i[PACKAGE_MATCH_FIELD], uri: self.class.get_fasp_uri_from_entry(i, raise_no_link: false)}}
|
|
356
|
-
elsif delivery_id.eql?(SpecialValues::INIT)
|
|
357
|
-
Aspera.assert(skip_ids_persistency, 'Only with option once_only')
|
|
358
|
-
skip_ids_persistency.data.clear.concat(mailbox_filtered_entries.map{ |i| {id: i[PACKAGE_MATCH_FIELD]}})
|
|
359
|
-
skip_ids_persistency.save
|
|
360
|
-
return Result::Status.new("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
|
361
|
-
elsif !recipient.nil? && recipient.start_with?('*')
|
|
362
|
-
found_package_link = mailbox_filtered_entries(stop_at_id: delivery_id).find{ |p| p[PACKAGE_MATCH_FIELD].eql?(delivery_id)}['link'].first['href']
|
|
363
|
-
raise "Not Found. Dropbox and Workgroup packages can use the link option with #{Transfer::Uri::SCHEME}" if found_package_link.nil?
|
|
364
|
-
pkg_id_uri = [{id: delivery_id, uri: found_package_link}]
|
|
365
|
-
else
|
|
366
|
-
# TODO: delivery id is the right one if package was receive by workgroup
|
|
367
|
-
endpoint =
|
|
368
|
-
case options.get_option(:box, mandatory: true)
|
|
369
|
-
when :inbox, :archive then'received'
|
|
370
|
-
when :sent then 'sent'
|
|
371
|
-
end
|
|
372
|
-
entry_xml = api_v3.call(operation: 'GET', subpath: "#{endpoint}/#{delivery_id}", headers: {'Accept' => 'application/xml'}, ret: :resp).body
|
|
373
|
-
package_entry = XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
|
|
374
|
-
pkg_id_uri = [{id: delivery_id, uri: self.class.get_fasp_uri_from_entry(package_entry)}]
|
|
375
|
-
end
|
|
376
|
-
when /^#{Transfer::Uri::SCHEME}:/o
|
|
377
|
-
pkg_id_uri = [{id: 'package', uri: link_url}]
|
|
378
|
-
else
|
|
379
|
-
link_data = self.class.get_link_data(link_url)
|
|
380
|
-
raise Cli::BadArgument, "Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}" if !link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH)
|
|
381
|
-
# NOTE: unauthenticated API (authorization is in url params)
|
|
382
|
-
api_public_link = Rest.new(base_url: link_data[:base_url])
|
|
383
|
-
pkg_xml = api_public_link.call(
|
|
384
|
-
operation: 'GET',
|
|
385
|
-
subpath: link_data[:subpath],
|
|
386
|
-
headers: {'Accept' => 'application/xml'},
|
|
387
|
-
query: {passcode: link_data[:query]['passcode']},
|
|
388
|
-
ret: :resp
|
|
389
|
-
).body
|
|
390
|
-
if !pkg_xml.start_with?('<?xml ')
|
|
391
|
-
Environment.instance.open_uri(link_url)
|
|
392
|
-
raise Cli::Error, 'Unexpected response: package not found ?'
|
|
393
|
-
end
|
|
394
|
-
package_entry = XmlSimple.xml_in(pkg_xml, {'ForceArray' => false})
|
|
395
|
-
Log.dump(:package_entry, package_entry)
|
|
396
|
-
transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
|
|
397
|
-
pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
|
|
398
|
-
end
|
|
399
|
-
# prune packages already downloaded
|
|
400
|
-
# TODO : remove ids from skip not present in inbox to avoid growing too big
|
|
401
|
-
# skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
|
|
402
|
-
pkg_id_uri.reject!{ |i| skip_ids_data.include?(i[:id])}
|
|
403
|
-
Log.dump(:pkg_id_uri, pkg_id_uri)
|
|
404
|
-
return Result::Status.new('no new package') if pkg_id_uri.empty?
|
|
405
|
-
result_transfer = []
|
|
406
|
-
pkg_id_uri.each do |id_uri|
|
|
407
|
-
if id_uri[:uri].nil?
|
|
408
|
-
# skip package with no link: empty or content deleted
|
|
409
|
-
statuses = [:success]
|
|
410
|
-
else
|
|
411
|
-
transfer_spec = Transfer::Uri.new(id_uri[:uri]).transfer_spec
|
|
412
|
-
# NOTE: only external users have token in Transfer::Uri::SCHEME link !
|
|
413
|
-
if !transfer_spec.key?('token')
|
|
414
|
-
sanitized = id_uri[:uri].gsub('&', '&')
|
|
415
|
-
xml_payload =
|
|
416
|
-
%Q(<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="#{sanitized}"/></url-list>)
|
|
417
|
-
transfer_spec['token'] = api_v3.call(
|
|
418
|
-
operation: 'POST',
|
|
419
|
-
subpath: 'issue-token',
|
|
420
|
-
query: {'direction' => 'down'},
|
|
421
|
-
content_type: Mime::TEXT,
|
|
422
|
-
body: xml_payload,
|
|
423
|
-
headers: {'Accept' => Mime::TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
|
|
424
|
-
ret: :resp
|
|
425
|
-
).body
|
|
426
|
-
end
|
|
427
|
-
transfer_spec['direction'] = Transfer::Spec::DIRECTION_RECEIVE
|
|
428
|
-
statuses = transfer.start(transfer_spec)
|
|
429
|
-
end
|
|
430
|
-
result_transfer.push({'package' => id_uri[:id], Runner::STATUS_FIELD => statuses})
|
|
431
|
-
# skip only if all sessions completed
|
|
432
|
-
skip_ids_data.push(id_uri[:id]) if TransferAgent.session_status(statuses).eql?(:success)
|
|
433
|
-
end
|
|
434
|
-
skip_ids_persistency&.save
|
|
435
|
-
return Runner.result_transfer_multiple(result_transfer)
|
|
364
|
+
first_source['id'] = source_id.to_i unless source_id.nil?
|
|
365
|
+
pkg_created = api_v3.create('send', package_create_params)
|
|
366
|
+
if first_source.key?('id')
|
|
367
|
+
# no transfer spec if remote source: handled by faspex
|
|
368
|
+
return Result::ValueList.new([pkg_created['links']['status']], name: 'link')
|
|
436
369
|
end
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return Result::SingleObject.new(source_info)
|
|
466
|
-
when :node
|
|
467
|
-
node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE], context: 'faspex node')
|
|
468
|
-
Log.log.debug{"node=#{node_config}"}
|
|
469
|
-
Aspera.assert_type(node_config, Hash, type: Cli::Error){source_info[KEY_NODE]}
|
|
470
|
-
api_node = Rest.new(
|
|
471
|
-
base_url: node_config['url'],
|
|
472
|
-
auth: {
|
|
473
|
-
type: :basic,
|
|
474
|
-
username: node_config['username'],
|
|
475
|
-
password: node_config['password']
|
|
476
|
-
}
|
|
370
|
+
Aspera.assert(pkg_created['xfer_sessions'].length == 1, type: Cli::BadArgument){'expecting one session exactly'}
|
|
371
|
+
transfer_spec = pkg_created['xfer_sessions'].first
|
|
372
|
+
# use source from cmd line, this one only contains destination (already in dest root)
|
|
373
|
+
transfer_spec.delete('paths')
|
|
374
|
+
else # public link
|
|
375
|
+
transfer_spec = send_public_link_to_ts(public_link_url, package_create_params)
|
|
376
|
+
end
|
|
377
|
+
# Log.dump(:transfer_spec,transfer_spec)
|
|
378
|
+
return Runner.result_transfer(transfer.start(transfer_spec))
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def action_package_receive
|
|
382
|
+
link_url = options.get_option(:link)
|
|
383
|
+
# list of faspex ID/URI to download
|
|
384
|
+
pkg_id_uri = nil
|
|
385
|
+
skip_ids_data = []
|
|
386
|
+
skip_ids_persistency = nil
|
|
387
|
+
case link_url
|
|
388
|
+
when nil # usual case: no link
|
|
389
|
+
if options.get_option(:once_only, mandatory: true)
|
|
390
|
+
skip_ids_persistency = PersistencyActionOnce.new(
|
|
391
|
+
manager: persistency,
|
|
392
|
+
data: skip_ids_data,
|
|
393
|
+
id: IdGenerator.from_list(
|
|
394
|
+
'faspex_recv',
|
|
395
|
+
options.get_option(:url, mandatory: true),
|
|
396
|
+
options.get_option(:username, mandatory: true),
|
|
397
|
+
options.get_option(:box, mandatory: true).to_s
|
|
477
398
|
)
|
|
478
|
-
|
|
479
|
-
return Node.new(context: context, api: api_node, prefix_path: source_info[KEY_PATH]).execute_action(command)
|
|
480
|
-
end
|
|
399
|
+
)
|
|
481
400
|
end
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
401
|
+
# get command line parameters
|
|
402
|
+
delivery_id = options.instance_identifier
|
|
403
|
+
Aspera.assert(!delivery_id.empty?, 'empty id')
|
|
404
|
+
recipient = options.get_option(:recipient)
|
|
405
|
+
if delivery_id.eql?(SpecialValues::ALL)
|
|
406
|
+
pkg_id_uri = mailbox_filtered_entries.map{ |i| {id: i[PACKAGE_MATCH_FIELD], uri: self.class.get_fasp_uri_from_entry(i, raise_no_link: false)}}
|
|
407
|
+
elsif delivery_id.eql?(SpecialValues::INIT)
|
|
408
|
+
Aspera.assert(skip_ids_persistency, 'Only with option once_only')
|
|
409
|
+
skip_ids_persistency.data.clear.concat(mailbox_filtered_entries.map{ |i| {id: i[PACKAGE_MATCH_FIELD]}})
|
|
410
|
+
skip_ids_persistency.save
|
|
411
|
+
return Result::Status.new("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
|
412
|
+
elsif !recipient.nil? && recipient.start_with?('*')
|
|
413
|
+
found_package_link = mailbox_filtered_entries(stop_at_id: delivery_id).find{ |p| p[PACKAGE_MATCH_FIELD].eql?(delivery_id)}['link'].first['href']
|
|
414
|
+
raise "Not Found. Dropbox and Workgroup packages can use the link option with #{Transfer::Uri::SCHEME}" if found_package_link.nil?
|
|
415
|
+
pkg_id_uri = [{id: delivery_id, uri: found_package_link}]
|
|
416
|
+
else
|
|
417
|
+
# TODO: delivery id is the right one if package was receive by workgroup
|
|
418
|
+
endpoint =
|
|
419
|
+
case options.get_option(:box, mandatory: true)
|
|
420
|
+
when :inbox, :archive then 'received'
|
|
421
|
+
when :sent then 'sent'
|
|
422
|
+
end
|
|
423
|
+
entry_xml = api_v3.call(operation: 'GET', subpath: "#{endpoint}/#{delivery_id}", headers: {'Accept' => 'application/xml'}, ret: :resp).body
|
|
424
|
+
package_entry = XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
|
|
425
|
+
pkg_id_uri = [{id: delivery_id, uri: self.class.get_fasp_uri_from_entry(package_entry)}]
|
|
491
426
|
end
|
|
492
|
-
when
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
pkg_box_id = options.instance_identifier
|
|
510
|
-
return entity_execute(api: api_v4, entity: "#{pkg_box_type}/#{pkg_box_id}/packages")
|
|
427
|
+
when /^#{Transfer::Uri::SCHEME}:/o
|
|
428
|
+
pkg_id_uri = [{id: 'package', uri: link_url}]
|
|
429
|
+
else
|
|
430
|
+
link_data = self.class.get_link_data(link_url)
|
|
431
|
+
Aspera.assert(link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH), type: Cli::BadArgument){"Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}"}
|
|
432
|
+
# NOTE: unauthenticated API (authorization is in url params)
|
|
433
|
+
api_public_link = Rest.new(base_url: link_data[:base_url])
|
|
434
|
+
pkg_xml = api_public_link.call(
|
|
435
|
+
operation: 'GET',
|
|
436
|
+
subpath: link_data[:subpath],
|
|
437
|
+
headers: {'Accept' => 'application/xml'},
|
|
438
|
+
query: {passcode: link_data[:query]['passcode']},
|
|
439
|
+
ret: :resp
|
|
440
|
+
).body
|
|
441
|
+
if !pkg_xml.start_with?('<?xml ')
|
|
442
|
+
Environment.instance.open_uri(link_url)
|
|
443
|
+
raise Cli::Error, 'Unexpected response: package not found ?'
|
|
511
444
|
end
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
445
|
+
package_entry = XmlSimple.xml_in(pkg_xml, {'ForceArray' => false})
|
|
446
|
+
Log.dump(:package_entry, package_entry)
|
|
447
|
+
transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
|
|
448
|
+
pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
|
|
449
|
+
end
|
|
450
|
+
# prune packages already downloaded
|
|
451
|
+
# TODO : remove ids from skip not present in inbox to avoid growing too big
|
|
452
|
+
# skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
|
|
453
|
+
pkg_id_uri.reject!{ |i| skip_ids_data.include?(i[:id])}
|
|
454
|
+
Log.dump(:pkg_id_uri, pkg_id_uri)
|
|
455
|
+
return Result::Status.new('no new package') if pkg_id_uri.empty?
|
|
456
|
+
result_transfer = []
|
|
457
|
+
pkg_id_uri.each do |id_uri|
|
|
458
|
+
if id_uri[:uri].nil?
|
|
459
|
+
# skip package with no link: empty or content deleted
|
|
460
|
+
statuses = Transfer::Result.success
|
|
461
|
+
else
|
|
462
|
+
transfer_spec = Transfer::Uri.new(id_uri[:uri]).transfer_spec
|
|
463
|
+
# NOTE: only external users have token in Transfer::Uri::SCHEME link !
|
|
464
|
+
if !transfer_spec.key?('token')
|
|
465
|
+
sanitized = id_uri[:uri].gsub('&', '&')
|
|
466
|
+
xml_payload =
|
|
467
|
+
%Q(<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="#{sanitized}"/></url-list>)
|
|
468
|
+
transfer_spec['token'] = api_v3.call(
|
|
469
|
+
operation: 'POST',
|
|
470
|
+
subpath: 'issue-token',
|
|
471
|
+
query: {'direction' => 'down'},
|
|
472
|
+
content_type: Mime::TEXT,
|
|
473
|
+
body: xml_payload,
|
|
474
|
+
headers: {'Accept' => Mime::TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
|
|
475
|
+
ret: :resp
|
|
476
|
+
).body
|
|
525
477
|
end
|
|
526
|
-
|
|
527
|
-
|
|
478
|
+
transfer_spec['direction'] = Transfer::Spec::DIRECTION_RECEIVE
|
|
479
|
+
statuses = transfer.start(transfer_spec)
|
|
528
480
|
end
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
login_methods = XmlSimple.xml_in(login_meths, {'ForceArray' => false})
|
|
533
|
-
return Result::ObjectList.new(login_methods['XRD']['Service'])
|
|
481
|
+
result_transfer.push({'package' => id_uri[:id], Runner::STATUS_FIELD => statuses})
|
|
482
|
+
# skip only if all sessions completed
|
|
483
|
+
skip_ids_data.push(id_uri[:id]) if statuses.is_a?(Transfer::Result::Success)
|
|
534
484
|
end
|
|
485
|
+
skip_ids_persistency&.save
|
|
486
|
+
return Runner.result_transfer_multiple(result_transfer)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Resolve a source by id/name and validate the storage option.
|
|
490
|
+
# @return [Hash] ctx with :source_info and :source_node_plugin
|
|
491
|
+
def setup_source_selected(**)
|
|
492
|
+
source_list = api_v3.read('source_shares')['items']
|
|
493
|
+
source_id = options.instance_identifier do |field, value|
|
|
494
|
+
Aspera.assert_values(field, ['name'], type: Cli::BadArgument){'selector field'}
|
|
495
|
+
self.class.get_source_id_by_name(value, source_list)
|
|
496
|
+
end.to_i
|
|
497
|
+
selected_source = source_list.find{ |i| i['id'].eql?(source_id)}
|
|
498
|
+
Aspera.assert(!selected_source.nil?, type: BadArgument){'No such source'}
|
|
499
|
+
source_name = selected_source['name']
|
|
500
|
+
source_hash = options.get_option(:storage, mandatory: true)
|
|
501
|
+
Aspera.assert_type(source_hash, Hash, type: Cli::Error){'storage option'}
|
|
502
|
+
source_hash.each do |name, storage|
|
|
503
|
+
Aspera.assert_type(storage, Hash, type: Cli::Error){"storage '#{name}'"}
|
|
504
|
+
[KEY_NODE, KEY_PATH].each do |key|
|
|
505
|
+
Aspera.assert(storage.key?(key), type: Cli::Error){"storage '#{name}' must have a '#{key}'"}
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
Aspera.assert(source_hash.key?(source_name), type: Cli::Error){"No such storage in config file: \"#{source_name}\" in [#{source_hash.keys.join(', ')}]"}
|
|
509
|
+
{source_info: source_hash[source_name]}
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# source > node sub-handlers: one per COMMANDS_FASPEX
|
|
513
|
+
# setup_source_selected injects :source_info; setup_source_node builds the Node plugin.
|
|
514
|
+
def setup_source_node(source_info:, **)
|
|
515
|
+
Log.dump(:source_info, source_info)
|
|
516
|
+
node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE], context: 'faspex node')
|
|
517
|
+
Log.log.debug{"node=#{node_config}"}
|
|
518
|
+
Aspera.assert_type(node_config, Hash, type: Cli::Error){source_info[KEY_NODE]}
|
|
519
|
+
api_node = Rest.new(
|
|
520
|
+
base_url: node_config['url'],
|
|
521
|
+
auth: {
|
|
522
|
+
type: :basic,
|
|
523
|
+
username: node_config['username'],
|
|
524
|
+
password: node_config['password']
|
|
525
|
+
}
|
|
526
|
+
)
|
|
527
|
+
{source_node_plugin: Node.new(context: context, api: api_node, prefix_path: source_info[KEY_PATH])}
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
Node::COMMANDS_FASPEX.each do |cmd|
|
|
531
|
+
define_action_method([:source, :node, cmd]) do |source_node_plugin:, **|
|
|
532
|
+
source_node_plugin.dispatch_v3_command(cmd)
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# dropbox sub-handler
|
|
537
|
+
|
|
538
|
+
def action_dropbox_list
|
|
539
|
+
dropbox_list = api_v3.read('dropboxes')
|
|
540
|
+
Result::ObjectList.new(dropbox_list['items'], fields: %w[name id description can_read can_write])
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def action_v4_package_users
|
|
544
|
+
pkg_box_id = options.instance_identifier
|
|
545
|
+
entity_execute(api: api_v4, entity: "users/#{pkg_box_id}/packages", command: :list)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def action_address_book
|
|
549
|
+
result = api_v3.read('address-book', {'format' => 'json', 'count' => 100_000})
|
|
550
|
+
formatter.display_status("users: #{result['itemsPerPage']}/#{result['totalResults']}, start:#{result['startIndex']}")
|
|
551
|
+
users = result['entry']
|
|
552
|
+
# add missing entries
|
|
553
|
+
users.each do |u|
|
|
554
|
+
unless u['emails'].nil?
|
|
555
|
+
email = u['emails'].find{ |i| i['primary'].eql?('true')}
|
|
556
|
+
u['email'] = email['value'] unless email.nil?
|
|
557
|
+
end
|
|
558
|
+
if u['email'].nil?
|
|
559
|
+
Log.log.warn{"Skip user without email: #{u}"}
|
|
560
|
+
next
|
|
561
|
+
end
|
|
562
|
+
u['first_name'], u['last_name'] = u['displayName'].split(' ', 2)
|
|
563
|
+
u['x'] = true
|
|
564
|
+
end
|
|
565
|
+
return Result::ObjectList.new(users)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def action_login_methods
|
|
569
|
+
login_meths = api_v3.call(operation: 'GET', subpath: 'login/new', headers: {'Accept' => 'application/xrds+xml'}, ret: :resp).body
|
|
570
|
+
login_methods = XmlSimple.xml_in(login_meths, {'ForceArray' => false})
|
|
571
|
+
return Result::ObjectList.new(login_methods['XRD']['Service'])
|
|
535
572
|
end
|
|
536
573
|
end
|
|
537
574
|
end
|