aspera-cli 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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))