aspera-cli 4.13.0 → 4.15.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +81 -7
- data/CONTRIBUTING.md +22 -6
- data/README.md +2038 -1080
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/dascli +1 -1
- data/examples/proxy.pac +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +219 -159
- data/lib/aspera/ascmd.rb +25 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -179
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +104 -156
- data/lib/aspera/cli/manager.rb +259 -209
- data/lib/aspera/cli/plugin.rb +123 -63
- data/lib/aspera/cli/plugins/alee.rb +2 -3
- data/lib/aspera/cli/plugins/aoc.rb +341 -261
- data/lib/aspera/cli/plugins/ats.rb +22 -21
- data/lib/aspera/cli/plugins/bss.rb +5 -5
- data/lib/aspera/cli/plugins/config.rb +578 -627
- data/lib/aspera/cli/plugins/console.rb +44 -6
- data/lib/aspera/cli/plugins/cos.rb +15 -17
- data/lib/aspera/cli/plugins/faspex.rb +114 -100
- data/lib/aspera/cli/plugins/faspex5.rb +411 -264
- data/lib/aspera/cli/plugins/node.rb +354 -259
- data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
- data/lib/aspera/cli/plugins/preview.rb +82 -90
- data/lib/aspera/cli/plugins/server.rb +79 -32
- data/lib/aspera/cli/plugins/shares.rb +55 -42
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +66 -73
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +12 -8
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +24 -9
- data/lib/aspera/fasp/agent_aspera.rb +126 -0
- data/lib/aspera/fasp/agent_base.rb +31 -77
- data/lib/aspera/fasp/agent_connect.rb +25 -21
- data/lib/aspera/fasp/agent_direct.rb +89 -103
- data/lib/aspera/fasp/agent_httpgw.rb +231 -149
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -32
- data/lib/aspera/fasp/error_info.rb +4 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +53 -195
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +71 -37
- data/lib/aspera/fasp/parameters.yaml +76 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +7 -6
- data/lib/aspera/fasp/uri.rb +26 -24
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +14 -4
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +58 -16
- data/lib/aspera/node.rb +157 -92
- data/lib/aspera/oauth.rb +37 -19
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -2
- data/lib/aspera/preview/generator.rb +22 -35
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +73 -16
- data/lib/aspera/preview/utils.rb +21 -28
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +136 -68
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +15 -14
- data/lib/aspera/rest_errors_aspera.rb +37 -34
- data/lib/aspera/secret_hider.rb +18 -15
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/sync.rb +127 -119
- data/lib/aspera/temp_file_manager.rb +10 -3
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +9 -4
- data.tar.gz.sig +0 -0
- metadata +34 -17
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -186
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/data/7 +0 -0
- data/lib/aspera/fasp/listener.rb +0 -13
@@ -3,45 +3,49 @@
|
|
3
3
|
require 'aspera/fasp/agent_base'
|
4
4
|
require 'aspera/fasp/installation'
|
5
5
|
require 'json'
|
6
|
+
require 'uri'
|
6
7
|
|
7
8
|
module Aspera
|
8
9
|
module Fasp
|
9
10
|
class AgentTrsdk < Aspera::Fasp::AgentBase
|
10
11
|
DEFAULT_OPTIONS = {
|
11
|
-
|
12
|
-
|
12
|
+
url: 'grpc://127.0.0.1:0',
|
13
|
+
external: false,
|
14
|
+
keep: false
|
13
15
|
}.freeze
|
14
16
|
private_constant :DEFAULT_OPTIONS
|
15
17
|
|
16
18
|
# options come from transfer_info
|
17
|
-
def initialize(user_opts)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
options[k] = v
|
24
|
-
end
|
25
|
-
Log.log.debug{"options= #{options}"}
|
26
|
-
super()
|
19
|
+
def initialize(user_opts={})
|
20
|
+
super(user_opts)
|
21
|
+
@options = AgentBase.options(default: DEFAULT_OPTIONS, options: user_opts)
|
22
|
+
daemon_uri = URI.parse(@options[:url])
|
23
|
+
raise Fasp::Error, "invalid url #{@options[:url]}" unless daemon_uri.scheme.eql?('grpc')
|
24
|
+
Log.log.debug{Log.dump(:agent_options, @options)}
|
27
25
|
# load and create SDK stub
|
28
26
|
$LOAD_PATH.unshift(Installation.instance.sdk_ruby_folder)
|
29
27
|
require 'transfer_services_pb'
|
30
|
-
|
28
|
+
# it stays
|
29
|
+
@daemon_pid = nil
|
31
30
|
begin
|
31
|
+
@transfer_client = Transfersdk::TransferService::Stub.new("#{daemon_uri.host}:#{daemon_uri.port}", :this_channel_is_insecure)
|
32
32
|
get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
|
33
33
|
Log.log.debug{"daemon info: #{get_info_response}"}
|
34
|
+
Log.log.warn{'attached to existing daemon'} unless @options[:external] || @options[:keep]
|
35
|
+
at_exit{shutdown}
|
34
36
|
rescue GRPC::Unavailable
|
35
|
-
|
37
|
+
raise if @options[:external]
|
38
|
+
raise "daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_uri}}" unless @daemon_pid.nil?
|
39
|
+
Log.log.warn('no daemon present, starting daemon...') if @options[:external]
|
36
40
|
# location of daemon binary
|
37
41
|
bin_folder = File.realpath(File.join(Installation.instance.sdk_ruby_folder, '..'))
|
38
42
|
# config file and logs are created in same folder
|
39
|
-
|
43
|
+
generated_config_file_path = File.join(bin_folder, 'sdk.conf')
|
40
44
|
log_base = File.join(bin_folder, 'transferd')
|
41
45
|
# create a config file for daemon
|
42
46
|
config = {
|
43
|
-
address:
|
44
|
-
port:
|
47
|
+
address: daemon_uri.host,
|
48
|
+
port: daemon_uri.port,
|
45
49
|
fasp_runtime: {
|
46
50
|
use_embedded: false,
|
47
51
|
user_defined: {
|
@@ -50,10 +54,30 @@ module Aspera
|
|
50
54
|
}
|
51
55
|
}
|
52
56
|
}
|
53
|
-
File.write(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
File.write(generated_config_file_path, config.to_json)
|
58
|
+
@daemon_pid = Process.spawn(Installation.instance.path(:transferd), '--config', generated_config_file_path, out: "#{log_base}.out", err: "#{log_base}.err")
|
59
|
+
begin
|
60
|
+
# wait for process to initialize
|
61
|
+
Timeout.timeout(2.0) do
|
62
|
+
_, status = Process.wait2(@daemon_pid)
|
63
|
+
raise "transfer daemon exited with status #{status.exitstatus}. Check files: #{log_base}.out #{log_base}.err"
|
64
|
+
end
|
65
|
+
rescue Timeout::Error
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
Log.log.debug{"daemon started with pid #{@daemon_pid}"}
|
69
|
+
Process.detach(@daemon_pid) if @options[:keep]
|
70
|
+
if daemon_uri.port.eql?(0)
|
71
|
+
# if port is zero, a dynamic port was created, get it
|
72
|
+
File.open("#{log_base}.out", 'r') do |file|
|
73
|
+
file.each_line do |line|
|
74
|
+
if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
|
75
|
+
daemon_uri.port = m[2].to_i
|
76
|
+
# no "break" , need to keep last one
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
57
81
|
retry
|
58
82
|
end
|
59
83
|
end
|
@@ -72,25 +96,34 @@ module Aspera
|
|
72
96
|
end
|
73
97
|
|
74
98
|
def wait_for_transfers_completion
|
75
|
-
|
99
|
+
# set to true when we know the total size of the transfer
|
100
|
+
session_started = false
|
101
|
+
bytes_expected = nil
|
76
102
|
# monitor transfer status
|
77
103
|
@transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
|
78
|
-
Log.dump(:response, response.to_h)
|
104
|
+
Log.log.debug{Log.dump(:response, response.to_h)}
|
79
105
|
# Log.log.debug{"#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}"}
|
80
106
|
case response.status
|
81
107
|
when :RUNNING
|
82
|
-
if !
|
83
|
-
|
84
|
-
|
85
|
-
elsif started
|
86
|
-
notify_progress(@transfer_id, response.transferInfo.bytesTransferred)
|
108
|
+
if !session_started
|
109
|
+
notify_progress(session_id: @transfer_id, type: :session_start)
|
110
|
+
session_started = true
|
87
111
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
112
|
+
if bytes_expected.nil? &&
|
113
|
+
!response.sessionInfo.preTransferBytes.eql?(0)
|
114
|
+
bytes_expected = response.sessionInfo.preTransferBytes
|
115
|
+
notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
|
116
|
+
end
|
117
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: response.transferInfo.bytesTransferred)
|
118
|
+
when :COMPLETED
|
119
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
|
120
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
91
121
|
break
|
122
|
+
when :FAILED, :CANCELED
|
123
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
124
|
+
raise Fasp::Error, JSON.parse(response.message)['Description']
|
92
125
|
when :QUEUED, :UNKNOWN_STATUS, :PAUSED, :ORPHANED
|
93
|
-
|
126
|
+
notify_progress(session_id: nil, type: :pre_start, info: response.status.to_s.downcase)
|
94
127
|
else
|
95
128
|
Log.log.error{"unknown status#{response.status}"}
|
96
129
|
end
|
@@ -98,6 +131,16 @@ module Aspera
|
|
98
131
|
# TODO: return status
|
99
132
|
return []
|
100
133
|
end
|
134
|
+
|
135
|
+
def shutdown
|
136
|
+
if !@options[:keep] && !@daemon_pid.nil?
|
137
|
+
Log.log.debug("stopping daemon #{@daemon_pid}")
|
138
|
+
Process.kill('INT', @daemon_pid)
|
139
|
+
_, status = Process.wait2(@daemon_pid)
|
140
|
+
Log.log.debug("daemon stopped #{status}")
|
141
|
+
@daemon_pid = nil
|
142
|
+
end
|
143
|
+
end
|
101
144
|
end
|
102
145
|
end
|
103
146
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# cspell:words mnemo PROTO RCVR NOLIC PMTU BRTT VLINK BWMEAS SSEAR FIPS
|
4
|
+
|
3
5
|
module Aspera
|
4
6
|
module Fasp
|
5
7
|
# from https://www.google.com/search?q=FASP+error+codes
|
6
|
-
# Note that the fact that an error is
|
8
|
+
# Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
|
7
9
|
# rubocop:disable Layout/MultilineHashKeyLineBreaks
|
8
10
|
# rubocop:disable Layout/FirstHashElementLineBreak
|
9
11
|
ERROR_INFO = {
|
10
|
-
# id
|
12
|
+
# id retry-able mnemo message additional info
|
11
13
|
1 => { r: false, c: 'FASP_PROTO', m: 'Generic fasp(tm) protocol error', a: 'fasp(tm) error'},
|
12
14
|
2 => { r: false, c: 'ASCP', m: 'Generic SCP error', a: 'ASCP error'},
|
13
15
|
3 => { r: false, c: 'AMBIGUOUS_TARGET', m: 'Target incorrectly specified', a: 'Ambiguous target'},
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aspera
|
4
|
+
module Fasp
|
5
|
+
# generates a pseudo file stream
|
6
|
+
class FauxFile
|
7
|
+
# marker for faux file
|
8
|
+
PREFIX = 'faux:///'
|
9
|
+
# size suffix
|
10
|
+
SUFFIX = %w[k m g t p e]
|
11
|
+
class << self
|
12
|
+
def open(name)
|
13
|
+
return nil unless name.start_with?(PREFIX)
|
14
|
+
parts = name[PREFIX.length..-1].split('?')
|
15
|
+
raise 'Format: #{PREFIX}<file path>?<size>' unless parts.length.eql?(2)
|
16
|
+
raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = parts[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
|
17
|
+
size = m[1].to_i
|
18
|
+
suffix = m[2]
|
19
|
+
SUFFIX.each do |s|
|
20
|
+
size *= 1024
|
21
|
+
break if s.eql?(suffix)
|
22
|
+
end
|
23
|
+
return FauxFile.new(parts[0], size)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
attr_reader :path, :size
|
27
|
+
|
28
|
+
def initialize(path, size)
|
29
|
+
@path = path
|
30
|
+
@size = size
|
31
|
+
@offset = 0
|
32
|
+
# we cache large chunks, anyway most of them will be the same size
|
33
|
+
@chunk_by_size = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def read(chunk_size)
|
37
|
+
return nil if eof?
|
38
|
+
bytes_to_read = [chunk_size, @size - @offset].min
|
39
|
+
@offset += bytes_to_read
|
40
|
+
@chunk_by_size[bytes_to_read] = "\x00" * bytes_to_read unless @chunk_by_size.key?(bytes_to_read)
|
41
|
+
return @chunk_by_size[bytes_to_read]
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
end
|
46
|
+
|
47
|
+
def eof?
|
48
|
+
return @offset >= @size
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'singleton'
|
5
|
-
require 'aspera/log'
|
3
|
+
# cspell:ignore protobuf ckpt
|
6
4
|
require 'aspera/environment'
|
7
5
|
require 'aspera/data_repository'
|
6
|
+
require 'aspera/fasp/products'
|
7
|
+
require 'aspera/log'
|
8
|
+
require 'aspera/web_server_simple'
|
9
|
+
require 'English'
|
10
|
+
require 'singleton'
|
8
11
|
require 'xmlsimple'
|
9
12
|
require 'zlib'
|
10
13
|
require 'base64'
|
@@ -13,24 +16,18 @@ require 'openssl'
|
|
13
16
|
|
14
17
|
module Aspera
|
15
18
|
module Fasp
|
16
|
-
# Singleton that tells where to find ascp and other local resources (keys..) , using the "path(
|
19
|
+
# Singleton that tells where to find ascp and other local resources (keys..) , using the "path(:name)" method.
|
17
20
|
# It is used by object : AgentDirect to find necessary resources
|
18
|
-
# By default it takes the first Aspera product found
|
21
|
+
# By default it takes the first Aspera product found
|
19
22
|
# but the user can specify ascp location by calling:
|
20
23
|
# Installation.instance.use_ascp_from_product(product_name)
|
21
24
|
# or
|
22
25
|
# Installation.instance.ascp_path=""
|
23
26
|
class Installation
|
24
27
|
include Singleton
|
25
|
-
# known product names
|
26
|
-
PRODUCT_CONNECT = 'Aspera Connect'
|
27
|
-
PRODUCT_CLI_V1 = 'Aspera CLI'
|
28
|
-
PRODUCT_DRIVE = 'Aspera Drive'
|
29
|
-
PRODUCT_ENTSRV = 'Enterprise Server'
|
30
28
|
# protobuf generated files from sdk
|
31
29
|
EXT_RUBY_PROTOBUF = '_pb.rb'
|
32
30
|
RB_SDK_FOLDER = 'lib'
|
33
|
-
ONE_YEAR_SECONDS = 365 * 24 * 60 * 60
|
34
31
|
DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
|
35
32
|
<?xml version='1.0' encoding='UTF-8'?>
|
36
33
|
<CONF version="2">
|
@@ -42,9 +39,9 @@ module Aspera
|
|
42
39
|
</default>
|
43
40
|
</CONF>
|
44
41
|
END_OF_CONFIG_FILE
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
# all ascp files (in SDK)
|
43
|
+
FILES = %i[ascp ascp4 transferd ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].freeze
|
44
|
+
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_FOLDER, :DEFAULT_ASPERA_CONF, :FILES
|
48
45
|
# set ascp executable path
|
49
46
|
def ascp_path=(v)
|
50
47
|
@path_to_ascp = v
|
@@ -56,7 +53,7 @@ module Aspera
|
|
56
53
|
|
57
54
|
def sdk_ruby_folder
|
58
55
|
ruby_pb_folder = File.join(sdk_folder, RB_SDK_FOLDER)
|
59
|
-
FileUtils.mkdir_p(ruby_pb_folder)
|
56
|
+
FileUtils.mkdir_p(ruby_pb_folder)
|
60
57
|
return ruby_pb_folder
|
61
58
|
end
|
62
59
|
|
@@ -73,60 +70,39 @@ module Aspera
|
|
73
70
|
# @return the path to folder where SDK is installed
|
74
71
|
def sdk_folder
|
75
72
|
raise 'SDK path was ot initialized' if @sdk_dir.nil?
|
76
|
-
FileUtils.mkdir_p(@sdk_dir)
|
73
|
+
FileUtils.mkdir_p(@sdk_dir)
|
77
74
|
@sdk_dir
|
78
75
|
end
|
79
76
|
|
80
77
|
# find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
|
81
|
-
# or select one from installed_products()
|
78
|
+
# or select one from Products.installed_products()
|
82
79
|
def use_ascp_from_product(product_name)
|
83
80
|
if product_name.eql?(FIRST_FOUND)
|
84
|
-
pl = installed_products.first
|
81
|
+
pl = Products.installed_products.first
|
85
82
|
raise "no FASP installation found\nPlease check manual on how to install FASP." if pl.nil?
|
86
83
|
else
|
87
|
-
pl = installed_products.find{|i|i[:name].eql?(product_name)}
|
84
|
+
pl = Products.installed_products.find{|i|i[:name].eql?(product_name)}
|
88
85
|
raise "no such product installed: #{product_name}" if pl.nil?
|
89
86
|
end
|
90
87
|
self.ascp_path = pl[:ascp_path]
|
91
88
|
Log.log.debug{"ascp_path=#{@path_to_ascp}"}
|
92
89
|
end
|
93
90
|
|
94
|
-
# @return
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
sub_bin: ''
|
103
|
-
})
|
104
|
-
# search installed products: with ascp
|
105
|
-
@found_products = scan_locations.select! do |item|
|
106
|
-
# skip if not main folder
|
107
|
-
next false unless Dir.exist?(item[:app_root])
|
108
|
-
Log.log.debug{"Found #{item[:app_root]}"}
|
109
|
-
sub_bin = item[:sub_bin] || BIN_SUBFOLDER
|
110
|
-
item[:ascp_path] = File.join(item[:app_root], sub_bin, ascp_filename)
|
111
|
-
# skip if no ascp
|
112
|
-
next false unless File.exist?(item[:ascp_path])
|
113
|
-
# read info from product info file if present
|
114
|
-
product_info_file = "#{item[:app_root]}/#{PRODUCT_INFO}"
|
115
|
-
if File.exist?(product_info_file)
|
116
|
-
res_s = XmlSimple.xml_in(File.read(product_info_file), {'ForceArray' => false})
|
117
|
-
item[:name] = res_s['name']
|
118
|
-
item[:version] = res_s['version']
|
119
|
-
else
|
120
|
-
item[:name] = item[:expected]
|
91
|
+
# @return [Hash] with key = file name (String), and value = path to file
|
92
|
+
def file_paths
|
93
|
+
return FILES.each_with_object({}) do |v, m|
|
94
|
+
m[v.to_s] =
|
95
|
+
begin
|
96
|
+
path(v)
|
97
|
+
rescue => e
|
98
|
+
e.message
|
121
99
|
end
|
122
|
-
true # select this version
|
123
|
-
end
|
124
100
|
end
|
125
|
-
return @found_products
|
126
101
|
end
|
127
102
|
|
128
|
-
|
129
|
-
|
103
|
+
def check_or_create_sdk_file(filename, force: false, &block)
|
104
|
+
return Environment.write_file_restricted(File.join(sdk_folder, filename), force: force, mode: 0o644, &block)
|
105
|
+
end
|
130
106
|
|
131
107
|
# get path of one resource file of currently activated product
|
132
108
|
# keys and certs are generated locally... (they are well known values, arch. independent)
|
@@ -139,40 +115,32 @@ module Aspera
|
|
139
115
|
file = file.gsub('ascp', 'ascp4') if k.eql?(:ascp4)
|
140
116
|
when :transferd
|
141
117
|
file = transferd_filepath
|
142
|
-
when :
|
143
|
-
|
144
|
-
|
145
|
-
file =
|
118
|
+
when :ssh_private_dsa, :ssh_private_rsa
|
119
|
+
# assume last 3 letters are type
|
120
|
+
type = k.to_s[-3..-1]
|
121
|
+
file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") do
|
122
|
+
# generate PEM from DER
|
123
|
+
OpenSSL::PKey.const_get(type.upcase).new(DataRepository.instance.data(type.eql?('dsa') ? 1 : 2)).to_pem
|
124
|
+
end
|
146
125
|
when :aspera_license
|
147
|
-
file =
|
148
|
-
|
149
|
-
Zlib::Inflate.inflate(DataRepository.instance.data(6)),
|
150
|
-
"==SIGNATURE==\n",
|
151
|
-
Base64.strict_encode64(DataRepository.instance.data(7))
|
152
|
-
]
|
153
|
-
Base64.strict_encode64(clear.join)
|
126
|
+
file = check_or_create_sdk_file('aspera-license') do
|
127
|
+
Zlib::Inflate.inflate(DataRepository.instance.data(6))
|
154
128
|
end
|
155
129
|
when :aspera_conf
|
156
|
-
file =
|
157
|
-
when :
|
158
|
-
file_key = File.join(sdk_folder, '
|
130
|
+
file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
|
131
|
+
when :fallback_certificate, :fallback_private_key
|
132
|
+
file_key = File.join(sdk_folder, 'aspera_fallback_cert_private_key.pem')
|
159
133
|
file_cert = File.join(sdk_folder, 'aspera_fallback_cert.pem')
|
160
134
|
if !File.exist?(file_key) || !File.exist?(file_cert)
|
161
135
|
require 'openssl'
|
162
136
|
# create new self signed certificate for http fallback
|
163
|
-
private_key = OpenSSL::PKey::RSA.new(1024)
|
164
137
|
cert = OpenSSL::X509::Certificate.new
|
165
|
-
|
166
|
-
cert
|
167
|
-
|
168
|
-
|
169
|
-
cert.serial = 0x0
|
170
|
-
cert.version = 2
|
171
|
-
cert.sign(private_key, OpenSSL::Digest.new('SHA1'))
|
172
|
-
Environment.write_file_restricted(file_key, force: true) {private_key.to_pem}
|
173
|
-
Environment.write_file_restricted(file_cert, force: true) {cert.to_pem}
|
138
|
+
private_key = OpenSSL::PKey::RSA.new(4096)
|
139
|
+
WebServerSimple.fill_self_signed_cert(cert, private_key)
|
140
|
+
check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) {private_key.to_pem}
|
141
|
+
check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
|
174
142
|
end
|
175
|
-
file = k.eql?(:
|
143
|
+
file = k.eql?(:fallback_certificate) ? file_cert : file_key
|
176
144
|
else
|
177
145
|
raise "INTERNAL ERROR: #{k}"
|
178
146
|
end
|
@@ -180,33 +148,13 @@ module Aspera
|
|
180
148
|
return file
|
181
149
|
end
|
182
150
|
|
183
|
-
# @return the file path of local connect where API's URI can be read
|
184
|
-
def connect_uri
|
185
|
-
connect = get_product_folders(PRODUCT_CONNECT)
|
186
|
-
folder = File.join(connect[:run_root], VAR_RUN_SUBFOLDER)
|
187
|
-
['', 's'].each do |ext|
|
188
|
-
uri_file = File.join(folder, "http#{ext}.uri")
|
189
|
-
Log.log.debug{"checking connect port file: #{uri_file}"}
|
190
|
-
if File.exist?(uri_file)
|
191
|
-
return File.open(uri_file, &:gets).strip
|
192
|
-
end
|
193
|
-
end
|
194
|
-
raise "no connect uri file found in #{folder}"
|
195
|
-
end
|
196
|
-
|
197
|
-
# @ return path to configuration file of aspera CLI
|
198
|
-
def cli_conf_file
|
199
|
-
connect = get_product_folders(PRODUCT_CLI_V1)
|
200
|
-
return File.join(connect[:app_root], BIN_SUBFOLDER, '.aspera_cli_conf')
|
201
|
-
end
|
202
|
-
|
203
151
|
# default bypass key phrase
|
204
|
-
def
|
152
|
+
def ssh_cert_uuid
|
205
153
|
return format('%08x-%04x-%04x-%04x-%04x%08x', *DataRepository.instance.data(3).unpack('NnnnnN'))
|
206
154
|
end
|
207
155
|
|
208
|
-
def
|
209
|
-
return %i[
|
156
|
+
def aspera_token_ssh_key_paths
|
157
|
+
return %i[ssh_private_dsa ssh_private_rsa].map{|i|Installation.instance.path(i)}
|
210
158
|
end
|
211
159
|
|
212
160
|
# use in plugin `config`
|
@@ -268,123 +216,33 @@ module Aspera
|
|
268
216
|
# ensure license file are generated so that ascp invocation for version works
|
269
217
|
path(:aspera_license)
|
270
218
|
path(:aspera_conf)
|
271
|
-
|
272
|
-
|
219
|
+
ascp_file = Products.ascp_filename
|
220
|
+
ascp_path = File.join(sdk_folder, ascp_file)
|
221
|
+
raise "No #{ascp_file} found in SDK archive" unless File.exist?(ascp_path)
|
273
222
|
Environment.restrict_file_access(ascp_path, mode: 0o755)
|
274
223
|
Environment.restrict_file_access(ascp_path.gsub('ascp', 'ascp4'), mode: 0o755)
|
275
|
-
ascp_version = get_ascp_version(
|
224
|
+
ascp_version = get_ascp_version(ascp_path)
|
276
225
|
trd_path = transferd_filepath
|
277
226
|
Log.log.warn{"No #{trd_path} in SDK archive"} unless File.exist?(trd_path)
|
278
227
|
Environment.restrict_file_access(trd_path, mode: 0o755) if File.exist?(trd_path)
|
279
228
|
transferd_version = get_exe_version(trd_path, 'version')
|
280
229
|
sdk_version = transferd_version || ascp_version
|
281
|
-
File.write(File.join(sdk_folder,
|
230
|
+
File.write(File.join(sdk_folder, Products::INFO_META_FILE), "<product><name>IBM Aspera SDK</name><version>#{sdk_version}</version></product>")
|
282
231
|
return sdk_version
|
283
232
|
end
|
284
233
|
|
285
234
|
private
|
286
235
|
|
287
|
-
BIN_SUBFOLDER = 'bin'
|
288
|
-
ETC_SUBFOLDER = 'etc'
|
289
|
-
VAR_RUN_SUBFOLDER = File.join('var', 'run')
|
290
|
-
# product information manifest: XML (part of aspera product)
|
291
|
-
PRODUCT_INFO = 'product-info.mf'
|
292
236
|
# policy for product selection
|
293
237
|
FIRST_FOUND = 'FIRST'
|
294
238
|
|
295
|
-
private_constant :BIN_SUBFOLDER, :ETC_SUBFOLDER, :VAR_RUN_SUBFOLDER, :PRODUCT_INFO
|
296
|
-
|
297
239
|
def initialize
|
298
240
|
@path_to_ascp = nil
|
299
241
|
@sdk_dir = nil
|
300
|
-
@found_products = nil
|
301
|
-
end
|
302
|
-
|
303
|
-
# @return folder paths for specified applications
|
304
|
-
# @param name Connect or CLI
|
305
|
-
def get_product_folders(name)
|
306
|
-
found = installed_products.select{|i|i[:expected].eql?(name) || i[:name].eql?(name)}
|
307
|
-
raise "Product: #{name} not found, please install." if found.empty?
|
308
|
-
return found.first
|
309
|
-
end
|
310
|
-
|
311
|
-
# filename for ascp with optional extension (Windows)
|
312
|
-
def ascp_filename
|
313
|
-
return 'ascp' + Environment.exe_extension
|
314
242
|
end
|
315
243
|
|
316
244
|
def transferd_filepath
|
317
|
-
return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension)
|
318
|
-
end
|
319
|
-
|
320
|
-
# @return product folders depending on OS fields
|
321
|
-
# :expected M app name is taken from the manifest if present, else defaults to this value
|
322
|
-
# :app_root M main folder for the application
|
323
|
-
# :log_root O location of log files (Linux uses syslog)
|
324
|
-
# :run_root O only for Connect Client, location of http port file
|
325
|
-
# :sub_bin O subfolder with executables, default : bin
|
326
|
-
def product_locations
|
327
|
-
case Aspera::Environment.os
|
328
|
-
when Aspera::Environment::OS_WINDOWS; return [{
|
329
|
-
expected: PRODUCT_CONNECT,
|
330
|
-
app_root: File.join(ENV['LOCALAPPDATA'], 'Programs', 'Aspera', 'Aspera Connect'),
|
331
|
-
log_root: File.join(ENV['LOCALAPPDATA'], 'Aspera', 'Aspera Connect', 'var', 'log'),
|
332
|
-
run_root: File.join(ENV['LOCALAPPDATA'], 'Aspera', 'Aspera Connect')
|
333
|
-
}, {
|
334
|
-
expected: PRODUCT_CLI_V1,
|
335
|
-
app_root: File.join('C:', 'Program Files', 'Aspera', 'cli'),
|
336
|
-
log_root: File.join('C:', 'Program Files', 'Aspera', 'cli', 'var', 'log')
|
337
|
-
}, {
|
338
|
-
expected: PRODUCT_ENTSRV,
|
339
|
-
app_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server'),
|
340
|
-
log_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server', 'var', 'log')
|
341
|
-
}]
|
342
|
-
when Aspera::Environment::OS_X; return [{
|
343
|
-
expected: PRODUCT_CONNECT,
|
344
|
-
app_root: File.join(Dir.home, 'Applications', 'Aspera Connect.app'),
|
345
|
-
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
|
346
|
-
run_root: File.join(Dir.home, 'Library', 'Application Support', 'Aspera', 'Aspera Connect'),
|
347
|
-
sub_bin: File.join('Contents', 'Resources')
|
348
|
-
}, {
|
349
|
-
expected: PRODUCT_CONNECT,
|
350
|
-
app_root: File.join('', 'Applications', 'Aspera Connect.app'),
|
351
|
-
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
|
352
|
-
run_root: File.join(Dir.home, 'Library', 'Application Support', 'Aspera', 'Aspera Connect'),
|
353
|
-
sub_bin: File.join('Contents', 'Resources')
|
354
|
-
}, {
|
355
|
-
expected: PRODUCT_CLI_V1,
|
356
|
-
app_root: File.join(Dir.home, 'Applications', 'Aspera CLI'),
|
357
|
-
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera')
|
358
|
-
}, {
|
359
|
-
expected: PRODUCT_ENTSRV,
|
360
|
-
app_root: File.join('', 'Library', 'Aspera'),
|
361
|
-
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera')
|
362
|
-
}, {
|
363
|
-
expected: PRODUCT_DRIVE,
|
364
|
-
app_root: File.join('', 'Applications', 'Aspera Drive.app'),
|
365
|
-
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Drive'),
|
366
|
-
sub_bin: File.join('Contents', 'Resources')
|
367
|
-
}]
|
368
|
-
else; return [{ # other: Linux and Unix family
|
369
|
-
expected: PRODUCT_CONNECT,
|
370
|
-
app_root: File.join(Dir.home, '.aspera', 'connect'),
|
371
|
-
run_root: File.join(Dir.home, '.aspera', 'connect')
|
372
|
-
}, {
|
373
|
-
expected: PRODUCT_CLI_V1,
|
374
|
-
app_root: File.join(Dir.home, '.aspera', 'cli')
|
375
|
-
}, {
|
376
|
-
expected: PRODUCT_ENTSRV,
|
377
|
-
app_root: File.join('', 'opt', 'aspera')
|
378
|
-
}]
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
# @return a standard bypass key
|
383
|
-
# @param type rsa or dsa
|
384
|
-
# @param id in repository 1 for dsa, 2 for rsa
|
385
|
-
def get_key(type, id)
|
386
|
-
# generate PEM from DER
|
387
|
-
OpenSSL::PKey.const_get(type.upcase).new(DataRepository.instance.data(id)).to_pem
|
245
|
+
return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension) # cspell:disable-line
|
388
246
|
end
|
389
247
|
end # Installation
|
390
248
|
end
|