aspera-cli 4.15.0 → 4.16.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 (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