Class: Aspera::Ascp::Installation

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/aspera/ascp/installation.rb

Overview

Singleton that tells where to find ascp and other local resources (keys..) , using the “path(:name)” method. It is used by object : AgentDirect to find necessary resources By default it takes the first Aspera product found but the user can specify ascp location by calling: Installation.instance.use_ascp_from_product(product_name) or Installation.instance.ascp_path=“”

Constant Summary collapse

EXE_FILES =

all ascp files (in SDK)

%i[ascp ascp4 async].freeze
CLIENT_SSH_KEY_OPTIONS =

options for SSH client private key

%i{dsa_rsa rsa per_client}.freeze

Instance Method Summary collapse

Instance Method Details

#ascp_infoObject



232
233
234
235
# File 'lib/aspera/ascp/installation.rb', line 232

def ascp_info
  files = file_paths
  return files.merge(ascp_pvcl_info).merge(ascp_ssl_info)
end

#ascp_pathObject



54
55
56
# File 'lib/aspera/ascp/installation.rb', line 54

def ascp_path
  path(:ascp)
end

#ascp_path=(v) ⇒ Object

set ascp executable path



50
51
52
# File 'lib/aspera/ascp/installation.rb', line 50

def ascp_path=(v)
  @path_to_ascp = v
end

#ascp_pvcl_infoObject



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/aspera/ascp/installation.rb', line 187

def ascp_pvcl_info
  data = {}
  # read PATHs from ascp directly, and pvcl modules as well
  Open3.popen3(ascp_path, '-DDL-') do |_stdin, _stdout, stderr, thread|
    last_line = ''
    while (line = stderr.gets)
      line.chomp!
      last_line = line
      case line
      when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
        data[Regexp.last_match(1)] = Regexp.last_match(3)
      when /^DBG Added module group:"(?<module>[^"]+)" name:"(?<scheme>[^"]+)", version:"(?<version>[^"]+)" interface:"(?<interface>[^"]+)"$/
        c = Regexp.last_match.named_captures.symbolize_keys
        data[c[:interface]] ||= {}
        data[c[:interface]][c[:module]] ||= []
        data[c[:interface]][c[:module]].push("#{c[:scheme]} v#{c[:version]}")
      when %r{^DBG License result \(/license/(\S+)\): (.+)$}
        data[Regexp.last_match(1)] = Regexp.last_match(2)
      when /^LOG (.+) version ([0-9.]+)$/
        data['product_name'] = Regexp.last_match(1)
        data['product_version'] = Regexp.last_match(2)
      when /^LOG Initializing FASP version ([^,]+),/
        data['sdk_ascp_version'] = Regexp.last_match(1)
      end
    end
    if !thread.value.exitstatus.eql?(1) && !data.key?('root')
      raise last_line
    end
  end
  return data
end

#ascp_ssl_infoObject

extract some stings from ascp binary



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/aspera/ascp/installation.rb', line 220

def ascp_ssl_info
  data = {}
  File.binread(ascp_path).scan(/[\x20-\x7E]{10,}/) do |bin_string|
    if (m = bin_string.match(/OPENSSLDIR.*"(.*)"/))
      data['openssldir'] = m[1]
    elsif (m = bin_string.match(/OpenSSL (\d[^ -]+)/))
      data['openssl_version'] = m[1]
    end
  end if File.file?(ascp_path)
  return data
end

#aspera_token_ssh_key_paths(types) ⇒ Object

get paths of SSH keys to use for ascp client

Parameters:

  • types (Symbol)

    types to use



159
160
161
162
163
164
165
166
167
# File 'lib/aspera/ascp/installation.rb', line 159

def aspera_token_ssh_key_paths(types)
  Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
  return case types
         when :dsa_rsa, :rsa
           types.to_s.split('_').map{|i|Installation.instance.path("ssh_private_#{i}".to_sym)}
         when :per_client
           raise 'Not yet implemented'
         end
end

#check_or_create_sdk_file(filename, force: false, &block) ⇒ Object



107
108
109
# File 'lib/aspera/ascp/installation.rb', line 107

def check_or_create_sdk_file(filename, force: false, &block)
  return Environment.write_file_restricted(File.join(sdk_folder, filename), force: force, mode: 0o644, &block)
end

#file_pathsHash

Returns with key = file name (String), and value = path to file.

Returns:

  • (Hash)

    with key = file name (String), and value = path to file



96
97
98
99
100
101
102
103
104
105
# File 'lib/aspera/ascp/installation.rb', line 96

def file_paths
  return FILES.each_with_object({}) do |v, m|
    m[v.to_s] =
      begin
        path(v)
      rescue => e
        e.message
      end
  end
end

#get_ascp_version(exe_path) ⇒ Object

use in plugin ‘config`



170
171
172
# File 'lib/aspera/ascp/installation.rb', line 170

def get_ascp_version(exe_path)
  return get_exe_version(exe_path, '-A')
end

#get_exe_version(exe_path, vers_arg) ⇒ Object

Check that specified path is ascp and get version



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/aspera/ascp/installation.rb', line 175

def get_exe_version(exe_path, vers_arg)
  raise 'ERROR: nil arg' if exe_path.nil?
  return nil unless File.exist?(exe_path)
  exe_version = nil
  cmd_out = %x("#{exe_path}" #{vers_arg})
  raise "An error occurred when testing #{ascp_filename}: #{cmd_out}" unless $CHILD_STATUS == 0
  # get version from ascp, only after full extract, as windows requires DLLs (SSL/TLS/etc...)
  m = cmd_out.match(/ version ([0-9.]+)/)
  exe_version = m[1].gsub(/\.$/, '') unless m.nil?
  return exe_version
end

#install_sdk(sdk_url) ⇒ Object

download aspera SDK or use local file extracts ascp binary for current system architecture

Returns:

  • ascp version (from execution)



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/aspera/ascp/installation.rb', line 240

def install_sdk(sdk_url)
  # SDK is organized by architecture, check this first, in case architecture is not supported
  arch_filter = "#{Environment.architecture}/"
  require 'zip'
  sdk_zip_path = File.join(Dir.tmpdir, 'sdk.zip')
  if sdk_url.start_with?('file:')
    # require specific file scheme: the path part is "relative", or absolute if there are 4 slash
    raise 'use format: file:///<path>' unless sdk_url.start_with?('file:///')
    sdk_zip_path = sdk_url.gsub(%r{^file:///}, '')
  else
    Aspera::Rest.new(base_url: sdk_url, redirect_max: 3).call(operation: 'GET', save_to_file: sdk_zip_path)
  end
  # rename old install
  if !Dir.empty?(sdk_folder)
    Log.log.warn('Previous install exists, renaming folder.')
    File.rename(sdk_folder, "#{sdk_folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
    # TODO: delete old archives ?
  end
  # extract files from archive
  Zip::File.open(sdk_zip_path) do |zip_file|
    zip_file.each do |entry|
      # skip folder entries
      next if entry.name.end_with?('/')
      dest_folder = nil
      # binaries
      dest_folder = sdk_folder if entry.name.include?(arch_filter)
      # ruby adapters
      dest_folder = sdk_ruby_folder if entry.name.end_with?(EXT_RUBY_PROTOBUF)
      next if dest_folder.nil?
      File.open(File.join(dest_folder, File.basename(entry.name)), 'wb') do |output_stream|
        IO.copy_stream(entry.get_input_stream, output_stream)
      end
    end
  end
  File.unlink(sdk_zip_path) rescue nil # Windows may give error
  # ensure license file are generated so that ascp invocation for version works
  path(:aspera_license)
  path(:aspera_conf)
  sdk_ascp_file = Products.ascp_filename
  sdk_ascp_path = File.join(sdk_folder, sdk_ascp_file)
  raise "No #{sdk_ascp_file} found in SDK archive" unless File.exist?(sdk_ascp_path)
  EXE_FILES.each do |exe_sym|
    exe_path = sdk_ascp_path.gsub('ascp', exe_sym.to_s)
    Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
  end
  sdk_ascp_version = get_ascp_version(sdk_ascp_path)
  sdk_daemon_path = transferd_filepath
  Log.log.warn{"No #{sdk_daemon_path} in SDK archive"} unless File.exist?(sdk_daemon_path)
  Environment.restrict_file_access(sdk_daemon_path, mode: 0o755) if File.exist?(sdk_daemon_path)
  transferd_version = get_exe_version(sdk_daemon_path, 'version')
  sdk_name = 'IBM Aspera Transfer SDK'
  sdk_version = transferd_version || sdk_ascp_version
  File.write(File.join(sdk_folder, Products::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
  return sdk_name, sdk_version
end

#path(k) ⇒ Object

get path of one resource file of currently activated product keys and certs are generated locally… (they are well known values, arch. independent)



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/aspera/ascp/installation.rb', line 113

def path(k)
  file_is_optional = false
  case k
  when *EXE_FILES
    file_is_optional = k.eql?(:async)
    use_ascp_from_product(FIRST_FOUND) if @path_to_ascp.nil?
    # NOTE: that there might be a .exe at the end
    file = @path_to_ascp.gsub('ascp', k.to_s)
  when :transferd
    file_is_optional = true
    file = transferd_filepath
  when :ssh_private_dsa, :ssh_private_rsa
    # assume last 3 letters are type
    type = k.to_s[-3..-1].to_sym
    file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") {DataRepository.instance.item(type)}
  when :aspera_license
    file = check_or_create_sdk_file('aspera-license') {DataRepository.instance.item(:license)}
  when :aspera_conf
    file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
  when :fallback_certificate, :fallback_private_key
    file_key = File.join(sdk_folder, 'aspera_fallback_cert_private_key.pem')
    file_cert = File.join(sdk_folder, 'aspera_fallback_cert.pem')
    if !File.exist?(file_key) || !File.exist?(file_cert)
      require 'openssl'
      # create new self signed certificate for http fallback
      cert = OpenSSL::X509::Certificate.new
      private_key = OpenSSL::PKey::RSA.new(4096)
      WebServerSimple.fill_self_signed_cert(cert, private_key)
      check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) {private_key.to_pem}
      check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
    end
    file = k.eql?(:fallback_certificate) ? file_cert : file_key
  else Aspera.error_unexpected_value(k)
  end
  return nil if file_is_optional && !File.exist?(file)
  Aspera.assert(File.exist?(file)){"no such file: #{file}"}
  return file
end

#sdk_folderObject

Returns the path to folder where SDK is installed.

Returns:

  • the path to folder where SDK is installed



75
76
77
78
79
# File 'lib/aspera/ascp/installation.rb', line 75

def sdk_folder
  raise 'SDK path was ot initialized' if @sdk_dir.nil?
  FileUtils.mkdir_p(@sdk_dir)
  @sdk_dir
end

#sdk_folder=(v) ⇒ Object Also known as: folder=

location of SDK files



65
66
67
68
69
# File 'lib/aspera/ascp/installation.rb', line 65

def sdk_folder=(v)
  Log.log.debug{"sdk_folder=#{v}"}
  @sdk_dir = v
  sdk_folder
end

#sdk_ruby_folderObject



58
59
60
61
62
# File 'lib/aspera/ascp/installation.rb', line 58

def sdk_ruby_folder
  ruby_pb_folder = File.join(sdk_folder, RB_SDK_FOLDER)
  FileUtils.mkdir_p(ruby_pb_folder)
  return ruby_pb_folder
end

#ssh_cert_uuidObject

default bypass key phrase



153
154
155
# File 'lib/aspera/ascp/installation.rb', line 153

def ssh_cert_uuid
  return DataRepository.instance.item(:uuid)
end

#use_ascp_from_product(product_name) ⇒ Object

find ascp in named product (use value : FIRST_FOUND=‘FIRST’ to just use first one) or select one from Products.installed_products()



83
84
85
86
87
88
89
90
91
92
93
# File 'lib/aspera/ascp/installation.rb', line 83

def use_ascp_from_product(product_name)
  if product_name.eql?(FIRST_FOUND)
    pl = Products.installed_products.first
    raise "no FASP installation found\nPlease check manual on how to install FASP." if pl.nil?
  else
    pl = Products.installed_products.find{|i|i[:name].eql?(product_name)}
    raise "no such product installed: #{product_name}" if pl.nil?
  end
  self.ascp_path = pl[:ascp_path]
  Log.log.debug{"ascp_path=#{@path_to_ascp}"}
end