aspera-cli 4.18.0 → 4.19.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 (60) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +23 -0
  4. data/CONTRIBUTING.md +5 -12
  5. data/README.md +152 -84
  6. data/examples/build_exec +85 -0
  7. data/examples/build_package.sh +28 -0
  8. data/lib/aspera/agent/alpha.rb +4 -4
  9. data/lib/aspera/agent/base.rb +2 -0
  10. data/lib/aspera/agent/connect.rb +3 -4
  11. data/lib/aspera/agent/direct.rb +108 -104
  12. data/lib/aspera/agent/httpgw.rb +1 -1
  13. data/lib/aspera/api/aoc.rb +2 -2
  14. data/lib/aspera/api/httpgw.rb +95 -57
  15. data/lib/aspera/api/node.rb +110 -77
  16. data/lib/aspera/ascp/installation.rb +47 -32
  17. data/lib/aspera/ascp/management.rb +4 -1
  18. data/lib/aspera/ascp/products.rb +2 -8
  19. data/lib/aspera/cli/extended_value.rb +27 -14
  20. data/lib/aspera/cli/formatter.rb +35 -28
  21. data/lib/aspera/cli/main.rb +11 -11
  22. data/lib/aspera/cli/manager.rb +109 -94
  23. data/lib/aspera/cli/plugin.rb +4 -7
  24. data/lib/aspera/cli/plugin_factory.rb +10 -1
  25. data/lib/aspera/cli/plugins/aoc.rb +15 -14
  26. data/lib/aspera/cli/plugins/config.rb +35 -29
  27. data/lib/aspera/cli/plugins/faspex.rb +5 -4
  28. data/lib/aspera/cli/plugins/faspex5.rb +16 -13
  29. data/lib/aspera/cli/plugins/node.rb +50 -41
  30. data/lib/aspera/cli/plugins/orchestrator.rb +3 -2
  31. data/lib/aspera/cli/plugins/preview.rb +1 -1
  32. data/lib/aspera/cli/plugins/server.rb +2 -2
  33. data/lib/aspera/cli/plugins/shares.rb +11 -7
  34. data/lib/aspera/cli/special_values.rb +13 -0
  35. data/lib/aspera/cli/sync_actions.rb +73 -32
  36. data/lib/aspera/cli/transfer_agent.rb +3 -2
  37. data/lib/aspera/cli/transfer_progress.rb +1 -1
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/environment.rb +100 -7
  40. data/lib/aspera/faspex_gw.rb +1 -1
  41. data/lib/aspera/keychain/encrypted_hash.rb +2 -0
  42. data/lib/aspera/log.rb +1 -0
  43. data/lib/aspera/node_simulator.rb +1 -1
  44. data/lib/aspera/oauth/jwt.rb +1 -1
  45. data/lib/aspera/oauth/url_json.rb +2 -0
  46. data/lib/aspera/oauth/web.rb +7 -6
  47. data/lib/aspera/rest.rb +46 -15
  48. data/lib/aspera/secret_hider.rb +3 -2
  49. data/lib/aspera/ssh.rb +1 -1
  50. data/lib/aspera/transfer/faux_file.rb +7 -5
  51. data/lib/aspera/transfer/parameters.rb +27 -19
  52. data/lib/aspera/transfer/spec.rb +8 -10
  53. data/lib/aspera/transfer/sync.rb +52 -47
  54. data/lib/aspera/web_auth.rb +0 -1
  55. data/lib/aspera/web_server_simple.rb +24 -13
  56. data.tar.gz.sig +0 -0
  57. metadata +5 -4
  58. metadata.gz.sig +0 -0
  59. data/examples/rubyc +0 -24
  60. data/lib/aspera/open_application.rb +0 -69
@@ -4,7 +4,7 @@ require 'aspera/agent/base'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/log'
6
6
  require 'aspera/json_rpc'
7
- require 'aspera/open_application'
7
+ require 'aspera/environment'
8
8
  require 'securerandom'
9
9
 
10
10
  module Aspera
@@ -22,7 +22,7 @@ module Aspera
22
22
  @application_id = SecureRandom.uuid
23
23
  @xfer_id = nil
24
24
  super(**base_options)
25
- raise 'Using client requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
25
+ raise 'Using client requires a graphical environment' if !Environment.default_gui_mode.eql?(:graphical)
26
26
  method_index = 0
27
27
  begin
28
28
  # curl 'http://127.0.0.1:33024/' -X POST -H 'content-type: application/json' --data-raw '{"jsonrpc":"2.0","params":[],"id":999999,"method":"rpc.discover"}'
@@ -36,8 +36,8 @@ module Aspera
36
36
  method_index += 1
37
37
  raise StandardError, "Unable to start #{APP_NAME} #{method_index} times" if start_url.nil?
38
38
  Log.log.warn{"#{APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
39
- if !OpenApplication.uri_graphical(start_url)
40
- OpenApplication.uri_graphical('https://www.ibm.com/aspera/connect/')
39
+ if !Environment.open_uri_graphical(start_url)
40
+ Environment.open_uri_graphical('https://www.ibm.com/aspera/connect/')
41
41
  raise StandardError, "#{APP_NAME} is not installed"
42
42
  end
43
43
  sleep(SLEEP_SEC_BETWEEN_RETRY)
@@ -38,6 +38,8 @@ module Aspera
38
38
  end.map{|file|file[0..(-1 - RUBY_EXT.length)].to_sym}
39
39
  end
40
40
  end
41
+
42
+ # Wait for all sessions to terminate and return the status of each session
41
43
  def wait_for_completion
42
44
  # list of: :success or "error message string"
43
45
  statuses = wait_for_transfers_completion
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'aspera/agent/base'
4
4
  require 'aspera/rest'
5
- require 'aspera/open_application'
6
5
  require 'securerandom'
7
6
 
8
7
  module Aspera
@@ -18,7 +17,7 @@ module Aspera
18
17
  @connect_settings = {
19
18
  'app_id' => SecureRandom.uuid
20
19
  }
21
- raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
20
+ raise 'Using connect requires a graphical environment' if !Environment.default_gui_mode.eql?(:graphical)
22
21
  method_index = 0
23
22
  begin
24
23
  connect_url = Ascp::Products.connect_uri
@@ -34,8 +33,8 @@ module Aspera
34
33
  method_index += 1
35
34
  raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
36
35
  Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
37
- if !OpenApplication.uri_graphical(start_url)
38
- OpenApplication.uri_graphical('https://www.ibm.com/aspera/connect/')
36
+ if !Environment.open_uri_graphical(start_url)
37
+ Environment.open_uri_graphical('https://www.ibm.com/aspera/connect/')
39
38
  raise StandardError, 'Connect is not installed'
40
39
  end
41
40
  sleep(SLEEP_SEC_BETWEEN_RETRY)
@@ -19,9 +19,10 @@ module Aspera
19
19
  # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
20
20
  class Direct < Base
21
21
  LISTEN_LOCAL_ADDRESS = '127.0.0.1'
22
- ANY_AVAILABLE_PORT = 0 # 0 means any available port
22
+ # 0 means: select an available port
23
+ SELECT_AVAILABLE_PORT = 0
23
24
  # spellchecker: enable
24
- private_constant :LISTEN_LOCAL_ADDRESS, :ANY_AVAILABLE_PORT
25
+ private_constant :LISTEN_LOCAL_ADDRESS, :SELECT_AVAILABLE_PORT
25
26
 
26
27
  # method of Base
27
28
  # start ascp transfer(s) (non blocking), single or multi-session
@@ -93,12 +94,12 @@ module Aspera
93
94
  # do deep copy (each thread has its own copy because it is modified here below and in thread)
94
95
  this_session = session.clone
95
96
  this_session[:ts] = this_session[:ts].clone
96
- this_session[:env_args] = this_session[:env_args].clone
97
- this_session[:env_args][:args] = this_session[:env_args][:args].clone
97
+ env_args = this_session[:env_args] = this_session[:env_args].clone
98
+ args = env_args[:args] = env_args[:args].clone
98
99
  # set multi session part
99
- this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_info[:count]}")
100
+ args.unshift("-C#{i}:#{multi_session_info[:count]}")
100
101
  # option: increment (default as per ascp manual) or not (cluster on other side ?)
101
- this_session[:env_args][:args].unshift('-O', (multi_session_info[:udp_base] + i - 1).to_s) if @multi_incr_udp
102
+ args.unshift('-O', (multi_session_info[:udp_base] + i - 1).to_s) if @multi_incr_udp
102
103
  # finally start the thread
103
104
  this_session[:thread] = Thread.new(this_session) {|session_info|transfer_thread_entry(session_info)}
104
105
  @sessions.push(this_session)
@@ -129,81 +130,46 @@ module Aspera
129
130
  Log.log.debug('fasp local shutdown')
130
131
  end
131
132
 
132
- # @param event management port event
133
- def process_progress(event)
134
- session_id = event['SessionId']
135
- case event['Type']
136
- when 'INIT'
137
- @pre_calc_sent = false
138
- @pre_calc_last_size = nil
139
- notify_progress(session_id: session_id, type: :session_start)
140
- when 'NOTIFICATION' # sent from remote
141
- if event.key?('PreTransferBytes')
142
- @pre_calc_sent = true
143
- notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
144
- end
145
- when 'STATS' # during transfer
146
- @pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
147
- notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
148
- when 'DONE', 'ERROR' # end of session
149
- total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
150
- if !@pre_calc_sent && !total_size.zero?
151
- notify_progress(session_id: session_id, type: :session_size, info: total_size)
152
- end
153
- if @pre_calc_last_size != total_size
154
- notify_progress(session_id: session_id, type: :transfer, info: total_size)
155
- end
156
- notify_progress(session_id: session_id, type: :end)
157
- # cspell:disable
158
- when 'SESSION'
159
- when 'ARGSTOP'
160
- when 'FILEERROR'
161
- when 'STOP'
162
- # cspell:enable
163
- # stop event when one file is completed
164
- else
165
- Log.log.debug{"unknown event type #{event['Type']}"}
166
- end
133
+ # @return [Array] list of sessions for a job
134
+ def sessions_by_job(job_id)
135
+ @sessions.select{|session_info| session_info[:job_id].eql?(job_id)}
167
136
  end
168
137
 
169
- # This is the low level method to start the "ascp" process
170
- # currently, relies on command line arguments
171
- # start ascp with management port.
138
+ # This is the low level method to start the transfer process
139
+ # Start process with management port.
140
+ # returns
172
141
  # raises FaspError on error
173
- # if there is a thread info: set and broadcast session id
174
- # runs in separate thread
175
- # @param env_args a hash containing :args :env :ascp_version
176
- # @param session this session information
177
- # could be private method
178
- def start_transfer_with_args_env(env_args, session)
179
- Aspera.assert_type(env_args, Hash)
142
+ # @param session this session information, keys :io and :token_regenerator
143
+ # @param env [Hash] environment variables
144
+ # @param name [Symbol] name of executable: :ascp, :ascp4 or :async
145
+ # @param args [Array] command line arguments
146
+ # @return [nil] when process has exited
147
+ def start_and_monitor_process(
148
+ session:,
149
+ env:,
150
+ name:,
151
+ args:
152
+ )
180
153
  Aspera.assert_type(session, Hash)
181
- Log.log.debug{"env_args=#{env_args.inspect}"}
182
154
  notify_progress(session_id: nil, type: :pre_start, info: 'starting')
183
155
  begin
184
- ascp_pid = nil
156
+ command_pid = nil
185
157
  # we use Socket directly, instead of TCPServer, as it gives access to lower level options
186
- socket_class = RUBY_ENGINE.eql?('jruby') ? ServerSocket : Socket
158
+ socket_class = defined?(JRUBY_VERSION) ? ServerSocket : Socket
187
159
  mgt_server_socket = socket_class.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
188
- # open any available (0) local TCP port for use as ascp management port
189
- mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS, ANY_AVAILABLE_PORT))
160
+ # open any available (0) local TCP port for use as management port
161
+ mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS, SELECT_AVAILABLE_PORT))
190
162
  # build arguments and add mgt port
191
- ascp_arguments = ['-M', mgt_server_socket.local_address.ip_port.to_s].concat(env_args[:args])
192
- # get location of ascp executable
193
- ascp_path = Ascp::Installation.instance.path(env_args[:ascp_version])
194
- # display ascp command line
195
- Log.log.debug do
196
- [
197
- 'execute:'.red,
198
- env_args[:env].map{|k, v| "#{k}=#{Shellwords.shellescape(v)}"},
199
- Shellwords.shellescape(ascp_path),
200
- ascp_arguments.map{|a|Shellwords.shellescape(a)}
201
- ].flatten.join(' ')
163
+ command_arguments = if name.eql?(:async)
164
+ ["--exclusive-mgmt-port=#{mgt_server_socket.local_address.ip_port}"]
165
+ else
166
+ ['-M', mgt_server_socket.local_address.ip_port.to_s]
202
167
  end
203
- # start ascp in separate process
204
- ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments, close_others: true)
205
- Log.log.debug{"spawned ascp pid #{ascp_pid}"}
206
- notify_progress(session_id: nil, type: :pre_start, info: 'waiting for ascp')
168
+ command_arguments.concat(args)
169
+ # get location of command executable (ascp, async)
170
+ command_path = Ascp::Installation.instance.path(name)
171
+ command_pid = Environment.secure_spawn(env: env, exec: command_path, args: command_arguments)
172
+ notify_progress(session_id: nil, type: :pre_start, info: "waiting for #{name} to start")
207
173
  mgt_server_socket.listen(1)
208
174
  # TODO: timeout does not work when Process.spawn is used... until process exits, then it works
209
175
  Log.log.debug{"before select, timeout: #{@spawn_timeout_sec}"}
@@ -213,15 +179,15 @@ module Aspera
213
179
  # There is a connection to accept
214
180
  client_socket, _client_addrinfo = mgt_server_socket.accept
215
181
  Log.log.debug('after accept')
216
- ascp_mgt_io = client_socket.to_io
182
+ management_port_io = client_socket.to_io
217
183
  # management messages include file names which may be utf8
218
184
  # by default socket is US-ASCII
219
185
  # TODO: use same value as Encoding.default_external
220
- ascp_mgt_io.set_encoding(Encoding::UTF_8)
221
- session[:io] = ascp_mgt_io
186
+ management_port_io.set_encoding(Encoding::UTF_8)
187
+ session[:io] = management_port_io
222
188
  processor = Ascp::Management.new
223
189
  # read management port, until socket is closed (gets returns nil)
224
- while (line = ascp_mgt_io.gets)
190
+ while (line = management_port_io.gets)
225
191
  event = processor.process_line(line.chomp)
226
192
  next unless event
227
193
  # event is ready
@@ -241,7 +207,7 @@ module Aspera
241
207
  # regenerate token here, expired, or error on it
242
208
  # Note: in multi-session, each session will have a different one.
243
209
  Log.log.warn('Regenerating token for transfer')
244
- env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
210
+ env['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
245
211
  end
246
212
  raise Transfer::Error.new(last_event['Description'], last_event['Code'].to_i)
247
213
  when 'DONE'
@@ -257,16 +223,16 @@ module Aspera
257
223
  raise Transfer::Error, 'transfer interrupted by user'
258
224
  ensure
259
225
  mgt_server_socket.close
260
- # if ascp was successfully started, check its status
261
- unless ascp_pid.nil?
226
+ # if command was successfully started, check its status
227
+ unless command_pid.nil?
262
228
  # "wait" for process to avoid zombie
263
- Process.wait(ascp_pid)
229
+ Process.wait(command_pid)
264
230
  status = $CHILD_STATUS
265
- ascp_pid = nil
231
+ # command_pid = nil
266
232
  session.delete(:io)
267
- # status is nil if an exception occurred before starting ascp
233
+ # status is nil if an exception occurred before starting command
268
234
  if !status&.success?
269
- message = status.nil? ? 'ascp not started' : "ascp failed (#{status})"
235
+ message = status.nil? ? "#{name} not started" : "#{name} failed (#{status})"
270
236
  # raise error only if there was not already an exception (ERROR_INFO)
271
237
  raise Transfer::Error, message unless $ERROR_INFO
272
238
  # else display this message also, as main exception is already here
@@ -274,11 +240,47 @@ module Aspera
274
240
  end
275
241
  end
276
242
  end
243
+ nil
277
244
  end
278
245
 
279
- # @return [Array] list of sessions for a job
280
- def sessions_by_job(job_id)
281
- @sessions.select{|session_info| session_info[:job_id].eql?(job_id)}
246
+ private
247
+
248
+ # notify progress to callback
249
+ # @param event management port event
250
+ def process_progress(event)
251
+ session_id = event['SessionId']
252
+ case event['Type']
253
+ when 'INIT'
254
+ @pre_calc_sent = false
255
+ @pre_calc_last_size = nil
256
+ notify_progress(session_id: session_id, type: :session_start)
257
+ when 'NOTIFICATION' # sent from remote
258
+ if event.key?('PreTransferBytes')
259
+ @pre_calc_sent = true
260
+ notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
261
+ end
262
+ when 'STATS' # during transfer
263
+ @pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
264
+ notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
265
+ when 'DONE', 'ERROR' # end of session
266
+ total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
267
+ if !@pre_calc_sent && !total_size.zero?
268
+ notify_progress(session_id: session_id, type: :session_size, info: total_size)
269
+ end
270
+ if @pre_calc_last_size != total_size
271
+ notify_progress(session_id: session_id, type: :transfer, info: total_size)
272
+ end
273
+ notify_progress(session_id: session_id, type: :end)
274
+ # cspell:disable
275
+ when 'SESSION'
276
+ when 'ARGSTOP'
277
+ when 'FILEERROR'
278
+ when 'STOP'
279
+ # cspell:enable
280
+ # stop event when one file is completed
281
+ else
282
+ Log.log.debug{"unknown event type #{event['Type']}"}
283
+ end
282
284
  end
283
285
 
284
286
  # @return [Hash] session information
@@ -289,7 +291,7 @@ module Aspera
289
291
  return matches.first
290
292
  end
291
293
 
292
- # send command of management port to ascp session (used in `asession)
294
+ # send command to management port of command (used in `asession)
293
295
  # @param job_id identified transfer process
294
296
  # @param session_index index of session (for multi session)
295
297
  # @param data command on mgt port, examples:
@@ -309,47 +311,49 @@ module Aspera
309
311
  end
310
312
  attr_reader :sessions
311
313
 
312
- private
313
-
314
314
  # options for initialize (same as values in option transfer_info)
315
- # @param wss [Boolean] true: if both SSH and wss in ts: prefer wss
316
315
  # @param ascp_args [Array] additional arguments to ascp
317
- # @param spawn_timeout_sec [Integer] timeout for ascp spawn
318
- # @param spawn_delay_sec [Integer] optional delay to start between sessions
319
- # @param multi_incr_udp [Boolean] true: increment udp port for each session
320
- # @param trusted_certs [Array] list of files with trusted certificates (stores)
321
- # @param resume [Hash] resume policy
316
+ # @param wss [Boolean] true: if both SSH and wss in ts: prefer wss
322
317
  # @param quiet [Boolean] by default no native ascp progress bar
318
+ # @param trusted_certs [Array,NilClass] list of files with trusted certificates (stores)
319
+ # @param client_ssh_key [String] client ssh key option (from CLIENT_SSH_KEY_OPTIONS)
323
320
  # @param check_ignore_cb [Proc] callback with host,port
321
+ # @param spawn_timeout_sec [Integer] timeout for ascp spawn
322
+ # @param spawn_delay_sec [Integer] optional delay to start between sessions
323
+ # @param multi_incr_udp [Boolean,NilClass] true: increment udp port for each session
324
+ # @param resume [Hash,NilClass] resume policy
324
325
  # @param management_cb [Proc] callback for management events
325
326
  # @param base_options [Hash] other options for base class
326
327
  def initialize(
328
+ ascp_args: nil,
327
329
  wss: true,
328
- ascp_args: [],
329
- spawn_timeout_sec: 2,
330
- spawn_delay_sec: 2,
331
- multi_incr_udp: true,
332
- trusted_certs: [],
333
- resume: {},
334
330
  quiet: true,
331
+ trusted_certs: nil,
332
+ client_ssh_key: nil,
335
333
  check_ignore_cb: nil,
334
+ spawn_timeout_sec: 2,
335
+ spawn_delay_sec: 2,
336
+ multi_incr_udp: nil,
337
+ resume: nil,
336
338
  management_cb: nil,
337
339
  **base_options
338
340
  )
339
341
  super(**base_options)
342
+ # special transfer parameters
340
343
  @tr_opts = {
341
344
  ascp_args: ascp_args,
342
345
  wss: wss,
343
346
  quiet: quiet,
344
347
  trusted_certs: trusted_certs,
348
+ client_ssh_key: client_ssh_key,
345
349
  check_ignore_cb: check_ignore_cb
346
350
  }
347
351
  @spawn_timeout_sec = spawn_timeout_sec
348
352
  @spawn_delay_sec = spawn_delay_sec
349
- @multi_incr_udp = multi_incr_udp
350
- @resume = resume
353
+ # default is true on windows, false on other platforms
354
+ @multi_incr_udp = multi_incr_udp.nil? ? Environment.os.eql?(Environment::OS_WINDOWS) : multi_incr_udp
351
355
  @management_cb = management_cb
352
- @resume_policy = Resumer.new(@resume.symbolize_keys)
356
+ @resume_policy = Resumer.new(resume.nil? ? {} : resume.symbolize_keys)
353
357
  # all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
354
358
  @sessions = []
355
359
  # mutex protects global data accessed by threads
@@ -365,7 +369,7 @@ module Aspera
365
369
  Log.log.debug{"ENTER (#{Thread.current[:name]})"}
366
370
  # start transfer with selected resumer policy
367
371
  @resume_policy.execute_with_resume do
368
- start_transfer_with_args_env(session[:env_args], session)
372
+ start_and_monitor_process(session: session, **session[:env_args])
369
373
  end
370
374
  Log.log.debug('transfer ok'.bg_green)
371
375
  rescue StandardError => e
@@ -50,7 +50,7 @@ module Aspera
50
50
  super(**base_options)
51
51
  @gw_api = Api::Httpgw.new(
52
52
  # remove /v1 from end of user-provided GW url: we need the base url only
53
- url: url.gsub(%r{/#{Api::Httpgw::API_V1}/*$}o, ''),
53
+ url: url,
54
54
  api_version: api_version,
55
55
  upload_chunk_size: upload_chunk_size,
56
56
  synchronous: synchronous,
@@ -439,7 +439,7 @@ module Aspera
439
439
  # create a package
440
440
  # @param package_data [Hash] package creation (with extensions...)
441
441
  # @param validate_meta [TrueClass,FalseClass] true to validate parameters locally
442
- # @param new_user_option [Hash] options if an unknown user is specified
442
+ # @param new_user_option [Hash,NilClass] options if an unknown user is specified
443
443
  # @return transfer spec, node api and package information
444
444
  def create_package_simple(package_data, validate_meta, new_user_option)
445
445
  update_package_metadata_for_api(package_data)
@@ -476,7 +476,7 @@ module Aspera
476
476
  # callback in Node (transfer_spec_gen4)
477
477
  def add_ts_tags(transfer_spec:, app_info:)
478
478
  # translate transfer direction to upload/download
479
- transfer_type = Transfer::Spec.action(transfer_spec)
479
+ transfer_type = Transfer::Spec.direction_to_transfer_type(transfer_spec['direction'])
480
480
  # Analytics tags
481
481
  ################
482
482
  transfer_spec.deep_merge!({