aspera-cli 4.1.0 → 4.3.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +455 -229
  3. data/docs/Makefile +4 -4
  4. data/docs/README.erb.md +457 -126
  5. data/docs/test_env.conf +19 -2
  6. data/examples/aoc.rb +14 -3
  7. data/examples/faspex4.rb +89 -0
  8. data/lib/aspera/aoc.rb +38 -40
  9. data/lib/aspera/cli/main.rb +65 -33
  10. data/lib/aspera/cli/plugins/aoc.rb +54 -65
  11. data/lib/aspera/cli/plugins/ats.rb +2 -2
  12. data/lib/aspera/cli/plugins/config.rb +158 -137
  13. data/lib/aspera/cli/plugins/faspex.rb +111 -64
  14. data/lib/aspera/cli/plugins/faspex5.rb +35 -48
  15. data/lib/aspera/cli/plugins/node.rb +3 -2
  16. data/lib/aspera/cli/plugins/preview.rb +88 -55
  17. data/lib/aspera/cli/transfer_agent.rb +98 -62
  18. data/lib/aspera/cli/version.rb +1 -1
  19. data/lib/aspera/command_line_builder.rb +48 -31
  20. data/lib/aspera/cos_node.rb +34 -28
  21. data/lib/aspera/environment.rb +2 -2
  22. data/lib/aspera/fasp/aoc.rb +1 -1
  23. data/lib/aspera/fasp/installation.rb +68 -45
  24. data/lib/aspera/fasp/local.rb +89 -45
  25. data/lib/aspera/fasp/manager.rb +3 -0
  26. data/lib/aspera/fasp/node.rb +23 -1
  27. data/lib/aspera/fasp/parameters.rb +57 -86
  28. data/lib/aspera/fasp/parameters.yaml +531 -0
  29. data/lib/aspera/fasp/resume_policy.rb +13 -12
  30. data/lib/aspera/fasp/uri.rb +1 -1
  31. data/lib/aspera/id_generator.rb +22 -0
  32. data/lib/aspera/node.rb +14 -3
  33. data/lib/aspera/oauth.rb +135 -129
  34. data/lib/aspera/persistency_action_once.rb +11 -7
  35. data/lib/aspera/persistency_folder.rb +6 -26
  36. data/lib/aspera/rest.rb +3 -12
  37. data/lib/aspera/secrets.rb +20 -0
  38. data/lib/aspera/sync.rb +40 -35
  39. data/lib/aspera/timer_limiter.rb +22 -0
  40. data/lib/aspera/web_auth.rb +105 -0
  41. metadata +22 -3
  42. data/docs/transfer_spec.html +0 -99
@@ -21,16 +21,25 @@ module Aspera
21
21
  ACCESS_KEY_TRANSFER_USER='xfer'
22
22
  # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
23
23
  class Local < Manager
24
- ASCP_SPAWN_TIMEOUT_SEC = 3
25
- private_constant :ASCP_SPAWN_TIMEOUT_SEC
26
- # set to false to keep ascp progress bar display (basically: removes ascp's option -q)
24
+ # options for initialize (same as values in option transfer_info)
25
+ DEFAULT_OPTIONS = {
26
+ :spawn_timeout_sec => 3,
27
+ :spawn_delay_sec => 2,
28
+ :wss => false,
29
+ :multi_incr_udp => true,
30
+ :resume => {}
31
+ }
32
+ DEFAULT_UDP_PORT=33001
33
+ private_constant :DEFAULT_OPTIONS
34
+ # set to false to keep ascp progress bar display ("true" adds ascp's option -q)
27
35
  attr_accessor :quiet
36
+
28
37
  # start ascp transfer (non blocking), single or multi-session
29
38
  # job information added to @jobs
30
39
  # @param transfer_spec [Hash] aspera transfer specification
31
40
  # @param options [Hash] :resumer, :regenerate_token
32
41
  def start_transfer(transfer_spec,options={})
33
- raise "option: must be hash (or nil)" unless options.is_a?(Hash)
42
+ raise 'option: must be hash (or nil)' unless options.is_a?(Hash)
34
43
  job_options = options.clone
35
44
  job_options[:resumer] ||= @resume_policy
36
45
  job_options[:job_id] ||= SecureRandom.uuid
@@ -56,23 +65,34 @@ module Aspera
56
65
  transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
57
66
  end
58
67
 
59
- # TODO: check if changing fasp(UDP) port is really necessary, not clear from doc
60
- # compute this before using transfer spec, even if the var is not used in single session
61
- multi_session_udp_port_base=33001
62
- multi_session_number=nil
68
+ # Compute this before using transfer spec because it potentially modifies the transfer spec
69
+ # (even if the var is not used in single session)
70
+ multi_session_info=nil
63
71
  if transfer_spec.has_key?('multi_session')
64
- multi_session_number=transfer_spec['multi_session'].to_i
65
- raise "multi_session(#{transfer_spec['multi_session']}) shall be integer > 1" unless multi_session_number >= 1
66
- # managed here, so delete from transfer spec
72
+ multi_session_info={
73
+ count: transfer_spec['multi_session'].to_i,
74
+ }
75
+ # Managed by multi-session, so delete from transfer spec
67
76
  transfer_spec.delete('multi_session')
68
- if transfer_spec.has_key?('fasp_port')
69
- multi_session_udp_port_base=transfer_spec['fasp_port']
70
- transfer_spec.delete('fasp_port')
77
+ if multi_session_info[:count] < 0
78
+ Log.log.error("multi_session(#{transfer_spec['multi_session']}) shall be integer >= 0")
79
+ multi_session_info = nil
80
+ elsif multi_session_info[:count].eql?(0)
81
+ Log.log.debug("multi_session count is zero: no multisession")
82
+ multi_session_info = nil
83
+ else # multi_session_info[:count] > 0
84
+ # if option not true: keep default udp port for all sessions
85
+ if @options[:multi_incr_udp]
86
+ # override if specified, else use default value
87
+ multi_session_info[:udp_base]=transfer_spec.has_key?('fasp_port') ? transfer_spec['fasp_port'] : DEFAULT_UDP_PORT
88
+ # delete from original transfer spec, as we will increment values
89
+ transfer_spec.delete('fasp_port')
90
+ end
71
91
  end
72
92
  end
73
93
 
74
94
  # compute known args
75
- env_args=Parameters.ts_to_env_args(transfer_spec,wss: @enable_wss)
95
+ env_args=Parameters.ts_to_env_args(transfer_spec,wss: @options[:wss])
76
96
 
77
97
  # add fallback cert and key as arguments if needed
78
98
  if ['1','force'].include?(transfer_spec['http_fallback'])
@@ -98,25 +118,28 @@ module Aspera
98
118
  :options => job_options # [Hash]
99
119
  }
100
120
 
101
- Log.log.debug("starting session thread(s)")
102
- if !multi_session_number
121
+ if multi_session_info.nil?
122
+ Log.log.debug('Starting single session thread')
103
123
  # single session for transfer : simple
104
124
  session[:thread] = Thread.new(session) {|s|transfer_thread_entry(s)}
105
125
  xfer_job[:sessions].push(session)
106
126
  else
107
- 1.upto(multi_session_number) do |i|
127
+ Log.log.debug('Starting multi session threads')
128
+ 1.upto(multi_session_info[:count]) do |i|
129
+ # do not delay the first session
130
+ sleep(@options[:spawn_delay_sec]) unless i.eql?(1)
108
131
  # do deep copy (each thread has its own copy because it is modified here below and in thread)
109
132
  this_session=session.clone()
110
133
  this_session[:env_args]=this_session[:env_args].clone()
111
134
  this_session[:env_args][:args]=this_session[:env_args][:args].clone()
112
- this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_number}")
113
- # necessary only if server is not linux, i.e. server does not support port re-use
114
- this_session[:env_args][:args].unshift("-O","#{multi_session_udp_port_base+i-1}")
135
+ this_session[:env_args][:args].unshift("-C#{i}:#{multi_session_info[:count]}")
136
+ # option: increment (default as per ascp manual) or not (cluster on other side ?)
137
+ this_session[:env_args][:args].unshift('-O',"#{multi_session_info[:udp_base]+i-1}") if @options[:multi_incr_udp]
115
138
  this_session[:thread] = Thread.new(this_session) {|s|transfer_thread_entry(s)}
116
139
  xfer_job[:sessions].push(this_session)
117
140
  end
118
141
  end
119
- Log.log.debug("started session thread(s)")
142
+ Log.log.debug('started session thread(s)')
120
143
 
121
144
  # add job to list of jobs
122
145
  @jobs[job_options[:job_id]]=xfer_job
@@ -128,7 +151,7 @@ module Aspera
128
151
  # wait for completion of all jobs started
129
152
  # @return list of :success or error message
130
153
  def wait_for_transfers_completion
131
- Log.log.debug("wait_for_transfers_completion")
154
+ Log.log.debug('wait_for_transfers_completion')
132
155
  # set to non-nil to exit loop
133
156
  result=[]
134
157
  @jobs.each do |id,job|
@@ -138,7 +161,7 @@ module Aspera
138
161
  result.push(session[:error] ? session[:error] : :success)
139
162
  end
140
163
  end
141
- Log.log.debug("all transfers joined")
164
+ Log.log.debug('all transfers joined')
142
165
  # since all are finished and we return the result, clear statuses
143
166
  @jobs.clear
144
167
  return result
@@ -146,7 +169,7 @@ module Aspera
146
169
 
147
170
  # used by asession (to be removed ?)
148
171
  def shutdown
149
- Log.log.debug("fasp local shutdown")
172
+ Log.log.debug('fasp local shutdown')
150
173
  end
151
174
 
152
175
  # This is the low level method to start the "ascp" process
@@ -158,8 +181,10 @@ module Aspera
158
181
  # @param session this session information
159
182
  # could be private method
160
183
  def start_transfer_with_args_env(env_args,session)
161
- raise "env_args must be Hash" unless env_args.is_a?(Hash)
162
- raise "session must be Hash" unless session.is_a?(Hash)
184
+ raise 'env_args must be Hash' unless env_args.is_a?(Hash)
185
+ raise 'session must be Hash' unless session.is_a?(Hash)
186
+ # by default we assume an exception will be raised (for ensure block)
187
+ exception_raised=true
163
188
  begin
164
189
  Log.log.debug("env_args=#{env_args.inspect}")
165
190
  # get location of ascp executable
@@ -182,7 +207,7 @@ module Aspera
182
207
  Log.log.debug("before accept for pid (#{ascp_pid})")
183
208
  # init management socket
184
209
  ascp_mgt_io=nil
185
- Timeout.timeout(ASCP_SPAWN_TIMEOUT_SEC) do
210
+ Timeout.timeout(@options[:spawn_timeout_sec]) do
186
211
  ascp_mgt_io = mgt_sock.accept
187
212
  # management messages include file names which may be utf8
188
213
  # by default socket is US-ASCII
@@ -216,7 +241,7 @@ module Aspera
216
241
  current_event_data[$1] = $2
217
242
  when ''
218
243
  # empty line is separator to end event information
219
- raise "unexpected empty line" if current_event_data.nil?
244
+ raise 'unexpected empty line' if current_event_data.nil?
220
245
  current_event_data[Manager::LISTENER_SESSION_ID_B]=ascp_pid
221
246
  notify_listeners(current_event_text,current_event_data)
222
247
  case current_event_data['Type']
@@ -232,26 +257,28 @@ module Aspera
232
257
  end # case
233
258
  end # loop (process mgt port lines)
234
259
  # check that last status was received before process exit
235
- if last_status_event.nil?
236
- Log.log.warn("no status read from ascp mgt port")
237
- else
260
+ if last_status_event.is_a?(Hash)
238
261
  case last_status_event['Type']
239
262
  when 'DONE'
240
- # return method (or just don't do anything)
241
- return
263
+ # all went well
264
+ exception_raised=false
242
265
  when 'ERROR'
243
266
  Log.log.error("code: #{last_status_event['Code']}")
244
267
  if last_status_event['Description'] =~ /bearer token/i
245
- Log.log.error("need to regenerate token".red)
268
+ Log.log.error('need to regenerate token'.red)
246
269
  if session[:options].is_a?(Hash) and session[:options].has_key?(:regenerate_token)
247
270
  # regenerate token here, expired, or error on it
271
+ # Note: in multi-session, each session will have a different one.
248
272
  env_args[:env]['ASPERA_SCP_TOKEN']=session[:options][:regenerate_token].call(true)
249
273
  end
250
274
  end
251
275
  raise Fasp::Error.new(last_status_event['Description'],last_status_event['Code'].to_i)
252
- else
276
+ else # case
253
277
  raise "unexpected last event type: #{last_status_event['Type']}"
254
278
  end
279
+ else
280
+ exception_raised=false
281
+ Log.log.debug('no status read from ascp mgt port')
255
282
  end
256
283
  rescue SystemCallError => e
257
284
  # Process.spawn
@@ -269,7 +296,13 @@ module Aspera
269
296
  ascp_pid=nil
270
297
  session.delete(:io)
271
298
  if !status.success?
272
- raise Fasp::Error.new("ascp failed with code #{status.exitstatus}")
299
+ message="ascp failed with code #{status.exitstatus}"
300
+ if exception_raised
301
+ # just debug, as main exception is already here
302
+ Log.log.debug(message)
303
+ else
304
+ raise Fasp::Error.new(message)
305
+ end
273
306
  end
274
307
  end
275
308
  end # begin-ensure
@@ -283,9 +316,9 @@ module Aspera
283
316
  # {'type'=>'DONE'}
284
317
  def send_command(job_id,session_index,data)
285
318
  job=@jobs[job_id]
286
- raise "no such job" if job.nil?
319
+ raise 'no such job' if job.nil?
287
320
  session=job[:sessions][session_index]
288
- raise "no such session" if session.nil?
321
+ raise 'no such session' if session.nil?
289
322
  Log.log.debug("command: #{data}")
290
323
  # build command
291
324
  command=data.
@@ -299,8 +332,8 @@ module Aspera
299
332
 
300
333
  private
301
334
 
302
- def initialize(agent_options=nil)
303
- agent_options||={}
335
+ # @param options : keys(symbol): see DEFAULT_OPTIONS
336
+ def initialize(options=nil)
304
337
  super()
305
338
  # by default no interactive progress bar
306
339
  @quiet=true
@@ -308,9 +341,20 @@ module Aspera
308
341
  @jobs={}
309
342
  # mutex protects global data accessed by threads
310
343
  @mutex=Mutex.new
311
- @enable_wss = agent_options[:wss] || false
312
- agent_options.delete(:wss)
313
- @resume_policy=ResumePolicy.new(agent_options)
344
+ # set default options and override if specified
345
+ @options=DEFAULT_OPTIONS.clone
346
+ if !options.nil?
347
+ raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
348
+ options.each do |k,v|
349
+ if DEFAULT_OPTIONS.has_key?(k)
350
+ @options[k]=v
351
+ else
352
+ raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map{|i|i.to_s}.join(",")}"
353
+ end
354
+ end
355
+ end
356
+ Log.log.debug("local options= #{options}")
357
+ @resume_policy=ResumePolicy.new(@options[:resume].symbolize_keys)
314
358
  end
315
359
 
316
360
  # transfer thread entry
@@ -318,7 +362,7 @@ module Aspera
318
362
  def transfer_thread_entry(session)
319
363
  begin
320
364
  # set name for logging
321
- Thread.current[:name]="transfer"
365
+ Thread.current[:name]='transfer'
322
366
  Log.log.debug("ENTER (#{Thread.current[:name]})")
323
367
  # start transfer with selected resumer policy
324
368
  session[:options][:resumer].process do
@@ -72,6 +72,9 @@ module Aspera
72
72
  # start_transfer(transfer_spec,options) : start and wait for completion
73
73
  # wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
74
74
  # optional: shutdown
75
+
76
+ # This checks the validity of the value returned by wait_for_transfers_completion
77
+ # it must be a list of :success or exception
75
78
  def self.validate_status_list(statuses)
76
79
  raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
77
80
  raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
@@ -7,9 +7,12 @@ module Aspera
7
7
  # this singleton class is used by the CLI to provide a common interface to start a transfer
8
8
  # before using it, the use must set the `node_api` member.
9
9
  class Node < Manager
10
- def initialize(node_api)
10
+ # option include: root_id if the node is an access key
11
+ attr_writer :options
12
+ def initialize(node_api,options={})
11
13
  super()
12
14
  @node_api=node_api
15
+ @options=options
13
16
  # TODO: currently only supports one transfer. This is bad shortcut. but ok for CLI.
14
17
  @transfer_id=nil
15
18
  end
@@ -32,6 +35,25 @@ module Aspera
32
35
 
33
36
  # generic method
34
37
  def start_transfer(transfer_spec,options=nil)
38
+ # add root id if access key
39
+ if @options.has_key?(:root_id)
40
+ case transfer_spec['direction']
41
+ when 'send';transfer_spec['source_root_id']=@options[:root_id]
42
+ when 'receive';transfer_spec['destination_root_id']=@options[:root_id]
43
+ else raise "unexpected direction in ts: #{transfer_spec['direction']}"
44
+ end
45
+ end
46
+ # manage special additional parameter
47
+ if transfer_spec.has_key?('EX_ssh_key_paths') and transfer_spec['EX_ssh_key_paths'].is_a?(Array) and !transfer_spec['EX_ssh_key_paths'].empty?
48
+ # not standard, so place standard field
49
+ if transfer_spec.has_key?('ssh_private_key')
50
+ Log.log.warn('Both ssh_private_key and EX_ssh_key_paths are present, using ssh_private_key')
51
+ else
52
+ Log.log.warn('EX_ssh_key_paths has multiple keys, using first one only') unless transfer_spec['EX_ssh_key_paths'].length.eql?(1)
53
+ transfer_spec['ssh_private_key']=File.read(transfer_spec['EX_ssh_key_paths'].first)
54
+ transfer_spec.delete('EX_ssh_key_paths')
55
+ end
56
+ end
35
57
  if transfer_spec['tags'].is_a?(Hash) and transfer_spec['tags']['aspera'].is_a?(Hash)
36
58
  transfer_spec['tags']['aspera']['xfer_retry']||=150
37
59
  end
@@ -4,6 +4,7 @@ require 'aspera/temp_file_manager'
4
4
  require 'securerandom'
5
5
  require 'base64'
6
6
  require 'json'
7
+ require 'yaml'
7
8
  require 'securerandom'
8
9
  require 'fileutils'
9
10
 
@@ -12,96 +13,67 @@ module Aspera
12
13
  # translate transfer specification to ascp parameter list
13
14
  class Parameters
14
15
  private
15
- # temp folder for file lists, must contain only file lists
16
+ # Temp folder for file lists, must contain only file lists
16
17
  # because of garbage collection takes any file there
17
18
  # this could be refined, as , for instance, on macos, temp folder is already user specific
18
19
  @@file_list_folder=TempFileManager.instance.new_file_path_global('asession_filelists')
19
- PARAM_DEFINITION={
20
- # parameters with env vars
21
- 'remote_password' => { :type => :envvar, :variable=>'ASPERA_SCP_PASS'},
22
- 'token' => { :type => :envvar, :variable=>'ASPERA_SCP_TOKEN'},
23
- 'cookie' => { :type => :envvar, :variable=>'ASPERA_SCP_COOKIE'},
24
- 'ssh_private_key' => { :type => :envvar, :variable=>'ASPERA_SCP_KEY'},
25
- 'EX_at_rest_password' => { :type => :envvar, :variable=>'ASPERA_SCP_FILEPASS'},
26
- 'EX_proxy_password' => { :type => :envvar, :variable=>'ASPERA_PROXY_PASS'},
27
- 'EX_license_text' => { :type => :envvar, :variable=>'ASPERA_SCP_LICENSE'},
28
- # bool params
29
- 'create_dir' => { :type => :opt_without_arg, :option_switch=>'-d'},
30
- 'precalculate_job_size' => { :type => :opt_without_arg},
31
- 'keepalive' => { :type => :opt_without_arg},
32
- 'delete_before_transfer' => { :type => :opt_without_arg}, #TODO: doc readme
33
- 'preserve_access_time' => { :type => :opt_without_arg}, #TODO: doc
34
- 'preserve_creation_time' => { :type => :opt_without_arg}, #TODO: doc
35
- 'preserve_times' => { :type => :opt_without_arg}, #TODO: doc
36
- 'preserve_modification_time'=> { :type => :opt_without_arg}, #TODO: doc
37
- 'remove_empty_directories'=> { :type => :opt_without_arg}, #TODO: doc
38
- 'remove_after_transfer' => { :type => :opt_without_arg}, #TODO: doc
39
- 'remove_empty_source_directory'=> { :type => :opt_without_arg}, #TODO: doc
40
- # value params
41
- 'cipher' => { :type => :opt_with_arg, :option_switch=>'-c',:accepted_types=>String,:encode=>lambda{|cipher|cipher.tr('-','')}},
42
- 'resume_policy' => { :type => :opt_with_arg, :option_switch=>'-k',:accepted_types=>String,:default=>'sparse_csum',:translate_values=>{'none'=>0,'attrs'=>1,'sparse_csum'=>2,'full_csum'=>3}},
43
- 'direction' => { :type => :opt_with_arg, :option_switch=>'--mode',:accepted_types=>String,:translate_values=>{'receive'=>'recv','send'=>'send'}},
44
- 'remote_user' => { :type => :opt_with_arg, :option_switch=>'--user',:accepted_types=>String},
45
- 'remote_host' => { :type => :opt_with_arg, :option_switch=>'--host',:accepted_types=>String},
46
- 'ssh_port' => { :type => :opt_with_arg, :option_switch=>'-P',:accepted_types=>Integer},
47
- 'fasp_port' => { :type => :opt_with_arg, :option_switch=>'-O',:accepted_types=>Integer},
48
- 'dgram_size' => { :type => :opt_with_arg, :option_switch=>'-Z',:accepted_types=>Integer},
49
- 'target_rate_kbps' => { :type => :opt_with_arg, :option_switch=>'-l',:accepted_types=>Integer},
50
- 'min_rate_kbps' => { :type => :opt_with_arg, :option_switch=>'-m',:accepted_types=>Integer},
51
- 'rate_policy' => { :type => :opt_with_arg, :option_switch=>'--policy',:accepted_types=>String},
52
- 'http_fallback' => { :type => :opt_with_arg, :option_switch=>'-y',:accepted_types=>[String,*Aspera::CommandLineBuilder::BOOLEAN_CLASSES],:translate_values=>{'force'=>'F',true=>1,false=>0}},
53
- 'http_fallback_port' => { :type => :opt_with_arg, :option_switch=>'-t',:accepted_types=>Integer},
54
- 'source_root' => { :type => :opt_with_arg, :option_switch=>'--source-prefix64',:accepted_types=>String,:encode=>lambda{|prefix|Base64.strict_encode64(prefix)}},
55
- 'sshfp' => { :type => :opt_with_arg, :option_switch=>'--check-sshfp',:accepted_types=>String},
56
- 'symlink_policy' => { :type => :opt_with_arg, :option_switch=>'--symbolic-links',:accepted_types=>String},
57
- 'overwrite' => { :type => :opt_with_arg, :accepted_types=>String},
58
- 'exclude_newer_than' => { :type => :opt_with_arg, :accepted_types=>Integer},
59
- 'exclude_older_than' => { :type => :opt_with_arg, :accepted_types=>Integer},
60
- 'preserve_acls' => { :type => :opt_with_arg, :accepted_types=>String},
61
- 'move_after_transfer' => { :type => :opt_with_arg, :accepted_types=>String},
62
- 'multi_session_threshold' => { :type => :opt_with_arg, :accepted_types=>String},
63
- # non standard parameters
64
- 'EX_fasp_proxy_url' => { :type => :opt_with_arg, :option_switch=>'--proxy',:accepted_types=>String},
65
- 'EX_http_proxy_url' => { :type => :opt_with_arg, :option_switch=>'-x',:accepted_types=>String},
66
- 'EX_ssh_key_paths' => { :type => :opt_with_arg, :option_switch=>'-i',:accepted_types=>Array},
67
- 'EX_http_transfer_jpeg' => { :type => :opt_with_arg, :option_switch=>'-j',:accepted_types=>Integer},
68
- 'EX_multi_session_part' => { :type => :opt_with_arg, :option_switch=>'-C',:accepted_types=>String},
69
- 'EX_no_read' => { :type => :opt_without_arg, :option_switch=>'--no-read'},
70
- 'EX_no_write' => { :type => :opt_without_arg, :option_switch=>'--no-write'},
71
- 'EX_apply_local_docroot' => { :type => :opt_without_arg, :option_switch=>'--apply-local-docroot'},
72
- # TODO: manage those parameters, some are for connect only ? node api ?
73
- 'target_rate_cap_kbps' => { :type => :ignore, :accepted_types=>Integer},
74
- 'target_rate_percentage' => { :type => :ignore, :accepted_types=>String}, # -wf -l<rate>p
75
- 'min_rate_cap_kbps' => { :type => :ignore, :accepted_types=>Integer},
76
- 'rate_policy_allowed' => { :type => :ignore, :accepted_types=>String},
77
- 'fasp_url' => { :type => :ignore, :accepted_types=>String},
78
- 'lock_rate_policy' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
79
- 'lock_min_rate' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
80
- 'lock_target_rate' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
81
- #'authentication' => { :type => :ignore, :accepted_types=>String}, # = token
82
- 'https_fallback_port' => { :type => :ignore, :accepted_types=>Integer}, # same as http fallback, option -t ?
83
- 'content_protection' => { :type => :ignore, :accepted_types=>String},
84
- 'cipher_allowed' => { :type => :ignore, :accepted_types=>String},
85
- 'multi_session' => { :type => :ignore, :accepted_types=>Integer}, # managed
86
- # optional tags ( additional option to generate: {:space=>' ',:object_nl=>' ',:space_before=>'+',:array_nl=>'1'} )
87
- 'tags' => { :type => :opt_with_arg, :option_switch=>'--tags64',:accepted_types=>Hash,:encode=>lambda{|tags|Base64.strict_encode64(JSON.generate(tags))}},
88
- # special processing @builder.process_param( called individually
89
- 'use_ascp4' => { :type => :defer, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
90
- 'paths' => { :type => :defer, :accepted_types=>Array},
91
- 'EX_file_list' => { :type => :defer, :option_switch=>'--file-list', :accepted_types=>String},
92
- 'EX_file_pair_list' => { :type => :defer, :option_switch=>'--file-pair-list', :accepted_types=>String},
93
- 'EX_ascp_args' => { :type => :defer, :accepted_types=>Array},
94
- 'destination_root' => { :type => :defer, :accepted_types=>String},
95
- 'wss_enabled' => { :type => :defer, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
96
- 'wss_port' => { :type => :defer, :accepted_types=>Integer},
97
- }
98
-
99
- private_constant :PARAM_DEFINITION
20
+ @@param_description_cache=nil
21
+ # @return normaiwed description of transfer spec parameters
22
+ def self.description
23
+ return @@param_description_cache unless @@param_description_cache.nil?
24
+ # config file in same folder with same name as this source
25
+ @@param_description_cache=YAML.load_file("#{__FILE__[0..-3]}yaml")
26
+ Aspera::CommandLineBuilder.normalize_description(@@param_description_cache)
27
+ end
28
+
29
+ # Agents shown in manual
30
+ SUPPORTED_AGENTS=[:direct,:node,:connect]
31
+ # Short names of columns in manual
32
+ SUPPORTED_AGENTS_SHORT=SUPPORTED_AGENTS.map{|a|a.to_s[0].to_sym}
33
+
34
+ # @return a table suitable to display a manual
35
+ def self.man_table
36
+ result=[]
37
+ description.keys.map do |k|
38
+ i=description[k]
39
+ param={name: k, type: [i[:accepted_types]].flatten.join(','),description: i[:desc]}
40
+ SUPPORTED_AGENTS.each do |a|
41
+ param[a.to_s[0].to_sym]=i[:context].nil? || i[:context].include?(a) ? 'Y' : ''
42
+ end
43
+ # only keep lines that are usable in supported agents
44
+ next if SUPPORTED_AGENTS_SHORT.inject(true){|m,i|m and param[i].empty?}
45
+ param[:cli]=case i[:cltype]
46
+ when :envvar; 'env:'+i[:clvarname]
47
+ when :opt_without_arg,:opt_with_arg; i[:option_switch]
48
+ else ''
49
+ end
50
+ if i.has_key?(:enum)
51
+ param[:description] << "\nAllowed values: #{i[:enum].join(', ')}"
52
+ end
53
+ result.push(param)
54
+ end
55
+ return result
56
+ end
57
+
58
+ # special encoding methods used in YAML (key: :encode)
59
+ def self.encode_cipher(v)
60
+ v.tr('-','')
61
+ end
62
+
63
+ # special encoding methods used in YAML (key: :encode)
64
+ def self.encode_source_root(v)
65
+ Base64.strict_encode64(v)
66
+ end
67
+
68
+ # special encoding methods used in YAML (key: :encode)
69
+ def self.encode_tags(v)
70
+ Base64.strict_encode64(JSON.generate(v))
71
+ end
100
72
 
101
73
  def initialize(job_spec,options)
102
74
  @job_spec=job_spec
103
- @builder=Aspera::CommandLineBuilder.new(@job_spec,PARAM_DEFINITION)
104
75
  @options=options
76
+ @builder=Aspera::CommandLineBuilder.new(@job_spec,self.class.description)
105
77
  end
106
78
 
107
79
  public
@@ -124,7 +96,7 @@ module Aspera
124
96
  # special cases
125
97
  @job_spec.delete('source_root') if @job_spec.has_key?('source_root') and @job_spec['source_root'].empty?
126
98
 
127
- # use web socket initiation ?
99
+ # use web socket session initiation ?
128
100
  if @builder.process_param('wss_enabled',:get_value) and @options[:wss]
129
101
  # by default use web socket session if available, unless removed by user
130
102
  @builder.add_command_line_options(['--ws-connect'])
@@ -150,8 +122,7 @@ module Aspera
150
122
  # destination will be base64 encoded, put before path arguments
151
123
  @builder.add_command_line_options(['--dest64'])
152
124
  end
153
-
154
- PARAM_DEFINITION['paths'][:mandatory]=!@job_spec.has_key?('keepalive')
125
+ @builder.params_definition['paths'][:mandatory]=!@job_spec.has_key?('keepalive')
155
126
  paths_array=@builder.process_param('paths',:get_value)
156
127
  unless paths_array.nil?
157
128
  # use file list if there is storage defined for it.