aspera-cli 4.21.2 → 4.22.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/BUGS.md +1 -1
  4. data/CHANGELOG.md +34 -16
  5. data/CONTRIBUTING.md +6 -10
  6. data/README.md +805 -574
  7. data/examples/get_proto_file.rb +1 -1
  8. data/lib/aspera/agent/base.rb +9 -5
  9. data/lib/aspera/agent/connect.rb +30 -28
  10. data/lib/aspera/agent/desktop.rb +29 -25
  11. data/lib/aspera/agent/direct.rb +137 -125
  12. data/lib/aspera/agent/httpgw.rb +22 -26
  13. data/lib/aspera/agent/node.rb +14 -11
  14. data/lib/aspera/agent/transferd.rb +6 -2
  15. data/lib/aspera/api/aoc.rb +6 -6
  16. data/lib/aspera/api/cos_node.rb +1 -1
  17. data/lib/aspera/api/httpgw.rb +7 -3
  18. data/lib/aspera/api/node.rb +6 -4
  19. data/lib/aspera/ascmd.rb +3 -3
  20. data/lib/aspera/ascp/installation.rb +15 -16
  21. data/lib/aspera/ascp/management.rb +1 -1
  22. data/lib/aspera/assert.rb +11 -2
  23. data/lib/aspera/cli/error.rb +2 -2
  24. data/lib/aspera/cli/extended_value.rb +38 -19
  25. data/lib/aspera/cli/formatter.rb +48 -48
  26. data/lib/aspera/cli/hints.rb +1 -1
  27. data/lib/aspera/cli/main.rb +190 -168
  28. data/lib/aspera/cli/manager.rb +15 -15
  29. data/lib/aspera/cli/plugin.rb +23 -20
  30. data/lib/aspera/cli/plugin_factory.rb +1 -1
  31. data/lib/aspera/cli/plugins/alee.rb +1 -1
  32. data/lib/aspera/cli/plugins/aoc.rb +144 -107
  33. data/lib/aspera/cli/plugins/ats.rb +19 -17
  34. data/lib/aspera/cli/plugins/config.rb +67 -83
  35. data/lib/aspera/cli/plugins/console.rb +5 -3
  36. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  37. data/lib/aspera/cli/plugins/faspex5.rb +104 -80
  38. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  39. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  40. data/lib/aspera/cli/plugins/node.rb +306 -179
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  42. data/lib/aspera/cli/plugins/preview.rb +3 -3
  43. data/lib/aspera/cli/plugins/server.rb +6 -6
  44. data/lib/aspera/cli/plugins/shares.rb +5 -5
  45. data/lib/aspera/cli/sync_actions.rb +19 -18
  46. data/lib/aspera/cli/transfer_agent.rb +5 -5
  47. data/lib/aspera/cli/transfer_progress.rb +2 -2
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/command_line_builder.rb +116 -95
  50. data/lib/aspera/coverage.rb +4 -3
  51. data/lib/aspera/environment.rb +6 -6
  52. data/lib/aspera/faspex_gw.rb +14 -14
  53. data/lib/aspera/faspex_postproc.rb +7 -6
  54. data/lib/aspera/hash_ext.rb +2 -2
  55. data/lib/aspera/json_rpc.rb +1 -1
  56. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  57. data/lib/aspera/keychain/factory.rb +41 -0
  58. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  59. data/lib/aspera/keychain/macos_security.rb +19 -11
  60. data/lib/aspera/log.rb +28 -34
  61. data/lib/aspera/nagios.rb +6 -6
  62. data/lib/aspera/node_simulator.rb +8 -8
  63. data/lib/aspera/oauth/base.rb +8 -6
  64. data/lib/aspera/oauth/factory.rb +5 -6
  65. data/lib/aspera/oauth/url_json.rb +6 -6
  66. data/lib/aspera/persistency_action_once.rb +6 -4
  67. data/lib/aspera/persistency_folder.rb +2 -2
  68. data/lib/aspera/preview/generator.rb +1 -1
  69. data/lib/aspera/preview/options.rb +16 -16
  70. data/lib/aspera/preview/terminal.rb +3 -3
  71. data/lib/aspera/preview/utils.rb +11 -13
  72. data/lib/aspera/products/connect.rb +1 -1
  73. data/lib/aspera/products/desktop.rb +1 -1
  74. data/lib/aspera/products/transferd.rb +1 -1
  75. data/lib/aspera/proxy_auto_config.rb +2 -2
  76. data/lib/aspera/rest.rb +52 -43
  77. data/lib/aspera/rest_errors_aspera.rb +1 -1
  78. data/lib/aspera/secret_hider.rb +5 -5
  79. data/lib/aspera/ssh.rb +4 -4
  80. data/lib/aspera/transfer/convert.rb +29 -0
  81. data/lib/aspera/transfer/error_info.rb +66 -66
  82. data/lib/aspera/transfer/parameters.rb +13 -68
  83. data/lib/aspera/transfer/spec.rb +5 -6
  84. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  85. data/lib/aspera/transfer/spec_doc.rb +62 -0
  86. data/lib/aspera/transfer/sync.rb +23 -72
  87. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  88. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  89. data/lib/aspera/transfer/uri.rb +6 -6
  90. data/lib/aspera/uri_reader.rb +1 -1
  91. data/lib/aspera/web_auth.rb +1 -1
  92. data/lib/aspera/web_server_simple.rb +53 -44
  93. data.tar.gz.sig +1 -2
  94. metadata +37 -4
  95. metadata.gz.sig +0 -0
  96. data/examples/build_package.sh +0 -28
  97. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -9,9 +9,28 @@ require 'aspera/assert'
9
9
  module Aspera
10
10
  module Agent
11
11
  class Httpgw < Base
12
- # start FASP transfer based on transfer spec (hash table)
13
- # note that it is asynchronous
12
+ def initialize(
13
+ url:,
14
+ api_version: Api::Httpgw::API_V2,
15
+ upload_chunk_size: 64_000,
16
+ synchronous: false,
17
+ **base_options
18
+ )
19
+ super(**base_options)
20
+ @gw_api = Api::Httpgw.new(
21
+ # remove /v1 from end of user-provided GW url: we need the base url only
22
+ url: url,
23
+ api_version: api_version,
24
+ upload_chunk_size: upload_chunk_size,
25
+ synchronous: synchronous,
26
+ notify_cb: ->(*pa, **ka){notify_progress(*pa, **ka)}
27
+ )
28
+ end
29
+
30
+ # Start FASP transfer based on transfer spec (hash table)
31
+ # note that this should be asynchronous, but it is not
14
32
  # HTTP download only supports file list
33
+ # :reek:UnusedParameters token_regenerator
15
34
  def start_transfer(transfer_spec, token_regenerator: nil)
16
35
  raise 'GW URL must be set' if @gw_api.nil?
17
36
  Aspera.assert_type(transfer_spec['paths'], Array){'paths'}
@@ -27,35 +46,12 @@ module Aspera
27
46
  end
28
47
  end
29
48
 
30
- # wait for completion of all jobs started
49
+ # Wait for completion of all jobs started
31
50
  # @return list of :success or error message
32
51
  def wait_for_transfers_completion
33
52
  # well ... transfer was done in "start"
34
53
  return [:success]
35
54
  end
36
-
37
- # TODO: is that useful?
38
- # def url=(api_url); end
39
-
40
- private
41
-
42
- def initialize(
43
- url:,
44
- api_version: Api::Httpgw::API_V2,
45
- upload_chunk_size: 64_000,
46
- synchronous: false,
47
- **base_options
48
- )
49
- super(**base_options)
50
- @gw_api = Api::Httpgw.new(
51
- # remove /v1 from end of user-provided GW url: we need the base url only
52
- url: url,
53
- api_version: api_version,
54
- upload_chunk_size: upload_chunk_size,
55
- synchronous: synchronous,
56
- notify_cb: ->(*pa, **ka) { notify_progress(*pa, **ka) }
57
- )
58
- end
59
55
  end
60
56
  end
61
57
  end
@@ -13,10 +13,10 @@ module Aspera
13
13
  # this singleton class is used by the CLI to provide a common interface to start a transfer
14
14
  # before using it, the use must set the `node_api` member.
15
15
  class Node < Base
16
- # @param url [String] the base url of the node api
17
- # @param username [String] the username to use for the node api
18
- # @param password [String] the password to use for the node api
19
- # @param root_id [String] root file id if the node is an access key
16
+ # @param url [String] the base url of the node api
17
+ # @param username [String] the username to use for the node api
18
+ # @param password [String] the password to use for the node api
19
+ # @param root_id [String] root file id if the node is an access key
20
20
  # @param base_options [Hash] options for base class
21
21
  def initialize(
22
22
  url:,
@@ -28,7 +28,7 @@ module Aspera
28
28
  super(**base_options)
29
29
  # root id is required for access key
30
30
  @root_id = root_id
31
- rest_params = { base_url: url}
31
+ rest_params = {base_url: url}
32
32
  if OAuth::Factory.bearer?(password)
33
33
  Aspera.assert(!@root_id.nil?){'root_id not allowed for access key'}
34
34
  rest_params[:headers] = Api::Node.bearer_headers(password, access_key: username)
@@ -44,13 +44,8 @@ module Aspera
44
44
  @transfer_id = nil
45
45
  end
46
46
 
47
- # used internally to ensure node api is set before using.
48
- def node_api_
49
- Aspera.assert(!@node_api.nil?){'Before using this object, set the node_api attribute to a Aspera::Rest object'}
50
- return @node_api
51
- end
52
-
53
47
  # generic method
48
+ # :reek:UnusedParameters token_regenerator
54
49
  def start_transfer(transfer_spec, token_regenerator: nil)
55
50
  # add root id if access key
56
51
  if !@root_id.nil?
@@ -119,6 +114,14 @@ module Aspera
119
114
  # TODO: get status of sessions
120
115
  return []
121
116
  end
117
+
118
+ private
119
+
120
+ # used internally to ensure node api is set before using.
121
+ def node_api_
122
+ Aspera.assert(!@node_api.nil?){'Before using this object, set the node_api attribute to a Aspera::Rest object'}
123
+ return @node_api
124
+ end
122
125
  end
123
126
  end
124
127
  end
@@ -31,6 +31,7 @@ module Aspera
31
31
  **base
32
32
  )
33
33
  super(**base)
34
+ @transfer_id = nil
34
35
  @stop = stop
35
36
  is_local_auto_port = url.eql?(AUTO_LOCAL_TCP_PORT)
36
37
  raise 'Cannot set options `stop` or `start` to false with port zero' if is_local_auto_port && (!@stop || !start)
@@ -94,7 +95,7 @@ module Aspera
94
95
  end
95
96
  Log.log.debug{"Daemon started with pid #{@daemon_pid}"}
96
97
  Process.detach(@daemon_pid) unless @stop
97
- at_exit {shutdown}
98
+ at_exit{shutdown}
98
99
  # update port for next connection attempt (if auto high port was requested)
99
100
  daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{Products::Transferd.daemon_port_from_log(log_stdout)}" if is_local_auto_port
100
101
  # local daemon started, try again
@@ -102,6 +103,7 @@ module Aspera
102
103
  end
103
104
  end
104
105
 
106
+ # :reek:UnusedParameters token_regenerator
105
107
  def start_transfer(transfer_spec, token_regenerator: nil)
106
108
  # create a transfer request
107
109
  transfer_request = ::Transferd::Api::TransferRequest.new(
@@ -156,10 +158,12 @@ module Aspera
156
158
  stop_daemon if @stop
157
159
  end
158
160
 
161
+ private
162
+
159
163
  def stop_daemon
160
164
  if !@daemon_pid.nil?
161
165
  Log.log.debug("Stopping daemon #{@daemon_pid}")
162
- Process.kill('INT', @daemon_pid)
166
+ Process.kill(:INT, @daemon_pid)
163
167
  _, status = Process.wait2(@daemon_pid)
164
168
  Log.log.debug("daemon stopped #{status}")
165
169
  @daemon_pid = nil
@@ -22,7 +22,7 @@ module Aspera
22
22
  MAX_AOC_URL_REDIRECT = 10
23
23
  CLIENT_ID_PREFIX = 'aspera.'
24
24
  # Well-known AoC global client apps
25
- GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{|i|i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
25
+ GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{ |i| i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
26
26
  # cookie prefix so that console can decode identity
27
27
  COOKIE_PREFIX_CONSOLE_AOC = 'aspera.aoc'
28
28
  # path in URL of public links
@@ -237,7 +237,7 @@ module Aspera
237
237
  Log.log.debug{"ignoring error: #{e}"}
238
238
  {}
239
239
  end
240
- USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = nil if @cache_user_info[f].nil?}
240
+ USER_INFO_FIELDS_MIN.each{ |f| @cache_user_info[f] = nil if @cache_user_info[f].nil?}
241
241
  return @cache_user_info
242
242
  end
243
243
 
@@ -283,7 +283,7 @@ module Aspera
283
283
  if ws_info.nil?
284
284
  {
285
285
  id: nil,
286
- name: 'Shared folders'
286
+ name: "Shared #{application}"
287
287
  }
288
288
  else
289
289
  {
@@ -386,10 +386,10 @@ module Aspera
386
386
  Aspera.assert(field.key?('name')){'metadata field must have name'}
387
387
  Aspera.assert(field.key?('values')){'metadata field must have values'}
388
388
  Aspera.assert_type(field['values'], Array){'metadata field values'}
389
- Aspera.assert(!meta_schema.none?{|i|i['name'].eql?(field['name'])}){"unknown metadata field: #{field['name']}"}
389
+ Aspera.assert(!meta_schema.none?{ |i| i['name'].eql?(field['name'])}){"unknown metadata field: #{field['name']}"}
390
390
  end
391
391
  meta_schema.each do |field|
392
- provided = pkg_meta.select{|i|i['name'].eql?(field['name'])}
392
+ provided = pkg_meta.select{ |i| i['name'].eql?(field['name'])}
393
393
  raise "only one field with name #{field['name']} allowed" if provided.count > 1
394
394
  raise "missing mandatory field: #{field['name']}" if field['required'] && provided.empty?
395
395
  end
@@ -519,7 +519,7 @@ module Aspera
519
519
  # Console cookie
520
520
  ################
521
521
  # we are sure that fields are not nil
522
- cookie_elements = [app_info[:app], current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{|e|Base64.strict_encode64(e)}
522
+ cookie_elements = [app_info[:app], current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{ |e| Base64.strict_encode64(e)}
523
523
  cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
524
524
  transfer_spec['cookie'] = cookie_elements.join(':')
525
525
  # Application tags
@@ -88,7 +88,7 @@ module Aspera
88
88
  )
89
89
  # get delegated token to be placed in rest call header and in transfer tags
90
90
  @storage_credentials['token'][TOKEN_FIELD] = delegated_oauth.token
91
- @headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
91
+ headers['X-Aspera-Storage-Credentials'] = JSON.generate(@storage_credentials)
92
92
  end
93
93
  end
94
94
  end
@@ -171,7 +171,7 @@ module Aspera
171
171
  cond_var: ConditionVariable.new
172
172
  }
173
173
  # start read thread after handshake
174
- @ws_read_thread = Thread.new {process_read_thread}
174
+ @ws_read_thread = Thread.new{process_read_thread}
175
175
  @notify_cb&.call(:session_start, session_id: session_id)
176
176
  @notify_cb&.call(:session_size, session_id: session_id, info: total_bytes_to_transfer)
177
177
  sleep(1)
@@ -309,11 +309,15 @@ module Aspera
309
309
  # web socket endpoint: by default use v2 (newer gateways), without base64 encoding
310
310
  # is the latest supported? else revert to old api
311
311
  if !@upload_version.eql?(API_V1)
312
- if !@api_info['endpoints'].any?{|i|i.include?(@upload_version)}
312
+ if !@api_info['endpoints'].any?{ |i| i.include?(@upload_version)}
313
313
  Log.log.warn{"API version #{@upload_version} not supported, reverting to #{API_V1}"}
314
314
  @upload_version = API_V1
315
315
  end
316
316
  end
317
+ @shared_info = nil
318
+ @ws_handshake = nil
319
+ @ws_io = nil
320
+ @ws_read_thread = nil
317
321
  end
318
322
 
319
323
  private
@@ -373,7 +377,7 @@ module Aspera
373
377
  raise "File not found: #{source_path}"
374
378
  end
375
379
  end
376
- transfer_spec['paths'] = files_to_send.map{|i|{'source' => i[:name]}}
380
+ transfer_spec['paths'] = files_to_send.map{ |i| {'source' => i[:name]}}
377
381
  files_to_send.push(total_bytes_to_transfer)
378
382
  return files_to_send
379
383
  end
@@ -27,7 +27,7 @@ module Aspera
27
27
  :SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
28
28
  :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
29
29
 
30
- # node api permissions
30
+ # Node API permissions
31
31
  ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
32
32
  HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
33
33
  HEADER_X_TOTAL_COUNT = 'X-Total-Count'
@@ -38,7 +38,7 @@ module Aspera
38
38
  PATH_SEPARATOR = '/'
39
39
 
40
40
  # register node special token decoder
41
- OAuth::Factory.instance.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
41
+ OAuth::Factory.instance.register_decoder(lambda{ |token| Node.decode_bearer_token(token)})
42
42
 
43
43
  # class instance variable, access with accessors on class
44
44
  @use_standard_ports = true
@@ -81,6 +81,7 @@ module Aspera
81
81
  end
82
82
 
83
83
  # Create an Aspera Node bearer token
84
+ # @param access_key [String] Access key identifier
84
85
  # @param payload [String] JSON payload to be included in the token
85
86
  # @param private_key [OpenSSL::PKey::RSA] Private key to sign the token
86
87
  def bearer_token(access_key:, payload:, private_key:)
@@ -106,6 +107,7 @@ module Aspera
106
107
  ].join("\n")))
107
108
  end
108
109
 
110
+ # Decode an Aspera Node bearer token
109
111
  def decode_bearer_token(token)
110
112
  return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
111
113
  end
@@ -149,7 +151,7 @@ module Aspera
149
151
 
150
152
  # Call node API, possibly adding cache control header, as globally specified
151
153
  def read_with_cache(subpath, query=nil)
152
- headers = {'Accept' => 'application/json'}
154
+ headers = {'Accept' => Rest::MIME_JSON}
153
155
  headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
154
156
  return call(
155
157
  operation: 'GET',
@@ -352,7 +354,7 @@ module Aspera
352
354
  # get the transfer user from info on access key
353
355
  transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
354
356
  # get settings from name.value array to hash key.value
355
- settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
357
+ settings = info['settings']&.each_with_object({}){ |i, h| h[i['name']] = i['value']}
356
358
  # check WSS ports
357
359
  Transfer::Spec::WSS_FIELDS.each do |i|
358
360
  transfer_spec[i] = settings[i] if settings.key?(i)
data/lib/aspera/ascmd.rb CHANGED
@@ -104,7 +104,7 @@ module Aspera
104
104
  # enclose arguments in double quotes, protect backslash and double quotes
105
105
  # ascmd uses space as token separator, and optional quotes ('") or \ to escape
106
106
  args.each do |v|
107
- command.push(%Q{"#{v.gsub(/["\\]/){|s|"\\#{s}"}}"})
107
+ command.push(%Q{"#{v.gsub(/["\\]/){ |s| "\\#{s}"}}"})
108
108
  end
109
109
  command_lines.push(command.join(' '))
110
110
  end
@@ -141,7 +141,7 @@ module Aspera
141
141
  end
142
142
  # raise error as exception
143
143
  raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
144
- result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
144
+ result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{ |i| i[:name]}.sort)
145
145
  return result
146
146
  end
147
147
 
@@ -209,7 +209,7 @@ module Aspera
209
209
  result[field_info[:name]] ||= []
210
210
  result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
211
211
  when :list_tlv_list # field is an array of values in a list of buffers
212
- result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
212
+ result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{ |r| parse(r[:buffer], field_info[:is_a], indent_level)}
213
213
  when :list_tlv_restart # field is an array of values, but a new value is started on index 1
214
214
  fl = result[field_info[:name]] = []
215
215
  parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
@@ -68,8 +68,8 @@ module Aspera
68
68
  # set ascp executable path
69
69
  def ascp_path=(v)
70
70
  Aspera.assert_type(v, String)
71
- Aspera.assert(!v.empty?) {'ascp path cannot be empty: check your config file'}
72
- Aspera.assert(File.exist?(v)) {"No such file: [#{v}]"}
71
+ Aspera.assert(!v.empty?){'ascp path cannot be empty: check your config file'}
72
+ Aspera.assert(File.exist?(v)){"No such file: [#{v}]"}
73
73
  @path_to_ascp = v
74
74
  end
75
75
 
@@ -89,7 +89,7 @@ module Aspera
89
89
  pl = installed_products.first
90
90
  raise "no Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf ascp install" if pl.nil?
91
91
  else
92
- pl = installed_products.find{|i|i[:name].eql?(product_name)}
92
+ pl = installed_products.find{ |i| i[:name].eql?(product_name)}
93
93
  raise "no such product installed: #{product_name}" if pl.nil?
94
94
  end
95
95
  self.ascp_path = pl[:ascp_path]
@@ -130,22 +130,21 @@ module Aspera
130
130
  when :ssh_private_dsa, :ssh_private_rsa
131
131
  # assume last 3 letters are type
132
132
  type = k.to_s[-3..-1].to_sym
133
- file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") {DataRepository.instance.item(type)}
133
+ file = check_or_create_sdk_file("aspera_bypass_#{type}.pem"){DataRepository.instance.item(type)}
134
134
  when :aspera_license
135
- file = check_or_create_sdk_file('aspera-license') {DataRepository.instance.item(:license)}
135
+ file = check_or_create_sdk_file('aspera-license'){DataRepository.instance.item(:license)}
136
136
  when :aspera_conf
137
- file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
137
+ file = check_or_create_sdk_file('aspera.conf'){DEFAULT_ASPERA_CONF}
138
138
  when :fallback_certificate, :fallback_private_key
139
139
  file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
140
140
  file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
141
141
  if !File.exist?(file_key) || !File.exist?(file_cert)
142
142
  require 'openssl'
143
143
  # create new self signed certificate for http fallback
144
- cert = OpenSSL::X509::Certificate.new
145
144
  private_key = OpenSSL::PKey::RSA.new(4096)
146
- WebServerSimple.fill_self_signed_cert(cert, private_key)
147
- check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) {private_key.to_pem}
148
- check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
145
+ cert = WebServerSimple.self_signed_cert(private_key)
146
+ check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true){private_key.to_pem}
147
+ check_or_create_sdk_file('aspera_fallback_cert.pem', force: true){cert.to_pem}
149
148
  end
150
149
  file = k.eql?(:fallback_certificate) ? file_cert : file_key
151
150
  else Aspera.error_unexpected_value(k)
@@ -166,7 +165,7 @@ module Aspera
166
165
  Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
167
166
  return case types
168
167
  when :dsa_rsa, :rsa
169
- types.to_s.split('_').map{|i|Installation.instance.path("ssh_private_#{i}".to_sym)}
168
+ types.to_s.split('_').map{ |i| Installation.instance.path("ssh_private_#{i}".to_sym)}
170
169
  when :per_client
171
170
  raise 'Not yet implemented'
172
171
  end
@@ -247,10 +246,10 @@ module Aspera
247
246
  def sdk_url_for_platform(platform: nil, version: nil)
248
247
  locations = sdk_locations
249
248
  platform = Environment.architecture if platform.nil?
250
- locations = locations.select{|l|l['platform'].eql?(platform)}
249
+ locations = locations.select{ |l| l['platform'].eql?(platform)}
251
250
  raise "No SDK for platform: #{platform}" if locations.empty?
252
- version = locations.max_by { |entry| Gem::Version.new(entry['version']) }['version'] if version.nil?
253
- info = locations.select{|entry| entry['version'].eql?(version)}
251
+ version = locations.max_by{ |entry| Gem::Version.new(entry['version'])}['version'] if version.nil?
252
+ info = locations.select{ |entry| entry['version'].eql?(version)}
254
253
  raise "No such version: #{version} for #{platform}" if info.empty?
255
254
  return info.first['url']
256
255
  end
@@ -300,7 +299,7 @@ module Aspera
300
299
  if subfolder_lambda.nil?
301
300
  # default files to extract directly to main folder if in selected source folders
302
301
  subfolder_lambda = ->(name) do
303
- Products::Transferd::RUNTIME_FOLDERS.any?{|i|name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
302
+ Products::Transferd::RUNTIME_FOLDERS.any?{ |i| name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
304
303
  end
305
304
  end
306
305
  # rename old install
@@ -322,7 +321,7 @@ module Aspera
322
321
  end
323
322
  FileUtils.mkdir_p(dest_folder)
324
323
  if link_target.nil?
325
- File.open(dest_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
324
+ File.open(dest_file, 'wb'){ |output_stream| IO.copy_stream(entry_stream, output_stream)}
326
325
  else
327
326
  File.symlink(link_target, dest_file)
328
327
  end
@@ -227,7 +227,7 @@ module Aspera
227
227
  # TODO: translate enhanced to capitalized ?
228
228
  data
229
229
  .keys
230
- .map{|k|"#{k.capitalize}: #{data[k]}"}
230
+ .map{ |k| "#{k.capitalize}: #{data[k]}"}
231
231
  .unshift(MGT_HEADER)
232
232
  .push('', '')
233
233
  .join("\n")
data/lib/aspera/assert.rb CHANGED
@@ -14,7 +14,7 @@ module Aspera
14
14
  message = 'assertion failed'
15
15
  info = yield if block_given?
16
16
  message = "#{message}: #{info}" if info
17
- message = "#{message}: #{caller.find{|call|!call.start_with?(__FILE__)}}"
17
+ message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
18
18
  raise exception_class, message
19
19
  end
20
20
 
@@ -42,9 +42,18 @@ module Aspera
42
42
  raise InternalError, "unreachable line reached: #{caller(2..2).first}"
43
43
  end
44
44
 
45
- # the value is not one of the expected values
45
+ # The value is not one of the expected values
46
+ # @param value the wrong value
47
+ # @param exception_class exception to raise
48
+ # @param block additional description in front
46
49
  def error_unexpected_value(value, exception_class: InternalError)
47
50
  raise exception_class, "#{block_given? ? "#{yield}: " : nil}unexpected value: #{value.inspect}"
48
51
  end
52
+
53
+ def require_method!(name)
54
+ define_method(name) do |*_args|
55
+ raise NotImplementedError, "#{self.class} must implement the #{name} method"
56
+ end
57
+ end
49
58
  end
50
59
  end
@@ -4,11 +4,11 @@ module Aspera
4
4
  module Cli
5
5
  # CLI base exception
6
6
  class Error < StandardError; end
7
-
8
7
  # raised when an unexpected argument is provided
9
8
  class BadArgument < Error; end
9
+ class NoSuchElement < Error; end
10
10
 
11
- class NoSuchIdentifier < Error
11
+ class BadIdentifier < Error
12
12
  def initialize(res_type, res_id)
13
13
  super("#{res_type} with identifier #{res_id} not found")
14
14
  end
@@ -56,25 +56,25 @@ module Aspera
56
56
  # base handlers
57
57
  # other handlers can be set using set_handler, e.g. `preset` is reader in config plugin
58
58
  @handlers = {
59
- val: lambda{|v|v},
60
- base64: lambda{|v|Base64.decode64(v)},
61
- csvt: lambda{|v|ExtendedValue.decode_csvt(v)},
62
- env: lambda{|v|ENV.fetch(v, nil)},
63
- file: lambda{|v|File.read(File.expand_path(v))},
64
- uri: lambda{|v|UriReader.read(v)},
65
- json: lambda{|v|JSON.parse(v)},
66
- lines: lambda{|v|v.split("\n")},
67
- list: lambda{|v|v[1..-1].split(v[0])},
68
- none: lambda{|v|ExtendedValue.assert_no_value(v, :none); nil}, # rubocop:disable Style/Semicolon
69
- path: lambda{|v|File.expand_path(v)},
70
- re: lambda{|v|Regexp.new(v, Regexp::MULTILINE)},
71
- ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
72
- secret: lambda{|v|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
73
- stdin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
74
- stdbin: lambda{|v|ExtendedValue.assert_no_value(v, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
75
- yaml: lambda{|v|YAML.load(v)},
76
- zlib: lambda{|v|Zlib::Inflate.inflate(v)},
77
- extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
59
+ val: lambda{ |v| v},
60
+ base64: lambda{ |v| Base64.decode64(v)},
61
+ csvt: lambda{ |v| ExtendedValue.decode_csvt(v)},
62
+ env: lambda{ |v| ENV.fetch(v, nil)},
63
+ file: lambda{ |v| File.read(File.expand_path(v))},
64
+ uri: lambda{ |v| UriReader.read(v)},
65
+ json: lambda{ |v| JSON_parse(v)},
66
+ lines: lambda{ |v| v.split("\n")},
67
+ list: lambda{ |v| v[1..-1].split(v[0])},
68
+ none: lambda{ |v| ExtendedValue.assert_no_value(v, :none); nil}, # rubocop:disable Style/Semicolon
69
+ path: lambda{ |v| File.expand_path(v)},
70
+ re: lambda{ |v| Regexp.new(v, Regexp::MULTILINE)},
71
+ ruby: lambda{ |v| Environment.secure_eval(v, __FILE__, __LINE__)},
72
+ secret: lambda{ |v| prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
73
+ stdin: lambda{ |v| ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
74
+ stdbin: lambda{ |v| ExtendedValue.assert_no_value(v, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
75
+ yaml: lambda{ |v| YAML.load(v)},
76
+ zlib: lambda{ |v| Zlib::Inflate.inflate(v)},
77
+ extend: lambda{ |v| ExtendedValue.instance.evaluate_all(v)}
78
78
  }
79
79
  @default_decoder = nil
80
80
  end
@@ -84,6 +84,25 @@ module Aspera
84
84
  "#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
85
85
  end
86
86
 
87
+ # JSON Parser, with more information on error location
88
+ def JSON_parse(v)
89
+ JSON.parse(v)
90
+ rescue JSON::ParserError => e
91
+ m = /at line (\d+) column (\d+)/.match(e.message)
92
+ raise if m.nil?
93
+ line = m[1].to_i - 1
94
+ column = m[2].to_i - 1
95
+ lines = v.lines
96
+ raise if line >= lines.size
97
+ error_line = lines[line].chomp
98
+ context_col_beg = [column - 10, 0].max
99
+ context_col_end = [column + 10, error_line.length].min
100
+ context = error_line[context_col_beg...context_col_end]
101
+ cursor_pos = column - context_col_beg
102
+ pointer = ' ' * cursor_pos + '^'.blink
103
+ raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
104
+ end
105
+
87
106
  public
88
107
 
89
108
  def default_decoder=(value)