aspera-cli 4.16.0 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -19
  4. data/CONTRIBUTING.md +3 -1
  5. data/README.md +965 -793
  6. data/bin/asession +29 -21
  7. data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
  8. data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
  9. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  10. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
  11. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
  12. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
  13. data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
  14. data/lib/aspera/api/aoc.rb +586 -0
  15. data/lib/aspera/api/ats.rb +46 -0
  16. data/lib/aspera/api/cos_node.rb +95 -0
  17. data/lib/aspera/api/node.rb +344 -0
  18. data/lib/aspera/ascmd.rb +46 -10
  19. data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
  20. data/lib/aspera/{fasp → ascp}/management.rb +3 -8
  21. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  22. data/lib/aspera/assert.rb +30 -30
  23. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  24. data/lib/aspera/cli/extended_value.rb +1 -1
  25. data/lib/aspera/cli/formatter.rb +13 -13
  26. data/lib/aspera/cli/hints.rb +5 -5
  27. data/lib/aspera/cli/main.rb +35 -28
  28. data/lib/aspera/cli/manager.rb +25 -24
  29. data/lib/aspera/cli/plugin.rb +22 -15
  30. data/lib/aspera/cli/plugin_factory.rb +61 -0
  31. data/lib/aspera/cli/plugins/alee.rb +7 -7
  32. data/lib/aspera/cli/plugins/aoc.rb +83 -77
  33. data/lib/aspera/cli/plugins/ats.rb +32 -33
  34. data/lib/aspera/cli/plugins/bss.rb +3 -4
  35. data/lib/aspera/cli/plugins/config.rb +169 -186
  36. data/lib/aspera/cli/plugins/console.rb +8 -6
  37. data/lib/aspera/cli/plugins/cos.rb +19 -18
  38. data/lib/aspera/cli/plugins/faspex.rb +61 -54
  39. data/lib/aspera/cli/plugins/faspex5.rb +150 -103
  40. data/lib/aspera/cli/plugins/node.rb +68 -73
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
  42. data/lib/aspera/cli/plugins/preview.rb +31 -31
  43. data/lib/aspera/cli/plugins/server.rb +31 -33
  44. data/lib/aspera/cli/plugins/shares.rb +13 -11
  45. data/lib/aspera/cli/sync_actions.rb +8 -8
  46. data/lib/aspera/cli/transfer_agent.rb +32 -19
  47. data/lib/aspera/cli/transfer_progress.rb +1 -1
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/colors.rb +5 -0
  50. data/lib/aspera/command_line_builder.rb +14 -14
  51. data/lib/aspera/coverage.rb +1 -2
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +2 -3
  54. data/lib/aspera/faspex_gw.rb +5 -6
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/id_generator.rb +2 -2
  57. data/lib/aspera/json_rpc.rb +5 -5
  58. data/lib/aspera/keychain/encrypted_hash.rb +6 -6
  59. data/lib/aspera/keychain/macos_security.rb +27 -22
  60. data/lib/aspera/log.rb +2 -2
  61. data/lib/aspera/nagios.rb +3 -3
  62. data/lib/aspera/node_simulator.rb +5 -6
  63. data/lib/aspera/oauth/base.rb +143 -0
  64. data/lib/aspera/oauth/factory.rb +124 -0
  65. data/lib/aspera/oauth/generic.rb +34 -0
  66. data/lib/aspera/oauth/jwt.rb +51 -0
  67. data/lib/aspera/oauth/url_json.rb +31 -0
  68. data/lib/aspera/oauth/web.rb +50 -0
  69. data/lib/aspera/oauth.rb +5 -331
  70. data/lib/aspera/open_application.rb +7 -7
  71. data/lib/aspera/persistency_action_once.rb +4 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +5 -5
  74. data/lib/aspera/preview/terminal.rb +3 -2
  75. data/lib/aspera/preview/utils.rb +3 -3
  76. data/lib/aspera/proxy_auto_config.rb +4 -4
  77. data/lib/aspera/rest.rb +175 -144
  78. data/lib/aspera/rest_errors_aspera.rb +3 -3
  79. data/lib/aspera/resumer.rb +77 -0
  80. data/lib/aspera/ssh.rb +6 -1
  81. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  82. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  83. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  84. data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
  85. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
  86. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  87. data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
  88. data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
  89. data/lib/aspera/web_server_simple.rb +11 -3
  90. data.tar.gz.sig +0 -0
  91. metadata +36 -63
  92. metadata.gz.sig +0 -0
  93. data/lib/aspera/aoc.rb +0 -601
  94. data/lib/aspera/ats_api.rb +0 -47
  95. data/lib/aspera/cos_node.rb +0 -94
  96. data/lib/aspera/fasp/resume_policy.rb +0 -79
  97. data/lib/aspera/node.rb +0 -339
@@ -29,20 +29,20 @@ module Aspera
29
29
  # Called by provider of definition before constructor of this class so that params_definition has all mandatory fields
30
30
  def normalize_description(full_description)
31
31
  full_description.each do |name, options|
32
- assert_type(options, Hash){name}
32
+ Aspera.assert_type(options, Hash){name}
33
33
  unsupported_keys = options.keys - OPTIONS_KEYS
34
- assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
35
- assert(options.key?(:cli)){"Missing key: cli for #{name}"}
36
- assert_type(options[:cli], Hash){'Key: cli'}
37
- assert(options[:cli].key?(:type)){'Missing key: cli.type'}
38
- assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
34
+ Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
35
+ Aspera.assert(options.key?(:cli)){"Missing key: cli for #{name}"}
36
+ Aspera.assert_type(options[:cli], Hash){'Key: cli'}
37
+ Aspera.assert(options[:cli].key?(:type)){'Missing key: cli.type'}
38
+ Aspera.assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
39
39
  # by default : optional
40
40
  options[:mandatory] ||= false
41
41
  options[:desc] ||= ''
42
42
  options[:desc] = "DEPRECATED: #{options[:deprecation]}\n#{options[:desc]}" if options.key?(:deprecation)
43
43
  cli = options[:cli]
44
44
  unsupported_cli_keys = cli.keys - CLI_KEYS
45
- assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
45
+ Aspera.assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
46
46
  # by default : string, unless it's without arg
47
47
  options[:accepted_types] ||= options[:cli][:type].eql?(:opt_without_arg) ? :bool : :string
48
48
  # single type is placed in array
@@ -111,7 +111,7 @@ module Aspera
111
111
  end
112
112
  processing_type = read ? :get_value : options[:cli][:type]
113
113
  # check mandatory parameter (nil is valid value)
114
- raise Fasp::Error, "Missing mandatory parameter: #{name}" if options[:mandatory] && !@param_hash.key?(name)
114
+ raise Transfer::Error, "Missing mandatory parameter: #{name}" if options[:mandatory] && !@param_hash.key?(name)
115
115
  parameter_value = @param_hash[name]
116
116
  # no default setting
117
117
  # parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
@@ -123,11 +123,11 @@ module Aspera
123
123
  when :hash then Hash
124
124
  when :int then Integer
125
125
  when :bool then [TrueClass, FalseClass]
126
- else error_unexpected_value(type_symbol)
126
+ else Aspera.error_unexpected_value(type_symbol)
127
127
  end
128
128
  end.flatten
129
129
  # check that value is of expected type
130
- raise Fasp::Error, "#{name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, " \
130
+ raise Transfer::Error, "#{name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, " \
131
131
  unless parameter_value.nil? || expected_classes.include?(parameter_value.class)
132
132
  # special processing will be requested with type get_value
133
133
  @used_param_names.push(name) unless processing_type.eql?(:special)
@@ -149,10 +149,10 @@ module Aspera
149
149
  # :convert has name of class and encoding method
150
150
  conversion_class, conversion_method = options[:cli][:convert].split('.')
151
151
  converted_value = Kernel.const_get(conversion_class).send(conversion_method, parameter_value)
152
- raise Fasp::Error, "unsupported #{name}: #{parameter_value}" if converted_value.nil?
152
+ raise Transfer::Error, "unsupported #{name}: #{parameter_value}" if converted_value.nil?
153
153
  parameter_value = converted_value
154
154
  when NilClass
155
- else error_unexpected_value(options[:cli][:convert].class)
155
+ else Aspera.error_unexpected_value(options[:cli][:convert].class)
156
156
  end
157
157
 
158
158
  case processing_type
@@ -161,14 +161,14 @@ module Aspera
161
161
  when :ignore, :special # ignore this parameter or process later
162
162
  return
163
163
  when :envvar # set in env var
164
- assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
164
+ Aspera.assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
165
165
  @result[:env][options[:cli][:variable]] = parameter_value
166
166
  when :opt_without_arg # if present and true : just add option without value
167
167
  add_param = false
168
168
  case parameter_value
169
169
  when false then nil # nothing to put on command line, no creation by default
170
170
  when true then add_param = true
171
- else error_unexpected_value(parameter_value){name}
171
+ else Aspera.error_unexpected_value(parameter_value){name}
172
172
  end
173
173
  add_param = !add_param if options[:add_on_false]
174
174
  add_command_line_options([options[:cli][:switch]]) if add_param
@@ -4,8 +4,7 @@
4
4
  if ENV.key?('ENABLE_COVERAGE')
5
5
  require 'simplecov'
6
6
  require 'securerandom'
7
- # compute gem source root based on this script location, assuming it is in bin/
8
- # use dirname instead of gsub, in case folder separator is not /
7
+ # compute development top folder based on this source location
9
8
  development_root = 3.times.inject(File.realpath(__FILE__)) { |p, _| File.dirname(p) }
10
9
  SimpleCov.root(development_root)
11
10
  SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
@@ -30,7 +30,7 @@ module Aspera
30
30
  return format('%08x-%04x-%04x-%04x-%04x%08x', *raw_data.unpack('NnnnnN'))
31
31
  when :'aspera.global-cli-client', :'aspera.drive'
32
32
  return Base64.urlsafe_encode64(raw_data)
33
- else error_unexpected_value(name)
33
+ else Aspera.error_unexpected_value(name)
34
34
  end
35
35
  end
36
36
 
@@ -56,8 +56,7 @@ module Aspera
56
56
  when /s390/
57
57
  return CPU_S390
58
58
  when /arm/, /aarch64/
59
- # arm on mac has rosetta 2
60
- return CPU_X86_64 if os.eql?(OS_X)
59
+ return CPU_ARM64
61
60
  end
62
61
  raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
63
62
  end
@@ -90,7 +89,7 @@ module Aspera
90
89
 
91
90
  # value is provided in block
92
91
  def write_file_restricted(path, force: false, mode: nil)
93
- assert(block_given?, exception_class: Aspera::InternalError)
92
+ Aspera.assert(block_given?, exception_class: Aspera::InternalError)
94
93
  if force || !File.exist?(path)
95
94
  # Windows may give error
96
95
  File.unlink(path) rescue nil
@@ -8,12 +8,11 @@ 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
- # @param app_api [Aspera::AoC]
11
+ # @param app_api
12
12
  # @param app_context [String]
13
13
  def initialize(server, app_api, app_context)
14
- assert_values(app_api.class.name, ['Aspera::AoC', 'Aspera::Rest'])
14
+ Aspera.assert_values(app_api.class.name, ['Aspera::Api::AoC', 'Aspera::Rest'])
15
15
  super(server)
16
- # typed: Aspera::AoC
17
16
  @app_api = app_api
18
17
  @app_context = app_context
19
18
  end
@@ -51,7 +50,7 @@ module Aspera
51
50
  operation: 'POST',
52
51
  subpath: "packages/#{package['id']}/transfer_spec/upload",
53
52
  headers: {'Accept' => 'application/json'},
54
- url_params: {transfer_type: Aspera::Cli::Plugins::Faspex5::TRANSFER_CONNECT},
53
+ url_params: {transfer_type: Cli::Plugins::Faspex5::TRANSFER_CONNECT},
55
54
  json_params: {paths: [{'destination'=>'/'}]}
56
55
  )[:data]
57
56
  transfer_spec.delete('authentication')
@@ -72,11 +71,11 @@ module Aspera
72
71
  # compare string, as class is not yet known here
73
72
  faspex_package_create_result =
74
73
  case @app_api.class.name
75
- when 'Aspera::AoC'
74
+ when 'Aspera::Api::AoC'
76
75
  faspex4_send_to_aoc(faspex_pkg_parameters)
77
76
  when 'Aspera::Rest'
78
77
  faspex4_send_to_faspex5(faspex_pkg_parameters)
79
- else error_unexpected_value(@app_api.class.name)
78
+ else Aspera.error_unexpected_value(@app_api.class.name)
80
79
  end
81
80
  Log.log.info{"faspex_package_create_result=#{faspex_package_create_result}"}
82
81
  response.status = 200
@@ -12,7 +12,7 @@ module Aspera
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)
15
- assert_type(parameters, Hash)
15
+ Aspera.assert_type(parameters, Hash)
16
16
  @parameters = parameters.symbolize_keys
17
17
  Log.log.debug{Log.dump(:post_proc_parameters, @parameters)}
18
18
  raise "unexpected key in parameters config: only: #{ALLOWED_PARAMETERS.join(', ')}" if @parameters.keys.any?{|k|!ALLOWED_PARAMETERS.include?(k)}
@@ -17,10 +17,10 @@ module Aspera
17
17
  i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
18
18
  end.join(ID_SEPARATOR)
19
19
  end
20
- assert_type(object_id, String)
20
+ Aspera.assert_type(object_id, String)
21
21
  return object_id
22
22
  .gsub(WINDOWS_PROTECTED_CHAR, PROTECTED_CHAR_REPLACE) # remove windows forbidden chars
23
- .gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
23
+ .gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
24
24
  .downcase
25
25
  end
26
26
  end
@@ -35,11 +35,11 @@ module Aspera
35
35
  params: args,
36
36
  id: @request_id += 1
37
37
  })[:data]
38
- assert_type(data, Hash){'response'}
39
- assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
- assert(data.key?('id')){'missing id in response'}
41
- assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
42
- assert(
38
+ Aspera.assert_type(data, Hash){'response'}
39
+ Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
+ Aspera.assert(data.key?('id')){'missing id in response'}
41
+ Aspera.assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
42
+ Aspera.assert(
43
43
  !data.key?('error') ||
44
44
  data['error'].is_a?(Hash) &&
45
45
  data['error']['code'].is_a?(Integer) &&
@@ -17,7 +17,7 @@ module Aspera
17
17
  CONTENT_KEYS = %i[label username password url description].freeze
18
18
  FILE_KEYS = %w[version type cipher data].sort.freeze
19
19
  def initialize(path, current_password)
20
- assert_type(path, String){'path to vault file'}
20
+ Aspera.assert_type(path, String){'path to vault file'}
21
21
  @path = path
22
22
  @all_secrets = {}
23
23
  vault_encrypted_data = nil
@@ -25,7 +25,7 @@ module Aspera
25
25
  vault_file = File.read(@path)
26
26
  if vault_file.start_with?('---')
27
27
  vault_info = YAML.parse(vault_file).to_ruby
28
- assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
28
+ Aspera.assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
29
29
  @cipher_name = vault_info['cipher']
30
30
  vault_encrypted_data = vault_info['data']
31
31
  else
@@ -65,11 +65,11 @@ module Aspera
65
65
  # set a secret
66
66
  # @param options [Hash] with keys :label, :username, :password, :url, :description
67
67
  def set(options)
68
- assert_type(options, Hash){'options'}
68
+ Aspera.assert_type(options, Hash){'options'}
69
69
  unsupported = options.keys - CONTENT_KEYS
70
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
70
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
71
71
  options.each_pair do |k, v|
72
- assert_type(v, String){k.to_s}
72
+ Aspera.assert_type(v, String){k.to_s}
73
73
  end
74
74
  label = options.delete(:label)
75
75
  raise "secret #{label} already exist, delete first" if @all_secrets.key?(label)
@@ -94,7 +94,7 @@ module Aspera
94
94
  end
95
95
 
96
96
  def get(label:, exception: true)
97
- assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
97
+ Aspera.assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
98
98
  result = @all_secrets[label].clone
99
99
  result[:label] = label if result.is_a?(Hash)
100
100
  return result
@@ -4,6 +4,7 @@
4
4
  require 'aspera/cli/info'
5
5
  require 'aspera/log'
6
6
  require 'aspera/assert'
7
+ require 'open3'
7
8
 
8
9
  # enhance the gem to support other key chains
9
10
  module Aspera
@@ -11,6 +12,8 @@ module Aspera
11
12
  module MacosSecurity
12
13
  # keychain based on macOS keychain, using `security` command line
13
14
  class Keychain
15
+ # https://www.unix.com/man-page/osx/1/security/
16
+ SECURITY_UTILITY = 'security'
14
17
  DOMAINS = %i[user system common dynamic].freeze
15
18
  LIST_OPTIONS = {
16
19
  domain: :c
@@ -38,25 +41,27 @@ module Aspera
38
41
  url = options&.delete(:url)
39
42
  if !url.nil?
40
43
  uri = URI.parse(url)
41
- assert(uri.scheme.eql?('https')){'only https'}
44
+ Aspera.assert(uri.scheme.eql?('https')){'only https'}
42
45
  options[:protocol] = 'htps' # cspell: disable-line
43
46
  raise 'host required in URL' if uri.host.nil?
44
47
  options[:server] = uri.host
45
48
  options[:path] = uri.path unless ['', '/'].include?(uri.path)
46
49
  options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
47
50
  end
48
- cmd = ['security', command]
51
+ command_line = [SECURITY_UTILITY, command]
49
52
  options&.each do |k, v|
50
- assert(supported.key?(k)){"unknown option: #{k}"}
53
+ Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
51
54
  next if v.nil?
52
- cmd.push("-#{supported[k]}")
53
- cmd.push(v.shellescape) unless v.empty?
55
+ command_line.push("-#{supported[k]}")
56
+ command_line.push(v.shellescape) unless v.empty?
54
57
  end
55
- cmd.push(last_opt) unless last_opt.nil?
56
- Log.log.debug{"executing>>#{cmd.join(' ')}"}
57
- result = %x(#{cmd.join(' ')} 2>&1)
58
- Log.log.debug{"result>>[#{result}]"}
59
- return result
58
+ command_line.push(last_opt) unless last_opt.nil?
59
+ Log.log.debug{"executing>>#{command_line.join(' ')}"}
60
+ stdout, stderr, status = Open3.capture3(*command_line)
61
+ Log.log.debug{"status=#{status}, stderr=#{stderr}"}
62
+ Log.log.trace1{"stdout=#{stdout}"}
63
+ raise "#{SECURITY_UTILITY} failed: #{status.exitstatus} : #{stderr}" unless status.success?
64
+ return stdout
60
65
  end
61
66
 
62
67
  def key_chains(output)
@@ -72,7 +77,7 @@ module Aspera
72
77
  end
73
78
 
74
79
  def list(options={})
75
- assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
80
+ Aspera.assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
76
81
  key_chains(execute('list-key_chains', options, LIST_OPTIONS))
77
82
  end
78
83
 
@@ -91,15 +96,15 @@ module Aspera
91
96
  end
92
97
 
93
98
  def password(operation, pass_type, options)
94
- assert_values(operation, %i[add find delete]){'operation'}
95
- assert_values(pass_type, %i[generic internet]){'pass_type'}
96
- assert_type(options, Hash)
99
+ Aspera.assert_values(operation, %i[add find delete]){'operation'}
100
+ Aspera.assert_values(pass_type, %i[generic internet]){'pass_type'}
101
+ Aspera.assert_type(options, Hash)
97
102
  missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - options.keys
98
- assert(missing.empty?){"missing options: #{missing}"}
103
+ Aspera.assert(missing.empty?){"missing options: #{missing}"}
99
104
  options[:getpass] = '' if operation.eql?(:find)
100
105
  output = self.class.execute("#{operation}-#{pass_type}-password", options, ADD_PASS_OPTIONS, @path)
101
106
  raise output.gsub(/^.*: /, '') if output.start_with?('security: ')
102
- return nil unless operation.eql?(:find)
107
+ return unless operation.eql?(:find)
103
108
  attributes = {}
104
109
  output.split("\n").each do |line|
105
110
  case line
@@ -129,18 +134,18 @@ module Aspera
129
134
  end
130
135
 
131
136
  def set(options)
132
- assert_type(options, Hash){'options'}
137
+ Aspera.assert_type(options, Hash){'options'}
133
138
  unsupported = options.keys - %i[label username password url description]
134
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
139
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
135
140
  @keychain.password(
136
141
  :add, :generic, service: options[:label],
137
142
  account: options[:username] || 'none', password: options[:password], comment: options[:description])
138
143
  end
139
144
 
140
145
  def get(options)
141
- assert_type(options, Hash){'options'}
146
+ Aspera.assert_type(options, Hash){'options'}
142
147
  unsupported = options.keys - %i[label]
143
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
148
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
144
149
  info = @keychain.password(:find, :generic, label: options[:label])
145
150
  raise 'not found' if info.nil?
146
151
  result = options.clone
@@ -155,9 +160,9 @@ module Aspera
155
160
  end
156
161
 
157
162
  def delete(options)
158
- assert_type(options, Hash){'options'}
163
+ Aspera.assert_type(options, Hash){'options'}
159
164
  unsupported = options.keys - %i[label]
160
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
165
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
161
166
  raise 'delete not implemented, use macos keychain app'
162
167
  end
163
168
  end
data/lib/aspera/log.rb CHANGED
@@ -14,7 +14,7 @@ class Logger
14
14
  TRACE_MAX = 2
15
15
  # add custom level to logger severity
16
16
  module Severity
17
- 1.upto(TRACE_MAX).each { |level| const_set("TRACE#{level}", - level)}
17
+ 1.upto(TRACE_MAX).each { |level| const_set(:"TRACE#{level}", - level)}
18
18
  end
19
19
  # quick access to label
20
20
  SEVERITY_LABEL = Severity.constants.each_with_object({}) { |name, hash| hash[Severity.const_get(name)] = name}
@@ -99,7 +99,7 @@ module Aspera
99
99
  Logger::Severity.constants.each do |name|
100
100
  return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
101
101
  end
102
- error_unexpected_value(@logger.level){'log level'}
102
+ Aspera.error_unexpected_value(@logger.level){'log level'}
103
103
  end
104
104
 
105
105
  # change underlying logger, but keep log level
data/lib/aspera/nagios.rb CHANGED
@@ -23,10 +23,10 @@ module Aspera
23
23
  class << self
24
24
  # process results of a analysis and display status and exit with code
25
25
  def process(data)
26
- assert_type(data, Array)
27
- assert(!data.empty?){'data is empty'}
26
+ Aspera.assert_type(data, Array)
27
+ Aspera.assert(!data.empty?){'data is empty'}
28
28
  %w[status component message].each do |c|
29
- assert(data.first.key?(c)){"result must have #{c}"}
29
+ Aspera.assert(data.first.key?(c)){"result must have #{c}"}
30
30
  end
31
31
  res_errors = data.reject{|s|s['status'].eql?('ok')}
32
32
  # keep only errors in case of problem, other ok are assumed so
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/ascp/installation'
4
+ require 'aspera/agent/direct'
3
5
  require 'aspera/log'
4
- require 'aspera/fasp/installation'
5
- require 'aspera/fasp/agent_direct'
6
6
  require 'webrick'
7
7
  require 'json'
8
8
 
@@ -11,12 +11,12 @@ module Aspera
11
11
  class NodeSimulatorServlet < WEBrick::HTTPServlet::AbstractServlet
12
12
  PATH_TRANSFERS = '/ops/transfers'
13
13
  PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
14
- # @param app_api [Aspera::AoC]
14
+ # @param app_api [Api::AoC]
15
15
  # @param app_context [String]
16
16
  def initialize(server, credentials, transfer)
17
17
  super(server)
18
18
  @credentials = credentials
19
- @xfer_manager = Aspera::Fasp::AgentDirect.new
19
+ @xfer_manager = Agent::Direct.new
20
20
  end
21
21
 
22
22
  def do_POST(request, response)
@@ -27,7 +27,6 @@ module Aspera
27
27
  result = session[:ts].clone
28
28
  result['id'] = job_id
29
29
  set_json_response(response, result)
30
- Log.log.debug{">>> transfer started: #{job_id}"}
31
30
  else
32
31
  set_json_response(response, [{error: 'Bad request'}], code: 400)
33
32
  end
@@ -36,7 +35,7 @@ module Aspera
36
35
  def do_GET(request, response)
37
36
  case request.path
38
37
  when '/info'
39
- info = Aspera::Fasp::Installation.instance.ascp_info
38
+ info = Ascp::Installation.instance.ascp_info
40
39
  set_json_response(response, {
41
40
  application: 'node',
42
41
  current_time: Time.now.utc.iso8601(0),
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/oauth/factory'
4
+ require 'aspera/log'
5
+ require 'aspera/assert'
6
+ require 'aspera/id_generator'
7
+ require 'date'
8
+
9
+ module Aspera
10
+ module OAuth
11
+ # Implement OAuth 2 for the REST client and generate a bearer token
12
+ # call get_authorization() to get a token.
13
+ # bearer tokens are kept in memory and also in a file cache for later re-use
14
+ # if a token is expired (api returns 4xx), call again get_authorization(refresh: true)
15
+ # https://tools.ietf.org/html/rfc6749
16
+ class Base
17
+ # scope can be modified after creation
18
+ attr_writer :scope
19
+
20
+ # [M]=mandatory [D]=has default value [O]=Optional/nil
21
+ # @param base_url [M] URL of authentication API
22
+ # @param auth [O] basic auth parameters
23
+ # @param client_id [O]
24
+ # @param client_secret [O]
25
+ # @param scope [O]
26
+ # @param path_token [D] API end point to create a token
27
+ # @param token_field [D] field in result that contains the token
28
+ def initialize(
29
+ base_url:,
30
+ auth: nil,
31
+ client_id: nil,
32
+ client_secret: nil,
33
+ scope: nil,
34
+ path_token: 'token', # default endpoint for /token to generate token
35
+ token_field: 'access_token' # field with token in result of call to path_token
36
+ )
37
+ Aspera.assert_type(base_url, String)
38
+ Aspera.assert(respond_to?(:create_token), 'create_token method must be defined', exception_class: InternalError)
39
+ @base_url = base_url
40
+ @path_token = path_token
41
+ @token_field = token_field
42
+ @client_id = client_id
43
+ @client_secret = client_secret
44
+ @scope = scope
45
+ @identifiers = []
46
+ @identifiers.push(auth[:username]) if auth.is_a?(Hash) && auth.key?(:username)
47
+ # this is the OAuth API
48
+ @api = Rest.new(
49
+ base_url: @base_url,
50
+ redirect_max: 2,
51
+ auth: auth)
52
+ end
53
+
54
+ # helper method to create token as per RFC
55
+ def create_token_call(www_params)
56
+ Log.log.debug{'Generating a new token'.bg_green}
57
+ return @api.call(
58
+ operation: 'POST',
59
+ subpath: @path_token,
60
+ headers: {'Accept' => 'application/json'},
61
+ www_body_params: www_params)
62
+ end
63
+
64
+ # @return Hash with optional general parameters
65
+ def optional_scope_client_id(add_secret: false)
66
+ call_params = {}
67
+ call_params[:scope] = @scope unless @scope.nil?
68
+ call_params[:client_id] = @client_id unless @client_id.nil?
69
+ call_params[:client_secret] = @client_secret if add_secret && !@client_id.nil?
70
+ return call_params
71
+ end
72
+
73
+ # OAuth v2 token generation
74
+ # @param use_refresh_token set to true to force refresh or re-generation (if previous failed)
75
+ def get_authorization(use_refresh_token: false, use_cache: true)
76
+ # generate token unique identifier for persistency (memory/disk cache)
77
+ token_id = IdGenerator.from_list(Factory.id(
78
+ @base_url,
79
+ @grant_method,
80
+ @identifiers,
81
+ @scope
82
+ ))
83
+
84
+ # get token_data from cache (or nil), token_data is what is returned by /token
85
+ token_data = Factory.instance.persist_mgr.get(token_id) if use_cache
86
+ token_data = JSON.parse(token_data) unless token_data.nil?
87
+ # Optional optimization: check if node token is expired based on decoded content then force refresh if close enough
88
+ # might help in case the transfer agent cannot refresh himself
89
+ # `direct` agent is equipped with refresh code
90
+ if !use_refresh_token && !token_data.nil?
91
+ decoded_token = OAuth::Factory.instance.decode_token(token_data[@token_field])
92
+ Log.log.debug{Log.dump('decoded_token', decoded_token)} unless decoded_token.nil?
93
+ if decoded_token.is_a?(Hash)
94
+ expires_at_sec =
95
+ if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
96
+ elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
97
+ end
98
+ # force refresh if we see a token too close from expiration
99
+ use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < OAuth::Factory.instance.globals[:token_expiration_guard_sec]
100
+ Log.log.debug{"Expiration: #{expires_at_sec} / #{use_refresh_token}"}
101
+ end
102
+ end
103
+
104
+ # an API was already called, but failed, we need to regenerate or refresh
105
+ if use_refresh_token
106
+ if token_data.is_a?(Hash) && token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
107
+ # save possible refresh token, before deleting the cache
108
+ refresh_token = token_data['refresh_token']
109
+ end
110
+ # delete cache
111
+ Factory.instance.persist_mgr.delete(token_id)
112
+ token_data = nil
113
+ # lets try the existing refresh token
114
+ if !refresh_token.nil?
115
+ Log.log.info{"refresh=[#{refresh_token}]".bg_green}
116
+ # try to refresh
117
+ # note: AoC admin token has no refresh, and lives by default 1800secs
118
+ resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
119
+ if resp[:http].code.start_with?('2')
120
+ # save only if success
121
+ json_data = resp[:http].body
122
+ token_data = JSON.parse(json_data)
123
+ Factory.instance.persist_mgr.put(token_id, json_data)
124
+ else
125
+ Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
126
+ end
127
+ end
128
+ end
129
+
130
+ # no cache, nor refresh: generate a token
131
+ if token_data.nil?
132
+ resp = create_token
133
+ json_data = resp[:http].body
134
+ token_data = JSON.parse(json_data)
135
+ Factory.instance.persist_mgr.put(token_id, json_data)
136
+ end # if ! in_cache
137
+ Aspera.assert(token_data.key?(@token_field)){"API error: No such field in answer: #{@token_field}"}
138
+ # ok we shall have a token here
139
+ return OAuth::Factory.bearer_build(token_data[@token_field])
140
+ end
141
+ end # OAuth
142
+ end
143
+ end