aspera-cli 4.24.2 → 4.25.0.pre2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1067 -758
  4. data/CONTRIBUTING.md +93 -120
  5. data/README.md +817 -510
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/transferd.rb +4 -4
  8. data/lib/aspera/api/aoc.rb +71 -43
  9. data/lib/aspera/api/cos_node.rb +3 -2
  10. data/lib/aspera/api/faspex.rb +6 -5
  11. data/lib/aspera/api/node.rb +10 -12
  12. data/lib/aspera/ascmd.rb +1 -2
  13. data/lib/aspera/ascp/installation.rb +55 -41
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +28 -6
  16. data/lib/aspera/cli/error.rb +4 -2
  17. data/lib/aspera/cli/extended_value.rb +94 -62
  18. data/lib/aspera/cli/formatter.rb +55 -22
  19. data/lib/aspera/cli/main.rb +21 -14
  20. data/lib/aspera/cli/manager.rb +349 -248
  21. data/lib/aspera/cli/plugins/alee.rb +3 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +94 -51
  23. data/lib/aspera/cli/plugins/base.rb +62 -49
  24. data/lib/aspera/cli/plugins/config.rb +85 -96
  25. data/lib/aspera/cli/plugins/console.rb +15 -9
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +34 -27
  28. data/lib/aspera/cli/plugins/faspex5.rb +47 -44
  29. data/lib/aspera/cli/plugins/faspio.rb +7 -6
  30. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  31. data/lib/aspera/cli/plugins/node.rb +132 -120
  32. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  33. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  34. data/lib/aspera/cli/plugins/preview.rb +26 -46
  35. data/lib/aspera/cli/plugins/server.rb +9 -10
  36. data/lib/aspera/cli/plugins/shares.rb +77 -43
  37. data/lib/aspera/cli/sync_actions.rb +49 -38
  38. data/lib/aspera/cli/transfer_agent.rb +16 -34
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/cli/wizard.rb +8 -5
  41. data/lib/aspera/command_line_builder.rb +20 -17
  42. data/lib/aspera/coverage.rb +6 -2
  43. data/lib/aspera/environment.rb +71 -84
  44. data/lib/aspera/faspex_gw.rb +1 -1
  45. data/lib/aspera/faspex_postproc.rb +1 -1
  46. data/lib/aspera/keychain/factory.rb +1 -2
  47. data/lib/aspera/keychain/macos_security.rb +2 -2
  48. data/lib/aspera/log.rb +2 -1
  49. data/lib/aspera/markdown.rb +31 -0
  50. data/lib/aspera/nagios.rb +6 -5
  51. data/lib/aspera/oauth/base.rb +17 -27
  52. data/lib/aspera/oauth/factory.rb +1 -1
  53. data/lib/aspera/oauth/url_json.rb +2 -1
  54. data/lib/aspera/preview/file_types.rb +23 -37
  55. data/lib/aspera/preview/terminal.rb +95 -29
  56. data/lib/aspera/preview/utils.rb +6 -5
  57. data/lib/aspera/products/connect.rb +3 -3
  58. data/lib/aspera/rest.rb +51 -39
  59. data/lib/aspera/rest_error_analyzer.rb +4 -4
  60. data/lib/aspera/ssh.rb +5 -2
  61. data/lib/aspera/ssl.rb +41 -0
  62. data/lib/aspera/sync/conf.schema.yaml +182 -34
  63. data/lib/aspera/sync/database.rb +2 -1
  64. data/lib/aspera/sync/operations.rb +128 -72
  65. data/lib/aspera/transfer/parameters.rb +3 -4
  66. data/lib/aspera/transfer/spec.rb +2 -3
  67. data/lib/aspera/transfer/spec.schema.yaml +49 -19
  68. data/lib/aspera/transfer/spec_doc.rb +14 -14
  69. data/lib/aspera/uri_reader.rb +1 -1
  70. data/lib/transferd_pb.rb +2 -2
  71. data.tar.gz.sig +0 -0
  72. metadata +33 -6
  73. metadata.gz.sig +0 -0
@@ -10,6 +10,7 @@ require 'aspera/cli/formatter'
10
10
  require 'aspera/cli/info'
11
11
  require 'aspera/cli/transfer_progress'
12
12
  require 'aspera/cli/wizard'
13
+ require 'aspera/cli/sync_actions'
13
14
  require 'aspera/ascp/installation'
14
15
  require 'aspera/sync/operations'
15
16
  require 'aspera/products/transferd'
@@ -29,7 +30,9 @@ require 'aspera/oauth/jwt'
29
30
  require 'aspera/log'
30
31
  require 'aspera/assert'
31
32
  require 'aspera/oauth'
33
+ require 'aspera/ssl'
32
34
  require 'openssl'
35
+ require 'digest'
33
36
  require 'open3'
34
37
  require 'date'
35
38
  require 'erb'
@@ -39,6 +42,8 @@ module Aspera
39
42
  module Plugins
40
43
  # Manage the CLI config file
41
44
  class Config < Base
45
+ include SyncActions
46
+
42
47
  class << self
43
48
  # Folder containing plugins in the gem's main folder
44
49
  def gem_plugins_folder
@@ -76,7 +81,7 @@ module Aspera
76
81
  # We need to defer parsing of options until we have the config file, so we can use @extend with @preset
77
82
  super
78
83
  @use_plugin_defaults = true
79
- @config_presets = nil
84
+ @config_presets = {}
80
85
  @config_checksum_on_disk = nil
81
86
  @vault_instance = nil
82
87
  @pac_exec = nil
@@ -89,7 +94,7 @@ module Aspera
89
94
  @option_cache_tokens = true
90
95
  @main_folder = nil
91
96
  @option_config_file = nil
92
- # Store is used for ruby https
97
+ # Store is used for ruby https (OpenSSL::X509::Store)
93
98
  @certificate_store = nil
94
99
  # Paths are used for ascp
95
100
  @certificate_paths = nil
@@ -98,7 +103,6 @@ module Aspera
98
103
  options.declare(
99
104
  :home, 'Home folder for tool',
100
105
  handler: {o: self, m: :main_folder},
101
- types: String,
102
106
  default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
103
107
  )
104
108
  options.parse_options!
@@ -118,65 +122,49 @@ module Aspera
118
122
  # Read config file (set @config_presets)
119
123
  read_config_file
120
124
  # Add preset handler (needed for smtp)
121
- ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{ |v| preset_by_name(v)})
122
- ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{ |v| vault_value(v)})
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)}
123
128
  # Load defaults before it can be overridden
124
129
  add_plugin_default_preset(CONF_GLOBAL_SYM)
125
130
  # Vault options
126
131
  options.declare(:secret, 'Secret for access keys')
127
- options.declare(:vault, 'Vault for secrets', types: Hash, default: {})
132
+ options.declare(:vault, 'Vault for secrets', allowed: Hash)
128
133
  options.declare(:vault_password, 'Vault password')
129
134
  options.parse_options!
130
135
  # Declare generic plugin options only after handlers are declared
131
136
  Base.declare_options(options)
132
137
  # Configuration options
133
- options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N'){@use_plugin_defaults = false}
138
+ options.declare(:no_default, 'Do not load default configuration for plugin', allowed: Allowed::TYPES_NONE, short: 'N'){@use_plugin_defaults = false}
134
139
  options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
135
- options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
140
+ options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', allowed: Allowed::TYPES_INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
136
141
  options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
137
142
  # Declare wizard options
138
143
  @wizard = Wizard.new(self, @main_folder)
139
144
  # Transfer SDK options
140
- options.declare(:ascp_path, 'Ascp: Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
141
- options.declare(:use_product, 'Ascp: Use ascp from specified product', handler: {o: self, m: :option_use_product})
142
145
  options.declare(:sdk_url, 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF)
143
- options.declare(:locations_url, 'Ascp: URL to get locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
144
- options.declare(:sdk_folder, 'Ascp: SDK folder path', handler: {o: Products::Transferd, m: :sdk_directory})
145
- options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
146
+ options.parse_options!
147
+ 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
+ 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})
151
+ options.declare(:progress_bar, 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
146
152
  # Email options
147
- options.declare(:smtp, 'Email: SMTP configuration', types: Hash)
153
+ options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
148
154
  options.declare(:notify_to, 'Email: Recipient for notification of transfers')
149
155
  options.declare(:notify_template, 'Email: ERB template for notification of transfers')
150
156
  # HTTP options
151
- options.declare(:insecure, 'HTTP/S: Do not validate any certificate', values: :bool, handler: {o: self, m: :option_insecure}, default: :no)
152
- options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', types: Array, handler: {o: self, m: :option_ignore_cert_host_port})
153
- options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', values: :bool, handler: {o: self, m: :option_warn_insecure_cert}, default: :yes)
154
- options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', types: [Array, String], handler: {o: self, m: :trusted_cert_locations})
155
- options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', types: Hash, handler: {o: self, m: :option_http_options}, default: {})
156
- options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', types: String, handler: {o: self, m: :option_http_proxy})
157
- options.declare(:cache_tokens, 'Save and reuse OAuth tokens', values: :bool, handler: {o: self, m: :option_cache_tokens})
157
+ options.declare(:insecure, 'HTTP/S: Do not validate any certificate', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_insecure}, default: false)
158
+ options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', allowed: [Array, NilClass], handler: {o: self, m: :option_ignore_cert_host_port})
159
+ options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_warn_insecure_cert}, default: true)
160
+ options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', allowed: Allowed::TYPES_STRING_ARRAY, handler: {o: self, m: :trusted_cert_locations})
161
+ options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', allowed: Hash, handler: {o: self, m: :option_http_options}, default: {})
162
+ options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', handler: {o: self, m: :option_http_proxy})
163
+ options.declare(:cache_tokens, 'Save and reuse OAuth tokens', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_cache_tokens})
158
164
  options.declare(:fpac, 'Proxy auto configuration script')
159
- options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', types: Array)
165
+ options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
160
166
  options.parse_options!
161
167
  @progress_bar = TransferProgress.new if options.get_option(:progress_bar)
162
- # Check SDK folder is set or not, for compatibility, we check in two places
163
- sdk_dir = Products::Transferd.sdk_directory rescue nil
164
- if sdk_dir.nil?
165
- @sdk_default_location = true
166
- Log.log.debug('SDK folder is not set, checking default')
167
- # New location
168
- sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
169
- Log.log.debug{"Checking: #{sdk_dir}"}
170
- if !Dir.exist?(sdk_dir)
171
- Log.log.debug{"No such folder: #{sdk_dir}"}
172
- # Former location
173
- former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
174
- Log.log.debug{"Checking: #{former_sdk_folder}"}
175
- sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
176
- end
177
- Log.log.debug{"Using: #{sdk_dir}"}
178
- Products::Transferd.sdk_directory = sdk_dir
179
- end
180
168
  pac_script = options.get_option(:fpac)
181
169
  # Create PAC executor
182
170
  if !pac_script.nil?
@@ -200,27 +188,7 @@ module Aspera
200
188
  RestParameters.instance.send(method, v)
201
189
  elsif k.eql?('ssl_options')
202
190
  keys_to_delete.push(k)
203
- # NOTE: here is a hack that allows setting SSLContext options
204
- Aspera.assert_type(v, Array){'ssl_options'}
205
- # Start with default options
206
- ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
207
- v.each do |opt|
208
- case opt
209
- when Integer
210
- ssl_options = opt
211
- when String
212
- name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
213
- raise Cli::BadArgument, "Unknown ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
214
- if opt.start_with?('-')
215
- ssl_options &= ~OpenSSL::SSL.const_get(name)
216
- else
217
- ssl_options |= OpenSSL::SSL.const_get(name)
218
- end
219
- else
220
- Aspera.error_unexpected_value(opt.class.name){'Expected String or Integer in ssl_options'}
221
- end
222
- end
223
- OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] = ssl_options
191
+ Aspera::SSL.option_list = v
224
192
  elsif OAuth::Factory.instance.parameters.key?(k.to_sym)
225
193
  keys_to_delete.push(k)
226
194
  OAuth::Factory.instance.parameters[k.to_sym] = v
@@ -238,11 +206,31 @@ module Aspera
238
206
  attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
239
207
  attr_reader :option_ignore_cert_host_port, :progress_bar
240
208
 
209
+ def set_sdk_dir
210
+ # Check SDK folder is set or not, for compatibility, we check in two places
211
+ sdk_dir = Products::Transferd.sdk_directory rescue nil
212
+ if sdk_dir.nil?
213
+ @sdk_default_location = true
214
+ Log.log.debug('SDK folder is not set, checking default')
215
+ # New location
216
+ sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
217
+ Log.log.debug{"Checking: #{sdk_dir}"}
218
+ if !Dir.exist?(sdk_dir)
219
+ Log.log.debug{"No such folder: #{sdk_dir}"}
220
+ # Former location
221
+ former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
222
+ Log.log.debug{"Checking: #{former_sdk_folder}"}
223
+ sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
224
+ end
225
+ Log.log.debug{"Using: #{sdk_dir}"}
226
+ Products::Transferd.sdk_directory = sdk_dir
227
+ end
228
+ end
229
+
241
230
  # Add files, folders or default locations to the certificate store
242
- # @param path_list [Array<String>] list of paths to add
231
+ # @param path_list [Array<String>] List of paths to add
243
232
  # @return the list of paths
244
233
  def trusted_cert_locations=(path_list)
245
- path_list = [path_list] unless path_list.is_a?(Array)
246
234
  Aspera.assert_type(path_list, Array){'cert locations'}
247
235
  if @certificate_store.nil?
248
236
  Log.log.debug('Creating SSL Cert store')
@@ -273,6 +261,7 @@ module Aspera
273
261
  pp = Dir.entries(p)
274
262
  .map{ |e| File.realpath(File.join(p, e))}
275
263
  .select{ |entry| File.file?(entry)}
264
+ .select{ |entry| CERT_EXT.any?{ |ext| entry.end_with?(ext)}}
276
265
  end
277
266
  @certificate_paths.concat(pp)
278
267
  end
@@ -285,7 +274,7 @@ module Aspera
285
274
  locations = @certificate_paths
286
275
  if locations.nil?
287
276
  # Compute default locations
288
- self.trusted_cert_locations = SpecialValues::DEF
277
+ self.trusted_cert_locations = [SpecialValues::DEF]
289
278
  locations = @certificate_paths
290
279
  # Restore defaults
291
280
  @certificate_paths = @certificate_store = nil
@@ -447,7 +436,7 @@ module Aspera
447
436
  Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
448
437
  return
449
438
  end
450
- Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
439
+ Log.log.warn{"overwriting value for #{param_name}: #{selected_preset[param_name]}"}
451
440
  end
452
441
  selected_preset[param_name] = param_value
453
442
  formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
@@ -479,21 +468,12 @@ module Aspera
479
468
  raise Cli::Error, "Unknown config preset: #{include_path}" if current.nil?
480
469
  end
481
470
  current = self.class.deep_clone(current) unless current.is_a?(String)
482
- return ExtendedValue.instance.evaluate(current)
483
- end
484
-
485
- def option_use_product=(value)
486
- Ascp::Installation.instance.use_ascp_from_product(value)
487
- end
488
-
489
- def option_use_product
490
- 'write-only option, see value of ascp_path'
471
+ return ExtendedValue.instance.evaluate(current, context: 'preset')
491
472
  end
492
473
 
493
474
  def option_plugin_folder=(value)
494
- Aspera.assert_values(value.class, [String, Array]){'plugin folder'}
495
- value = [value] if value.is_a?(String)
496
- Aspera.assert(value.all?(String)){'plugin folder'}
475
+ value = [value] unless value.is_a?(Array)
476
+ Aspera.assert_array_all(value, String){'plugin folder(s)'}
497
477
  value.each{ |f| Plugins::Factory.instance.add_lookup_folder(f)}
498
478
  end
499
479
 
@@ -514,8 +494,9 @@ module Aspera
514
494
  end
515
495
  end
516
496
 
497
+ # @return [Integer]
517
498
  def config_checksum
518
- JSON.generate(@config_presets).hash
499
+ Digest::SHA1.hexdigest(JSON.generate(@config_presets))
519
500
  end
520
501
 
521
502
  # Read config file and validate format
@@ -556,6 +537,7 @@ module Aspera
556
537
  Log.log.warn{"#{file} -> #{@main_folder}"}
557
538
  end
558
539
  end
540
+ return
559
541
  rescue Psych::SyntaxError => e
560
542
  Log.log.error('YAML error in config file')
561
543
  raise e
@@ -605,6 +587,15 @@ module Aspera
605
587
  return Main.result_status("Opened: #{one_link['href']}")
606
588
  end
607
589
  end
590
+ Aspera.error_unreachable_line
591
+ end
592
+
593
+ 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
+ asked_version = options.get_next_argument('transferd version', mandatory: false)
597
+ name, version, folder = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: asked_version)
598
+ return Main.result_status("Installed #{name} version #{version} in #{folder}")
608
599
  end
609
600
 
610
601
  def execute_action_ascp
@@ -624,7 +615,7 @@ module Aspera
624
615
  # Collect info from ascp executable
625
616
  data = Ascp::Installation.instance.ascp_info
626
617
  # Add command line transfer spec
627
- data['ts'] = transfer.option_transfer_spec
618
+ data['ts'] = transfer.user_transfer_spec
628
619
  # Add keys
629
620
  DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
630
621
  # Declare those as secrets
@@ -638,15 +629,11 @@ module Aspera
638
629
  when :use
639
630
  default_product = options.get_next_argument('product name')
640
631
  Ascp::Installation.instance.use_ascp_from_product(default_product)
641
- set_global_default(:ascp_path, Ascp::Installation.instance.path(:ascp))
632
+ set_global_default(:ascp_path, "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{default_product}")
642
633
  return Main.result_nothing
643
634
  end
644
635
  when :install
645
- # Reset to default location, if older default was used
646
- Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
647
- version = options.get_next_argument('transferd version', mandatory: false)
648
- n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
649
- return Main.result_status("Installed #{n} version #{v}")
636
+ return install_transfer_sdk
650
637
  when :spec
651
638
  fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true)
652
639
  return Main.result_object_list(data, fields: fields.map(&:to_s))
@@ -671,11 +658,7 @@ module Aspera
671
658
  command = options.get_next_command(%i[list install])
672
659
  case command
673
660
  when :install
674
- # Reset to default location, if older default was used
675
- Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
676
- version = options.get_next_argument('transferd version', mandatory: false)
677
- n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
678
- return Main.result_status("Installed #{n} version #{v}")
661
+ return install_transfer_sdk
679
662
  when :list
680
663
  sdk_list = Ascp::Installation.instance.sdk_locations
681
664
  return Main.result_object_list(
@@ -725,7 +708,7 @@ module Aspera
725
708
  value = @config_presets[name][param_name]
726
709
  raise "no such option in preset #{name} : #{param_name}" if value.nil?
727
710
  case value
728
- when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s))
711
+ when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
729
712
  end
730
713
  return Main.result_single_object(value)
731
714
  when :unset
@@ -754,7 +737,7 @@ module Aspera
754
737
  options.ask_missing_mandatory = true
755
738
  @config_presets[name] ||= {}
756
739
  options.get_next_argument('option names', multiple: true).each do |option_name|
757
- option_value = options.get_interactive(option_name, option: true)
740
+ option_value = options.get_interactive(option_name, check_option: true)
758
741
  @config_presets[name][option_name] = option_value
759
742
  end
760
743
  return Main.result_status("Updated: #{name}")
@@ -889,7 +872,7 @@ module Aspera
889
872
  result.push({
890
873
  plugin: name,
891
874
  detect: Formatter.tick(plugin_class.respond_to?(:detect)),
892
- wizard: Formatter.tick(plugin_class.instance_methods.include?(:wizard)),
875
+ wizard: Formatter.tick(plugin_class.method_defined?(:wizard)),
893
876
  path: Plugins::Factory.instance.plugin_source(name)
894
877
  })
895
878
  end
@@ -930,10 +913,14 @@ module Aspera
930
913
  when :ascp
931
914
  execute_action_ascp
932
915
  when :sync
933
- case options.get_next_command(%i[spec])
916
+ case options.get_next_command(%i[spec admin translate])
934
917
  when :spec
935
918
  fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true, agent_columns: false, schema: Sync::Operations::CONF_SCHEMA)
936
919
  return Main.result_object_list(data, fields: fields.map(&:to_s))
920
+ when :admin
921
+ return execute_sync_admin
922
+ when :translate
923
+ return Main.result_single_object(Sync::Operations.args_to_conf(options.get_next_argument('async arguments', multiple: true)))
937
924
  else Aspera.error_unreachable_line
938
925
  end
939
926
  when :transferd
@@ -1227,6 +1214,7 @@ module Aspera
1227
1214
  # Special extended values
1228
1215
  EXTEND_PRESET = :preset
1229
1216
  EXTEND_VAULT = :vault
1217
+ EXTEND_ARGS = :''
1230
1218
  PRESET_DIG_SEPARATOR = '.'
1231
1219
  DEFAULT_CHECK_NEW_VERSION_DAYS = 7
1232
1220
  COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
@@ -1235,7 +1223,7 @@ module Aspera
1235
1223
  SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
1236
1224
  CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
1237
1225
  SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
1238
-
1226
+ CERT_EXT = %w[crt cer pem der].freeze
1239
1227
  private_constant :ASPERA_HOME_FOLDER_NAME,
1240
1228
  :DEFAULT_CONFIG_FILENAME,
1241
1229
  :CONF_PRESET_CONFIG,
@@ -1260,7 +1248,8 @@ module Aspera
1260
1248
  :TRANSFERD_APP_NAME,
1261
1249
  :GLOBAL_DEFAULT_KEYWORD,
1262
1250
  :CONF_GLOBAL_SYM,
1263
- :GEM_CHECK_DATE_FMT
1251
+ :GEM_CHECK_DATE_FMT,
1252
+ :CERT_EXT
1264
1253
  end
1265
1254
  end
1266
1255
  end
@@ -13,6 +13,7 @@ module Aspera
13
13
  private_constant :STANDARD_PATH, :DEFAULT_FILTER_AGE_SECONDS, :EXPR_RE
14
14
 
15
15
  class << self
16
+ # @return [Hash,NilClass]
16
17
  def detect(address_or_url)
17
18
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
18
19
  urls = [address_or_url]
@@ -22,17 +23,18 @@ module Aspera
22
23
  next unless base_url.start_with?('https://')
23
24
  api = Rest.new(base_url: base_url, redirect_max: 2)
24
25
  test_endpoint = 'login'
25
- test_page = api.call(
26
+ http = api.call(
26
27
  operation: 'GET',
27
28
  subpath: test_endpoint,
28
- query: {local: true}
29
+ query: {local: true},
30
+ ret: :resp
29
31
  )
30
- next unless test_page[:http].body.include?('Aspera Console')
32
+ next unless http.body.include?('Aspera Console')
31
33
  version = 'unknown'
32
- if (m = test_page[:http].body.match(/\(v([1-9]\..*)\)/))
34
+ if (m = http.body.match(/\(v([1-9]\..*)\)/))
33
35
  version = m[1]
34
36
  end
35
- url = test_page[:http].uri.to_s
37
+ url = http.uri.to_s
36
38
  return {
37
39
  version: version,
38
40
  url: url[0..url.index(test_endpoint) - 2]
@@ -44,10 +46,14 @@ module Aspera
44
46
  raise error if error
45
47
  return
46
48
  end
49
+
50
+ def time_to_string(time)
51
+ return time.strftime('%Y-%m-%d %H:%M:%S')
52
+ end
47
53
  end
48
54
 
49
55
  # @param wizard [Wizard] The wizard object
50
- # @param app_url [Wizard] The wizard object
56
+ # @param app_url [String] Tested URL
51
57
  # @return [Hash] :preset_value, :test_args
52
58
  def wizard(wizard, app_url)
53
59
  return {
@@ -91,7 +97,7 @@ module Aspera
91
97
  rescue StandardError => e
92
98
  nagios.add_critical('console api', e.to_s)
93
99
  end
94
- return nagios.result
100
+ Main.result_object_list(nagios.status_list)
95
101
  when :transfer
96
102
  command = options.get_next_command(%i[current smart])
97
103
  case command
@@ -113,8 +119,8 @@ module Aspera
113
119
  query = query_read_delete(default: {})
114
120
  if query['from'].nil? && query['to'].nil?
115
121
  time_now = Time.now
116
- query['from'] = Manager.time_to_string(time_now - DEFAULT_FILTER_AGE_SECONDS)
117
- query['to'] = Manager.time_to_string(time_now)
122
+ query['from'] = self.class.time_to_string(time_now - DEFAULT_FILTER_AGE_SECONDS)
123
+ query['to'] = self.class.time_to_string(time_now)
118
124
  end
119
125
  if (filter = query.delete('filter'))
120
126
  parse_extended_filter(filter, query)
@@ -15,7 +15,7 @@ module Aspera
15
15
  options.declare(:endpoint, 'Storage endpoint (URL)')
16
16
  options.declare(:apikey, 'Storage API key')
17
17
  options.declare(:crn, 'Resource instance id (CRN)')
18
- options.declare(:service_credentials, 'IBM Cloud service credentials', types: Hash)
18
+ options.declare(:service_credentials, 'IBM Cloud service credentials', allowed: [Hash, NilClass])
19
19
  options.declare(:region, 'Storage region')
20
20
  options.declare(:identity, "Authentication URL (#{Api::CosNode::IBM_CLOUD_TOKEN_URL})", default: Api::CosNode::IBM_CLOUD_TOKEN_URL)
21
21
  options.parse_options!
@@ -39,6 +39,7 @@ module Aspera
39
39
  private_constant :KEY_NODE, :KEY_PATH, :PACKAGE_MATCH_FIELD, :ATOM_MAILBOXES, :ATOM_PARAMS, :ATOM_EXT_PARAMS, :PUB_LINK_EXTERNAL_MATCH, :HEADER_FASPEX_VERSION
40
40
 
41
41
  class << self
42
+ # @return [Hash,NilClass]
42
43
  def detect(address_or_url)
43
44
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
44
45
  urls = [address_or_url]
@@ -47,22 +48,23 @@ module Aspera
47
48
  urls.each do |base_url|
48
49
  next unless base_url.start_with?('https://')
49
50
  api = Rest.new(base_url: base_url, redirect_max: 1)
50
- result = api.call(
51
+ http = api.call(
51
52
  operation: 'POST',
52
53
  headers: {
53
54
  'Content-type' => Rest::MIME_TEXT,
54
55
  'Accept' => 'application/xrds+xml'
55
- }
56
+ },
57
+ ret: :resp
56
58
  )
57
59
  # 4.x
58
- next unless result[:http].body.start_with?('<?xml')
59
- res_s = XmlSimple.xml_in(result[:http].body, {'ForceArray' => false})
60
- Log.log.debug{"version: #{result[:http][HEADER_FASPEX_VERSION]}"}
60
+ next unless http.body.start_with?('<?xml')
61
+ res_s = XmlSimple.xml_in(http.body, {'ForceArray' => false})
62
+ Log.log.debug{"version: #{http[HEADER_FASPEX_VERSION]}"}
61
63
  version = res_s['XRD']['application']['version']
62
64
  # take redirect if any
63
65
  return {
64
66
  version: version,
65
- url: result[:http].uri.to_s
67
+ url: http.uri.to_s
66
68
  }
67
69
  rescue StandardError => e
68
70
  error = e
@@ -107,7 +109,7 @@ module Aspera
107
109
  end
108
110
 
109
111
  # @param wizard [Wizard] The wizard object
110
- # @param app_url [Wizard] The wizard object
112
+ # @param app_url [String] Tested URL
111
113
  # @return [Hash] :preset_value, :test_args
112
114
  def wizard(wizard, app_url)
113
115
  return {
@@ -125,11 +127,11 @@ module Aspera
125
127
  @api_v3 = nil
126
128
  @api_v4 = nil
127
129
  options.declare(:link, 'Public link for specific operation')
128
- options.declare(:delivery_info, 'Package delivery information', types: Hash)
130
+ options.declare(:delivery_info, 'Package delivery information', allowed: Hash)
129
131
  options.declare(:remote_source, 'Remote source for package send (id or %name:)')
130
132
  options.declare(:storage, 'Faspex local storage definition (for browsing source)')
131
133
  options.declare(:recipient, 'Use if recipient is a dropbox (with *)')
132
- options.declare(:box, 'Package box', values: ATOM_MAILBOXES, default: :inbox)
134
+ options.declare(:box, 'Package box', allowed: ATOM_MAILBOXES, default: :inbox)
133
135
  options.parse_options!
134
136
  end
135
137
 
@@ -184,8 +186,9 @@ module Aspera
184
186
  operation: 'GET',
185
187
  subpath: "#{mailbox}.atom",
186
188
  headers: {'Accept' => 'application/xml'},
187
- query: mailbox_query
188
- )[:http].body
189
+ query: mailbox_query,
190
+ ret: :resp
191
+ ).body
189
192
  box_data = XmlSimple.xml_in(atom_xml, {'ForceArray' => %w[entry field link to]})
190
193
  Log.dump(:box_data, box_data)
191
194
  items = box_data.key?('entry') ? box_data['entry'] : []
@@ -251,8 +254,9 @@ module Aspera
251
254
  subpath: create_path,
252
255
  content_type: Rest::MIME_JSON,
253
256
  body: package_create_params,
254
- headers: {'Accept' => 'text/javascript'}
255
- )[:http].body
257
+ headers: {'Accept' => 'text/javascript'},
258
+ ret: :resp
259
+ ).body
256
260
  # get arguments of function call
257
261
  package_creation_data.delete!("\n") # one line
258
262
  package_creation_data.gsub!(/^[^"]+\("\{/, '{') # delete header
@@ -280,7 +284,7 @@ module Aspera
280
284
  rescue StandardError => e
281
285
  nagios.add_critical('faspex api', e.to_s)
282
286
  end
283
- return nagios.result
287
+ Main.result_object_list(nagios.status_list)
284
288
  when :package
285
289
  command_pkg = options.get_next_command(%i[send receive list show], aliases: {recv: :receive})
286
290
  case command_pkg
@@ -300,10 +304,11 @@ module Aspera
300
304
  delivery_info['sources'] ||= [{'paths' => []}]
301
305
  first_source = delivery_info['sources'].first
302
306
  first_source['paths'].concat(transfer.source_list)
303
- source_id = instance_identifier(as_option: :remote_source) do |field, value|
304
- Aspera.assert(field.eql?('name'), type: Cli::BadArgument){'only name as selector, or give id'}
307
+ source_id = options.get_option(:remote_source)
308
+ if source_id && (m = Base.percent_selector(source_id))
309
+ Aspera.assert(m[:field].eql?('name'), type: Cli::BadArgument){'only name as selector, or give id'}
305
310
  source_list = api_v3.read('source_shares')['items']
306
- self.class.get_source_id_by_name(value, source_list)
311
+ source_id = self.class.get_source_id_by_name(m[:value], source_list)
307
312
  end
308
313
  first_source['id'] = source_id.to_i unless source_id.nil?
309
314
  pkg_created = api_v3.create('send', package_create_params)
@@ -362,7 +367,7 @@ module Aspera
362
367
  when :inbox, :archive then'received'
363
368
  when :sent then 'sent'
364
369
  end
365
- entry_xml = api_v3.call(operation: 'GET', subpath: "#{endpoint}/#{delivery_id}", headers: {'Accept' => 'application/xml'})[:http].body
370
+ entry_xml = api_v3.call(operation: 'GET', subpath: "#{endpoint}/#{delivery_id}", headers: {'Accept' => 'application/xml'}, ret: :resp).body
366
371
  package_entry = XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
367
372
  pkg_id_uri = [{id: delivery_id, uri: self.class.get_fasp_uri_from_entry(package_entry)}]
368
373
  end
@@ -373,17 +378,18 @@ module Aspera
373
378
  raise Cli::BadArgument, "Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}" if !link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH)
374
379
  # NOTE: unauthenticated API (authorization is in url params)
375
380
  api_public_link = Rest.new(base_url: link_data[:base_url])
376
- package_creation_data = api_public_link.call(
381
+ pkg_xml = api_public_link.call(
377
382
  operation: 'GET',
378
383
  subpath: link_data[:subpath],
379
384
  headers: {'Accept' => 'application/xml'},
380
- query: {passcode: link_data[:query]['passcode']}
381
- )
382
- if !package_creation_data[:http].body.start_with?('<?xml ')
385
+ query: {passcode: link_data[:query]['passcode']},
386
+ ret: :resp
387
+ ).body
388
+ if !pkg_xml.start_with?('<?xml ')
383
389
  Environment.instance.open_uri(link_url)
384
390
  raise Cli::Error, 'Unexpected response: package not found ?'
385
391
  end
386
- package_entry = XmlSimple.xml_in(package_creation_data[:http].body, {'ForceArray' => false})
392
+ package_entry = XmlSimple.xml_in(pkg_xml, {'ForceArray' => false})
387
393
  Log.dump(:package_entry, package_entry)
388
394
  transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
389
395
  pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
@@ -412,8 +418,9 @@ module Aspera
412
418
  query: {'direction' => 'down'},
413
419
  content_type: Rest::MIME_TEXT,
414
420
  body: xml_payload,
415
- headers: {'Accept' => Rest::MIME_TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'}
416
- )[:http].body
421
+ headers: {'Accept' => Rest::MIME_TEXT, 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
422
+ ret: :resp
423
+ ).body
417
424
  end
418
425
  transfer_spec['direction'] = Transfer::Spec::DIRECTION_RECEIVE
419
426
  statuses = transfer.start(transfer_spec)
@@ -455,7 +462,7 @@ module Aspera
455
462
  when :info
456
463
  return Main.result_single_object(source_info)
457
464
  when :node
458
- node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE])
465
+ node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE], context: 'faspex node')
459
466
  Log.log.debug{"node=#{node_config}"}
460
467
  Aspera.assert_type(node_config, Hash, type: Cli::Error){source_info[KEY_NODE]}
461
468
  api_node = Rest.new(
@@ -519,7 +526,7 @@ module Aspera
519
526
  end
520
527
  return Main.result_object_list(users)
521
528
  when :login_methods
522
- login_meths = api_v3.call(operation: 'GET', subpath: 'login/new', headers: {'Accept' => 'application/xrds+xml'})[:http].body
529
+ login_meths = api_v3.call(operation: 'GET', subpath: 'login/new', headers: {'Accept' => 'application/xrds+xml'}, ret: :resp).body
523
530
  login_methods = XmlSimple.xml_in(login_meths, {'ForceArray' => false})
524
531
  return Main.result_object_list(login_methods['XRD']['Service'])
525
532
  end