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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +292 -228
- data/CONTRIBUTING.md +69 -18
- data/README.md +1102 -952
- data/bin/ascli +13 -31
- data/bin/asession +3 -1
- data/examples/dascli +2 -2
- data/lib/aspera/aoc.rb +28 -33
- data/lib/aspera/ascmd.rb +3 -6
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formatter.rb +26 -13
- data/lib/aspera/cli/hints.rb +4 -3
- data/lib/aspera/cli/main.rb +16 -3
- data/lib/aspera/cli/manager.rb +45 -36
- data/lib/aspera/cli/plugin.rb +20 -13
- data/lib/aspera/cli/plugins/aoc.rb +103 -73
- data/lib/aspera/cli/plugins/ats.rb +4 -3
- data/lib/aspera/cli/plugins/config.rb +114 -119
- data/lib/aspera/cli/plugins/cos.rb +2 -2
- data/lib/aspera/cli/plugins/faspex.rb +23 -19
- data/lib/aspera/cli/plugins/faspex5.rb +75 -43
- data/lib/aspera/cli/plugins/node.rb +28 -15
- data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
- data/lib/aspera/cli/plugins/preview.rb +9 -7
- data/lib/aspera/cli/plugins/server.rb +6 -3
- data/lib/aspera/cli/plugins/shares.rb +30 -26
- data/lib/aspera/cli/sync_actions.rb +9 -9
- data/lib/aspera/cli/transfer_agent.rb +21 -14
- data/lib/aspera/cli/transfer_progress.rb +2 -3
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +13 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +4 -2
- data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
- data/lib/aspera/fasp/agent_base.rb +17 -7
- data/lib/aspera/fasp/agent_direct.rb +88 -84
- data/lib/aspera/fasp/agent_httpgw.rb +4 -3
- data/lib/aspera/fasp/agent_node.rb +3 -2
- data/lib/aspera/fasp/agent_trsdk.rb +79 -37
- data/lib/aspera/fasp/installation.rb +51 -12
- data/lib/aspera/fasp/management.rb +11 -6
- data/lib/aspera/fasp/parameters.rb +53 -47
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +2 -2
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +6 -5
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +10 -8
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/log.rb +4 -3
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +17 -16
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +22 -19
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/preview/file_types.rb +53 -267
- data/lib/aspera/preview/generator.rb +7 -5
- data/lib/aspera/preview/terminal.rb +14 -5
- data/lib/aspera/preview/utils.rb +8 -7
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +29 -13
- data/lib/aspera/rest_error_analyzer.rb +1 -0
- data/lib/aspera/rest_errors_aspera.rb +2 -0
- data/lib/aspera/secret_hider.rb +5 -2
- data/lib/aspera/ssh.rb +10 -8
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/web_server_simple.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +96 -45
- metadata.gz.sig +0 -0
- 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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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(
|
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 =
|
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=
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
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
|
-
|
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] =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
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|
|
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
|
-
|
81
|
+
assert_type(name, Symbol){'name'}
|
82
82
|
@handlers[name] = method
|
83
83
|
end
|
84
84
|
|
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
-
|
327
|
-
|
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]))
|
data/lib/aspera/cli/hints.rb
CHANGED
@@ -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)
|
data/lib/aspera/cli/main.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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
|
|