aspera-cli 4.25.1 → 4.25.3

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +456 -405
  4. data/CONTRIBUTING.md +22 -18
  5. data/README.md +33 -9741
  6. data/bin/asession +111 -88
  7. data/lib/aspera/agent/connect.rb +1 -1
  8. data/lib/aspera/agent/desktop.rb +1 -1
  9. data/lib/aspera/agent/direct.rb +19 -18
  10. data/lib/aspera/agent/node.rb +1 -1
  11. data/lib/aspera/api/aoc.rb +44 -20
  12. data/lib/aspera/api/faspex.rb +25 -6
  13. data/lib/aspera/api/node.rb +20 -16
  14. data/lib/aspera/ascp/installation.rb +32 -51
  15. data/lib/aspera/assert.rb +2 -2
  16. data/lib/aspera/cli/extended_value.rb +1 -0
  17. data/lib/aspera/cli/formatter.rb +0 -4
  18. data/lib/aspera/cli/hints.rb +18 -4
  19. data/lib/aspera/cli/main.rb +3 -6
  20. data/lib/aspera/cli/manager.rb +46 -30
  21. data/lib/aspera/cli/plugins/aoc.rb +155 -131
  22. data/lib/aspera/cli/plugins/base.rb +15 -18
  23. data/lib/aspera/cli/plugins/config.rb +50 -87
  24. data/lib/aspera/cli/plugins/factory.rb +2 -2
  25. data/lib/aspera/cli/plugins/faspex.rb +4 -4
  26. data/lib/aspera/cli/plugins/faspex5.rb +74 -76
  27. data/lib/aspera/cli/plugins/node.rb +3 -5
  28. data/lib/aspera/cli/plugins/oauth.rb +26 -25
  29. data/lib/aspera/cli/plugins/preview.rb +9 -14
  30. data/lib/aspera/cli/plugins/shares.rb +15 -7
  31. data/lib/aspera/cli/transfer_agent.rb +2 -2
  32. data/lib/aspera/cli/version.rb +1 -1
  33. data/lib/aspera/colors.rb +7 -0
  34. data/lib/aspera/environment.rb +30 -16
  35. data/lib/aspera/faspex_gw.rb +6 -6
  36. data/lib/aspera/faspex_postproc.rb +20 -14
  37. data/lib/aspera/hash_ext.rb +8 -0
  38. data/lib/aspera/log.rb +15 -15
  39. data/lib/aspera/markdown.rb +22 -0
  40. data/lib/aspera/node_simulator.rb +1 -1
  41. data/lib/aspera/oauth/base.rb +2 -2
  42. data/lib/aspera/oauth/url_json.rb +2 -2
  43. data/lib/aspera/oauth/web.rb +1 -1
  44. data/lib/aspera/preview/generator.rb +9 -9
  45. data/lib/aspera/rest.rb +44 -37
  46. data/lib/aspera/rest_call_error.rb +16 -8
  47. data/lib/aspera/rest_error_analyzer.rb +38 -36
  48. data/lib/aspera/rest_errors_aspera.rb +19 -18
  49. data/lib/aspera/transfer/resumer.rb +2 -2
  50. data/lib/aspera/yaml.rb +49 -0
  51. data.tar.gz.sig +0 -0
  52. metadata +17 -3
  53. metadata.gz.sig +0 -0
  54. data/release_notes.md +0 -8
@@ -36,6 +36,7 @@ require 'digest'
36
36
  require 'open3'
37
37
  require 'date'
38
38
  require 'erb'
39
+ require 'net/http'
39
40
 
40
41
  module Aspera
41
42
  module Cli
@@ -107,11 +108,7 @@ module Aspera
107
108
  )
108
109
  options.parse_options!
109
110
  Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
110
- # Data persistency manager, created by config plugin, set for global object
111
- context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
112
- # Set folders for plugin lookup
113
- Plugins::Factory.instance.add_lookup_folder(self.class.gem_plugins_folder)
114
- Plugins::Factory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
111
+ setup_persistency_and_plugin_folders
115
112
  # Option to set config file
116
113
  options.declare(
117
114
  :config_file, 'Path to YAML file with preset configuration',
@@ -121,12 +118,7 @@ module Aspera
121
118
  options.parse_options!
122
119
  # Read config file (set @config_presets)
123
120
  read_config_file
124
- # Add preset handler (needed for smtp)
125
- ExtendedValue.instance.on(EXTEND_PRESET){ |v| preset_by_name(v)}
126
- ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
127
- ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
128
- # Load defaults before it can be overridden
129
- add_plugin_default_preset(CONF_GLOBAL_SYM)
121
+ setup_extended_value_handlers
130
122
  # Vault options
131
123
  options.declare(:secret, 'Secret for access keys')
132
124
  options.declare(:vault, 'Vault for secrets', allowed: Hash)
@@ -145,9 +137,8 @@ module Aspera
145
137
  options.declare(:sdk_url, 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF)
146
138
  options.parse_options!
147
139
  set_sdk_dir
148
- options.declare(:ascp_path, 'Ascp: Path to ascp (or product with "product:")', handler: {o: Ascp::Installation.instance, m: :ascp_path}, default: "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{Ascp::Installation::FIRST_FOUND}")
149
140
  options.declare(:locations_url, 'Ascp: URL to get download locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
150
- options.declare(:sdk_folder, 'Ascp: SDK installation folder path', handler: {o: Products::Transferd, m: :sdk_directory})
141
+ options.declare(:sdk_folder, 'Ascp: Path to folder with ascp (or product with "product:")', handler: {o: Products::Transferd, m: :sdk_directory})
151
142
  options.declare(:progress_bar, 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
152
143
  # Email options
153
144
  options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
@@ -165,17 +156,39 @@ module Aspera
165
156
  options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
166
157
  options.parse_options!
167
158
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
159
+ setup_pac_executor
160
+ setup_rest_and_transfer_runtime
161
+ end
162
+
163
+ private
164
+
165
+ def setup_persistency_and_plugin_folders
166
+ context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
167
+ Plugins::Factory.instance.add_lookup_folder(self.class.gem_plugins_folder)
168
+ Plugins::Factory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
169
+ end
170
+
171
+ def setup_extended_value_handlers
172
+ ExtendedValue.instance.on(EXTEND_PRESET){ |v| preset_by_name(v)}
173
+ ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
174
+ ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
175
+ add_plugin_default_preset(CONF_GLOBAL_SYM)
176
+ end
177
+
178
+ def setup_pac_executor
168
179
  pac_script = options.get_option(:fpac)
169
- # Create PAC executor
170
- if !pac_script.nil?
171
- @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
172
- proxy_user_pass = options.get_option(:proxy_credentials)
173
- if !proxy_user_pass.nil?
174
- Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
175
- @pac_exec.proxy_user = proxy_user_pass[0]
176
- @pac_exec.proxy_pass = proxy_user_pass[1]
177
- end
180
+ return unless pac_script
181
+
182
+ @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
183
+ proxy_user_pass = options.get_option(:proxy_credentials)
184
+ if proxy_user_pass
185
+ Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
186
+ @pac_exec.proxy_user = proxy_user_pass[0]
187
+ @pac_exec.proxy_pass = proxy_user_pass[1]
178
188
  end
189
+ end
190
+
191
+ def setup_rest_and_transfer_runtime
179
192
  RestParameters.instance.user_agent = Info::CMD_NAME
180
193
  RestParameters.instance.progress_bar = @progress_bar
181
194
  RestParameters.instance.session_cb = lambda{ |http_session| update_http_session(http_session)}
@@ -197,12 +210,14 @@ module Aspera
197
210
  keys_to_delete.each{ |k| @option_http_options.delete(k)}
198
211
  OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
199
212
  OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
200
- Transfer::Parameters.file_list_folder = File.join(@main_folder, 'filelists')
201
- RestErrorAnalyzer.instance.log_file = File.join(@main_folder, 'rest_exceptions.log')
213
+ Transfer::Parameters.file_list_folder = File.join(@main_folder, FILE_LIST_FOLDER_NAME)
214
+ RestErrorAnalyzer.instance.log_file = File.join(@main_folder, REST_EXCEPTIONS_LOG_FILENAME)
202
215
  # Register aspera REST call error handlers
203
216
  RestErrorsAspera.register_handlers
204
217
  end
205
218
 
219
+ public
220
+
206
221
  attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
207
222
  attr_reader :option_ignore_cert_host_port, :progress_bar
208
223
 
@@ -307,10 +322,7 @@ module Aspera
307
322
  if @option_warn_insecure_cert
308
323
  base_url = "https://#{address}:#{port}"
309
324
  if !@ssl_warned_urls.include?(base_url)
310
- formatter.display_message(
311
- :error,
312
- "#{Formatter::WARNING_FLASH} Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production."
313
- )
325
+ Log.log.warn{"Ignoring certificate for: #{base_url}. Do not deactivate certificate verification in production."}
314
326
  @ssl_warned_urls.push(base_url)
315
327
  end
316
328
  end
@@ -416,7 +428,7 @@ module Aspera
416
428
  raise Cli::Error, "Preset already exists: #{preset_name} (use --override=yes or provide alternate name on command line)" \
417
429
  if !option_override && @config_presets.key?(preset_name)
418
430
  if option_default
419
- formatter.display_status("Setting config preset as default for #{plugin_name}")
431
+ Log.log.info("Setting config preset as default for #{plugin_name}")
420
432
  @config_presets[CONF_PRESET_DEFAULTS][plugin_name.to_s] = preset_name
421
433
  end
422
434
  @config_presets[preset_name] = preset_values
@@ -439,7 +451,7 @@ module Aspera
439
451
  Log.log.warn{"overwriting value for #{param_name}: #{selected_preset[param_name]}"}
440
452
  end
441
453
  selected_preset[param_name] = param_value
442
- formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
454
+ Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
443
455
  nil
444
456
  end
445
457
 
@@ -553,62 +565,15 @@ module Aspera
553
565
  raise Cli::Error, e.to_s
554
566
  end
555
567
 
556
- def execute_connect_action
557
- command = options.get_next_command(%i[list info version])
558
- if %i[info version].include?(command)
559
- connect_id = options.get_next_argument('id or title')
560
- one_res = Products::Connect.instance.versions.find{ |i| i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
561
- raise Cli::BadIdentifier.new(:connect, connect_id) if one_res.nil?
562
- end
563
- case command
564
- when :list
565
- return Main.result_object_list(Products::Connect.instance.versions, fields: %w[id title version])
566
- when :info
567
- one_res.delete('links')
568
- return Main.result_single_object(one_res)
569
- when :version
570
- all_links = one_res['links']
571
- command = options.get_next_command(%i[list download open])
572
- if %i[download open].include?(command)
573
- link_title = options.get_next_argument('title or rel')
574
- one_link = all_links.find{ |i| i['title'].eql?(link_title) || i['rel'].eql?(link_title)}
575
- raise "no such value: #{link_title}" if one_link.nil?
576
- end
577
- case command
578
- when :list
579
- return Main.result_object_list(all_links)
580
- when :download
581
- archive_path = one_link['href']
582
- save_to_path = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), archive_path.gsub(%r{.*/}, ''))
583
- Products::Connect.instance.cdn_api.call(operation: 'GET', subpath: archive_path, save_to_file: save_to_path)
584
- return Main.result_status("Downloaded: #{save_to_path}")
585
- when :open
586
- Environment.instance.open_uri(one_link['href'])
587
- return Main.result_status("Opened: #{one_link['href']}")
588
- end
589
- end
590
- Aspera.error_unreachable_line
591
- end
592
-
593
568
  def install_transfer_sdk
594
- # Reset to default location, if older default was used
595
- Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
596
569
  asked_version = options.get_next_argument('transferd version', mandatory: false)
597
570
  name, version, folder = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: asked_version)
598
571
  return Main.result_status("Installed #{name} version #{version} in #{folder}")
599
572
  end
600
573
 
601
574
  def execute_action_ascp
602
- command = options.get_next_command(%i[connect use show products info install spec schema errors])
575
+ command = options.get_next_command(%i[show products info install spec schema errors])
603
576
  case command
604
- when :connect
605
- return execute_connect_action
606
- when :use
607
- ascp_path = options.get_next_argument('path to ascp')
608
- Ascp::Installation.instance.ascp_path = ascp_path
609
- formatter.display_status("ascp version: #{Ascp::Installation.instance.get_ascp_version(ascp_path)}")
610
- set_global_default(:ascp_path, ascp_path)
611
- return Main.result_nothing
612
577
  when :show
613
578
  return Main.result_text(Ascp::Installation.instance.path(:ascp))
614
579
  when :info
@@ -622,15 +587,10 @@ module Aspera
622
587
  SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
623
588
  return Main.result_single_object(data)
624
589
  when :products
625
- command = options.get_next_command(%i[list use])
590
+ command = options.get_next_command(%i[list])
626
591
  case command
627
592
  when :list
628
593
  return Main.result_object_list(Ascp::Installation.instance.installed_products, fields: %w[name app_root])
629
- when :use
630
- default_product = options.get_next_argument('product name')
631
- Ascp::Installation.instance.use_ascp_from_product(default_product)
632
- set_global_default(:ascp_path, "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{default_product}")
633
- return Main.result_nothing
634
594
  end
635
595
  when :install
636
596
  return install_transfer_sdk
@@ -848,7 +808,7 @@ module Aspera
848
808
  file_url = options.get_next_argument('source URL').chomp
849
809
  file_dest = options.get_next_argument('file path', mandatory: false)
850
810
  file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
851
- formatter.display_status("Downloading: #{file_url}")
811
+ Log.log.info("Downloading: #{file_url}")
852
812
  Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
853
813
  return Main.result_status("Saved to: #{file_dest}")
854
814
  when :tokens
@@ -1047,8 +1007,7 @@ module Aspera
1047
1007
  return false if @config_checksum_on_disk.eql?(current_checksum)
1048
1008
  FileUtils.mkdir_p(@main_folder)
1049
1009
  Environment.restrict_file_access(@main_folder)
1050
- Log.log.info{"Writing #{@option_config_file}"}
1051
- formatter.display_status('Saving config file.')
1010
+ Log.log.info{"Saving config file: #{@option_config_file}"}
1052
1011
  Environment.write_file_restricted(@option_config_file, force: true){@config_presets.to_yaml}
1053
1012
  @config_checksum_on_disk = current_checksum
1054
1013
  return true
@@ -1200,6 +1159,8 @@ module Aspera
1200
1159
  # Folder containing custom plugins in user's config folder
1201
1160
  ASPERA_PLUGINS_FOLDERNAME = 'plugins'
1202
1161
  PERSISTENCY_FOLDER = 'persist_store'
1162
+ FILE_LIST_FOLDER_NAME = 'filelists'
1163
+ REST_EXCEPTIONS_LOG_FILENAME = 'rest_exceptions.log'
1203
1164
  ASPERA = 'aspera'
1204
1165
  SERVER_COMMAND = 'server'
1205
1166
  TRANSFERD_APP_NAME = 'sdk'
@@ -1232,6 +1193,8 @@ module Aspera
1232
1193
  :CONF_PRESET_DEFAULTS,
1233
1194
  :CONF_PRESET_GLOBAL,
1234
1195
  :ASPERA_PLUGINS_FOLDERNAME,
1196
+ :FILE_LIST_FOLDER_NAME,
1197
+ :REST_EXCEPTIONS_LOG_FILENAME,
1235
1198
  :ASPERA,
1236
1199
  :DEMO_SERVER,
1237
1200
  :DEMO_PRESET,
@@ -20,9 +20,9 @@ module Aspera
20
20
  @plugins = {}
21
21
  end
22
22
 
23
- # @return list of registered plugins
23
+ # @return [Array<Symbol>] Sorted list of registered plugins
24
24
  def plugin_list
25
- @plugins.keys
25
+ @plugins.keys.sort
26
26
  end
27
27
 
28
28
  # add a folder to the list of folders to look for plugins
@@ -51,7 +51,7 @@ module Aspera
51
51
  http = api.call(
52
52
  operation: 'POST',
53
53
  headers: {
54
- 'Content-type' => Rest::MIME_TEXT,
54
+ 'Content-type' => Mime::TEXT,
55
55
  'Accept' => 'application/xrds+xml'
56
56
  },
57
57
  ret: :resp
@@ -254,7 +254,7 @@ module Aspera
254
254
  package_creation_data = api_public_link.call(
255
255
  operation: 'POST',
256
256
  subpath: create_path,
257
- content_type: Rest::MIME_JSON,
257
+ content_type: Mime::JSON,
258
258
  body: package_create_params,
259
259
  headers: {'Accept' => 'text/javascript'},
260
260
  ret: :resp
@@ -418,9 +418,9 @@ module Aspera
418
418
  operation: 'POST',
419
419
  subpath: 'issue-token',
420
420
  query: {'direction' => 'down'},
421
- content_type: Rest::MIME_TEXT,
421
+ content_type: Mime::TEXT,
422
422
  body: xml_payload,
423
- headers: {'Accept' => Rest::MIME_TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
423
+ headers: {'Accept' => Mime::TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
424
424
  ret: :resp
425
425
  ).body
426
426
  end
@@ -94,7 +94,7 @@ module Aspera
94
94
 
95
95
  def initialize(**_)
96
96
  super
97
- options.declare(:box, "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox')
97
+ options.declare(:box, "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox_all')
98
98
  options.declare(:shared_folder, 'Send package with files from shared folder')
99
99
  options.declare(:group_type, 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes)
100
100
  options.parse_options!
@@ -102,7 +102,7 @@ module Aspera
102
102
 
103
103
  def set_api
104
104
  # create an API object with the same options, but with a different subpath
105
- @api_v5 = new_with_options(Api::Faspex)
105
+ @api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
106
106
  # in case user wants to use HTTPGW tell transfer agent how to get address
107
107
  transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
108
108
  end
@@ -118,7 +118,7 @@ module Aspera
118
118
  recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
119
119
  end
120
120
  parameters['recipients'].map! do |recipient_data|
121
- # if just a string, make a general lookup and build expected name/type hash
121
+ # If just a string, make a general lookup and build expected name/type hash
122
122
  if recipient_data.is_a?(String)
123
123
  matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
124
124
  recipient_data = {
@@ -174,8 +174,8 @@ module Aspera
174
174
  # list all packages with optional filter
175
175
  def list_packages_with_filter(query: {})
176
176
  filter = options.get_next_argument('filter', mandatory: false, validation: Proc, default: ->(_x){true})
177
- # translate box name to API prefix (with ending slash)
178
177
  box = options.get_option(:box)
178
+ # Translate box name to API prefix (with ending slash)
179
179
  entity =
180
180
  case box
181
181
  when SpecialValues::ALL then 'packages' # only admin can list all packages globally
@@ -236,17 +236,12 @@ module Aspera
236
236
  rescue Cli::BadArgument
237
237
  # paths is optional
238
238
  end
239
+ box = options.get_option(:box)
239
240
  download_params = {
240
- type: 'received',
241
+ type: Api::Faspex.box_type(box),
241
242
  transfer_type: Api::Faspex::TRANSFER_CONNECT
242
243
  }
243
- box = options.get_option(:box)
244
- case box
245
- when /outbox/ then download_params[:type] = 'sent'
246
- when *Api::Faspex::API_LIST_MAILBOX_TYPES then nil # nothing to do
247
- else # shared inbox / workgroup
248
- download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id']
249
- end
244
+ download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
250
245
  packages.each do |package|
251
246
  pkg_id = package['id']
252
247
  formatter.display_status("Receiving package #{pkg_id}")
@@ -255,9 +250,9 @@ module Aspera
255
250
  operation: 'POST',
256
251
  subpath: "packages/#{pkg_id}/transfer_spec/download",
257
252
  query: download_params,
258
- content_type: Rest::MIME_JSON,
253
+ content_type: Mime::JSON,
259
254
  body: param_file_list,
260
- headers: {'Accept' => Rest::MIME_JSON}
255
+ headers: {'Accept' => Mime::JSON}
261
256
  )
262
257
  # delete flag for Connect Client
263
258
  transfer_spec.delete('authentication')
@@ -272,6 +267,56 @@ module Aspera
272
267
  return Main.result_transfer_multiple(result_transfer)
273
268
  end
274
269
 
270
+ def package_send
271
+ parameters = value_create_modify(command: :send)
272
+ # autofill recipient for public url
273
+ if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
274
+ parameters['recipients'] = [{
275
+ name: @api_v5.pub_link_context['name'],
276
+ recipient_type: @api_v5.pub_link_context['recipient_type']
277
+ }]
278
+ end
279
+ normalize_recipients(parameters)
280
+ # User specified content prot in tspec, but faspex requires in package creation
281
+ # `transfer_spec/upload` will set `content_protection`
282
+ if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
283
+ transfer.user_transfer_spec.delete('content_protection')
284
+ parameters['ear_enabled'] = true
285
+ end
286
+ package = @api_v5.create('packages', parameters)
287
+ shared_folder = options.get_option(:shared_folder)
288
+ if shared_folder.nil?
289
+ # send from local files
290
+ transfer_spec = @api_v5.create(
291
+ "packages/#{package['id']}/transfer_spec/upload",
292
+ {paths: transfer.source_list},
293
+ query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
294
+ )
295
+ # well, we asked a TS for connect, but we actually want a generic one
296
+ transfer_spec.delete('authentication')
297
+ return Main.result_transfer(transfer.start(transfer_spec))
298
+ else
299
+ # send from remote shared folder
300
+ if (m = Base.percent_selector(shared_folder))
301
+ shared_folder = lookup_entity_by_field(
302
+ api: @api_v5,
303
+ entity: 'shared_folders',
304
+ field: m[:field],
305
+ value: m[:value]
306
+ )['id']
307
+ end
308
+ transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
309
+ # start remote transfer and get first status
310
+ result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
311
+ result['id'] = package['id']
312
+ unless result['status'].eql?('completed')
313
+ formatter.display_status("Package #{package['id']}")
314
+ result = wait_package_status(package['id'])
315
+ end
316
+ return Main.result_single_object(result)
317
+ end
318
+ end
319
+
275
320
  # Browse a folder
276
321
  # @param browse_endpoint [String] the endpoint to browse
277
322
  def browse_folder(browse_endpoint)
@@ -300,9 +345,9 @@ module Aspera
300
345
  operation: 'POST',
301
346
  subpath: browse_endpoint,
302
347
  query: query,
303
- content_type: Rest::MIME_JSON,
348
+ content_type: Mime::JSON,
304
349
  body: {'path' => path, 'filters' => filters},
305
- headers: {'Accept' => Rest::MIME_JSON},
350
+ headers: {'Accept' => Mime::JSON},
306
351
  ret: :both
307
352
  )
308
353
  all_items.concat(data['items'])
@@ -339,12 +384,7 @@ module Aspera
339
384
  when :show
340
385
  return Main.result_single_object(@api_v5.read("packages/#{package_id}"))
341
386
  when :browse
342
- location = case options.get_option(:box)
343
- when 'inbox' then 'received'
344
- when 'outbox' then 'sent'
345
- else raise BadArgument, 'Browse only available for inbox and outbox'
346
- end
347
- return browse_folder("packages/#{package_id}/files/#{location}")
387
+ return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}")
348
388
  when :status
349
389
  status_list = options.get_next_argument('list of states, or nothing', mandatory: false, validation: Array)
350
390
  status = wait_package_status(package_id, status_list: status_list)
@@ -357,64 +397,21 @@ module Aspera
357
397
  @api_v5.call(
358
398
  operation: 'DELETE',
359
399
  subpath: 'packages',
360
- content_type: Rest::MIME_JSON,
400
+ content_type: Mime::JSON,
361
401
  body: {ids: ids},
362
- headers: {'Accept' => Rest::MIME_JSON}
402
+ headers: {'Accept' => Mime::JSON}
363
403
  )
364
404
  return Main.result_status('Package(s) deleted')
365
405
  when :receive
366
406
  return package_receive(package_id)
367
407
  when :send
368
- parameters = value_create_modify(command: command)
369
- # autofill recipient for public url
370
- if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
371
- parameters['recipients'] = [{
372
- name: @api_v5.pub_link_context['name'],
373
- recipient_type: @api_v5.pub_link_context['recipient_type']
374
- }]
375
- end
376
- normalize_recipients(parameters)
377
- # User specified content prot in tspec, but faspex requires in package creation
378
- # `transfer_spec/upload` will set `content_protection`
379
- if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
380
- transfer.user_transfer_spec.delete('content_protection')
381
- parameters['ear_enabled'] = true
382
- end
383
- package = @api_v5.create('packages', parameters)
384
- shared_folder = options.get_option(:shared_folder)
385
- if shared_folder.nil?
386
- # send from local files
387
- transfer_spec = @api_v5.create(
388
- "packages/#{package['id']}/transfer_spec/upload",
389
- {paths: transfer.source_list},
390
- query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
391
- )
392
- # well, we asked a TS for connect, but we actually want a generic one
393
- transfer_spec.delete('authentication')
394
- return Main.result_transfer(transfer.start(transfer_spec))
395
- else
396
- # send from remote shared folder
397
- if (m = Base.percent_selector(shared_folder))
398
- shared_folder = lookup_entity_by_field(
399
- api: @api_v5,
400
- entity: 'shared_folders',
401
- field: m[:field],
402
- value: m[:value]
403
- )['id']
404
- end
405
- transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
406
- # start remote transfer and get first status
407
- result = @api_v5.create("packages/#{package['id']}/remote_transfer", transfer_request)
408
- result['id'] = package['id']
409
- unless result['status'].eql?('completed')
410
- formatter.display_status("Package #{package['id']}")
411
- result = wait_package_status(package['id'])
412
- end
413
- return Main.result_single_object(result)
414
- end
408
+ return package_send
415
409
  when :list
416
410
  list, total = list_packages_with_filter
417
- return Main.result_object_list(list, total: total, fields: %w[id title release_date total_bytes total_files created_time state])
411
+ fields = %w[id title status sender.name recipients.0.name release_date total_bytes total_files]
412
+ fields.delete('recipients.0.name') if %w[inbox inbox_history].include?(options.get_option(:box))
413
+ fields.delete('sender.name') if %w[outbox outbox_history].include?(options.get_option(:box))
414
+ return Main.result_object_list(list, total: total, fields: fields)
418
415
  end
419
416
  end
420
417
 
@@ -506,7 +503,7 @@ module Aspera
506
503
  )
507
504
  return Main.result_single_object(result)
508
505
  when :members, :saml_groups
509
- # :shared_inboxes, :workgroups
506
+ # res_command := :shared_inboxes, :workgroups
510
507
  res_id = instance_identifier{ |field, value| lookup_entity_by_field(api: @api_v5, entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
511
508
  res_path = "#{res_sym}/#{res_id}/#{res_command}"
512
509
  list_key = res_command.to_s
@@ -533,6 +530,7 @@ module Aspera
533
530
  access = options.get_next_argument('level', mandatory: false, accept_list: SHARED_INBOX_MEMBER_LEVELS, default: :standard)
534
531
  options.unshift_next_argument({user: users.map{ |u| {id: u, access: access}}})
535
532
  end
533
+ # TODO: test SAML group
536
534
  return entity_execute(
537
535
  api: @api_v5,
538
536
  entity: res_path,
@@ -541,11 +539,11 @@ module Aspera
541
539
  ) do |field, value|
542
540
  lookup_entity_by_field(
543
541
  api: @api_v5,
544
- entity: 'contacts',
542
+ entity: res_path,
545
543
  field: field,
546
544
  value: value,
547
545
  query: Rest.php_style({type: %w[user]})
548
- )['id']
546
+ )['user_id']
549
547
  end
550
548
  when :reset_password
551
549
  # :accounts
@@ -686,7 +684,7 @@ module Aspera
686
684
  @api_v5.create(invitation_endpoint, params)
687
685
  end
688
686
  when :resend
689
- @api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend")
687
+ @api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend", nil)
690
688
  return Main.result_status('Invitation resent')
691
689
  else
692
690
  return entity_execute(
@@ -450,7 +450,7 @@ module Aspera
450
450
  @api_node.call(
451
451
  operation: 'POST',
452
452
  subpath: 'services/soap/Transfer-201210',
453
- content_type: Rest::MIME_TEXT,
453
+ content_type: Mime::TEXT,
454
454
  body: CENTRAL_SOAP_API_TEST,
455
455
  headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
456
456
  ret: :resp
@@ -471,9 +471,7 @@ module Aspera
471
471
  return Main.result_single_object(nd_info)
472
472
  when :license
473
473
  # requires: asnodeadmin -mu <node user> --acl-add=internal --internal
474
- node_license = @api_node.read('license')
475
- Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal') if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
476
- return Main.result_single_object(node_license)
474
+ return Main.result_single_object(@api_node.read('license'))
477
475
  when :api_details
478
476
  return Main.result_single_object({base_url: @api_node.base_url}.merge(@api_node.params))
479
477
  end
@@ -837,7 +835,7 @@ module Aspera
837
835
  @api_node.call(
838
836
  operation: 'POST',
839
837
  subpath: "asyncs/#{asyncs_id}/#{sync_command}",
840
- content_type: Rest::MIME_TEXT,
838
+ content_type: Mime::TEXT,
841
839
  body: '',
842
840
  ret: :resp
843
841
  ).body
@@ -7,9 +7,33 @@ module Aspera
7
7
  module Plugins
8
8
  # base class for applications supporting OAuth 2.0 authentication
9
9
  class Oauth < BasicAuth
10
- # OAuth methods supported
10
+ class << self
11
+ # Get command line options specified by `AUTH_OPTIONS` and `defaults.keys` (value is default).
12
+ # Adds those not nil to the `kwargs`.
13
+ # Instantiate the provided `klass` with those `kwargs`.
14
+ # `defaults` can specify a default value (not `nil`)
15
+ # @param options [Cli::Manager] Object to get command line options.
16
+ # @param kwargs [Hash] Object creation arguments
17
+ # @param defaults [Hash] Additional options, key=symbol, value=default value or nil
18
+ # @return [Object] instance of `klass`
19
+ # @raise [Cli::Error] if a required option is missing
20
+ def args_from_options(options, defaults: nil, **kwargs)
21
+ defaults ||= {}
22
+ (AUTH_OPTIONS + defaults.keys).each_with_object(kwargs) do |i, m|
23
+ v = options.get_option(i)
24
+ m[i] = v unless v.nil?
25
+ m[i] = defaults[i] if m[i].nil? && !defaults[i].nil?
26
+ end
27
+ rescue ::ArgumentError => e
28
+ if (m = e.message.match(/missing keyword: :(.*)$/))
29
+ raise Cli::Error, "Missing option: #{m[1]}"
30
+ end
31
+ raise
32
+ end
33
+ end
34
+ # OAuth methods supported (web, jwt)
11
35
  AUTH_TYPES = %i[web jwt boot].freeze
12
- # Options used for authentication
36
+ # Options used for authentication (url, auth, client_id, etc...)
13
37
  AUTH_OPTIONS = %i[url auth client_id client_secret redirect_uri private_key passphrase username password].freeze
14
38
  def initialize(**_)
15
39
  super
@@ -20,29 +44,6 @@ module Aspera
20
44
  options.declare(:private_key, 'OAuth (JWT) RSA private key PEM value (prefix file path with @file:)')
21
45
  options.declare(:passphrase, 'OAuth (JWT) RSA private key passphrase')
22
46
  end
23
-
24
- # Get command line options specified by `AUTH_OPTIONS` and `option.keys` (value is default).
25
- # Adds those not nil to the `kwargs`.
26
- # Instantiate the provided `klass` with those kwargs.
27
- # `option` can specify a default value (not `nil`)
28
- # @param klass [Class] API object to create
29
- # @param kwargs [Hash] The fixed keyword arguments for creation
30
- # @param option [Hash] Additional options, key=symbol, value:default value or nil
31
- # @return [Object] instance of `klass`
32
- # @raise [Cli::Error] if a required option is missing
33
- def new_with_options(klass, kwargs: {}, option: {})
34
- klass.new(**
35
- (AUTH_OPTIONS + option.keys).each_with_object(kwargs) do |i, m|
36
- v = options.get_option(i)
37
- m[i] = v unless v.nil?
38
- m[i] = option[i] unless !m[i].nil? || option[i].nil?
39
- end)
40
- rescue ::ArgumentError => e
41
- if (m = e.message.match(/missing keyword: :(.*)$/))
42
- raise Cli::Error, "Missing option: #{m[1]}"
43
- end
44
- raise
45
- end
46
47
  end
47
48
  end
48
49
  end