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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +81 -7
  4. data/CONTRIBUTING.md +22 -6
  5. data/README.md +2038 -1080
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/dascli +1 -1
  9. data/examples/proxy.pac +1 -1
  10. data/examples/rubyc +24 -0
  11. data/lib/aspera/aoc.rb +219 -159
  12. data/lib/aspera/ascmd.rb +25 -14
  13. data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
  14. data/lib/aspera/cli/error.rb +17 -0
  15. data/lib/aspera/cli/extended_value.rb +47 -12
  16. data/lib/aspera/cli/formatter.rb +260 -179
  17. data/lib/aspera/cli/hints.rb +80 -0
  18. data/lib/aspera/cli/main.rb +104 -156
  19. data/lib/aspera/cli/manager.rb +259 -209
  20. data/lib/aspera/cli/plugin.rb +123 -63
  21. data/lib/aspera/cli/plugins/alee.rb +2 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +341 -261
  23. data/lib/aspera/cli/plugins/ats.rb +22 -21
  24. data/lib/aspera/cli/plugins/bss.rb +5 -5
  25. data/lib/aspera/cli/plugins/config.rb +578 -627
  26. data/lib/aspera/cli/plugins/console.rb +44 -6
  27. data/lib/aspera/cli/plugins/cos.rb +15 -17
  28. data/lib/aspera/cli/plugins/faspex.rb +114 -100
  29. data/lib/aspera/cli/plugins/faspex5.rb +411 -264
  30. data/lib/aspera/cli/plugins/node.rb +354 -259
  31. data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
  32. data/lib/aspera/cli/plugins/preview.rb +82 -90
  33. data/lib/aspera/cli/plugins/server.rb +79 -32
  34. data/lib/aspera/cli/plugins/shares.rb +55 -42
  35. data/lib/aspera/cli/sync_actions.rb +68 -0
  36. data/lib/aspera/cli/transfer_agent.rb +66 -73
  37. data/lib/aspera/cli/transfer_progress.rb +74 -0
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/colors.rb +12 -8
  40. data/lib/aspera/command_line_builder.rb +14 -11
  41. data/lib/aspera/cos_node.rb +3 -2
  42. data/lib/aspera/data/6 +0 -0
  43. data/lib/aspera/environment.rb +24 -9
  44. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  45. data/lib/aspera/fasp/agent_base.rb +31 -77
  46. data/lib/aspera/fasp/agent_connect.rb +25 -21
  47. data/lib/aspera/fasp/agent_direct.rb +89 -103
  48. data/lib/aspera/fasp/agent_httpgw.rb +231 -149
  49. data/lib/aspera/fasp/agent_node.rb +41 -34
  50. data/lib/aspera/fasp/agent_trsdk.rb +75 -32
  51. data/lib/aspera/fasp/error_info.rb +4 -2
  52. data/lib/aspera/fasp/faux_file.rb +52 -0
  53. data/lib/aspera/fasp/installation.rb +53 -195
  54. data/lib/aspera/fasp/management.rb +244 -0
  55. data/lib/aspera/fasp/parameters.rb +71 -37
  56. data/lib/aspera/fasp/parameters.yaml +76 -8
  57. data/lib/aspera/fasp/products.rb +162 -0
  58. data/lib/aspera/fasp/resume_policy.rb +3 -3
  59. data/lib/aspera/fasp/transfer_spec.rb +7 -6
  60. data/lib/aspera/fasp/uri.rb +26 -24
  61. data/lib/aspera/faspex_gw.rb +2 -2
  62. data/lib/aspera/faspex_postproc.rb +2 -2
  63. data/lib/aspera/hash_ext.rb +14 -4
  64. data/lib/aspera/json_rpc.rb +49 -0
  65. data/lib/aspera/keychain/macos_security.rb +13 -13
  66. data/lib/aspera/line_logger.rb +23 -0
  67. data/lib/aspera/log.rb +58 -16
  68. data/lib/aspera/node.rb +157 -92
  69. data/lib/aspera/oauth.rb +37 -19
  70. data/lib/aspera/open_application.rb +4 -4
  71. data/lib/aspera/persistency_action_once.rb +1 -1
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -2
  74. data/lib/aspera/preview/generator.rb +22 -35
  75. data/lib/aspera/preview/options.rb +2 -0
  76. data/lib/aspera/preview/terminal.rb +73 -16
  77. data/lib/aspera/preview/utils.rb +21 -28
  78. data/lib/aspera/proxy_auto_config.js +2 -2
  79. data/lib/aspera/rest.rb +136 -68
  80. data/lib/aspera/rest_call_error.rb +1 -1
  81. data/lib/aspera/rest_error_analyzer.rb +15 -14
  82. data/lib/aspera/rest_errors_aspera.rb +37 -34
  83. data/lib/aspera/secret_hider.rb +18 -15
  84. data/lib/aspera/ssh.rb +5 -2
  85. data/lib/aspera/sync.rb +127 -119
  86. data/lib/aspera/temp_file_manager.rb +10 -3
  87. data/lib/aspera/web_auth.rb +10 -7
  88. data/lib/aspera/web_server_simple.rb +9 -4
  89. data.tar.gz.sig +0 -0
  90. metadata +34 -17
  91. metadata.gz.sig +0 -0
  92. data/docs/test_env.conf +0 -186
  93. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  94. data/lib/aspera/cli/listener/logger.rb +0 -22
  95. data/lib/aspera/cli/listener/progress.rb +0 -50
  96. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  97. data/lib/aspera/cli/plugins/sync.rb +0 -44
  98. data/lib/aspera/data/7 +0 -0
  99. 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
- address: '127.0.0.1',
12
- port: 55_002
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
- raise "expecting Hash (or nil), but have #{user_opts.class}" unless user_opts.nil? || user_opts.is_a?(Hash)
19
- # set default options and override if specified
20
- options = DEFAULT_OPTIONS.dup
21
- user_opts&.each do |k, v|
22
- raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.key?(k)
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
- @transfer_client = Transfersdk::TransferService::Stub.new("#{options[:address]}:#{options[:port]}", :this_channel_is_insecure)
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
- Log.log.warn('no daemon present, starting daemon...')
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
- conf_file = File.join(bin_folder, 'sdk.conf')
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: options[:address],
44
- port: options[: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(conf_file, config.to_json)
54
- trd_pid = Process.spawn(Installation.instance.path(:transferd), '--config', conf_file, out: "#{log_base}.out", err: "#{log_base}.err")
55
- Process.detach(trd_pid)
56
- sleep(2.0)
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
- started = false
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 !started && !response.sessionInfo.preTransferBytes.eql?(0)
83
- notify_begin(@transfer_id, response.sessionInfo.preTransferBytes)
84
- started = true
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
- when :FAILED, :COMPLETED, :CANCELED
89
- notify_end(@transfer_id)
90
- raise Fasp::Error, JSON.parse(response.message)['Description'] unless :COMPLETED.eql?(response.status)
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
- # ignore
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 retryable is not internally defined by protocol, it's client-side responsibility
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 retryable mnemo message additional info
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
- require 'English'
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(symb)" method.
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 specified in product_locations
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
- DUMMY_CERT_INFO = '/C=US/ST=California/L=Emeryville/O=Aspera Inc./OU=Corporate/CN=Aspera Inc./emailAddress=info@asperasoft.com'
46
- private_constant :PRODUCT_CONNECT, :PRODUCT_CLI_V1, :PRODUCT_DRIVE, :PRODUCT_ENTSRV, :EXT_RUBY_PROTOBUF, :RB_SDK_FOLDER,
47
- :ONE_YEAR_SECONDS, :DEFAULT_ASPERA_CONF, :DUMMY_CERT_INFO
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) unless Dir.exist?(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) unless Dir.exist?(@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 the list of installed products in format of product_locations
95
- def installed_products
96
- if @found_products.nil?
97
- scan_locations = product_locations.clone
98
- # add SDK as first search path
99
- scan_locations.unshift({
100
- expected: 'SDK',
101
- app_root: sdk_folder,
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
- # all ascp files (in SDK)
129
- FILES = %i[ascp ascp4 ssh_bypass_key_dsa ssh_bypass_key_rsa aspera_license aspera_conf fallback_cert fallback_key].freeze
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 :ssh_bypass_key_dsa
143
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera_bypass_dsa.pem')) {get_key('dsa', 1)}
144
- when :ssh_bypass_key_rsa
145
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera_bypass_rsa.pem')) {get_key('rsa', 2)}
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 = Environment.write_file_restricted(File.join(sdk_folder, 'aspera-license')) do
148
- clear = [
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 = Environment.write_file_restricted(File.join(sdk_folder, 'aspera.conf')) {DEFAULT_ASPERA_CONF}
157
- when :fallback_cert, :fallback_key
158
- file_key = File.join(sdk_folder, 'aspera_fallback_key.pem')
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
- cert.subject = cert.issuer = OpenSSL::X509::Name.parse(DUMMY_CERT_INFO)
166
- cert.not_before = Time.now
167
- cert.not_after = Time.now + ONE_YEAR_SECONDS
168
- cert.public_key = private_key.public_key
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?(:fallback_cert) ? file_cert : file_key
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 bypass_pass
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 bypass_keys
209
- return %i[ssh_bypass_key_dsa ssh_bypass_key_rsa].map{|i|Installation.instance.path(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
- ascp_path = File.join(sdk_folder, ascp_filename)
272
- raise "No #{ascp_filename} found in SDK archive" unless File.exist?(ascp_path)
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(File.join(sdk_folder, ascp_filename))
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, PRODUCT_INFO), "<product><name>IBM Aspera SDK</name><version>#{sdk_version}</version></product>")
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