Skip to content

Commit

Permalink
split agent / product
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent-martin committed Feb 19, 2025
1 parent 6fe5855 commit 3bb2aa4
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 262 deletions.
4 changes: 2 additions & 2 deletions bin/asession
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Kernel.load(File.join(gem_lib_folder, 'aspera/coverage.rb'))
$LOAD_PATH.unshift(gem_lib_folder)
require 'aspera/agent/direct'
require 'aspera/cli/extended_value'
require 'aspera/ascp/installation'
require 'aspera/products/trsdk'
require 'aspera/log'
require 'json'
# extended transfer spec parameter (only used in asession)
Expand Down Expand Up @@ -77,7 +77,7 @@ if session_spec.key?(PARAM_TMP_FILE_LIST_FOLDER)
Aspera::Transfer::Parameters.file_list_folder = session_spec[PARAM_TMP_FILE_LIST_FOLDER]
end
session_spec[PARAM_SDK] = File.join(Dir.home, '.aspera', 'sdk') unless session_spec.key?(PARAM_SDK)
Aspera::Ascp::Installation.instance.sdk_folder = session_spec[PARAM_SDK]
Aspera::Products::Trsdk.sdk_directory = session_spec[PARAM_SDK]
session_spec[PARAM_AGENT] = {} unless session_spec.key?(PARAM_AGENT)
agent_params = session_spec[PARAM_AGENT]
agent_params['quiet'] = true
Expand Down
19 changes: 7 additions & 12 deletions lib/aspera/agent/alpha.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

require 'aspera/agent/base'
require 'aspera/rest'
require 'aspera/log'
require 'aspera/json_rpc'
require 'aspera/environment'
require 'aspera/json_rpc'
require 'aspera/products/alpha'
require 'securerandom'

module Aspera
Expand All @@ -15,9 +15,8 @@ class Alpha < Base
START_URIS = ['aspera://', 'aspera://', 'aspera://']
# delay between each try to start the app
SLEEP_SEC_BETWEEN_RETRY = 5
APP_IDENTIFIER = 'com.ibm.software.aspera.desktop'
APP_NAME = 'Aspera Desktop Alpha Client'
private_constant :START_URIS, :SLEEP_SEC_BETWEEN_RETRY

def initialize(**base_options)
@application_id = SecureRandom.uuid
@xfer_id = nil
Expand All @@ -34,23 +33,19 @@ def initialize(**base_options)
rescue Errno::ECONNREFUSED => e
start_url = START_URIS[method_index]
method_index += 1
raise StandardError, "Unable to start #{APP_NAME} #{method_index} times" if start_url.nil?
Log.log.warn{"#{APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
raise StandardError, "Unable to start #{Products::Alpha::APP_NAME} #{method_index} times" if start_url.nil?
Log.log.warn{"#{Products::Alpha::APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
if !Environment.open_uri_graphical(start_url)
Environment.open_uri_graphical('https://www.ibm.com/aspera/connect/')
raise StandardError, "#{APP_NAME} is not installed"
raise StandardError, "#{Products::Alpha::APP_NAME} is not installed"
end
sleep(SLEEP_SEC_BETWEEN_RETRY)
retry
end
end

def sdk_log_file
File.join(Dir.home, 'Library', 'Logs', APP_IDENTIFIER, 'ibm-aspera-desktop.log')
end

def aspera_client_api_url
log_file = sdk_log_file
log_file = Products::Alpha.log_file
url = nil
File.open(log_file, 'r') do |file|
file.each_line do |line|
Expand Down
20 changes: 19 additions & 1 deletion lib/aspera/agent/connect.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# frozen_string_literal: true

require 'aspera/agent/base'
require 'aspera/products/connect'
require 'aspera/products/other'
require 'aspera/rest'
require 'aspera/environment'
require 'securerandom'

module Aspera
Expand All @@ -20,7 +23,7 @@ def initialize(**base_options)
raise 'Using connect requires a graphical environment' if !Environment.default_gui_mode.eql?(:graphical)
method_index = 0
begin
connect_url = Ascp::Products.connect_uri
connect_url = connect_api_url
Log.log.debug{"found: #{connect_url}"}
@connect_api = Rest.new(
base_url: "#{connect_url}/v5/connect", # could use v6 also now
Expand All @@ -43,6 +46,21 @@ def initialize(**base_options)
end
end

# @return the file path of local connect where API's URI can be read
def connect_api_url
connect_locations = Products::Other.find(Products::Connect.locations).first
raise "Product: #{name} not found, please install." if connect_locations.nil?
folder = File.join(connect_locations[:run_root], 'var', 'run')
['', 's'].each do |ext|
uri_file = File.join(folder, "http#{ext}.uri")
Log.log.debug{"checking connect port file: #{uri_file}"}
if File.exist?(uri_file)
return File.open(uri_file, &:gets).strip
end
end
raise "no connect uri file found in #{folder}"
end

def start_transfer(transfer_spec, token_regenerator: nil)
if transfer_spec['direction'] == 'send'
Log.log.warn{"Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red}
Expand Down
29 changes: 4 additions & 25 deletions lib/aspera/agent/trsdk.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# frozen_string_literal: true

require 'aspera/agent/base'
require 'aspera/ascp/installation'
require 'aspera/products/trsdk'
require 'aspera/temp_file_manager'
require 'aspera/log'
require 'aspera/assert'
require 'json'
require 'uri'
require 'transfer_services_pb'
Expand All @@ -18,25 +16,6 @@ class Trsdk < Base
PORT_SEP = ':'
# port zero means select a random available high port
AUTO_LOCAL_TCP_PORT = "#{PORT_SEP}0"
class << self
# Well, the port number is only in log file
def daemon_port_from_log(log_file)
result = nil
# if port is zero, a dynamic port was created, get it
File.open(log_file, 'r') do |file|
file.each_line do |line|
# Well, it's tricky to depend on log
if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
result = m[2].to_i
# no "break" , need to read last matching log line
end
end
end
raise 'Port not found in daemon logs' if result.nil?
Log.log.debug{"Got port #{result} from log"}
return result
end
end

# @param url [String] URL of the transfer manager daemon
# @param external [Boolean] if true, expect that an external daemon is already running
Expand Down Expand Up @@ -83,8 +62,8 @@ def initialize(
fasp_runtime: {
use_embedded: false,
user_defined: {
bin: Ascp::Installation.instance.sdk_folder,
etc: Ascp::Installation.instance.sdk_folder
bin: Products::Trsdk.sdk_directory,
etc: Products::Trsdk.sdk_directory
}
}
}
Expand All @@ -110,7 +89,7 @@ def initialize(
Process.detach(@daemon_pid) if @keep
at_exit {shutdown}
# update port for next connection attempt (if auto high port was requested)
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{self.class.daemon_port_from_log(log_stdout)}" if is_local_auto_port
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{Products::Trsdk.daemon_port_from_log(log_stdout)}" if is_local_auto_port
# local daemon started, try again
retry
end
Expand Down
70 changes: 40 additions & 30 deletions lib/aspera/ascp/installation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
require 'aspera/web_server_simple'
require 'aspera/cli/info'
require 'aspera/cli/version'
require 'aspera/products/alpha'
require 'aspera/products/connect'
require 'aspera/products/trsdk'
require 'aspera/products/other'
require 'English'
require 'singleton'
require 'xmlsimple'
Expand All @@ -21,7 +25,7 @@ module Ascp
# 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:
# The user can specify ascp location by calling:
# Installation.instance.use_ascp_from_product(product_name)
# or
# Installation.instance.ascp_path=""
Expand All @@ -47,11 +51,10 @@ class Installation
TRANSFER_SDK_LOCATION_URL = 'https://ibm.biz/sdk_location'
FILE_SCHEME_PREFIX = 'file:///'
SDK_ARCHIVE_FOLDERS = ['/bin/', '/aspera/'].freeze
# filename for ascp with optional extension (Windows)
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_SUBFOLDER, :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL, :FILE_SCHEME_PREFIX
# options for SSH client private key
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
# product information manifest: XML (part of aspera product)
INFO_META_FILE = 'product-info.mf'

# set ascp executable path
def ascp_path=(v)
Expand All @@ -65,31 +68,19 @@ def ascp_path
path(:ascp)
end

# location of SDK files
# Compatibility
def sdk_folder=(v)
Log.log.debug{"sdk_folder=#{v}"}
@sdk_dir = v
sdk_folder
end

# backward compatibility in sample program
alias_method :folder=, :sdk_folder=

# @return the path to folder where SDK is installed
def sdk_folder
Aspera.assert(!@sdk_dir.nil?){'SDK path was not initialized'}
FileUtils.mkdir_p(@sdk_dir)
@sdk_dir
Products::Trsdk.sdk_directory = v
end

# find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
# or select one from Products.installed_products()
# or select one from installed_products()
def use_ascp_from_product(product_name)
if product_name.eql?(FIRST_FOUND)
pl = Products.installed_products.first
pl = installed_products.first
raise "no Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf ascp install" if pl.nil?
else
pl = Products.installed_products.find{|i|i[:name].eql?(product_name)}
pl = 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]
Expand All @@ -109,7 +100,7 @@ def file_paths
end

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)
return Environment.write_file_restricted(File.join(Products::Trsdk.sdk_directory, filename), force: force, mode: 0o644, &block)
end

# get path of one resource file of currently activated product
Expand All @@ -124,7 +115,7 @@ def path(k)
file = @path_to_ascp.gsub('ascp', k.to_s)
when :transferd
file_is_optional = true
file = transferd_filepath
file = Products::Trsdk.transferd_path
when :ssh_private_dsa, :ssh_private_rsa
# assume last 3 letters are type
type = k.to_s[-3..-1].to_sym
Expand All @@ -134,8 +125,8 @@ def path(k)
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')
file_key = File.join(Products::Trsdk.sdk_directory, 'aspera_fallback_cert_private_key.pem')
file_cert = File.join(Products::Trsdk.sdk_directory, 'aspera_fallback_cert.pem')
if !File.exist?(file_key) || !File.exist?(file_cert)
require 'openssl'
# create new self signed certificate for http fallback
Expand Down Expand Up @@ -309,7 +300,7 @@ def extract_archive_files(sdk_archive_path)
# @return ascp version (from execution)
def install_sdk(url: nil, folder: nil, backup: true, with_exe: true, &block)
url = sdk_url_for_platform if url.nil? || url.eql?('DEF')
folder = sdk_folder if folder.nil?
folder = Products::Trsdk.sdk_directory if folder.nil?
subfolder_lambda = block
if subfolder_lambda.nil?
subfolder_lambda = ->(name) do
Expand Down Expand Up @@ -353,21 +344,21 @@ def install_sdk(url: nil, folder: nil, backup: true, with_exe: true, &block)
# 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_file = Environment.exe_file('ascp')
sdk_ascp_path = File.join(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
sdk_daemon_path = Products::Trsdk.transferd_path
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(folder, INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
return sdk_name, sdk_version
end

Expand All @@ -379,10 +370,29 @@ def install_sdk(url: nil, folder: nil, backup: true, with_exe: true, &block)
def initialize
@path_to_ascp = nil
@sdk_dir = nil
@found_products = nil
end

def transferd_filepath
return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension) # cspell:disable-line
public

# @return the list of installed products in format of product_locations_on_current_os
def installed_products
if @found_products.nil?
# :expected M app name is taken from the manifest if present, else defaults to this value
# :app_root M main folder for the application
# :log_root O location of log files (Linux uses syslog)
# :run_root O only for Connect Client, location of http port file
# :sub_bin O subfolder with executables, default : bin
scan_locations = Products::Trsdk.locations.concat(
Products::Alpha.locations,
Products::Connect.locations,
Products::Other::LOCATION_ON_THIS_OS
)
# .each {|item| item.deep_do {|h, _k, _v, _m|h.freeze}}.freeze
# search installed products: with ascp
@found_products = Products::Other.find(scan_locations)
end
return @found_products
end
end
end
Expand Down
Loading

0 comments on commit 3bb2aa4

Please sign in to comment.