aspera-cli 4.14.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 (90) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +54 -3
  4. data/CONTRIBUTING.md +7 -7
  5. data/README.md +1457 -880
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/proxy.pac +1 -1
  9. data/lib/aspera/aoc.rb +198 -127
  10. data/lib/aspera/ascmd.rb +24 -14
  11. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  12. data/lib/aspera/cli/error.rb +17 -0
  13. data/lib/aspera/cli/extended_value.rb +47 -12
  14. data/lib/aspera/cli/formatter.rb +260 -171
  15. data/lib/aspera/cli/hints.rb +80 -0
  16. data/lib/aspera/cli/main.rb +101 -147
  17. data/lib/aspera/cli/manager.rb +160 -124
  18. data/lib/aspera/cli/plugin.rb +70 -59
  19. data/lib/aspera/cli/plugins/alee.rb +0 -1
  20. data/lib/aspera/cli/plugins/aoc.rb +239 -273
  21. data/lib/aspera/cli/plugins/ats.rb +8 -5
  22. data/lib/aspera/cli/plugins/bss.rb +2 -2
  23. data/lib/aspera/cli/plugins/config.rb +516 -375
  24. data/lib/aspera/cli/plugins/console.rb +40 -0
  25. data/lib/aspera/cli/plugins/cos.rb +4 -5
  26. data/lib/aspera/cli/plugins/faspex.rb +99 -84
  27. data/lib/aspera/cli/plugins/faspex5.rb +179 -148
  28. data/lib/aspera/cli/plugins/node.rb +219 -153
  29. data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
  30. data/lib/aspera/cli/plugins/preview.rb +46 -32
  31. data/lib/aspera/cli/plugins/server.rb +57 -17
  32. data/lib/aspera/cli/plugins/shares.rb +34 -12
  33. data/lib/aspera/cli/sync_actions.rb +68 -0
  34. data/lib/aspera/cli/transfer_agent.rb +45 -55
  35. data/lib/aspera/cli/transfer_progress.rb +74 -0
  36. data/lib/aspera/cli/version.rb +1 -1
  37. data/lib/aspera/colors.rb +3 -1
  38. data/lib/aspera/command_line_builder.rb +14 -11
  39. data/lib/aspera/cos_node.rb +3 -2
  40. data/lib/aspera/environment.rb +17 -6
  41. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  42. data/lib/aspera/fasp/agent_base.rb +31 -77
  43. data/lib/aspera/fasp/agent_connect.rb +21 -22
  44. data/lib/aspera/fasp/agent_direct.rb +88 -102
  45. data/lib/aspera/fasp/agent_httpgw.rb +196 -192
  46. data/lib/aspera/fasp/agent_node.rb +41 -34
  47. data/lib/aspera/fasp/agent_trsdk.rb +75 -34
  48. data/lib/aspera/fasp/error_info.rb +2 -2
  49. data/lib/aspera/fasp/faux_file.rb +52 -0
  50. data/lib/aspera/fasp/installation.rb +43 -184
  51. data/lib/aspera/fasp/management.rb +244 -0
  52. data/lib/aspera/fasp/parameters.rb +59 -26
  53. data/lib/aspera/fasp/parameters.yaml +75 -8
  54. data/lib/aspera/fasp/products.rb +162 -0
  55. data/lib/aspera/fasp/transfer_spec.rb +1 -1
  56. data/lib/aspera/fasp/uri.rb +4 -4
  57. data/lib/aspera/faspex_gw.rb +2 -2
  58. data/lib/aspera/faspex_postproc.rb +2 -2
  59. data/lib/aspera/hash_ext.rb +2 -2
  60. data/lib/aspera/json_rpc.rb +49 -0
  61. data/lib/aspera/line_logger.rb +23 -0
  62. data/lib/aspera/log.rb +57 -16
  63. data/lib/aspera/node.rb +97 -14
  64. data/lib/aspera/oauth.rb +36 -18
  65. data/lib/aspera/open_application.rb +4 -4
  66. data/lib/aspera/persistency_folder.rb +2 -2
  67. data/lib/aspera/preview/file_types.rb +4 -2
  68. data/lib/aspera/preview/generator.rb +22 -35
  69. data/lib/aspera/preview/options.rb +2 -0
  70. data/lib/aspera/preview/terminal.rb +24 -13
  71. data/lib/aspera/preview/utils.rb +19 -26
  72. data/lib/aspera/rest.rb +103 -72
  73. data/lib/aspera/rest_call_error.rb +1 -1
  74. data/lib/aspera/rest_error_analyzer.rb +15 -14
  75. data/lib/aspera/rest_errors_aspera.rb +37 -34
  76. data/lib/aspera/secret_hider.rb +14 -16
  77. data/lib/aspera/ssh.rb +4 -1
  78. data/lib/aspera/sync.rb +128 -122
  79. data/lib/aspera/temp_file_manager.rb +10 -3
  80. data/lib/aspera/web_auth.rb +10 -7
  81. data/lib/aspera/web_server_simple.rb +9 -4
  82. data.tar.gz.sig +0 -0
  83. metadata +33 -15
  84. metadata.gz.sig +0 -0
  85. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  86. data/lib/aspera/cli/listener/logger.rb +0 -22
  87. data/lib/aspera/cli/listener/progress.rb +0 -50
  88. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  89. data/lib/aspera/cli/plugins/sync.rb +0 -44
  90. data/lib/aspera/fasp/listener.rb +0 -13
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/fasp/transfer_spec'
4
- require 'aspera/cli/listener/logger'
5
- require 'aspera/cli/listener/progress_multi'
6
4
  require 'aspera/cli/info'
7
5
 
8
6
  module Aspera
@@ -16,7 +14,7 @@ module Aspera
16
14
  # special value for --sources : read file list from transfer spec (--ts)
17
15
  FILE_LIST_FROM_TRANSFER_SPEC = '@ts'
18
16
  FILE_LIST_OPTIONS = [FILE_LIST_FROM_ARGS, FILE_LIST_FROM_TRANSFER_SPEC, 'Array'].freeze
19
- DEFAULT_TRANSFER_NOTIF_TMPL = <<~END_OF_TEMPLATE
17
+ DEFAULT_TRANSFER_NOTIFY_TEMPLATE = <<~END_OF_TEMPLATE
20
18
  From: <%=from_name%> <<%=from_email%>>
21
19
  To: <<%=to%>>
22
20
  Subject: <%=subject%>
@@ -29,7 +27,7 @@ module Aspera
29
27
  private_constant :FILE_LIST_FROM_ARGS,
30
28
  :FILE_LIST_FROM_TRANSFER_SPEC,
31
29
  :FILE_LIST_OPTIONS,
32
- :DEFAULT_TRANSFER_NOTIF_TMPL
30
+ :DEFAULT_TRANSFER_NOTIFY_TEMPLATE
33
31
  TRANSFER_AGENTS = %i[direct node connect httpgw trsdk].freeze
34
32
 
35
33
  class << self
@@ -43,15 +41,15 @@ module Aspera
43
41
  end
44
42
 
45
43
  # @param env external objects: option manager, config file manager
46
- def initialize(opt_mgr, config)
44
+ def initialize(opt_mgr, config_plugin)
47
45
  @opt_mgr = opt_mgr
48
- @config = config
46
+ @config = config_plugin
49
47
  # command line can override transfer spec
50
- @transfer_spec_cmdline = {'create_dir' => true}
48
+ @transfer_spec_command_line = {'create_dir' => true}
49
+ # options for transfer agent
51
50
  @transfer_info = {}
52
51
  # the currently selected transfer agent
53
52
  @agent = nil
54
- @progress_listener = Listener::ProgressMulti.new
55
53
  # source/destination pair, like "paths" of transfer spec
56
54
  @transfer_paths = nil
57
55
  @opt_mgr.declare(:ts, 'Override transfer spec values', types: Hash, handler: {o: self, m: :option_transfer_spec})
@@ -59,46 +57,38 @@ module Aspera
59
57
  @opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})")
60
58
  @opt_mgr.declare(:src_type, 'Type of file list', values: %i[list pair], default: :list)
61
59
  @opt_mgr.declare(:transfer, 'Type of transfer agent', values: TRANSFER_AGENTS, default: :direct)
62
- @opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :option_transfer_info})
63
- @opt_mgr.declare(:progress, 'Type of progress bar', values: %i[none native multi], default: :native)
60
+ @opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :transfer_info})
64
61
  @opt_mgr.parse_options!
65
62
  end
66
63
 
67
- def option_transfer_spec; @transfer_spec_cmdline; end
64
+ def option_transfer_spec; @transfer_spec_command_line; end
68
65
 
69
66
  # multiple option are merged
70
67
  def option_transfer_spec=(value)
71
68
  raise 'option ts shall be a Hash' unless value.is_a?(Hash)
72
- @transfer_spec_cmdline.deep_merge!(value)
69
+ @transfer_spec_command_line.deep_merge!(value)
73
70
  end
74
71
 
75
72
  # add other transfer spec parameters
76
- def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
73
+ def option_transfer_spec_deep_merge(ts); @transfer_spec_command_line.deep_merge!(ts); end
77
74
 
78
75
  # @return [Hash] transfer spec with updated values from command line, including removed values
79
76
  def updated_ts(transfer_spec={})
80
- transfer_spec.deep_merge!(@transfer_spec_cmdline)
77
+ transfer_spec.deep_merge!(@transfer_spec_command_line)
81
78
  # recursively remove values that are nil (user wants to delete)
82
79
  transfer_spec.deep_do { |hash, key, value, _unused| hash.delete(key) if value.nil?}
83
80
  return transfer_spec
84
81
  end
85
82
 
86
- def option_transfer_info; @transfer_info; end
83
+ attr_reader :transfer_info
87
84
 
88
85
  # multiple option are merged
89
- def option_transfer_info=(value)
90
- raise 'option transfer_info shall be a Hash' unless value.is_a?(Hash)
86
+ def transfer_info=(value)
91
87
  @transfer_info.deep_merge!(value)
92
88
  end
93
89
 
94
90
  def agent_instance=(instance)
95
91
  @agent = instance
96
- @agent.add_listener(Listener::Logger.new)
97
- # use local progress bar if asked so, or if native and non local ascp (because only local ascp has native progress bar)
98
- if @opt_mgr.get_option(:progress, mandatory: true).eql?(:multi) ||
99
- (@opt_mgr.get_option(:progress, mandatory: true).eql?(:native) && !instance.class.to_s.eql?('Aspera::Fasp::AgentDirect'))
100
- @agent.add_listener(@progress_listener)
101
- end
102
92
  end
103
93
 
104
94
  # analyze options and create new agent if not already created or set
@@ -107,21 +97,23 @@ module Aspera
107
97
  agent_type = @opt_mgr.get_option(:transfer, mandatory: true)
108
98
  # agent plugin is loaded on demand to avoid loading unnecessary dependencies
109
99
  require "aspera/fasp/agent_#{agent_type}"
110
- agent_options = @opt_mgr.get_option(:transfer_info)
111
- raise CliBadArgument, "the transfer agent configuration shall be Hash, not #{agent_options.class} (#{agent_options}), "\
112
- 'e.g. use @json:<json>' unless agent_options.is_a?(Hash)
113
- # special case: use default node
114
- if agent_type.eql?(:node) && agent_options.empty?
115
- param_set_name = @config.get_plugin_default_config_name(:node)
116
- raise CliBadArgument, "No default node configured. Please specify #{Manager.option_name_to_line(:transfer_info)}" if param_set_name.nil?
117
- agent_options = @config.preset_by_name(param_set_name)
118
- end
119
- # special case: native progress bar
120
- if agent_type.eql?(:direct) && @opt_mgr.get_option(:progress, mandatory: true).eql?(:native)
121
- agent_options[:quiet] = false
100
+ # set keys as symbols
101
+ agent_options = @opt_mgr.get_option(:transfer_info).symbolize_keys
102
+ # special cases
103
+ case agent_type
104
+ when :node
105
+ if agent_options.empty?
106
+ param_set_name = @config.get_plugin_default_config_name(:node)
107
+ raise Cli::BadArgument, "No default node configured. Please specify #{Manager.option_name_to_line(:transfer_info)}" if param_set_name.nil?
108
+ agent_options = @config.preset_by_name(param_set_name).symbolize_keys
109
+ end
110
+ when :direct
111
+ # by default do not display ascp native progress bar
112
+ agent_options[:quiet] = true unless agent_options.key?(:quiet)
113
+ agent_options[:check_ignore] = ->(host, port){@config.ignore_cert?(host, port)}
114
+ agent_options[:trusted_certs] = @config.trusted_cert_locations(files_only: true) unless agent_options.key?(:trusted_certs)
122
115
  end
123
- # normalize after getting from user or default node
124
- agent_options = agent_options.symbolize_keys
116
+ agent_options[:progress] = @config.progress_bar
125
117
  # get agent instance
126
118
  new_agent = Kernel.const_get("Aspera::Fasp::Agent#{agent_type.capitalize}").new(agent_options)
127
119
  self.agent_instance = new_agent
@@ -135,7 +127,7 @@ module Aspera
135
127
  dest_folder = @opt_mgr.get_option(:to_folder)
136
128
  # do not expand path, if user wants to expand path: user @path:
137
129
  return dest_folder unless dest_folder.nil?
138
- dest_folder = @transfer_spec_cmdline['destination_root']
130
+ dest_folder = @transfer_spec_command_line['destination_root']
139
131
  return dest_folder unless dest_folder.nil?
140
132
  # default: / on remote, . on local
141
133
  case direction.to_s
@@ -161,7 +153,7 @@ module Aspera
161
153
  # return cache if set
162
154
  return @transfer_paths unless @transfer_paths.nil?
163
155
  # start with lower priority : get paths from transfer spec on command line
164
- @transfer_paths = @transfer_spec_cmdline['paths'] if @transfer_spec_cmdline.key?('paths')
156
+ @transfer_paths = @transfer_spec_command_line['paths'] if @transfer_spec_command_line.key?('paths')
165
157
  # is there a source list option ?
166
158
  file_list = @opt_mgr.get_option(:sources)
167
159
  case file_list
@@ -169,21 +161,21 @@ module Aspera
169
161
  Log.log.debug('getting file list as parameters')
170
162
  # get remaining arguments
171
163
  file_list = @opt_mgr.get_next_argument('source file list', expected: :multiple)
172
- raise CliBadArgument, 'specify at least one file on command line or use '\
164
+ raise Cli::BadArgument, 'specify at least one file on command line or use ' \
173
165
  "--sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !file_list.is_a?(Array) || file_list.empty?
174
166
  when FILE_LIST_FROM_TRANSFER_SPEC
175
167
  Log.log.debug('assume list provided in transfer spec')
176
168
  special_case_direct_with_list =
177
169
  @opt_mgr.get_option(:transfer, mandatory: true).eql?(:direct) &&
178
- Fasp::Parameters.ts_has_ascp_file_list(@transfer_spec_cmdline, @opt_mgr.get_option(:transfer_info))
179
- raise CliBadArgument, 'transfer spec on command line must have sources' if @transfer_paths.nil? && !special_case_direct_with_list
170
+ Fasp::Parameters.ts_has_ascp_file_list(@transfer_spec_command_line, @opt_mgr.get_option(:transfer_info))
171
+ raise Cli::BadArgument, 'transfer spec on command line must have sources' if @transfer_paths.nil? && !special_case_direct_with_list
180
172
  # here we assume check of sources is made in transfer agent
181
173
  return @transfer_paths
182
174
  when Array
183
175
  Log.log.debug('getting file list as extended value')
184
- raise CliBadArgument, 'sources must be a Array of String' if !file_list.reject{|f|f.is_a?(String)}.empty?
176
+ raise Cli::BadArgument, 'sources must be a Array of String' if !file_list.reject{|f|f.is_a?(String)}.empty?
185
177
  else
186
- raise CliBadArgument, "sources must be a Array, not #{file_list.class}"
178
+ raise Cli::BadArgument, "sources must be a Array, not #{file_list.class}"
187
179
  end
188
180
  # here, file_list is an Array or String
189
181
  if !@transfer_paths.nil?
@@ -194,7 +186,7 @@ module Aspera
194
186
  # when providing a list, just specify source
195
187
  @transfer_paths = file_list.map{|i|{'source' => i}}
196
188
  when :pair
197
- raise CliBadArgument, "When using pair, provide an even number of paths: #{file_list.length}" unless file_list.length.even?
189
+ raise Cli::BadArgument, "When using pair, provide an even number of paths: #{file_list.length}" unless file_list.length.even?
198
190
  @transfer_paths = file_list.each_slice(2).to_a.map{|s, d|{'source' => s, 'destination' => d}}
199
191
  else raise 'Unsupported src_type'
200
192
  end
@@ -212,23 +204,23 @@ module Aspera
212
204
  case transfer_spec['direction']
213
205
  when Fasp::TransferSpec::DIRECTION_RECEIVE
214
206
  # init default if required in any case
215
- @transfer_spec_cmdline['destination_root'] ||= destination_folder(transfer_spec['direction'])
207
+ @transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
216
208
  when Fasp::TransferSpec::DIRECTION_SEND
217
209
  if transfer_spec.dig('tags', Fasp::TransferSpec::TAG_RESERVED, 'node', 'access_key')
218
210
  # gen4
219
- @transfer_spec_cmdline.delete('destination_root') if @transfer_spec_cmdline.key?('destination_root_id')
211
+ @transfer_spec_command_line.delete('destination_root') if @transfer_spec_command_line.key?('destination_root_id')
220
212
  elsif transfer_spec.key?('token')
221
213
  # gen3
222
214
  # in that case, destination is set in return by application (API/upload_setup)
223
215
  # but to_folder was used in initial API call
224
- @transfer_spec_cmdline.delete('destination_root')
216
+ @transfer_spec_command_line.delete('destination_root')
225
217
  else
226
218
  # init default if required
227
- @transfer_spec_cmdline['destination_root'] ||= destination_folder(transfer_spec['direction'])
219
+ @transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
228
220
  end
229
221
  end
230
222
  # update command line paths, unless destination already has one
231
- @transfer_spec_cmdline['paths'] = transfer_spec['paths'] || ts_source_paths
223
+ @transfer_spec_command_line['paths'] = transfer_spec['paths'] || ts_source_paths
232
224
  # updated transfer spec with command line
233
225
  updated_ts(transfer_spec)
234
226
  # create transfer agent
@@ -236,15 +228,13 @@ module Aspera
236
228
  Log.log.debug{"transfer agent is a #{@agent.class}"}
237
229
  @agent.start_transfer(transfer_spec, token_regenerator: rest_token)
238
230
  # list of: :success or "error message string"
239
- result = @agent.wait_for_transfers_completion
240
- @progress_listener.reset
241
- Fasp::AgentBase.validate_status_list(result)
231
+ result = @agent.wait_for_completion
242
232
  send_email_transfer_notification(transfer_spec, result)
243
233
  return result
244
234
  end
245
235
 
246
236
  def send_email_transfer_notification(transfer_spec, statuses)
247
- return if @opt_mgr.get_option(:notif_to).nil?
237
+ return if @opt_mgr.get_option(:notify_to).nil?
248
238
  global_status = self.class.session_status(statuses)
249
239
  email_vars = {
250
240
  global_transfer_status: global_status,
@@ -252,7 +242,7 @@ module Aspera
252
242
  body: "Transfer is: #{global_status}",
253
243
  ts: transfer_spec
254
244
  }
255
- @config.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIF_TMPL, values: email_vars)
245
+ @config.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIFY_TEMPLATE, values: email_vars)
256
246
  end
257
247
 
258
248
  # shut down if agent requires it
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/log'
4
+ require 'ruby-progressbar'
5
+
6
+ module Aspera
7
+ module Cli
8
+ # progress bar for transfers, supports multi-session
9
+ class TransferProgress
10
+ def initialize
11
+ reset
12
+ end
13
+
14
+ def reset
15
+ @progress_bar = nil
16
+ # key is session id
17
+ @sessions = {}
18
+ @completed = false
19
+ @title = ''
20
+ end
21
+
22
+ def total(key)
23
+ @sessions.values.inject(0){|m, s|m + s[key]}
24
+ end
25
+
26
+ def event(session_id:, type:, info: nil)
27
+ Log.log.debug{"progress: #{type} #{session_id} #{info}"}
28
+ if session_id.nil? && !type.eql?(:pre_start)
29
+ raise 'Internal error: session_id is nil'
30
+ end
31
+ return if @completed
32
+ if @progress_bar.nil?
33
+ @progress_bar = ProgressBar.create(
34
+ format: '%t %a %B %p%% %r Mbps %E',
35
+ rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
36
+ title: '',
37
+ total: nil)
38
+ end
39
+ need_increment = true
40
+ case type
41
+ when :pre_start
42
+ @title = info
43
+ when :session_start
44
+ raise "Session #{session_id} already started" if @sessions[session_id]
45
+ @sessions[session_id] = {
46
+ job_size: 0, # total size of transfer (pre-calc)
47
+ current: 0
48
+ }
49
+ @title = ''
50
+ when :session_size
51
+ @sessions[session_id][:job_size] = info.to_i
52
+ current_total = total(:job_size)
53
+ @progress_bar.total = current_total unless current_total.eql?(@progress_bar.total) || current_total < @progress_bar.progress
54
+ when :transfer
55
+ if !@progress_bar.total.nil?
56
+ need_increment = false
57
+ @sessions[session_id][:current] = info.to_i
58
+ current_total = total(:current)
59
+ @progress_bar.progress = current_total unless @progress_bar.progress.eql?(current_total)
60
+ end
61
+ when :end
62
+ @title = ''
63
+ @completed = true
64
+ @progress_bar.finish
65
+ else
66
+ raise "Unknown event type #{type}"
67
+ end
68
+ new_title = @sessions.length < 2 ? @title : "[#{@sessions.length}] #{@title}"
69
+ @progress_bar.title = new_title unless @progress_bar.title.eql?(new_title)
70
+ @progress_bar.increment if need_increment && !@completed
71
+ end
72
+ end
73
+ end
74
+ end
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # for beta add extension : .beta1
6
6
  # for dev version add extension : .pre
7
- VERSION = '4.14.0'
7
+ VERSION = '4.15.0'
8
8
  end
9
9
  end
data/lib/aspera/colors.rb CHANGED
@@ -14,10 +14,12 @@ class String
14
14
  # it adds control chars to set color (and reset at the end).
15
15
  VT_STYLES = {
16
16
  bold: 1,
17
+ dim: 2,
17
18
  italic: 3,
18
19
  underline: 4,
19
20
  blink: 5,
20
21
  reverse_color: 7,
22
+ invisible: 8,
21
23
  black: 30,
22
24
  red: 31,
23
25
  green: 32,
@@ -38,7 +40,7 @@ class String
38
40
  private_constant :VT_STYLES
39
41
  # defines methods to String, one per entry in VT_STYLES
40
42
  VT_STYLES.each do |name, code|
41
- if $stderr.tty?
43
+ if $stdout.tty?
42
44
  begin_seq = vt_cmd(code)
43
45
  end_code = 0 # by default reset all
44
46
  if code <= 7 then code + 20
@@ -55,31 +55,34 @@ module Aspera
55
55
 
56
56
  attr_reader :params_definition
57
57
 
58
- # @param param_hash
58
+ # @param [Hash] param_hash with parameters
59
+ # @param [Hash] params_definition with definition of parameters
59
60
  def initialize(param_hash, params_definition)
60
61
  @param_hash = param_hash # keep reference so that it can be modified by caller before calling `process_params`
61
62
  @params_definition = params_definition
62
- @result_env = {}
63
- @result_args = []
63
+ @result = {
64
+ env: {},
65
+ args: []
66
+ }
64
67
  @used_param_names = []
65
68
  end
66
69
 
67
- # adds keys :env :args with resulting values after processing
68
- # warns if some parameters were not used
69
- def add_env_args(env, args)
70
- Log.log.debug{"ENV=#{@result_env}, ARGS=#{@result_args}"}
70
+ # add processed parameters to env and args, warns about unused parameters
71
+ # @param [Hash] env_args with :env and :args
72
+ def add_env_args(env_args)
73
+ Log.log.debug{"add_env_args: ENV=#{@result[:env]}, ARGS=#{@result[:args]}"}
71
74
  # warn about non translated arguments
72
75
  @param_hash.each_pair{|key, val|Log.log.warn{"unrecognized parameter: #{key} = \"#{val}\""} if !@used_param_names.include?(key)}
73
76
  # set result
74
- env.merge!(@result_env)
75
- args.push(*@result_args)
77
+ env_args[:env].merge!(@result[:env])
78
+ env_args[:args].push(*@result[:args])
76
79
  return nil
77
80
  end
78
81
 
79
82
  # add options directly to command line
80
83
  def add_command_line_options(options)
81
84
  return if options.nil?
82
- options.each{|o|@result_args.push(o.to_s)}
85
+ options.each{|o|@result[:args].push(o.to_s)}
83
86
  end
84
87
 
85
88
  def process_params
@@ -157,7 +160,7 @@ module Aspera
157
160
  return
158
161
  when :envvar # set in env var
159
162
  raise 'error' unless options[:cli].key?(:variable)
160
- @result_env[options[:cli][:variable]] = parameter_value
163
+ @result[:env][options[:cli][:variable]] = parameter_value
161
164
  when :opt_without_arg # if present and true : just add option without value
162
165
  add_param = false
163
166
  case parameter_value
@@ -2,12 +2,13 @@
2
2
 
3
3
  require 'aspera/log'
4
4
  require 'aspera/rest'
5
+ require 'aspera/oauth'
5
6
  require 'xmlsimple'
6
7
 
7
8
  module Aspera
8
9
  class CosNode < Aspera::Node
9
10
  class << self
10
- def parameters_from_svc_creds(service_credentials, bucket_region)
11
+ def parameters_from_svc_credentials(service_credentials, bucket_region)
11
12
  # check necessary contents
12
13
  raise 'service_credentials must be a Hash' unless service_credentials.is_a?(Hash)
13
14
  %w[apikey resource_instance_id endpoints].each do |field|
@@ -85,7 +86,7 @@ module Aspera
85
86
  receiver_client_ids: 'aspera_ats'
86
87
  }})
87
88
  # get delegated token to be placed in rest call header and in transfer tags
88
- @storage_credentials['token'][TOKEN_FIELD] = delegated_oauth.get_authorization.gsub(/^Bearer /, '')
89
+ @storage_credentials['token'][TOKEN_FIELD] = Oauth.bearer_extract(delegated_oauth.get_authorization)
89
90
  @params[:headers] = {'X-Aspera-Storage-Credentials' => JSON.generate(@storage_credentials)}
90
91
  end
91
92
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore USERPROFILE HOMEDRIVE HOMEPATH LC_CTYPE msys aarch
3
4
  require 'aspera/log'
4
5
  require 'rbconfig'
5
6
 
@@ -52,7 +53,7 @@ module Aspera
52
53
  return CPU_PPC64
53
54
  when /s390/
54
55
  return CPU_S390
55
- when /arm/
56
+ when /arm/, /aarch64/
56
57
  # arm on mac has rosetta 2
57
58
  return CPU_X86_64 if os.eql?(OS_X)
58
59
  end
@@ -71,9 +72,9 @@ module Aspera
71
72
  # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
72
73
  # so, tell Ruby the right way
73
74
  def fix_home
74
- return unless os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV['USERPROFILE'])
75
- ENV['HOME'] = ENV['USERPROFILE']
76
- Log.log.debug{"Windows: set home to USERPROFILE: #{ENV['HOME']}"}
75
+ return unless os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
76
+ ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
77
+ Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
77
78
  end
78
79
 
79
80
  def empty_binding
@@ -81,8 +82,8 @@ module Aspera
81
82
  end
82
83
 
83
84
  # secure execution of Ruby code
84
- def secure_eval(code)
85
- Kernel.send('lave'.reverse, code, empty_binding, __FILE__, __LINE__)
85
+ def secure_eval(code, file, line)
86
+ Kernel.send('lave'.reverse, code, empty_binding, file, line)
86
87
  end
87
88
 
88
89
  # value is provided in block
@@ -113,6 +114,16 @@ module Aspera
113
114
  rescue => e
114
115
  Log.log.warn(e.message)
115
116
  end
117
+
118
+ def terminal?
119
+ $stdout.tty?
120
+ end
121
+
122
+ # @return true if we can display Unicode characters
123
+ def use_unicode?
124
+ @use_unicode = terminal? && ENV.values_at('LC_ALL', 'LC_CTYPE', 'LANG').compact.first.include?('UTF-8') if @use_unicode.nil?
125
+ return @use_unicode
126
+ end
116
127
  end # self
117
128
  end # Environment
118
129
  end # Aspera
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/fasp/agent_base'
4
+ require 'aspera/rest'
5
+ require 'aspera/json_rpc'
6
+ require 'aspera/open_application'
7
+ require 'securerandom'
8
+
9
+ module Aspera
10
+ module Fasp
11
+ class AgentAspera < Aspera::Fasp::AgentBase
12
+ # try twice the main init url in sequence
13
+ START_URIS = ['aspera://']
14
+ # delay between each try to start connect
15
+ SLEEP_SEC_BETWEEN_RETRY = 3
16
+ private_constant :START_URIS, :SLEEP_SEC_BETWEEN_RETRY
17
+ def initialize(options)
18
+ @application_id = SecureRandom.uuid
19
+ super(options)
20
+ raise 'Using client requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
21
+ method_index = 0
22
+ begin
23
+ @client_app_api = Aspera::JsonRpcClient.new(Aspera::Rest.new(base_url: aspera_client_api_url))
24
+ client_info = @client_app_api.get_info
25
+ Log.log.debug{Log.dump(:client_version, client_info)}
26
+ # my_transfer_id = '0513fe85-65cf-465b-ad5f-18fd40d8c69f'
27
+ # @client_app_api.get_all_transfers({app_id: @application_id})
28
+ # @client_app_api.get_transfer(app_id: @application_id, transfer_id: my_transfer_id)
29
+ # @client_app_api.start_transfer(app_id: @application_id,transfer_spec: {})
30
+ # @client_app_api.remove_transfer
31
+ # @client_app_api.stop_transfer
32
+ # @client_app_api.modify_transfer
33
+ # @client_app_api.show_directory({app_id: @application_id, transfer_id: my_transfer_id})
34
+ # @client_app_api.get_files_list({app_id: @application_id, transfer_id: my_transfer_id})
35
+ Log.log.info('Client was reached') if method_index > 0
36
+ rescue StandardError => e # Errno::ECONNREFUSED
37
+ start_url = START_URIS[method_index]
38
+ method_index += 1
39
+ raise StandardError, "Unable to start connect #{method_index} times" if start_url.nil?
40
+ Log.log.warn{"Aspera Connect is not started (#{e}). Trying to start it ##{method_index}..."}
41
+ if !OpenApplication.uri_graphical(start_url)
42
+ OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
43
+ raise StandardError, 'Connect is not installed'
44
+ end
45
+ sleep(SLEEP_SEC_BETWEEN_RETRY)
46
+ retry
47
+ end
48
+ end
49
+
50
+ def aspera_client_api_url
51
+ log_file = File.join(Dir.home, 'Library', 'Logs', 'IBM Aspera', 'ibm-aspera-desktop.log')
52
+ url = nil
53
+ File.open(log_file, 'r') do |file|
54
+ file.each_line do |line|
55
+ line = line.chomp
56
+ if (m = line.match(/JSON-RPC server listening on (.*)/))
57
+ url = "http://#{m[1]}"
58
+ end
59
+ end
60
+ end
61
+ return url
62
+ end
63
+
64
+ def start_transfer(transfer_spec, token_regenerator: nil)
65
+ @request_id = SecureRandom.uuid
66
+ # if there is a token, we ask connect client to use well known ssh private keys
67
+ # instead of asking password
68
+ transfer_spec['authentication'] = 'token' if transfer_spec.key?('token')
69
+ @client_app_api.start_transfer(app_id: @application_id,transfer_spec: transfer_spec)
70
+ # @xfer_id = res['transfer_specs'].first['transfer_spec']['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_id']
71
+ end
72
+
73
+ def wait_for_transfers_completion
74
+ client_activity_args = {'aspera_client_settings' => @client_settings}
75
+ started = false
76
+ pre_calc = false
77
+ session_id = @xfer_id
78
+ begin
79
+ loop do
80
+ tr_info = @client_api.create("transfers/info/#{@xfer_id}", client_activity_args)[:data]
81
+ Log.log.trace1{Log.dump(:tr_info, tr_info)}
82
+ if tr_info['transfer_info'].is_a?(Hash)
83
+ transfer = tr_info['transfer_info']
84
+ if transfer.nil?
85
+ Log.log.warn('no session in Connect')
86
+ break
87
+ end
88
+ # TODO: get session id
89
+ case transfer['status']
90
+ when 'initiating', 'queued'
91
+ notify_progress(session_id: nil, type: :pre_start, info: transfer['status'])
92
+ when 'running'
93
+ if !started
94
+ notify_progress(session_id: session_id, type: :session_start)
95
+ started = true
96
+ end
97
+ if !pre_calc && (transfer['bytes_expected'] != 0)
98
+ notify_progress(type: :session_size, session_id: session_id, info: transfer['bytes_expected'])
99
+ pre_calc = true
100
+ else
101
+ notify_progress(type: :transfer, session_id: session_id, info: transfer['bytes_written'])
102
+ end
103
+ when 'completed'
104
+ notify_progress(type: :end, session_id: session_id)
105
+ break
106
+ when 'failed'
107
+ notify_progress(type: :end, session_id: session_id)
108
+ raise Fasp::Error, transfer['error_desc']
109
+ when 'cancelled'
110
+ notify_progress(type: :end, session_id: session_id)
111
+ raise Fasp::Error, 'Transfer cancelled by user'
112
+ else
113
+ notify_progress(type: :end, session_id: session_id)
114
+ raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
115
+ end
116
+ end
117
+ sleep(1)
118
+ end
119
+ rescue StandardError => e
120
+ return [e]
121
+ end
122
+ return [:success]
123
+ end # wait
124
+ end # AgentAspera
125
+ end
126
+ end