aspera-cli 4.14.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
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