aspera-cli 4.1.0 → 4.2.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.
@@ -72,7 +72,7 @@ module Aspera
72
72
  def self.get_source_id(source_list,source_name)
73
73
  source_ids=source_list.select { |i| i['name'].eql?(source_name) }
74
74
  if source_ids.empty?
75
- raise CliError,"No such Faspex source #{field_sym.to_s}: #{field_value} in [#{source_list.map{|i| i[field_sym.to_s]}.join(', ')}]"
75
+ raise CliError,%Q{No such Faspex source "#{source_name}" in [#{source_list.map{|i| %Q{"#{i['name']}"}}.join(', ')}]}
76
76
  end
77
77
  return source_ids.first['id']
78
78
  end
@@ -46,39 +46,22 @@ module Aspera
46
46
  })
47
47
  when :jwt
48
48
  #raise "JWT to be implemented"
49
+ app_client_id=options.get_option(:client_id,:mandatory)
49
50
  @api_v5=Rest.new({
50
- :base_url => faxpex5_api_base_url,
51
+ :base_url => faxpex5_api_v5_url,
51
52
  :auth => {
52
53
  :type => :oauth2,
53
54
  :base_url => faxpex5_api_auth_url,
54
55
  :grant => :jwt,
55
- :client_id => options.get_option(:client_id,:mandatory),
56
+ :client_id => app_client_id,
56
57
  :client_secret => options.get_option(:client_secret,:mandatory),
57
58
  #:redirect_uri => options.get_option(:redirect_uri,:mandatory),
58
- :jwt_subject => "client:#{options.get_option(:client_id,:mandatory)}", # TODO Mmmm
59
+ :jwt_subject => "client:#{app_client_id}", # TODO Mmmm
60
+ :jwt_audience => app_client_id, # TODO Mmmm
59
61
  :jwt_private_key_obj => OpenSSL::PKey::RSA.new(options.get_option(:private_key,:mandatory)),
60
- :jwt_audience =>options.get_option(:client_id,:mandatory), # TODO Mmmm
61
- #:token_field =>'auth_token',
62
- #:path_token => 'authenticate',
63
- #:path_authorize => :unused,
64
- #:userpass_body => {name: options.get_option(:username,:mandatory),password: options.get_option(:password,:mandatory)}
62
+ :jwt_is_f5 => true,
63
+ :jwt_headers => {typ: 'JWT'}
65
64
  }})
66
- # former version
67
- # # get parameters
68
- # faxpex5_username=options.get_option(:username,:mandatory)
69
- # faxpex5_password=options.get_option(:password,:mandatory)
70
- # # create object for REST calls to Shares2
71
- # @api_v5=Rest.new({
72
- # :base_url => faxpex5_api_base_url,
73
- # :auth => {
74
- # :type => :oauth2,
75
- # :base_url => faxpex5_api_base_url,
76
- # :grant => :body_data,
77
- # :token_field =>'auth_token',
78
- # :path_token => 'authenticate',
79
- # :path_authorize => :unused,
80
- # :userpass_body => {name: faxpex5_username,password: faxpex5_password}
81
- # }})
82
65
  end
83
66
  end
84
67
 
@@ -4,6 +4,7 @@ require 'aspera/preview/options'
4
4
  require 'aspera/preview/utils'
5
5
  require 'aspera/preview/file_types'
6
6
  require 'aspera/persistency_action_once'
7
+ require 'aspera/node'
7
8
  require 'aspera/hash_ext'
8
9
  require 'date'
9
10
  require 'securerandom'
@@ -322,12 +323,14 @@ module Aspera
322
323
  end # generate_preview
323
324
 
324
325
  # scan all files in provided folder entry
326
+ # @param scan_start subpath to start folder scan inside
325
327
  def scan_folder_files(top_entry,scan_start=nil)
326
328
  if !scan_start.nil?
327
329
  # canonical path: start with / and ends with /
328
330
  scan_start='/'+scan_start.split('/').select{|i|!i.empty?}.join('/')
329
331
  scan_start="#{scan_start}/" #unless scan_start.end_with?('/')
330
332
  end
333
+ filter_block=Aspera::Node.file_matcher(options.get_option(:value,:optional))
331
334
  Log.log.debug("scan: #{top_entry} : #{scan_start}".green)
332
335
  # don't use recursive call, use list instead
333
336
  entries_to_process=[top_entry]
@@ -343,7 +346,11 @@ module Aspera
343
346
  Log.log.debug("item:#{entry}")
344
347
  case entry['type']
345
348
  when 'file'
346
- generate_preview(entry)
349
+ if filter_block.call(entry)
350
+ generate_preview(entry)
351
+ else
352
+ Log.log.debug('skip by filter')
353
+ end
347
354
  when 'link'
348
355
  Log.log.debug('Ignoring link.')
349
356
  when 'folder'
@@ -438,6 +445,8 @@ module Aspera
438
445
  data: iteration_data,
439
446
  ids: ["preview_iteration_#{command}",self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory)])
440
447
  end
448
+
449
+ # call method specified
441
450
  iteration_data[0]=send("process_#{command}",iteration_data[0])
442
451
  iteration_persistency.save unless iteration_persistency.nil?
443
452
  return Main.result_status("#{command} finished")
@@ -10,7 +10,7 @@ module Aspera
10
10
  module Cli
11
11
  # The Transfer agent is a common interface to start a transfer using
12
12
  # one of the supported transfer agents
13
- # provides CLI options to select one of the transfer agents (fasp client)
13
+ # provides CLI options to select one of the transfer agents (FASP/ascp client)
14
14
  class TransferAgent
15
15
  # special value for --sources : read file list from arguments
16
16
  FILE_LIST_FROM_ARGS='@args'
@@ -45,6 +45,7 @@ module Aspera
45
45
 
46
46
  def option_transfer_spec; @transfer_spec_cmdline; end
47
47
 
48
+ # multiple option are merged
48
49
  def option_transfer_spec=(value); @transfer_spec_cmdline.merge!(value); end
49
50
 
50
51
  def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
@@ -75,7 +76,7 @@ module Aspera
75
76
  when :connect
76
77
  new_agent=Fasp::Connect.new
77
78
  when :node
78
- # way for code to setup alternate node api in avance
79
+ # way for code to setup alternate node api in advance
79
80
  # support: @preset:<name>
80
81
  # support extended values
81
82
  node_config=@opt_mgr.get_option(:transfer_info,:optional)
@@ -118,7 +119,7 @@ module Aspera
118
119
  aoc_config[:private_key]=ExtendedValue.instance.evaluate(aoc_config[:private_key])
119
120
  new_agent=Fasp::Aoc.new(aoc_config)
120
121
  else
121
- raise "INTERNAL ERROR"
122
+ raise "Unexpected transfer agent type: #{agent_type}"
122
123
  end
123
124
  set_agent_instance(new_agent)
124
125
  return nil
@@ -178,9 +179,9 @@ module Aspera
178
179
  # when providing a list, just specify source
179
180
  @transfer_paths=file_list.map{|i|{'source'=>i}}
180
181
  when :pair
181
- raise CliBadArgument,"whe using pair, provide even number of paths: #{file_list.length}" unless file_list.length.even?
182
+ raise CliBadArgument,"When using pair, provide an even number of paths: #{file_list.length}" unless file_list.length.even?
182
183
  @transfer_paths=file_list.each_slice(2).to_a.map{|s,d|{'source'=>s,'destination'=>d}}
183
- else raise "ERROR"
184
+ else raise "Unsupported src_type"
184
185
  end
185
186
  Log.log.debug("paths=#{@transfer_paths}")
186
187
  return @transfer_paths
@@ -1,5 +1,5 @@
1
1
  module Aspera
2
2
  module Cli
3
- VERSION = "4.1.0"
3
+ VERSION = "4.2.0"
4
4
  end
5
5
  end
@@ -5,46 +5,51 @@ require 'xmlsimple'
5
5
  module Aspera
6
6
  class CosNode < Rest
7
7
  attr_reader :add_ts
8
- def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url='https://iam.cloud.ibm.com/identity')
8
+ IBM_CLOUD_TOKEN_URL='https://iam.cloud.ibm.com/identity'
9
+ def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url=IBM_CLOUD_TOKEN_URL)
10
+ @auth_url=auth_url
11
+ @api_key=api_key
9
12
  s3_api=Aspera::Rest.new({
10
- :base_url => storage_endpoint,
11
- :not_auth_codes => ['401','403'],
12
- :headers => {'ibm-service-instance-id' => instance_id},
13
- :auth => {
14
- :type => :oauth2,
15
- :base_url => auth_url,
16
- :grant => :ibm_apikey,
17
- :api_key => api_key
13
+ :base_url => storage_endpoint,
14
+ :not_auth_codes => ['401','403'], # error codes when not authorized
15
+ :headers => {'ibm-service-instance-id' => instance_id},
16
+ :auth => {
17
+ :type => :oauth2,
18
+ :base_url => @auth_url,
19
+ :grant => :ibm_apikey,
20
+ :api_key => @api_key
18
21
  }})
19
22
  # read FASP connection information for bucket
20
23
  xml_result_text=s3_api.call({:operation=>'GET',:subpath=>bucket_name,:headers=>{'Accept'=>'application/xml'},:url_params=>{'faspConnectionInfo'=>nil}})[:http].body
21
24
  ats_info=XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
22
25
  Aspera::Log.dump('ats_info',ats_info)
23
- # get delegated token
24
- delegated_oauth=Oauth.new({
25
- :type => :oauth2,
26
- :base_url => auth_url,
27
- :grant => :delegated_refresh,
28
- :api_key => api_key,
29
- :token_field=> 'delegated_refresh_token'
30
- })
31
- # to be placed in rest call header and in transfer tags
32
- aspera_storage_credentials={
33
- 'type' => 'token',
34
- 'token' => {'delegated_refresh_token'=>delegated_oauth.get_authorization().gsub(/^Bearer /,'')}
35
- }
36
- # transfer spec addition
37
- @add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>aspera_storage_credentials}}}}
38
- # set a general addon to transfer spec
39
- # here we choose to use the add_request_param
40
- #self.transfer.option_transfer_spec_deep_merge(@add_ts)
41
26
  super({
42
27
  :base_url => ats_info['ATSEndpoint'],
43
- :headers => {'X-Aspera-Storage-Credentials'=>JSON.generate(aspera_storage_credentials)},
44
28
  :auth => {
45
29
  :type => :basic,
46
30
  :username => ats_info['AccessKey']['Id'],
47
31
  :password => ats_info['AccessKey']['Secret']}})
32
+ # prepare transfer spec addition
33
+ @add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>{
34
+ 'type' => 'token',
35
+ 'token' => {'delegated_refresh_token'=>nil}
36
+ }}}}}
37
+ generate_token
38
+ end
39
+
40
+ # potentially call this if delegated token is expired
41
+ def generate_token
42
+ # OAuth API to get delegated token
43
+ delegated_oauth=Oauth.new({
44
+ :type => :oauth2,
45
+ :base_url => @auth_url,
46
+ :grant => :delegated_refresh,
47
+ :api_key => @api_key,
48
+ :token_field=> 'delegated_refresh_token'
49
+ })
50
+ # get delagated token to be placed in rest call header and in transfer tags
51
+ @add_ts['tags']['aspera']['node']['storage_credentials']['token']['delegated_refresh_token']=delegated_oauth.get_authorization().gsub(/^Bearer /,'')
52
+ @params[:headers]={'X-Aspera-Storage-Credentials'=>JSON.generate(@add_ts['tags']['aspera']['node']['storage_credentials'])}
48
53
  end
49
54
  end
50
55
  end
@@ -2,7 +2,7 @@ require 'aspera/log'
2
2
  require 'rbconfig'
3
3
 
4
4
  module Aspera
5
- # a simple binary data repository
5
+ # detect OS, architecture, and OS specific stuff
6
6
  class Environment
7
7
  OS_WINDOWS = :windows
8
8
  OS_X = :osx
@@ -53,7 +53,7 @@ module Aspera
53
53
  return ''
54
54
  end
55
55
 
56
- # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably then %HOMEDRIVE%%HOMEPATH%
56
+ # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
57
57
  def self.fix_home
58
58
  if os.eql?(OS_WINDOWS)
59
59
  if ENV.has_key?('USERPROFILE') and Dir.exist?(ENV['USERPROFILE'])
@@ -22,11 +22,18 @@ module Aspera
22
22
  PRODUCT_CLI_V1='Aspera CLI'
23
23
  PRODUCT_DRIVE='Aspera Drive'
24
24
  PRODUCT_ENTSRV='Enterprise Server'
25
- # currently used ascp executable
25
+ MAX_REDIRECT_SDK=2
26
+ private_constant :MAX_REDIRECT_SDK
27
+ # set ascp executable path
26
28
  def ascp_path=(v)
27
29
  @path_to_ascp=v
28
30
  end
29
31
 
32
+ # filename for ascp with optional extension (Windows)
33
+ def ascp_filename
34
+ return 'ascp'+Environment.exe_extension
35
+ end
36
+
30
37
  # location of SDK files
31
38
  def folder=(v)
32
39
  @sdk_folder=v
@@ -50,26 +57,30 @@ module Aspera
50
57
  # @return the list of installed products in format of product_locations
51
58
  def installed_products
52
59
  if @found_products.nil?
53
- @found_products=product_locations
54
- # add sdk as first search path
55
- @found_products.unshift({# SDK
60
+ scan_locations=product_locations.clone
61
+ # add SDK as first search path
62
+ scan_locations.unshift({
56
63
  :expected =>'SDK',
57
64
  :app_root =>folder_path,
58
65
  :sub_bin =>''
59
66
  })
60
- @found_products.select! do |pl|
61
- next false unless Dir.exist?(pl[:app_root])
62
- Log.log.debug("found #{pl[:app_root]}")
63
- sub_bin = pl[:sub_bin] || BIN_SUBFOLDER
64
- pl[:ascp_path]=File.join(pl[:app_root],sub_bin,'ascp'+Environment.exe_extension)
65
- next false unless File.exist?(pl[:ascp_path])
66
- product_info_file="#{pl[:app_root]}/#{PRODUCT_INFO}"
67
+ # search installed products: with ascp
68
+ @found_products=scan_locations.select! do |item|
69
+ # skip if not main folder
70
+ next false unless Dir.exist?(item[:app_root])
71
+ Log.log.debug("Found #{item[:app_root]}")
72
+ sub_bin = item[:sub_bin] || BIN_SUBFOLDER
73
+ item[:ascp_path]=File.join(item[:app_root],sub_bin,ascp_filename)
74
+ # skip if no ascp
75
+ next false unless File.exist?(item[:ascp_path])
76
+ # read info from product info file if present
77
+ product_info_file="#{item[:app_root]}/#{PRODUCT_INFO}"
67
78
  if File.exist?(product_info_file)
68
- res_s=XmlSimple.xml_in(File.read(product_info_file),{"ForceArray"=>false})
69
- pl[:name]=res_s['name']
70
- pl[:version]=res_s['version']
79
+ res_s=XmlSimple.xml_in(File.read(product_info_file),{'ForceArray'=>false})
80
+ item[:name]=res_s['name']
81
+ item[:version]=res_s['version']
71
82
  else
72
- pl[:name]=pl[:expected]
83
+ item[:name]=item[:expected]
73
84
  end
74
85
  true # select this version
75
86
  end
@@ -77,6 +88,7 @@ module Aspera
77
88
  return @found_products
78
89
  end
79
90
 
91
+ # all ascp files (in SDK)
80
92
  FILES=[:ascp,:ascp4,:ssh_bypass_key_dsa,:ssh_bypass_key_rsa,:aspera_license,:aspera_conf,:fallback_cert,:fallback_key]
81
93
 
82
94
  # get path of one resource file of currently activated product
@@ -106,14 +118,8 @@ module Aspera
106
118
  <CONF version="2">
107
119
  <default>
108
120
  <file_system>
109
- <storage_rc>
110
- <adaptive>
111
- true
112
- </adaptive>
113
- </storage_rc>
114
121
  <resume_suffix>.aspera-ckpt</resume_suffix>
115
122
  <partial_file_suffix>.partial</partial_file_suffix>
116
- <replace_illegal_chars>_</replace_illegal_chars>
117
123
  </file_system>
118
124
  </default>
119
125
  </CONF>
@@ -147,7 +153,7 @@ module Aspera
147
153
  return file
148
154
  end
149
155
 
150
- # @returns the file path of local connect where API's URI can be read
156
+ # @return the file path of local connect where API's URI can be read
151
157
  def connect_uri
152
158
  connect=get_product_folders(PRODUCT_CONNECT)
153
159
  folder=File.join(connect[:run_root],VARRUN_SUBFOLDER)
@@ -176,8 +182,21 @@ module Aspera
176
182
  return [:ssh_bypass_key_dsa,:ssh_bypass_key_rsa].map{|i|Installation.instance.path(i)}
177
183
  end
178
184
 
185
+ # Check that specified path is ascp and get version
186
+ def get_ascp_version(ascp_path)
187
+ raise "File basename of #{ascp_path} must be #{ascp_filename}" unless File.basename(ascp_path).eql?(ascp_filename)
188
+ ascp_version='n/a'
189
+ raise "error in sdk: no ascp included" if ascp_path.nil?
190
+ cmd_out=%x{"#{ascp_path}" -A}
191
+ raise "An error occured when testing #{ascp_filename}: #{cmd_out}" unless $? == 0
192
+ # get version from ascp, only after full extract, as windows requires DLLs (SSL/TLS/etc...)
193
+ m=cmd_out.match(/ascp version (.*)/)
194
+ ascp_version=m[1] unless m.nil?
195
+ end
196
+
179
197
  # download aspera SDK or use local file
180
- # extract only ascp binary for current system architecture
198
+ # extracts ascp binary for current system architecture
199
+ # @return ascp version (from execution)
181
200
  def install_sdk(sdk_url)
182
201
  require 'zip'
183
202
  sdk_zip_path=File.join(Dir.tmpdir,'sdk.zip')
@@ -186,7 +205,7 @@ module Aspera
186
205
  raise 'use format: file:///<path>' unless sdk_url.start_with?('file:///')
187
206
  sdk_zip_path=sdk_url.gsub(%r{^file:///},'')
188
207
  else
189
- redirect_remain=2
208
+ redirect_remain=MAX_REDIRECT_SDK
190
209
  begin
191
210
  Aspera::Rest.new(base_url: sdk_url).call(operation: 'GET',save_to_file: sdk_zip_path)
192
211
  rescue Aspera::RestCallError => e
@@ -196,7 +215,7 @@ module Aspera
196
215
  sdk_url=e.response['location']
197
216
  retry
198
217
  else
199
- raise "too meny redirect"
218
+ raise "Too many redirect"
200
219
  end
201
220
  else
202
221
  raise e
@@ -206,6 +225,12 @@ module Aspera
206
225
  # SDK is organized by architecture
207
226
  filter="/#{Environment.architecture}/"
208
227
  ascp_path=nil
228
+ sdk_path=folder_path
229
+ # rename old install
230
+ if File.exist?(File.join(sdk_path,ascp_filename))
231
+ Log.log.warn("Previous install exists, renaming.")
232
+ File.rename(sdk_path,"#{sdk_path}.#{Time.now.strftime("%Y%m%d%H%M%S")}")
233
+ end
209
234
  # first ensure license file is here so that ascp invokation for version works
210
235
  self.path(:aspera_license)
211
236
  self.path(:aspera_conf)
@@ -213,11 +238,11 @@ module Aspera
213
238
  zip_file.each do |entry|
214
239
  # get only specified arch, but not folder, only files
215
240
  if entry.name.include?(filter) and !entry.name.end_with?('/')
216
- archive_file=File.join(folder_path,File.basename(entry.name))
241
+ archive_file=File.join(sdk_path,File.basename(entry.name))
217
242
  File.open(archive_file, 'wb') do |output_stream|
218
243
  IO.copy_stream(entry.get_input_stream, output_stream)
219
244
  end
220
- if entry.name.include?('ascp')
245
+ if File.basename(entry.name).eql?(ascp_filename)
221
246
  FileUtils.chmod(0755,archive_file)
222
247
  ascp_path=archive_file
223
248
  end
@@ -225,13 +250,7 @@ module Aspera
225
250
  end
226
251
  end
227
252
  File.unlink(sdk_zip_path) rescue nil # Windows may give error
228
- ascp_version='n/a'
229
- raise "error in sdk: no ascp included" if ascp_path.nil?
230
- cmd_out=%x{#{ascp_path} -A}
231
- raise "An error occured when testing ascp: #{cmd_out}" unless $? == 0
232
- # get version from ascp, only after full extract, as windows requires DLLs (SSL/TLS/etc...)
233
- m=cmd_out.match(/ascp version (.*)/)
234
- ascp_version=m[1] unless m.nil?
253
+ ascp_version=get_ascp_version(ascp_path)
235
254
  File.write(File.join(folder_path,PRODUCT_INFO),"<product><name>IBM Aspera SDK</name><version>#{ascp_version}</version></product>")
236
255
  return ascp_version
237
256
  end
@@ -248,27 +267,28 @@ module Aspera
248
267
 
249
268
  private_constant :BIN_SUBFOLDER,:ETC_SUBFOLDER,:VARRUN_SUBFOLDER,:PRODUCT_INFO
250
269
 
251
- # get some specific folder from specific applications: Connect or CLI
252
- def get_product_folders(name)
253
- found=installed_products.select{|i|i[:expected].eql?(name) or i[:name].eql?(name)}
254
- raise "Product: #{name} not found, please install." if found.empty?
255
- return found.first
256
- end
257
-
258
270
  def initialize
259
271
  @path_to_ascp=nil
260
272
  @sdk_folder=nil
261
273
  @found_products=nil
262
274
  end
263
275
 
276
+ # @return folder paths for specified applications
277
+ # @param name Connect or CLI
278
+ def get_product_folders(name)
279
+ found=installed_products.select{|i|i[:expected].eql?(name) or i[:name].eql?(name)}
280
+ raise "Product: #{name} not found, please install." if found.empty?
281
+ return found.first
282
+ end
283
+
284
+ # @return the path to folder where SDK is installed
264
285
  def folder_path
265
- raise "undefined path to SDK" if @sdk_folder.nil?
286
+ raise "Undefined path to SDK" if @sdk_folder.nil?
266
287
  FileUtils.mkdir_p(@sdk_folder) unless Dir.exist?(@sdk_folder)
267
288
  @sdk_folder
268
289
  end
269
290
 
270
- # returns product folders depending on OS
271
- # fields
291
+ # @return product folders depending on OS fields
272
292
  # :expected M app name is taken from the manifest if present, else defaults to this value
273
293
  # :app_root M main folder for the application
274
294
  # :log_root O location of log files (Linux uses syslog)
@@ -310,7 +330,7 @@ module Aspera
310
330
  :log_root =>File.join(Dir.home,'Library','Logs','Aspera_Drive'),
311
331
  :sub_bin =>File.join('Contents','Resources'),
312
332
  }]
313
- else; return [{ # other: Linux and unix family
333
+ else; return [{ # other: Linux and Unix family
314
334
  :expected =>PRODUCT_CONNECT,
315
335
  :app_root =>File.join(Dir.home,'.aspera','connect'),
316
336
  :run_root =>File.join(Dir.home,'.aspera','connect')
@@ -324,6 +344,9 @@ module Aspera
324
344
  end
325
345
  end
326
346
 
347
+ # @return a standard bypass key
348
+ # @param type rsa or dsa
349
+ # @param id in repository 1 for dsa, 2 for rsa
327
350
  def get_key(type,id)
328
351
  hf=['begin','end'].map{|t|"-----#{t} #{type} private key-----".upcase}
329
352
  bin=Base64.strict_encode64(DataRepository.instance.get_bin(id))