aspera-cli 4.24.1 → 4.24.2

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 (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +15 -2
  4. data/README.md +745 -436
  5. data/bin/ascli +20 -1
  6. data/bin/asession +23 -27
  7. data/lib/aspera/agent/base.rb +10 -21
  8. data/lib/aspera/agent/connect.rb +2 -3
  9. data/lib/aspera/agent/desktop.rb +2 -2
  10. data/lib/aspera/agent/direct.rb +49 -32
  11. data/lib/aspera/agent/factory.rb +31 -0
  12. data/lib/aspera/api/aoc.rb +79 -49
  13. data/lib/aspera/api/faspex.rb +212 -0
  14. data/lib/aspera/api/node.rb +99 -84
  15. data/lib/aspera/ascp/installation.rb +22 -21
  16. data/lib/aspera/ascp/management.rb +119 -23
  17. data/lib/aspera/assert.rb +14 -8
  18. data/lib/aspera/cli/extended_value.rb +15 -15
  19. data/lib/aspera/cli/formatter.rb +7 -5
  20. data/lib/aspera/cli/hints.rb +8 -0
  21. data/lib/aspera/cli/info.rb +4 -4
  22. data/lib/aspera/cli/main.rb +55 -70
  23. data/lib/aspera/cli/manager.rb +7 -4
  24. data/lib/aspera/cli/plugins/alee.rb +2 -1
  25. data/lib/aspera/cli/plugins/aoc.rb +110 -186
  26. data/lib/aspera/cli/plugins/ats.rb +4 -4
  27. data/lib/aspera/cli/plugins/base.rb +335 -0
  28. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  29. data/lib/aspera/cli/plugins/config.rb +249 -220
  30. data/lib/aspera/cli/plugins/console.rb +15 -15
  31. data/lib/aspera/cli/plugins/cos.rb +2 -2
  32. data/lib/aspera/cli/plugins/factory.rb +78 -0
  33. data/lib/aspera/cli/plugins/faspex.rb +17 -20
  34. data/lib/aspera/cli/plugins/faspex5.rb +79 -193
  35. data/lib/aspera/cli/plugins/faspio.rb +14 -13
  36. data/lib/aspera/cli/plugins/httpgw.rb +13 -12
  37. data/lib/aspera/cli/plugins/node.rb +34 -32
  38. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  39. data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
  40. data/lib/aspera/cli/plugins/preview.rb +4 -4
  41. data/lib/aspera/cli/plugins/server.rb +15 -13
  42. data/lib/aspera/cli/plugins/shares.rb +18 -15
  43. data/lib/aspera/cli/sync_actions.rb +1 -1
  44. data/lib/aspera/cli/transfer_agent.rb +24 -20
  45. data/lib/aspera/cli/transfer_progress.rb +6 -6
  46. data/lib/aspera/cli/version.rb +3 -3
  47. data/lib/aspera/cli/wizard.rb +65 -53
  48. data/lib/aspera/colors.rb +6 -0
  49. data/lib/aspera/command_line_builder.rb +45 -50
  50. data/lib/aspera/command_line_converter.rb +2 -1
  51. data/lib/aspera/coverage.rb +1 -1
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +10 -7
  54. data/lib/aspera/faspex_gw.rb +6 -4
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/keychain/macos_security.rb +1 -1
  57. data/lib/aspera/log.rb +37 -9
  58. data/lib/aspera/nagios.rb +1 -1
  59. data/lib/aspera/oauth/base.rb +17 -10
  60. data/lib/aspera/oauth/factory.rb +8 -8
  61. data/lib/aspera/oauth/web.rb +2 -2
  62. data/lib/aspera/products/connect.rb +4 -3
  63. data/lib/aspera/products/desktop.rb +1 -4
  64. data/lib/aspera/products/other.rb +9 -1
  65. data/lib/aspera/products/transferd.rb +0 -1
  66. data/lib/aspera/rest.rb +126 -83
  67. data/lib/aspera/ssh.rb +3 -3
  68. data/lib/aspera/sync/args.schema.yaml +46 -3
  69. data/lib/aspera/sync/conf.schema.yaml +130 -94
  70. data/lib/aspera/sync/operations.rb +16 -16
  71. data/lib/aspera/temp_file_manager.rb +17 -5
  72. data/lib/aspera/transfer/error.rb +16 -7
  73. data/lib/aspera/transfer/parameters.rb +34 -20
  74. data/lib/aspera/transfer/resumer.rb +74 -0
  75. data/lib/aspera/transfer/spec.rb +4 -3
  76. data/lib/aspera/transfer/spec.schema.yaml +132 -51
  77. data/lib/aspera/transfer/spec_doc.rb +41 -35
  78. data/lib/aspera/uri_reader.rb +1 -1
  79. data/lib/aspera/web_auth.rb +6 -6
  80. data.tar.gz.sig +0 -0
  81. metadata +9 -7
  82. metadata.gz.sig +0 -0
  83. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  84. data/lib/aspera/cli/plugin.rb +0 -333
  85. data/lib/aspera/cli/plugin_factory.rb +0 -81
  86. data/lib/aspera/resumer.rb +0 -77
  87. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/oauth/jwt'
4
+ require 'aspera/assert'
5
+ require 'aspera/cli/plugins/factory'
4
6
 
5
7
  module Aspera
6
8
  module Cli
9
+ # The wizard detects applications and generates a config
7
10
  class Wizard
8
11
  WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
9
12
  DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
@@ -13,13 +16,17 @@ module Aspera
13
16
  def initialize(parent, main_folder)
14
17
  @parent = parent
15
18
  @main_folder = main_folder
16
- # wizard options
19
+ # Wizard options
17
20
  options.declare(:override, 'Wizard: override existing value', values: :bool, default: :no)
18
21
  options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', values: :bool, default: true)
19
- options.declare(:test_mode, 'Wizard: skip private key check step', values: :bool, default: false)
20
22
  options.declare(:key_path, 'Wizard: path to private key for JWT')
21
23
  end
22
24
 
25
+ # @return false if in test mode to avoid interactive input
26
+ def required
27
+ !ENV['ASCLI_WIZ_TEST']
28
+ end
29
+
23
30
  def options
24
31
  @parent.options
25
32
  end
@@ -32,6 +39,10 @@ module Aspera
32
39
  @parent.config
33
40
  end
34
41
 
42
+ def check_email(email)
43
+ Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError){"Username shall be an email: #{email}"}
44
+ end
45
+
35
46
  # Find a plugin, and issue the "require"
36
47
  # @return [Hash] plugin info: { product:, name:, url:, version: }
37
48
  def identify_plugins_for_url
@@ -40,19 +51,19 @@ module Aspera
40
51
  check_only = check_only.to_sym unless check_only.nil?
41
52
  found_apps = []
42
53
  my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
43
- PluginFactory.instance.plugin_list.each do |plugin_name_sym|
44
- # no detection for internal plugin
54
+ Plugins::Factory.instance.plugin_list.each do |plugin_name_sym|
55
+ # No detection for internal plugin
45
56
  next if plugin_name_sym.eql?(my_self_plugin_sym)
46
57
  next if check_only && !check_only.eql?(plugin_name_sym)
47
- # load plugin class
48
- detect_plugin_class = PluginFactory.instance.plugin_class(plugin_name_sym)
49
- # requires detection method
50
- next unless detect_plugin_class.respond_to?(:detect)
58
+ # Load plugin class
59
+ plugin_klass = Plugins::Factory.instance.plugin_class(plugin_name_sym)
60
+ # Requires detection method
61
+ next unless plugin_klass.respond_to?(:detect)
51
62
  detection_info = nil
52
63
  begin
53
64
  Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
54
65
  formatter.long_operation_running("#{plugin_name_sym}\r")
55
- detection_info = detect_plugin_class.detect(app_url)
66
+ detection_info = plugin_klass.detect(app_url)
56
67
  rescue OpenSSL::SSL::SSLError => e
57
68
  Log.log.warn(e.message)
58
69
  Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
@@ -63,8 +74,8 @@ module Aspera
63
74
  next if detection_info.nil?
64
75
  Aspera.assert_type(detection_info, Hash)
65
76
  Aspera.assert_type(detection_info[:url], String) if detection_info.key?(:url)
66
- app_name = detect_plugin_class.respond_to?(:application_name) ? detect_plugin_class.application_name : detect_plugin_class.name.split('::').last
67
- # if there is a redirect, then the detector can override the url.
77
+ app_name = plugin_klass.respond_to?(:application_name) ? plugin_klass.application_name : plugin_klass.name.split('::').last
78
+ # If there is a redirect, then the detector can override the url.
68
79
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
69
80
  end
70
81
  raise "No known application found at #{app_url}" if found_apps.empty?
@@ -72,6 +83,39 @@ module Aspera
72
83
  return found_apps
73
84
  end
74
85
 
86
+ # To be called in public wizard method to get private key
87
+ # @return [Array] Private key path, pub key PEM
88
+ def ask_private_key(user:, url:, page:)
89
+ # Lets see if path to priv key is provided
90
+ private_key_path = options.get_option(:key_path)
91
+ # Give a chance to provide
92
+ if private_key_path.nil?
93
+ formatter.display_status('Path to private RSA key (leave empty to generate):')
94
+ private_key_path = options.get_option(:key_path, mandatory: true).to_s
95
+ end
96
+ # Else generate path
97
+ private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME) if private_key_path.empty?
98
+ if File.exist?(private_key_path)
99
+ formatter.display_status('Using existing key:')
100
+ else
101
+ formatter.display_status("Generating #{OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
102
+ OAuth::Jwt.generate_rsa_private_key(path: private_key_path)
103
+ formatter.display_status('Created key:')
104
+ end
105
+ formatter.display_status(private_key_path)
106
+ private_key_pem = File.read(private_key_path)
107
+ pub_key_pem = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
108
+ options.set_option(:private_key, private_key_pem)
109
+ formatter.display_status("Please Log in as user #{user.red} at: #{url.red}")
110
+ formatter.display_status("Navigate to: #{page}")
111
+ formatter.display_status("Check or update the value to (#{'including BEGIN/END lines'.red}):".blink)
112
+ formatter.display_status(pub_key_pem, hide_secrets: false)
113
+ formatter.display_status('Once updated or validated, press [Enter].')
114
+ Environment.instance.open_uri(url)
115
+ $stdin.gets if required
116
+ private_key_path
117
+ end
118
+
75
119
  # Wizard function, creates configuration
76
120
  # @param apps [Array] list of detected apps
77
121
  def find(apps)
@@ -88,49 +132,18 @@ module Aspera
88
132
  Log.dump(:identification, identification)
89
133
  wiz_url = identification[:url]
90
134
  formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
91
- # set url for instantiation of plugin
135
+ # Set url for instantiation of plugin
92
136
  options.add_option_preset({url: wiz_url}, 'wizard')
93
- # instantiate plugin: command line options will be known and wizard can be called
94
- wiz_plugin_class = PluginFactory.instance.plugin_class(identification[:product])
95
- Aspera.assert(wiz_plugin_class.respond_to?(:wizard), type: Cli::BadArgument) do
137
+ # Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
138
+ plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
139
+ Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument) do
96
140
  "Detected: #{identification[:product]}, but this application has no wizard"
97
141
  end
98
- # instantiate plugin: command line options will be known, e.g. private_key
99
- plugin_instance = wiz_plugin_class.new(context: @parent.context)
100
- wiz_params = {
101
- object: plugin_instance
102
- }
103
- # is private key needed ?
104
- if options.known_options.key?(:private_key) &&
105
- (!wiz_plugin_class.respond_to?(:private_key_required?) || wiz_plugin_class.private_key_required?(wiz_url))
106
- # lets see if path to priv key is provided
107
- private_key_path = options.get_option(:key_path)
108
- # give a chance to provide
109
- if private_key_path.nil?
110
- formatter.display_status('Path to private RSA key (leave empty to generate):')
111
- private_key_path = options.get_option(:key_path, mandatory: true).to_s
112
- end
113
- # else generate path
114
- private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME) if private_key_path.empty?
115
- if File.exist?(private_key_path)
116
- formatter.display_status('Using existing key:')
117
- else
118
- formatter.display_status("Generating #{OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
119
- OAuth::Jwt.generate_rsa_private_key(path: private_key_path)
120
- formatter.display_status('Created key:')
121
- end
122
- formatter.display_status(private_key_path)
123
- private_key_pem = File.read(private_key_path)
124
- options.set_option(:private_key, private_key_pem)
125
- wiz_params[:private_key_path] = private_key_path
126
- wiz_params[:pub_key_pem] = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
127
- end
128
- Log.dump(:wiz_params, wiz_params)
129
- # finally, call the wizard
130
- wizard_result = wiz_plugin_class.wizard(**wiz_params)
142
+ # Call the wizard
143
+ wizard_result = plugin_instance.wizard(self, wiz_url)
131
144
  Log.log.debug{"wizard result: #{wizard_result}"}
132
145
  Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
133
- # get preset name from user or default
146
+ # Get preset name from user or default
134
147
  if wiz_preset_name.empty?
135
148
  elements = [
136
149
  identification[:product],
@@ -139,18 +152,17 @@ module Aspera
139
152
  elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link) rescue nil
140
153
  wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
141
154
  end
142
- # test mode does not change conf file
143
- return Main.result_single_object(wizard_result) if options.get_option(:test_mode)
144
155
  # Write configuration file
145
156
  formatter.display_status("Preparing preset: #{wiz_preset_name}")
146
- # init defaults if necessary
157
+ # Init defaults if necessary
147
158
  option_override = options.get_option(:override, mandatory: true)
148
159
  option_default = options.get_option(:default, mandatory: true)
149
160
  config.defaults_set(identification[:product], wiz_preset_name, wizard_result[:preset_value].stringify_keys, option_default, option_override)
150
161
  test_args = wizard_result[:test_args]
151
162
  test_args = "-P#{wiz_preset_name} #{test_args}" unless option_default
152
163
  # TODO: actually test the command
153
- return Main.result_status("You can test with:\n#{Info::CMD_NAME} #{identification[:product]} #{test_args}")
164
+ test_cmd = "#{Info::CMD_NAME} #{identification[:product]} #{test_args}"
165
+ return Main.result_status("You can test with:\n#{test_cmd.red}")
154
166
  end
155
167
  end
156
168
  end
data/lib/aspera/colors.rb CHANGED
@@ -57,10 +57,16 @@ class String
57
57
  define_method(name){self}
58
58
  end
59
59
  end
60
+
60
61
  # Transform capitalized to snake case
61
62
  def capital_to_snake
62
63
  return gsub(/([a-z\d])([A-Z])/, '\1_\2')
63
64
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
64
65
  .downcase
65
66
  end
67
+
68
+ # Transform snake case to capitalized
69
+ def snake_to_capital
70
+ split('_').map(&:capitalize).join
71
+ end
66
72
  end
@@ -4,43 +4,31 @@ require 'aspera/log'
4
4
  require 'aspera/assert'
5
5
  require 'yaml'
6
6
  module Aspera
7
- # helper class to build command line from a parameter list (key-value hash)
8
- # constructor takes hash: { 'param1':'value1', ...}
9
- # process_param is called repeatedly with all known parameters
10
- # add_env_args is called to get resulting param list and env var (also checks that all params were used)
7
+ # Helper class to build command line from a parameter list (key-value hash)
8
+ # Constructor takes hash: `{ 'param1':'value1', ...}`
9
+ # `process_param` is called repeatedly with all known parameters
10
+ # `add_env_args` is called to get resulting param list and env var (also checks that all params were used)
11
11
  class CommandLineBuilder
12
- # description [String] Description
13
- # type [String,Array] Accepted type(s) for non-enum
14
- # default [String] Default value if not specified
15
- # enum [Array] Set with list of values for enum types accepted in transfer spec
16
- # items [Array]
17
- # properties [Array]
18
- # x-cli-envvar [String] Name of env var
19
- # x-cli-option [String] Command line option (starts with "-")
20
- # x-cli-switch [Bool] true if option has no arg, else by default option has a value
21
- # x-cli-special [Bool] true if special handling (defered)
22
- # x-cli-convert [String,Hash] Method name for Convert object or Conversion for enum ts to arg
23
- # x-agents [Array] Supported agents (for doc only), if not specified: all
24
- # x-ts-name [Bool,String] (async) true if same name in transfer spec, else real name in transfer spec, else ignored
25
- # x-ts-convert [String] (async) Method name for Convert object
26
- # x-deprecation [String] Deprecation message for doc
27
- PROPERTY_KEYS = %w[
28
- description
29
- type
30
- default
31
- enum
32
- items
33
- properties
34
- required
35
- $comment
36
- x-cli-envvar
37
- x-cli-option
38
- x-cli-switch
39
- x-cli-special
40
- x-cli-convert
41
- x-agents
42
- x-ts-name
43
- x-deprecation
12
+ # Supported keys in JSON schema
13
+ PROPERTY_KEYS = [
14
+ 'description', # [String] Description
15
+ 'type', # [String,Array] Accepted type(s) for non-enum
16
+ 'default', # [String] Default value if not specified
17
+ 'enum', # [Array] Set with list of values for enum types accepted in transfer spec
18
+ 'items', # [Array]
19
+ 'properties', # [Array]
20
+ 'required', # [Array]
21
+ '$comment', # [String]
22
+ 'x-cli-envvar', # [String] Name of env var
23
+ 'x-cli-option', # [String] Command line option (starts with "-")
24
+ 'x-cli-short', # [String] Command line option (starts with "-")
25
+ 'x-cli-switch', # [Bool] `true` if option has no arg, else by default option has a value
26
+ 'x-cli-special', # [Bool] `true` if special handling (deferred)
27
+ 'x-cli-convert', # [String,Hash] Method name for Convert object or Conversion for enum ts to arg
28
+ 'x-agents', # [Array] Supported agents (for doc only), if not specified: all
29
+ 'x-ts-name', # [Bool,String] (async) true if same name in transfer spec, else real name in transfer spec, else ignored
30
+ 'x-ts-convert', # [String] (async) Name of methods to convert value from transfer spec to `conf` API.
31
+ 'x-deprecation' # [String] Deprecation message for doc
44
32
  ].freeze
45
33
 
46
34
  CLI_AGENT = 'direct'
@@ -53,17 +41,23 @@ module Aspera
53
41
  !properties.key?('x-agents') || properties['x-agents'].include?(agent)
54
42
  end
55
43
 
56
- # fill default values
57
- def adjust_properties_defaults(properties)
58
- properties.each do |name, info|
44
+ # Fill default values for some fields in the schema
45
+ # @param schema [Hash] The JSON schema
46
+ def validate_schema(schema, ascp: false)
47
+ schema['properties'].each do |name, info|
59
48
  Aspera.assert_type(info, Hash){"#{info.class} for #{name}"}
60
49
  unsupported_keys = info.keys - PROPERTY_KEYS
61
50
  Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
62
- # by default : string, unless it's without arg
63
- info['type'] ||= info['x-cli-switch'] ? 'boolean' : 'string'
64
- # add default cli option name if not present, and if supported in "direct".
65
- info['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if !info.key?('x-cli-option') && !info['x-cli-envvar'] && (info.key?('x-cli-switch') || supported_by_agent(CLI_AGENT, info))
51
+ # By default : string, unless it's without arg (switch)
52
+ # info['type'] ||= info['x-cli-switch'] ? 'boolean' : 'string'
53
+ Aspera.assert(info.key?('type') || info.key?('enum')){"Missing type for #{name} in #{schema['description']}"}
54
+ Aspera.assert(info['type'].eql?('boolean')){"switch must be bool: #{name}"} if info['x-cli-switch']
55
+ # Add default cli option name if not present, and if supported in "direct".
56
+ info['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if info['x-cli-option'].eql?(true) || (info['x-cli-switch'].eql?(true) && !info.key?('x-cli-option'))
57
+ Aspera.assert(%w[x-cli-option x-cli-envvar x-cli-special].any?{ |i| info.key?(i)}, type: :warn){name} if ascp && supported_by_agent(CLI_AGENT, info)
58
+ # info['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if ascp && !info.key?('x-cli-option') && !info['x-cli-envvar'] && (info.key?('x-cli-switch') || supported_by_agent(CLI_AGENT, info))
66
59
  info.freeze
60
+ validate_schema(info, ascp: ascp) if info['type'].eql?('object') && info['properties']
67
61
  end
68
62
  end
69
63
 
@@ -98,10 +92,10 @@ module Aspera
98
92
  # Add processed parameters to env and args, warns about unused parameters
99
93
  # @param [Hash] env_args with :env and :args
100
94
  def add_env_args(env_args)
101
- Log.log.debug{"add_env_args: ENV=#{@result[:env]}, ARGS=#{@result[:args]}"}
95
+ Log.dump(:env_args, @result)
102
96
  # warn about non translated arguments
103
97
  @object.each_pair do |name, value|
104
- Log.log.warn{raise "Unknown transfer spec parameter: #{name} = \"#{value}\""} unless @processed_parameters.include?(name)
98
+ Log.log.warn{"Unknown transfer spec parameter: #{name} = \"#{value}\""} unless @processed_parameters.include?(name)
105
99
  end
106
100
  # set result
107
101
  env_args[:env].merge!(@result[:env])
@@ -109,9 +103,10 @@ module Aspera
109
103
  return
110
104
  end
111
105
 
112
- # add options directly to command line
113
- def add_command_line_options(options)
114
- return if options.nil?
106
+ # Add options directly to command line
107
+ def add_command_line_options(*options)
108
+ options = options.first if options.first.is_a?(Array) && options.length.eql?(1)
109
+ Aspera.assert_type(options, Array)
115
110
  options.each{ |o| @result[:args].push(o.to_s)}
116
111
  end
117
112
 
@@ -198,13 +193,13 @@ module Aspera
198
193
  else Aspera.error_unexpected_value(parameter_value){name}
199
194
  end
200
195
  # add_param = !add_param if properties[:add_on_false]
201
- add_command_line_options([properties['x-cli-option']]) if add_param
196
+ add_command_line_options(properties['x-cli-option']) if add_param
202
197
  else
203
198
  # transform into command line option with value
204
199
  # parameter_value=parameter_value.to_s if parameter_value.is_a?(Integer)
205
200
  parameter_value = [parameter_value] unless parameter_value.is_a?(Array)
206
201
  # if transfer_spec value is an array, applies option many times
207
- parameter_value.each{ |v| add_command_line_options([properties['x-cli-option'], v])}
202
+ parameter_value.each{ |v| add_command_line_options(properties['x-cli-option'], v)}
208
203
  end
209
204
  end
210
205
  end
@@ -23,8 +23,9 @@ module Aspera
23
23
  end
24
24
  end
25
25
 
26
+ # Kbps to bps
26
27
  def kbps_to_bps(value)
27
- 1000 * value
28
+ 1000 * value.to_i
28
29
  end
29
30
  end
30
31
  end
@@ -5,7 +5,7 @@ if ENV.key?('ENABLE_COVERAGE')
5
5
  require 'simplecov'
6
6
  require 'securerandom'
7
7
  # compute development top folder based on this source location
8
- development_root = 3.times.inject(File.realpath(__FILE__)){ |p, _| File.dirname(p)}
8
+ development_root = File.dirname(File.realpath(__FILE__), 3)
9
9
  SimpleCov.root(development_root)
10
10
  SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
11
11
  # keep cache data for 1 day (must be longer that time to run the whole test suite)
@@ -19,7 +19,7 @@ module Aspera
19
19
  # @return [String] decoded data
20
20
  def item(name)
21
21
  index = ELEMENTS.index(name)
22
- raise ArgumentError, "unknown data item #{name} (#{name.class})" unless index
22
+ raise ParameterError, "Unknown data item #{name} (#{name.class})" unless index
23
23
  raw_data = data(START_INDEX + index)
24
24
  case name
25
25
  when :dsa, :rsa
@@ -38,6 +38,8 @@ module Aspera
38
38
  WINDOWS_FILENAME_INVALID_CHARACTERS = '<>:"/\\|?*'
39
39
  REPLACE_CHARACTER = '_'
40
40
 
41
+ RB_EXT = '.rb'
42
+
41
43
  class << self
42
44
  def ruby_version
43
45
  return RbConfig::CONFIG['RUBY_PROGRAM_VERSION']
@@ -186,7 +188,7 @@ module Aspera
186
188
  end
187
189
  end
188
190
  attr_accessor :url_method, :file_illegal_characters
189
- attr_reader :os, :cpu, :executable_extension, :default_gui_mode
191
+ attr_reader :os, :cpu, :default_gui_mode
190
192
 
191
193
  def initialize
192
194
  initialize_fields
@@ -218,7 +220,7 @@ module Aspera
218
220
  CPU_ARM64
219
221
  else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'}
220
222
  end
221
- @executable_extension = @os.eql?(OS_WINDOWS) ? 'exe' : nil
223
+ @executable_extension = @os.eql?(OS_WINDOWS) ? '.exe' : nil
222
224
  # :text or :graphical depending on the environment
223
225
  @default_gui_mode =
224
226
  if [Environment::OS_WINDOWS, Environment::OS_MACOS].include?(os) ||
@@ -239,8 +241,10 @@ module Aspera
239
241
  "#{@os}-#{@cpu}"
240
242
  end
241
243
 
242
- # executable file extension for current OS
243
- def exe_file(name)
244
+ # Add executable file extension (e.g. ".exe") for current OS
245
+ # @param name [String,nil] Path or file name
246
+ # @return [String] Executable name with extension
247
+ def exe_file(name = nil)
244
248
  return name unless @executable_extension
245
249
  return "#{name}#{@executable_extension}"
246
250
  end
@@ -314,9 +318,8 @@ module Aspera
314
318
  # Windows does not allow file name:
315
319
  # - with control characters anywhere
316
320
  # - ending with space or dot
317
- filename = filename
318
- .gsub(/[\x00-\x1F\x7F]/, safe_char)
319
- .sub(/[. ]+\z/, safe_char)
321
+ filename = filename.gsub(/[\x00-\x1F\x7F]/, safe_char)
322
+ filename = filename.chop while filename.end_with?(' ', '.')
320
323
  if @file_illegal_characters&.size.to_i >= 2
321
324
  # replace all illegal characters with safe_char
322
325
  filename = filename.tr(@file_illegal_characters[1..-1], safe_char)
@@ -8,10 +8,12 @@ require 'json'
8
8
  module Aspera
9
9
  # Simulate the Faspex 4 /send API and creates a package on Aspera on Cloud or Faspex 5
10
10
  class Faspex4GWServlet < WEBrick::HTTPServlet::AbstractServlet
11
+ AOC_API = 'Aspera::Api::AoC'
12
+ FX_API = 'Aspera::Api::Faspex'
11
13
  # @param app_api [Rest] API object
12
14
  # @param app_context [String] workspace id (aoc only)
13
15
  def initialize(server, app_api, app_context)
14
- Aspera.assert_values(app_api.class.name, ['Aspera::Api::AoC', 'Aspera::Rest'])
16
+ Aspera.assert_values(app_api.class.name, [AOC_API, FX_API])
15
17
  super(server)
16
18
  @app_api = app_api
17
19
  @app_context = app_context
@@ -49,7 +51,7 @@ module Aspera
49
51
  transfer_spec = @app_api.call(
50
52
  operation: 'POST',
51
53
  subpath: "packages/#{package['id']}/transfer_spec/upload",
52
- query: {transfer_type: Cli::Plugins::Faspex5::TRANSFER_CONNECT},
54
+ query: {transfer_type: Api::Faspex::TRANSFER_CONNECT},
53
55
  content_type: Rest::MIME_JSON,
54
56
  body: {paths: [{'destination'=>'/'}]},
55
57
  headers: {'Accept' => Rest::MIME_JSON}
@@ -72,9 +74,9 @@ module Aspera
72
74
  # compare string, as class is not yet known here
73
75
  faspex_package_create_result =
74
76
  case @app_api.class.name
75
- when 'Aspera::Api::AoC'
77
+ when AOC_API
76
78
  faspex4_send_to_aoc(faspex_pkg_parameters)
77
- when 'Aspera::Rest'
79
+ when FX_API
78
80
  faspex4_send_to_faspex5(faspex_pkg_parameters)
79
81
  else Aspera.error_unexpected_value(@app_api.class.name)
80
82
  end
@@ -8,7 +8,7 @@ require 'aspera/log'
8
8
  require 'aspera/assert'
9
9
 
10
10
  module Aspera
11
- # this class answers the Faspex /send API and creates a package on Aspera on Cloud
11
+ # Start a Faspex-4 style post-processing script using Faspex-5 webhook
12
12
  class Faspex4PostProcServlet < WEBrick::HTTPServlet::AbstractServlet
13
13
  ALLOWED_PARAMETERS = %i[root script_folder fail_on_error timeout_seconds].freeze
14
14
  def initialize(server, parameters)
@@ -73,7 +73,7 @@ module Aspera
73
73
  end
74
74
 
75
75
  def list(options = {})
76
- Aspera.assert_values(options[:domain], DOMAINS, type: ArgumentError){'domain'} unless options[:domain].nil?
76
+ Aspera.assert_values(options[:domain], DOMAINS, type: ParameterError){'domain'} unless options[:domain].nil?
77
77
  key_chains(execute('list-keychains', options, LIST_OPTIONS))
78
78
  end
79
79
 
data/lib/aspera/log.rb CHANGED
@@ -15,7 +15,7 @@ $VERBOSE = nil
15
15
 
16
16
  # Extend Ruby logger with trace levels
17
17
  class Logger
18
- # Two additionnal trace levels
18
+ # Two additional trace levels
19
19
  TRACE_MAX = 2
20
20
 
21
21
  # Add custom level to logger severity, below debug level
@@ -23,9 +23,12 @@ class Logger
23
23
  1.upto(TRACE_MAX).each{ |level| const_set(:"TRACE#{level}", - level)}
24
24
  end
25
25
 
26
- # Hash : key: log level int, value: uppercase log level label
26
+ # Hash
27
+ # key [Integer] Log level (e.g. 0 for DEBUG)
28
+ # value [Symbol] Uppercase log level label (e.g. :DEBUG)
27
29
  SEVERITY_LABEL = Severity.constants.each_with_object({}){ |name, hash| hash[Severity.const_get(name)] = name}
28
30
 
31
+ # Override
29
32
  # @param severity [Integer] Log severity as int
30
33
  # @return [String] Log severity upper case label
31
34
  def format_severity(severity)
@@ -106,6 +109,31 @@ module Aspera
106
109
  ensure
107
110
  $stderr = real_stderr
108
111
  end
112
+
113
+ # Returns the last 2 containers (module/class) and method caller
114
+ def caller_method
115
+ stack = caller
116
+ i = stack.rindex{ |line| line.include?('Logger')}
117
+ frame = stack[i + 1] if i && stack[i + 1]
118
+ return '???' unless frame
119
+ # Extract the "Class::Module::Method" or "Class#method" part
120
+ full = frame[/'([^']+)'/, 1]
121
+ return '???' unless full
122
+ # Split into class/module and method parts
123
+ parts = full.split(/(::|#)/)
124
+ # Reconstruct keeping only last two class/module names + separator + method
125
+ if parts.include?('#')
126
+ sep_index = parts.index('#')
127
+ classes = parts[0...sep_index].join
128
+ method = parts[sep_index + 1]
129
+ else
130
+ classes = parts[0..-2].join
131
+ method = parts.last
132
+ end
133
+ class_parts = classes.split('::')
134
+ selected_classes = class_parts.last(2).join('::')
135
+ "#{selected_classes}.#{method}"
136
+ end
109
137
  end
110
138
 
111
139
  attr_reader :logger_type, :logger
@@ -119,6 +147,7 @@ module Aspera
119
147
  # Set log level of underlying logger given symbol level
120
148
  # @param new_level [Symbol] One of LEVELS
121
149
  def level=(new_level)
150
+ Aspera.assert_values(new_level, LEVELS)
122
151
  @logger.level = Logger::Severity.const_get(new_level.to_sym.upcase)
123
152
  end
124
153
 
@@ -140,10 +169,8 @@ module Aspera
140
169
  # Get symbol of debug level of underlying logger
141
170
  # @return [Symbol] One of LEVELS
142
171
  def level
143
- Logger::Severity.constants.each do |name|
144
- return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
145
- end
146
- Aspera.error_unexpected_value(@logger.level){'log level'}
172
+ Aspera.assert(Logger::SEVERITY_LABEL.key?(@logger.level))
173
+ Logger::SEVERITY_LABEL[@logger.level].downcase
147
174
  end
148
175
 
149
176
  # Change underlying logger, but keep log level
@@ -158,8 +185,8 @@ module Aspera
158
185
  @logger = Logger.new($stdout, progname: @program_name, formatter: DEFAULT_FORMATTER)
159
186
  when :syslog
160
187
  require 'syslog/logger'
161
- # the syslog class automatically creates methods from the severity names
162
- # we just need to add the mapping (but syslog lowest is DEBUG)
188
+ # The syslog class automatically creates methods from the severity names.
189
+ # We just need to add the mapping (but syslog lowest is DEBUG)
163
190
  1.upto(Logger::TRACE_MAX).each do |level|
164
191
  Syslog::Logger.const_get(:LEVEL_MAP)[Logger.const_get("TRACE#{level}")] = Syslog::LOG_DEBUG
165
192
  end
@@ -207,7 +234,8 @@ module Aspera
207
234
  # pre-defined formatters
208
235
  FORMATTERS = {
209
236
  standard: Logger::Formatter.new,
210
- default: DEFAULT_FORMATTER
237
+ default: DEFAULT_FORMATTER,
238
+ caller: ->(s, _d, _p, m){"#{LVL_COLOR[s]} #{Log.caller_method}\n#{m}\n"}
211
239
  }.freeze
212
240
 
213
241
  private_constant :LVL_DECO, :LVL_COLOR, :DEFAULT_FORMATTER, :FORMATTERS
data/lib/aspera/nagios.rb CHANGED
@@ -57,7 +57,7 @@ module Aspera
57
57
  # compare remote time with local time
58
58
  def check_time_offset(remote_date, component)
59
59
  # check date if specified : 2015-10-13T07:32:01Z
60
- remote_time = Time.strptime(remote_date)
60
+ remote_time = Time.parse(remote_date)
61
61
  diff_time = (remote_time - Time.now).abs
62
62
  diff_rounded = diff_time.round(-2)
63
63
  Log.log.debug{"DATE: #{remote_date} #{remote_time} diff=#{diff_rounded}"}