aspera-cli 4.15.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +292 -228
  5. data/CONTRIBUTING.md +69 -18
  6. data/README.md +1102 -952
  7. data/bin/ascli +13 -31
  8. data/bin/asession +3 -1
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/aoc.rb +28 -33
  11. data/lib/aspera/ascmd.rb +3 -6
  12. data/lib/aspera/assert.rb +45 -0
  13. data/lib/aspera/cli/extended_value.rb +5 -5
  14. data/lib/aspera/cli/formatter.rb +26 -13
  15. data/lib/aspera/cli/hints.rb +4 -3
  16. data/lib/aspera/cli/main.rb +16 -3
  17. data/lib/aspera/cli/manager.rb +45 -36
  18. data/lib/aspera/cli/plugin.rb +20 -13
  19. data/lib/aspera/cli/plugins/aoc.rb +103 -73
  20. data/lib/aspera/cli/plugins/ats.rb +4 -3
  21. data/lib/aspera/cli/plugins/config.rb +114 -119
  22. data/lib/aspera/cli/plugins/cos.rb +2 -2
  23. data/lib/aspera/cli/plugins/faspex.rb +23 -19
  24. data/lib/aspera/cli/plugins/faspex5.rb +75 -43
  25. data/lib/aspera/cli/plugins/node.rb +28 -15
  26. data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
  27. data/lib/aspera/cli/plugins/preview.rb +9 -7
  28. data/lib/aspera/cli/plugins/server.rb +6 -3
  29. data/lib/aspera/cli/plugins/shares.rb +30 -26
  30. data/lib/aspera/cli/sync_actions.rb +9 -9
  31. data/lib/aspera/cli/transfer_agent.rb +21 -14
  32. data/lib/aspera/cli/transfer_progress.rb +2 -3
  33. data/lib/aspera/cli/version.rb +1 -1
  34. data/lib/aspera/command_line_builder.rb +13 -11
  35. data/lib/aspera/cos_node.rb +3 -2
  36. data/lib/aspera/coverage.rb +22 -0
  37. data/lib/aspera/data_repository.rb +33 -2
  38. data/lib/aspera/environment.rb +4 -2
  39. data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
  40. data/lib/aspera/fasp/agent_base.rb +17 -7
  41. data/lib/aspera/fasp/agent_direct.rb +88 -84
  42. data/lib/aspera/fasp/agent_httpgw.rb +4 -3
  43. data/lib/aspera/fasp/agent_node.rb +3 -2
  44. data/lib/aspera/fasp/agent_trsdk.rb +79 -37
  45. data/lib/aspera/fasp/installation.rb +51 -12
  46. data/lib/aspera/fasp/management.rb +11 -6
  47. data/lib/aspera/fasp/parameters.rb +53 -47
  48. data/lib/aspera/fasp/resume_policy.rb +7 -5
  49. data/lib/aspera/fasp/sync.rb +273 -0
  50. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  51. data/lib/aspera/fasp/uri.rb +2 -2
  52. data/lib/aspera/faspex_gw.rb +11 -8
  53. data/lib/aspera/faspex_postproc.rb +6 -5
  54. data/lib/aspera/id_generator.rb +3 -1
  55. data/lib/aspera/json_rpc.rb +10 -8
  56. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  57. data/lib/aspera/keychain/macos_security.rb +15 -13
  58. data/lib/aspera/log.rb +4 -3
  59. data/lib/aspera/nagios.rb +7 -2
  60. data/lib/aspera/node.rb +17 -16
  61. data/lib/aspera/node_simulator.rb +214 -0
  62. data/lib/aspera/oauth.rb +22 -19
  63. data/lib/aspera/persistency_action_once.rb +13 -14
  64. data/lib/aspera/persistency_folder.rb +3 -2
  65. data/lib/aspera/preview/file_types.rb +53 -267
  66. data/lib/aspera/preview/generator.rb +7 -5
  67. data/lib/aspera/preview/terminal.rb +14 -5
  68. data/lib/aspera/preview/utils.rb +8 -7
  69. data/lib/aspera/proxy_auto_config.rb +6 -3
  70. data/lib/aspera/rest.rb +29 -13
  71. data/lib/aspera/rest_error_analyzer.rb +1 -0
  72. data/lib/aspera/rest_errors_aspera.rb +2 -0
  73. data/lib/aspera/secret_hider.rb +5 -2
  74. data/lib/aspera/ssh.rb +10 -8
  75. data/lib/aspera/temp_file_manager.rb +1 -1
  76. data/lib/aspera/web_server_simple.rb +2 -1
  77. data.tar.gz.sig +0 -0
  78. metadata +96 -45
  79. metadata.gz.sig +0 -0
  80. data/lib/aspera/sync.rb +0 -219
data/bin/ascli CHANGED
@@ -3,36 +3,18 @@
3
3
 
4
4
  Encoding.default_internal = Encoding::UTF_8
5
5
  Encoding.default_external = Encoding::UTF_8
6
- # coverage for tests
7
- if ENV.key?('ENABLE_COVERAGE')
8
- require 'simplecov'
9
- require 'securerandom'
10
- # compute gem source root based on this script location, assuming it is in bin/
11
- # use dirname instead of gsub, in case folder separator is not /
12
- development_root = File.dirname(File.dirname(File.realpath(__FILE__)))
13
- SimpleCov.root(development_root)
14
- SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
15
- # keep cache data for 1 day (must be longer that time to run the whole test suite)
16
- SimpleCov.merge_timeout(86400)
17
- SimpleCov.command_name(SecureRandom.uuid)
18
- SimpleCov.at_exit do
19
- original_file_descriptor = $stdout
20
- $stdout.reopen(File.join(development_root, 'simplecov.log'))
21
- SimpleCov.result.format!
22
- $stdout.reopen(original_file_descriptor)
23
- end
24
- SimpleCov.start
25
- end
26
- # if in development, add path to gem
27
- #
6
+
28
7
  begin
29
- require 'aspera/cli/main'
30
- rescue LoadError
31
- # development environment
32
- development_root = File.dirname(File.dirname(File.realpath(__FILE__)))
33
- $LOAD_PATH.unshift(File.join(development_root, 'lib'))
34
- require 'aspera/cli/main'
8
+ gem_lib_folder = File.join(File.dirname(File.dirname(File.realpath(__FILE__))), 'lib')
9
+ Kernel.load(File.join(gem_lib_folder, 'aspera/coverage.rb'))
10
+ begin
11
+ require 'aspera/cli/main'
12
+ rescue LoadError
13
+ # if in development, add path toward gem
14
+ $LOAD_PATH.unshift(gem_lib_folder)
15
+ require 'aspera/cli/main'
16
+ end
17
+ require 'aspera/environment'
18
+ Aspera::Environment.fix_home
19
+ Aspera::Cli::Main.new(ARGV).process_command_line
35
20
  end
36
- require 'aspera/environment'
37
- Aspera::Environment.fix_home
38
- Aspera::Cli::Main.new(ARGV).process_command_line
data/bin/asession CHANGED
@@ -75,10 +75,12 @@ client = Aspera::Fasp::AgentDirect.new({quiet: true})
75
75
  job_id = client.start_transfer(transfer_spec)
76
76
  # async commands
77
77
  Thread.new do
78
+ # we assume here a single session
79
+ session_id = client.sessions_by_job(job_id).first
78
80
  begin # rubocop:disable Style/RedundantBegin
79
81
  loop do
80
82
  data = JSON.parse($stdin.gets)
81
- client.send_command(job_id, 0, data)
83
+ client.send_command(session_id, data)
82
84
  end
83
85
  rescue
84
86
  Process.exit(1)
data/examples/dascli CHANGED
@@ -7,7 +7,7 @@
7
7
  : "${imgtag=$image:$version}"
8
8
  # set env var `docker` to podman, to use podman
9
9
  : "${docker:=docker}"
10
- # set env var docker_args to add options to docker run (transform var into array) # spellcheck disable=SC2086
10
+ # set env var `docker_args` to add options to docker run (then, transform this var into array) # spellcheck disable=SC2086
11
11
  read -a add_dock_args <<< $docker_args
12
12
  # set env var ASCLI_HOME to set the config folder on host
13
13
  : "${ASCLI_HOME:=$HOME/.aspera/ascli}"
@@ -24,7 +24,7 @@ exec $docker run \
24
24
  --interactive \
25
25
  --user root \
26
26
  --env ASCLI_HOME="$ascli_home_container" \
27
- --volume "$ASCLI_HOME:$ascli_home_container" \
27
+ --volume "$ASCLI_HOME:$ascli_home_container:z" \
28
28
  "${add_dock_args[@]}" \
29
29
  "$imgtag" \
30
30
  "$@"
data/lib/aspera/aoc.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/log'
4
+ require 'aspera/assert'
4
5
  require 'aspera/rest'
5
6
  require 'aspera/hash_ext'
6
7
  require 'aspera/data_repository'
@@ -31,10 +32,9 @@ module Aspera
31
32
  PROD_DOMAIN = 'ibmaspera.com' # cspell:disable-line
32
33
  # to avoid infinite loop in pub link redirection
33
34
  MAX_AOC_URL_REDIRECT = 10
35
+ CLIENT_ID_PREFIX = 'aspera.'
34
36
  # Well-known AoC globals client apps
35
- GLOBAL_CLIENT_APPS = %w[aspera.global-cli-client aspera.drive].freeze
36
- # index offset in data repository of client app
37
- DATA_REPO_INDEX_START = 4
37
+ GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{|i|i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
38
38
  # cookie prefix so that console can decode identity
39
39
  COOKIE_PREFIX_CONSOLE_AOC = 'aspera.aoc'
40
40
  # path in URL of public links
@@ -50,7 +50,6 @@ module Aspera
50
50
 
51
51
  private_constant :MAX_AOC_URL_REDIRECT,
52
52
  :GLOBAL_CLIENT_APPS,
53
- :DATA_REPO_INDEX_START,
54
53
  :COOKIE_PREFIX_CONSOLE_AOC,
55
54
  :PUBLIC_LINK_PATHS,
56
55
  :JWT_AUDIENCE,
@@ -71,10 +70,9 @@ module Aspera
71
70
  # class static methods
72
71
  class << self
73
72
  # strings /Applications/Aspera\ Drive.app/Contents/MacOS/AsperaDrive|grep -E '.{100}==$'|base64 --decode
74
- def get_client_info(client_name=GLOBAL_CLIENT_APPS.first)
75
- client_index = GLOBAL_CLIENT_APPS.index(client_name)
76
- raise "no such pre-defined client: #{client_name}" if client_index.nil?
77
- return client_name, Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START + client_index))
73
+ def get_client_info(client_name=nil)
74
+ client_key = client_name.nil? ? GLOBAL_CLIENT_APPS.first : client_name.to_sym
75
+ return client_key, DataRepository.instance.item(client_key)
78
76
  end
79
77
 
80
78
  # base API url depends on domain, which could be "qa.xxx"
@@ -93,7 +91,7 @@ module Aspera
93
91
  def url_parts(uri)
94
92
  raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if uri.host.nil?
95
93
  parts = uri.host.split('.', 2)
96
- raise "expecting a public FQDN for #{PRODUCT_NAME}" unless parts.length == 2
94
+ assert(parts.length == 2){"expecting a public FQDN for #{PRODUCT_NAME}"}
97
95
  return parts
98
96
  end
99
97
 
@@ -161,6 +159,7 @@ module Aspera
161
159
  @workspace_name = workspace
162
160
  @cache_user_info = nil
163
161
  @cache_url_token_info = nil
162
+ @context_cache = nil
164
163
  # init rest params
165
164
  aoc_rest_p = {auth: {type: :oauth2}}
166
165
  # shortcut to auth section
@@ -212,7 +211,7 @@ module Aspera
212
211
  aoc_auth_p[:aoc_pub_link][:json][:password] = password unless password.nil?
213
212
  # basic auth required for /token
214
213
  aoc_auth_p[:auth] = {type: :basic, username: aoc_auth_p[:client_id], password: aoc_auth_p[:client_secret]}
215
- else raise "ERROR: unsupported auth method: #{aoc_auth_p[:grant_method]}"
214
+ else error_unexpected_value(aoc_auth_p[:grant_method])
216
215
  end
217
216
  super(aoc_rest_p)
218
217
  end
@@ -226,7 +225,7 @@ module Aspera
226
225
  end
227
226
 
228
227
  def assert_public_link_types(expected)
229
- raise "public link type is #{public_link['purpose']} but action requires one of #{expected.join(',')}" unless expected.include?(public_link['purpose'])
228
+ assert_values(public_link['purpose'], expected){'public link type'}
230
229
  end
231
230
 
232
231
  def additional_persistence_ids
@@ -234,12 +233,6 @@ module Aspera
234
233
  return [] # TODO : public_link['id'] ?
235
234
  end
236
235
 
237
- # def secret_finder=(secret_finder)
238
- # raise 'secret finder already set' unless @secret_finder.nil?
239
- # raise 'secret finder must have lookup_secret' unless secret_finder.respond_to?(:lookup_secret)
240
- # @secret_finder = secret_finder
241
- # end
242
-
243
236
  # cached user information
244
237
  def current_user_info(exception: false)
245
238
  return @cache_user_info unless @cache_user_info.nil?
@@ -252,7 +245,7 @@ module Aspera
252
245
  Log.log.debug{"ignoring error: #{e}"}
253
246
  {}
254
247
  end
255
- USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = 'unknown' if @cache_user_info[f].nil?}
248
+ USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = nil if @cache_user_info[f].nil?}
256
249
  return @cache_user_info
257
250
  end
258
251
 
@@ -260,7 +253,8 @@ module Aspera
260
253
  # @return [Hash] current context information: workspace, and home node/file if app is "Files"
261
254
  def context(application = nil)
262
255
  return @context_cache unless @context_cache.nil?
263
- raise 'context must be initialized with application' if application.nil?
256
+ assert(!application.nil?){'application must be set once'}
257
+ assert_values(application, %i[files packages])
264
258
  ws_id =
265
259
  if !public_link.nil?
266
260
  Log.log.debug('Using workspace of public link')
@@ -323,7 +317,7 @@ module Aspera
323
317
  # @param package_info [Hash] created package information
324
318
  # @returns [Aspera::Node] a node API for access key
325
319
  def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Aspera::Node::SCOPE_USER, package_info: nil)
326
- raise 'invalid type for node_id' unless node_id.is_a?(String)
320
+ assert_type(node_id, String)
327
321
  node_info = read("nodes/#{node_id}")[:data]
328
322
  if workspace_name.nil? && !workspace_id.nil?
329
323
  workspace_name = read("workspaces/#{workspace_id}")[:data]['name']
@@ -370,16 +364,16 @@ module Aspera
370
364
  Log.log.debug('no metadata in shared inbox')
371
365
  return
372
366
  end
367
+ assert(pkg_data.key?('metadata')){"package requires metadata: #{meta_schema}"}
373
368
  pkg_meta = pkg_data['metadata']
374
- raise "package requires metadata: #{meta_schema}" unless pkg_data.key?('metadata')
375
- raise 'metadata must be an Array' unless pkg_meta.is_a?(Array)
369
+ assert_type(pkg_meta, Array){'metadata'}
376
370
  Log.log.debug{Log.dump(:metadata, pkg_meta)}
377
371
  pkg_meta.each do |field|
378
- raise 'metadata field must be Hash' unless field.is_a?(Hash)
379
- raise 'metadata field must have name' unless field.key?('name')
380
- raise 'metadata field must have values' unless field.key?('values')
381
- raise 'metadata values must be an Array' unless field['values'].is_a?(Array)
382
- raise "unknown metadata field: #{field['name']}" if meta_schema.select{|i|i['name'].eql?(field['name'])}.empty?
372
+ assert_type(field, Hash){'metadata field'}
373
+ assert(field.key?('name')){'metadata field must have name'}
374
+ assert(field.key?('values')){'metadata field must have values'}
375
+ assert_type(field['values'], Array){'metadata field values'}
376
+ assert(!meta_schema.select{|i|i['name'].eql?(field['name'])}.empty?){"unknown metadata field: #{field['name']}"}
383
377
  end
384
378
  meta_schema.each do |field|
385
379
  provided = pkg_meta.select{|i|i['name'].eql?(field['name'])}
@@ -396,15 +390,15 @@ module Aspera
396
390
  # @return nil package_data is modified
397
391
  def resolve_package_recipients(package_data, ws_id, recipient_list_field, new_user_option)
398
392
  return unless package_data.key?(recipient_list_field)
399
- raise "#{recipient_list_field} must be an Array" unless package_data[recipient_list_field].is_a?(Array)
393
+ assert_type(package_data[recipient_list_field], Array){recipient_list_field}
400
394
  new_user_option = {'package_contact' => true} if new_user_option.nil?
401
- raise 'new_user_option must be a Hash' unless new_user_option.is_a?(Hash)
395
+ assert_type(new_user_option, Hash){'new_user_option'}
402
396
  # list with resolved elements
403
397
  resolved_list = []
404
398
  package_data[recipient_list_field].each do |short_recipient_info|
405
399
  case short_recipient_info
406
400
  when Hash # native API information, check keys
407
- raise "#{recipient_list_field} element shall have fields: id and type" unless short_recipient_info.keys.sort.eql?(%w[id type])
401
+ assert(short_recipient_info.keys.sort.eql?(%w[id type])){"#{recipient_list_field} element shall have fields: id and type"}
408
402
  when String # CLI helper: need to resolve provided name to type/id
409
403
  # email: user, else dropbox
410
404
  entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
@@ -450,7 +444,7 @@ module Aspera
450
444
  })
451
445
  end
452
446
  pkg_data['metadata'] = api_meta
453
- else raise "metadata field if not of expected type: #{pkg_meta.class}"
447
+ else error_unexpected_value(pkg_meta.class)
454
448
  end
455
449
  return nil
456
450
  end
@@ -513,7 +507,7 @@ module Aspera
513
507
  # Console cookie
514
508
  ################
515
509
  # we are sure that fields are not nil
516
- cookie_elements = [app_info[:app], current_user_info['name'], current_user_info['email']].map{|e|Base64.strict_encode64(e)}
510
+ cookie_elements = [app_info[:app], current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{|e|Base64.strict_encode64(e)}
517
511
  cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
518
512
  transfer_spec['cookie'] = cookie_elements.join(':')
519
513
  # Application tags
@@ -590,7 +584,8 @@ module Aspera
590
584
  # @param app_info [Hash] hash with app info
591
585
  # @param types [Array] event types
592
586
  def permissions_send_event(created_data:, app_info:, types: PERMISSIONS_CREATED)
593
- raise "INTERNAL: (assert) Invalid event types: #{types}" unless types.is_a?(Array) && !types.empty?
587
+ assert_type(types, Array)
588
+ assert(!types.empty?)
594
589
  event_creation = {
595
590
  'types' => types,
596
591
  'node_id' => app_info[:node_info]['id'],
data/lib/aspera/ascmd.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # cspell:ignore ascmd smode errstr zstr zmode zuid zgid zctime zatime zmtime fcount dcount btype blist codeset lc_ctype ascmdtypes
4
4
  require 'aspera/log'
5
+ require 'aspera/assert'
5
6
 
6
7
  module Aspera
7
8
  # Run +ascmd+ commands using specified executor (usually, remotely on transfer node)
@@ -52,7 +53,7 @@ module Aspera
52
53
  end
53
54
  end
54
55
  # for info, second overrides first, so restore it
55
- case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else raise 'error'; end
56
+ case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else error_unexpected_value(result.keys.length); end
56
57
  # raise error as exception
57
58
  raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
58
59
  result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
@@ -65,10 +66,6 @@ module Aspera
65
66
  super(); @errno = errno; @errstr = errstr; @command = cmd; @arguments = arguments; end # rubocop:disable Style/Semicolon
66
67
 
67
68
  def message; "ascmd: #{@errstr} (#{@errno})"; end
68
-
69
- # TODO : delete : attr_reader :errno #, :errstr, :command
70
- # TODO : delete :def args; @arguments; end
71
-
72
69
  def extended_message; "ascmd: errno=#{@errno} errstr=\"#{@errstr}\" command=#{@command} arguments=#{@arguments.join(',')}"; end
73
70
  end # Error
74
71
 
@@ -177,7 +174,7 @@ module Aspera
177
174
  end
178
175
  end
179
176
  end
180
- else raise "error: unknown decode:#{type_descr[:decode]}"
177
+ else error_unexpected_value(type_descr[:decode])
181
178
  end # is_a
182
179
  return result
183
180
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ class InternalError < StandardError
5
+ end
6
+
7
+ class AssertError < StandardError
8
+ end
9
+ end
10
+
11
+ class Object
12
+ def assert(assertion, info = nil, level: 2, exception_class: Aspera::AssertError)
13
+ raise Aspera::InternalError, 'bad assert: both info and block given' unless info.nil? || !block_given?
14
+ return if assertion
15
+ message = 'assertion failed'
16
+ info = yield if block_given?
17
+ message = "#{message}: #{info}" if info
18
+ message = "#{message}: #{caller(level..level).first}"
19
+ raise exception_class, message
20
+ end
21
+
22
+ # assert that value has the given type
23
+ # @param value [Object] the value to check
24
+ # @param type [Class] the expected type
25
+ def assert_type(value, type, exception_class: Aspera::AssertError)
26
+ assert(value.is_a?(type), level: 3, exception_class: exception_class){"#{block_given? ? "#{yield}: " : nil}expecting #{type}, but have #{value.inspect}"}
27
+ end
28
+
29
+ # assert that value is one of the given values
30
+ def assert_values(value, values, exception_class: Aspera::AssertError)
31
+ assert(values.include?(value), level: 3, exception_class: exception_class) do
32
+ "#{block_given? ? "#{yield}: " : nil}expecting one of #{values.inspect}, but have #{value.inspect}"
33
+ end
34
+ end
35
+
36
+ # the line with this shall never be reached
37
+ def error_unreachable_line
38
+ raise Aspera::InternalError, "unreachable line reached: #{caller(2..2).first}"
39
+ end
40
+
41
+ # the value is not one of the expected values
42
+ def error_unexpected_value(value, exception_class: Aspera::InternalError)
43
+ raise exception_class, "#{block_given? ? "#{yield}: " : nil}unexpected value: #{value.inspect}"
44
+ end
45
+ end
@@ -4,6 +4,7 @@
4
4
  require 'aspera/uri_reader'
5
5
  require 'aspera/environment'
6
6
  require 'aspera/log'
7
+ require 'aspera/assert'
7
8
  require 'json'
8
9
  require 'base64'
9
10
  require 'zlib'
@@ -17,6 +18,7 @@ module Aspera
17
18
  include Singleton
18
19
 
19
20
  # special values
21
+ INIT = 'INIT'
20
22
  ALL = 'ALL'
21
23
  DEF = 'DEF'
22
24
 
@@ -30,9 +32,7 @@ module Aspera
30
32
  if col_titles.nil?
31
33
  col_titles = values
32
34
  else
33
- entry = {}
34
- col_titles.each{|title|entry[title] = values.shift}
35
- hash_array.push(entry)
35
+ hash_array.push(col_titles.zip(values).to_h)
36
36
  end
37
37
  end
38
38
  Log.log.warn('Titled CSV file without any line') if hash_array.empty?
@@ -63,7 +63,7 @@ module Aspera
63
63
  path: lambda{|v|File.expand_path(v)},
64
64
  re: lambda{|v|Regexp.new(v)},
65
65
  ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
66
- secret: lambda{|v|ExtendedValue.assert_no_value(v, :secret); $stdin.getpass('secret> ')}, # rubocop:disable Style/Semicolon
66
+ secret: lambda{|v|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
67
67
  stdin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
68
68
  yaml: lambda{|v|YAML.load(v)},
69
69
  zlib: lambda{|v|Zlib::Inflate.inflate(v)},
@@ -78,7 +78,7 @@ module Aspera
78
78
  # add a new handler
79
79
  def set_handler(name, method)
80
80
  Log.log.debug{"setting handler for #{name}"}
81
- raise 'name must be Symbol' unless name.is_a?(Symbol)
81
+ assert_type(name, Symbol){'name'}
82
82
  @handlers[name] = method
83
83
  end
84
84
 
@@ -3,6 +3,8 @@
3
3
  # cspell:ignore jsonpp
4
4
  require 'aspera/secret_hider'
5
5
  require 'aspera/environment'
6
+ require 'aspera/log'
7
+ require 'aspera/assert'
6
8
  require 'terminal-table'
7
9
  require 'yaml'
8
10
  require 'pp'
@@ -18,7 +20,7 @@ module Aspera
18
20
 
19
21
  # General method
20
22
  def flatten(something)
21
- raise 'only Hash' unless something.is_a?(Hash)
23
+ assert_type(something, Hash)
22
24
  @result = {}
23
25
  flatten_any(something, '')
24
26
  return @result
@@ -94,6 +96,7 @@ module Aspera
94
96
  DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv].freeze
95
97
  # user output levels
96
98
  DISPLAY_LEVELS = %i[info data error].freeze
99
+ RESULT_PARAMS = %i[type data total fields name].freeze
97
100
 
98
101
  private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR
99
102
  # prefix to display error messages in user messages (terminal)
@@ -155,16 +158,26 @@ module Aspera
155
158
  end
156
159
 
157
160
  def option_handler(option_symbol, operation, value=nil)
161
+ assert_values(operation, %i[set get])
158
162
  case operation
159
- when :set then @options[option_symbol] = value
163
+ when :set
164
+ @options[option_symbol] = value
165
+ if option_symbol.eql?(:output)
166
+ $stdout = if value.eql?('-')
167
+ STDOUT # rubocop:disable Style/GlobalStdStream
168
+ else
169
+ File.open(value, 'w')
170
+ end
171
+ end
160
172
  when :get then return @options[option_symbol]
161
- else raise "internal error: no such operation: #{operation}"
173
+ else error_unreachable_line
162
174
  end
163
175
  nil
164
176
  end
165
177
 
166
178
  def declare_options(options)
167
179
  options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
180
+ options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
168
181
  options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
169
182
  options.declare(
170
183
  :fields, "Comma separated list of: fields, or #{ExtendedValue::ALL}, or #{ExtendedValue::DEF}", handler: {o: self, m: :option_handler},
@@ -186,7 +199,7 @@ module Aspera
186
199
  when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
187
200
  when :info then $stdout.puts(message) if @options[:display].eql?(:info)
188
201
  when :error then $stderr.puts(message)
189
- else raise "wrong message_level:#{message_level}"
202
+ else error_unexpected_value(message_level)
190
203
  end
191
204
  end
192
205
 
@@ -219,7 +232,7 @@ module Aspera
219
232
  when Array then @options[:fields]
220
233
  when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
221
234
  when Proc then return all_fields(data).select{|i|@options[:fields].call(i)}
222
- else raise "internal error: option: fields: #{@options[:fields]}"
235
+ else error_unexpected_value(@options[:fields])
223
236
  end
224
237
  result = []
225
238
  until request.empty?
@@ -252,7 +265,7 @@ module Aspera
252
265
  # object_array: array of hash
253
266
  # fields: list of column names
254
267
  def display_table(object_array, fields)
255
- raise 'internal error: no field specified' if fields.nil?
268
+ assert(!fields.nil?){'missing fields parameter'}
256
269
  case @options[:select]
257
270
  when Proc
258
271
  object_array.select!{|i|@options[:select].call(i)}
@@ -296,12 +309,12 @@ module Aspera
296
309
 
297
310
  # this method displays the results, especially the table format
298
311
  def display_results(results)
299
- raise "INTERNAL ERROR, result unsupported key: #{results.keys - %i[type data fields name]}" unless (results.keys - %i[type data fields name]).empty?
300
- # :type :data :fields :name
301
- raise "INTERNAL ERROR, result must be Hash (got: #{results.class}: #{results})" unless results.is_a?(Hash)
302
- raise "INTERNAL ERROR, result must have type (#{results})" unless results.key?(:type)
303
- raise 'INTERNAL ERROR, result must have data' unless results.key?(:data) || %i[empty nothing].include?(results[:type])
312
+ assert_type(results, Hash)
313
+ assert((results.keys - RESULT_PARAMS).empty?){"result unsupported key: #{results.keys - RESULT_PARAMS}"}
314
+ assert(results.key?(:type)){"result must have type (#{results})"}
315
+ assert(results.key?(:data) || %i[empty nothing].include?(results[:type])){'result must have data'}
304
316
  Log.log.debug{"display_results: #{results[:data].class} #{results[:type]}"}
317
+ display_item_count(results[:data].length, results[:total]) if results.key?(:total)
305
318
  SecretHider.deep_remove_secret(results[:data]) unless @options[:show_secrets] || @options[:display].eql?(:data)
306
319
  case @options[:format]
307
320
  when :text
@@ -323,8 +336,8 @@ module Aspera
323
336
  when :object_list, :single_object
324
337
  obj_list = results[:data]
325
338
  obj_list = [obj_list] if results[:type].eql?(:single_object)
326
- raise "internal error: expecting Array: got #{obj_list.class}" unless obj_list.is_a?(Array)
327
- raise 'internal error: expecting Array of Hash' unless obj_list.all?(Hash)
339
+ assert_type(obj_list, Array)
340
+ assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
328
341
  # :object_list is an array of hash tables, where key=colum name
329
342
  obj_list = obj_list.map{|obj|Flattener.new.flatten(obj)} if @options[:flat_hash]
330
343
  display_table(obj_list, compute_fields(obj_list, results[:fields]))
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'aspera/fasp/error'
4
4
  require 'aspera/rest'
5
+ require 'aspera/log'
6
+ require 'aspera/assert'
5
7
  require 'net/ssh'
6
8
  require 'openssl'
7
9
 
@@ -58,14 +60,13 @@ module Aspera
58
60
  matches = hint[:match]
59
61
  matches = [matches] unless matches.is_a?(Array)
60
62
  matches.each do |m|
63
+ assert_values(m.class, [String, Regexp])
61
64
  case m
62
65
  when String
63
66
  next unless message.eql?(m)
64
67
  when Regexp
65
68
  next unless message.match?(m)
66
- else
67
- Log.log.warn("Internal error: hint is a #{m.class}")
68
- next
69
+ else error_unexpected_value(m)
69
70
  end
70
71
  remediation = hint[:remediation]
71
72
  remediation = [remediation] unless remediation.is_a?(Array)
@@ -8,8 +8,10 @@ require 'aspera/cli/transfer_agent'
8
8
  require 'aspera/cli/version'
9
9
  require 'aspera/cli/info'
10
10
  require 'aspera/cli/hints'
11
+ require 'aspera/preview/terminal'
11
12
  require 'aspera/secret_hider'
12
13
  require 'aspera/log'
14
+ require 'aspera/assert'
13
15
 
14
16
  module Aspera
15
17
  module Cli
@@ -53,6 +55,14 @@ module Aspera
53
55
  raise global_status unless global_status.eql?(:success)
54
56
  return {type: :object_list, data: status_table}
55
57
  end
58
+
59
+ def result_picture_in_terminal(options, blob)
60
+ terminal_options = options.get_option(:query, default: {}).symbolize_keys
61
+ allowed_options = Preview::Terminal.method(:build).parameters.select{|i|i[0].eql?(:key)}.map{|i|i[1]}
62
+ unknown_options = terminal_options.keys - allowed_options
63
+ raise "invalid options: #{unknown_options.join(', ')}, use #{allowed_options.join(', ')}" unless unknown_options.empty?
64
+ return Main.result_status(Preview::Terminal.build(blob, **terminal_options))
65
+ end
56
66
  end # self
57
67
 
58
68
  private
@@ -78,9 +88,10 @@ module Aspera
78
88
 
79
89
  # This can throw exception if there is a problem with the environment, needs to be caught by execute method
80
90
  def init_agents_and_options
81
- # first thing : manage debug level (allows debugging of option parser)
82
- early_debug_setup
91
+ # create formatter, in case there is an exception, it is used to display.
83
92
  @agents[:formatter] = Formatter.new
93
+ # second : manage debug level (allows debugging of option parser)
94
+ early_debug_setup
84
95
  @agents[:options] = Manager.new(PROGRAM_NAME)
85
96
  # give command line arguments to option manager
86
97
  options.parse_command_line(@argv)
@@ -96,7 +107,7 @@ module Aspera
96
107
  # the Config plugin adds the @preset parser, so declare before TransferAgent which may use it
97
108
  @agents[:config] = Plugins::Config.new(@agents, gem: GEM_NAME, name: PROGRAM_NAME, help: DOC_URL, version: Aspera::Cli::VERSION)
98
109
  # data persistency
99
- raise 'internal error: missing persistency object' unless @agents[:persistency]
110
+ assert(@agents[:persistency]){'missing persistency object'}
100
111
  # the TransferAgent plugin may use the @preset parser
101
112
  @agents[:transfer] = TransferAgent.new(options, config)
102
113
  Log.log.debug('plugin env created'.red)
@@ -219,6 +230,8 @@ module Aspera
219
230
  when /^--log-level=(.*)/ then Aspera::Log.instance.level = Regexp.last_match(1).to_sym
220
231
  when /^--logger=(.*)/ then Aspera::Log.instance.logger_type = Regexp.last_match(1).to_sym
221
232
  end
233
+ rescue => e
234
+ $stderr.puts("Error: #{e}")
222
235
  end
223
236
  end
224
237