aspera-cli 4.12.0 → 4.14.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/CHANGELOG.md +45 -5
- data/CONTRIBUTING.md +113 -22
- data/README.md +1289 -754
- data/bin/ascli +3 -3
- data/examples/dascli +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +63 -74
- data/lib/aspera/ascmd.rb +5 -3
- data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
- data/lib/aspera/cli/extended_value.rb +24 -37
- data/lib/aspera/cli/formatter.rb +23 -25
- data/lib/aspera/cli/info.rb +2 -4
- data/lib/aspera/cli/main.rb +27 -27
- data/lib/aspera/cli/manager.rb +143 -120
- data/lib/aspera/cli/plugin.rb +88 -43
- data/lib/aspera/cli/plugins/alee.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +235 -104
- data/lib/aspera/cli/plugins/ats.rb +16 -18
- data/lib/aspera/cli/plugins/bss.rb +3 -3
- data/lib/aspera/cli/plugins/config.rb +190 -373
- data/lib/aspera/cli/plugins/console.rb +4 -6
- data/lib/aspera/cli/plugins/cos.rb +12 -13
- data/lib/aspera/cli/plugins/faspex.rb +21 -21
- data/lib/aspera/cli/plugins/faspex5.rb +399 -150
- data/lib/aspera/cli/plugins/node.rb +260 -174
- data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
- data/lib/aspera/cli/plugins/preview.rb +40 -62
- data/lib/aspera/cli/plugins/server.rb +33 -16
- data/lib/aspera/cli/plugins/shares.rb +24 -33
- data/lib/aspera/cli/plugins/sync.rb +6 -6
- data/lib/aspera/cli/transfer_agent.rb +47 -30
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +9 -7
- data/lib/aspera/command_line_builder.rb +2 -1
- data/lib/aspera/cos_node.rb +1 -1
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +7 -3
- data/lib/aspera/fasp/agent_connect.rb +6 -1
- data/lib/aspera/fasp/agent_direct.rb +17 -17
- data/lib/aspera/fasp/agent_httpgw.rb +138 -60
- data/lib/aspera/fasp/agent_node.rb +14 -4
- data/lib/aspera/fasp/agent_trsdk.rb +2 -0
- data/lib/aspera/fasp/error_info.rb +2 -0
- data/lib/aspera/fasp/installation.rb +19 -19
- data/lib/aspera/fasp/parameters.rb +29 -20
- data/lib/aspera/fasp/parameters.yaml +5 -2
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +8 -5
- data/lib/aspera/fasp/uri.rb +23 -21
- data/lib/aspera/faspex_gw.rb +1 -0
- data/lib/aspera/faspex_postproc.rb +3 -3
- data/lib/aspera/hash_ext.rb +12 -2
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node.rb +73 -84
- data/lib/aspera/oauth.rb +4 -3
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/preview/file_types.rb +8 -6
- data/lib/aspera/preview/generator.rb +23 -11
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +80 -0
- data/lib/aspera/preview/utils.rb +11 -11
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +42 -4
- data/lib/aspera/rest_call_error.rb +3 -1
- data/lib/aspera/secret_hider.rb +10 -5
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/sync.rb +41 -33
- data/lib/aspera/web_server_simple.rb +22 -18
- data.tar.gz.sig +0 -0
- metadata +40 -48
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -179
- data/examples/aoc.rb +0 -30
- data/examples/faspex4.rb +0 -94
- data/examples/node.rb +0 -96
- data/examples/server.rb +0 -93
- data/lib/aspera/data/7 +0 -0
data/bin/ascli
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby -EUTF-8:UTF-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'securerandom'
|
6
|
+
# compute gem root based on this script location
|
6
7
|
GEM_ROOT = File.realpath(File.join(File.dirname(File.realpath(__FILE__)), '..'))
|
7
8
|
# coverage for tests
|
8
9
|
if ENV.key?('ENABLE_COVERAGE')
|
@@ -20,10 +21,9 @@ if ENV.key?('ENABLE_COVERAGE')
|
|
20
21
|
end
|
21
22
|
SimpleCov.start
|
22
23
|
end
|
24
|
+
# if in development, add path to gem
|
23
25
|
$LOAD_PATH.unshift(File.join(GEM_ROOT, 'lib'))
|
24
26
|
require 'aspera/cli/main'
|
25
27
|
require 'aspera/environment'
|
26
|
-
Encoding.default_internal = Encoding::UTF_8
|
27
|
-
Encoding.default_external = Encoding::UTF_8
|
28
28
|
Aspera::Environment.fix_home
|
29
29
|
Aspera::Cli::Main.new(ARGV).process_command_line
|
data/examples/dascli
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
# set env var image to specify another docker image
|
3
|
-
: "${image:=martinlaurent/ascli}"
|
3
|
+
: "${image:=docker.io/martinlaurent/ascli}"
|
4
4
|
# set env var version to specify another image version (default: latest version)
|
5
5
|
: "${version:=latest}"
|
6
6
|
# set env var imgtag to specify a specific image/version
|
data/examples/rubyc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# https://github.com/you54f/ruby-packer
|
3
|
+
# https://github.com/YOU54F/ruby-packer/releases
|
4
|
+
set -e
|
5
|
+
FOLDER="$(dirname $0)/../tmp"
|
6
|
+
RUBYC="$FOLDER/rubyc"
|
7
|
+
if test ! -e "$RUBYC"; then
|
8
|
+
mkdir -p "$FOLDER"
|
9
|
+
case $(uname -sm|tr ' ' -) in
|
10
|
+
Darwin-arm64)
|
11
|
+
curl -L https://github.com/YOU54F/ruby-packer/releases/download/rel-20230812/rubyc-Darwin-arm64.tar.gz | tar -xz -C "$FOLDER"
|
12
|
+
mv "$FOLDER/rubyc-Darwin-arm64" "$RUBYC"
|
13
|
+
;;
|
14
|
+
Linux-x86_64)
|
15
|
+
curl -L https://github.com/YOU54F/ruby-packer/releases/download/rel-20230812/rubyc-Linux-x86_64.tar.gz | tar -xz -C "$FOLDER"
|
16
|
+
mv "$FOLDER/rubyc-Linux-x86_64" "$RUBYC"
|
17
|
+
;;
|
18
|
+
*)
|
19
|
+
echo "This architecture is not supported." >&2
|
20
|
+
exit 1
|
21
|
+
;;
|
22
|
+
esac
|
23
|
+
fi
|
24
|
+
exec "$RUBYC" "$@"
|
data/lib/aspera/aoc.rb
CHANGED
@@ -42,6 +42,9 @@ module Aspera
|
|
42
42
|
OAUTH_API_SUBPATH = 'api/v1/oauth2'
|
43
43
|
# minimum fields for user info if retrieval fails
|
44
44
|
USER_INFO_FIELDS_MIN = %w[name email id default_workspace_id organization_id].freeze
|
45
|
+
# types of events for shared folder creation
|
46
|
+
# Node events: permission.created permission.modified permission.deleted
|
47
|
+
PERMISSIONS_CREATED = ['permission.created'].freeze
|
45
48
|
|
46
49
|
private_constant :MAX_REDIRECT,
|
47
50
|
:GLOBAL_CLIENT_APPS,
|
@@ -50,7 +53,8 @@ module Aspera
|
|
50
53
|
:PUBLIC_LINK_PATHS,
|
51
54
|
:JWT_AUDIENCE,
|
52
55
|
:OAUTH_API_SUBPATH,
|
53
|
-
:USER_INFO_FIELDS_MIN
|
56
|
+
:USER_INFO_FIELDS_MIN,
|
57
|
+
:PERMISSIONS_CREATED
|
54
58
|
|
55
59
|
# various API scopes supported
|
56
60
|
SCOPE_FILES_SELF = 'self'
|
@@ -63,8 +67,6 @@ module Aspera
|
|
63
67
|
FILES_APP = 'files'
|
64
68
|
PACKAGES_APP = 'packages'
|
65
69
|
API_V1 = 'api/v1'
|
66
|
-
# error message when entity not found
|
67
|
-
ENTITY_NOT_FOUND = 'No such'
|
68
70
|
|
69
71
|
# class static methods
|
70
72
|
class << self
|
@@ -252,25 +254,30 @@ module Aspera
|
|
252
254
|
return @cache_user_info
|
253
255
|
end
|
254
256
|
|
255
|
-
# @returns [Aspera::Node] a node API for access key
|
256
257
|
# @param node_id [String] identifier of node in AoC
|
258
|
+
# @param workspace_id [String] workspace identifier
|
259
|
+
# @param workspace_name [String] workspace name
|
257
260
|
# @param scope e.g. SCOPE_NODE_USER, or nil (requires secret)
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
261
|
+
# @param package_info [Hash] created package information
|
262
|
+
# @returns [Aspera::Node] a node API for access key
|
263
|
+
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: SCOPE_NODE_USER, package_info: nil)
|
264
|
+
raise 'invalid type for node_id' unless node_id.is_a?(String)
|
265
|
+
node_info = read("nodes/#{node_id}")[:data]
|
266
|
+
if workspace_name.nil? && !workspace_id.nil?
|
267
|
+
workspace_name = read("workspaces/#{workspace_id}")[:data]['name']
|
265
268
|
end
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
269
|
+
app_info = {
|
270
|
+
api: self, # for callback
|
271
|
+
app: package_info.nil? ? FILES_APP : PACKAGES_APP,
|
272
|
+
node_info: node_info,
|
273
|
+
workspace_id: workspace_id,
|
274
|
+
workspace_name: workspace_name
|
275
|
+
}
|
276
|
+
if PACKAGES_APP.eql?(app_info[:app])
|
277
|
+
raise 'package info required' if package_info.nil?
|
278
|
+
app_info[:package_id] = package_info['id']
|
279
|
+
app_info[:package_name] = package_info['name']
|
272
280
|
end
|
273
|
-
node_info = read("nodes/#{node_id}")[:data]
|
274
281
|
node_rest_params = {base_url: node_info['url']}
|
275
282
|
# if secret is available
|
276
283
|
if scope.nil?
|
@@ -286,38 +293,9 @@ module Aspera
|
|
286
293
|
# special header required for bearer token only
|
287
294
|
node_rest_params[:headers] = {Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
|
288
295
|
end
|
289
|
-
app_info = {
|
290
|
-
node_info: node_info,
|
291
|
-
workspace_info: workspace_info,
|
292
|
-
app: package_info.nil? ? FILES_APP : PACKAGES_APP,
|
293
|
-
api: self # for callback
|
294
|
-
}
|
295
|
-
app_info[:package_info] = package_info unless package_info.nil?
|
296
296
|
return Node.new(params: node_rest_params, app_info: app_info)
|
297
297
|
end
|
298
298
|
|
299
|
-
# Query entity type by name and returns the id if a single entry only
|
300
|
-
# @param entity_type path of entity in API
|
301
|
-
# @param entity_name name of searched entity
|
302
|
-
# @param options additional search options
|
303
|
-
def lookup_entity_by_name(entity_type, entity_name, options={})
|
304
|
-
# returns entities whose name contains value (case insensitive)
|
305
|
-
matching_items = read(entity_type, options.merge({'q' => CGI.escape(entity_name)}))[:data]
|
306
|
-
case matching_items.length
|
307
|
-
when 1 then return matching_items.first
|
308
|
-
when 0 then raise %Q{#{ENTITY_NOT_FOUND} #{entity_type}: "#{entity_name}"}
|
309
|
-
else
|
310
|
-
# multiple case insensitive partial matches, try case insensitive full match
|
311
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
312
|
-
name_matches = matching_items.select{|i|i['name'].casecmp?(entity_name)}
|
313
|
-
case name_matches.length
|
314
|
-
when 1 then return name_matches.first
|
315
|
-
when 0 then raise %Q(#{entity_type}: multiple case insensitive partial match for: "#{entity_name}": #{matching_items.map{|i|i['name']}} but no case insensitive full match. Please be more specific or give exact name.) # rubocop:disable Layout/LineLength
|
316
|
-
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{|i|i['name']}}"
|
317
|
-
end
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
299
|
# Check metadata: remove when validation is done server side
|
322
300
|
def validate_metadata(pkg_data)
|
323
301
|
# validate only for shared inboxes
|
@@ -369,7 +347,7 @@ module Aspera
|
|
369
347
|
# email: user, else dropbox
|
370
348
|
entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
|
371
349
|
begin
|
372
|
-
full_recipient_info =
|
350
|
+
full_recipient_info = lookup_by_name(entity_type, short_recipient_info, {'current_workspace_id' => ws_id})
|
373
351
|
rescue RuntimeError => e
|
374
352
|
raise e unless e.message.start_with?(ENTITY_NOT_FOUND)
|
375
353
|
# dropboxes cannot be created on the fly
|
@@ -433,7 +411,10 @@ module Aspera
|
|
433
411
|
# create a new package container
|
434
412
|
created_package = create('packages', package_data)[:data]
|
435
413
|
|
436
|
-
package_node_api = node_api_from(
|
414
|
+
package_node_api = node_api_from(
|
415
|
+
node_id: created_package['node_id'],
|
416
|
+
workspace_id: created_package['workspace_id'],
|
417
|
+
package_info: created_package)
|
437
418
|
|
438
419
|
# tell AoC what to expect in package: 1 transfer (can also be done after transfer)
|
439
420
|
# TODO: if multi session was used we should probably tell
|
@@ -454,15 +435,14 @@ module Aspera
|
|
454
435
|
transfer_type = Fasp::TransferSpec.action(transfer_spec)
|
455
436
|
# Analytics tags
|
456
437
|
################
|
457
|
-
ws_info = app_info[:workspace_info]
|
458
438
|
transfer_spec.deep_merge!({
|
459
439
|
'tags' => {
|
460
|
-
|
461
|
-
'usage_id' => "aspera.files.workspace.#{
|
440
|
+
Fasp::TransferSpec::TAG_RESERVED => {
|
441
|
+
'usage_id' => "aspera.files.workspace.#{app_info[:workspace_id]}", # activity tracking
|
462
442
|
'files' => {
|
463
443
|
'files_transfer_action' => "#{transfer_type}_#{app_info[:app].gsub(/s$/, '')}",
|
464
|
-
'workspace_name' =>
|
465
|
-
'workspace_id' =>
|
444
|
+
'workspace_name' => app_info[:workspace_name], # activity tracking
|
445
|
+
'workspace_id' => app_info[:workspace_id]
|
466
446
|
}
|
467
447
|
}
|
468
448
|
}
|
@@ -477,37 +457,40 @@ module Aspera
|
|
477
457
|
##################
|
478
458
|
case app_info[:app]
|
479
459
|
when FILES_APP
|
480
|
-
file_id = transfer_spec['tags'][
|
481
|
-
transfer_spec.deep_merge!({'tags' => {
|
460
|
+
file_id = transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['node']['file_id']
|
461
|
+
transfer_spec.deep_merge!({'tags' => {Fasp::TransferSpec::TAG_RESERVED => {'files' => {'parentCwd' => "#{app_info[:node_info]['id']}:#{file_id}"}}}}) \
|
482
462
|
unless transfer_spec.key?('remote_access_key')
|
483
463
|
when PACKAGES_APP
|
484
464
|
transfer_spec.deep_merge!({
|
485
465
|
'tags' => {
|
486
|
-
|
466
|
+
Fasp::TransferSpec::TAG_RESERVED => {
|
487
467
|
'files' => {
|
488
|
-
'package_id' => app_info[:
|
489
|
-
'package_name' => app_info[:
|
468
|
+
'package_id' => app_info[:package_id],
|
469
|
+
'package_name' => app_info[:package_name],
|
490
470
|
'package_operation' => transfer_type
|
491
471
|
}}}})
|
492
472
|
end
|
493
|
-
transfer_spec['tags'][
|
494
|
-
transfer_spec['tags'][
|
473
|
+
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['files']['node_id'] = app_info[:node_info]['id']
|
474
|
+
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['app'] = app_info[:app]
|
495
475
|
end
|
496
476
|
|
497
477
|
ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
|
498
478
|
# Callback from Plugins::Node
|
499
|
-
|
479
|
+
# add application specific tags to permissions creation
|
480
|
+
# @param create_param [Hash] parameters for creating permissions
|
481
|
+
# @param app_info [Hash] application information
|
482
|
+
def permissions_set_create_params(create_param:, app_info:)
|
500
483
|
# workspace shared folder:
|
501
|
-
# access_id = "#{ID_AK_ADMIN}_WS_#{
|
484
|
+
# access_id = "#{ID_AK_ADMIN}_WS_#{app_info[:workspace_id]}"
|
502
485
|
default_params = {
|
503
486
|
# 'access_type' => 'user', # mandatory: user or group
|
504
487
|
# 'access_id' => access_id, # id of user or group
|
505
488
|
'tags' => {
|
506
|
-
|
489
|
+
Fasp::TransferSpec::TAG_RESERVED => {
|
507
490
|
'files' => {
|
508
491
|
'workspace' => {
|
509
|
-
'id' => app_info[:
|
510
|
-
'workspace_name' => app_info[:
|
492
|
+
'id' => app_info[:workspace_id],
|
493
|
+
'workspace_name' => app_info[:workspace_name],
|
511
494
|
'user_name' => current_user_info['name'],
|
512
495
|
'shared_by_user_id' => current_user_info['id'],
|
513
496
|
'shared_by_name' => current_user_info['name'],
|
@@ -517,28 +500,34 @@ module Aspera
|
|
517
500
|
'node' => app_info[:node_info]['name']}}}}}
|
518
501
|
create_param.deep_merge!(default_params)
|
519
502
|
if create_param.key?('with')
|
520
|
-
contact_info =
|
503
|
+
contact_info = lookup_by_name(
|
521
504
|
'contacts',
|
522
505
|
create_param['with'],
|
523
|
-
{'current_workspace_id' => app_info[:
|
506
|
+
{'current_workspace_id' => app_info[:workspace_id], 'context' => 'share_folder'})
|
524
507
|
create_param.delete('with')
|
525
508
|
create_param['access_type'] = contact_info['source_type']
|
526
509
|
create_param['access_id'] = contact_info['source_id']
|
527
|
-
create_param['tags'][
|
510
|
+
create_param['tags'][Fasp::TransferSpec::TAG_RESERVED]['files']['workspace']['shared_with_name'] = contact_info['email']
|
528
511
|
end
|
529
512
|
# optional
|
530
513
|
app_info[:opt_link_name] = create_param.delete('link_name')
|
531
514
|
end
|
532
515
|
|
533
516
|
# Callback from Plugins::Node
|
534
|
-
|
517
|
+
# send shared folder event to AoC
|
518
|
+
# @param created_data [Hash] response from permission creation
|
519
|
+
# @param app_info [Hash] hash with app info
|
520
|
+
# @param types [Array] event types
|
521
|
+
def permissions_send_event(created_data:, app_info:, types: PERMISSIONS_CREATED)
|
522
|
+
raise "INTERNAL: (assert) Invalid event types: #{types}" unless types.is_a?(Array) && !types.empty?
|
535
523
|
event_creation = {
|
536
|
-
'types' =>
|
524
|
+
'types' => types,
|
537
525
|
'node_id' => app_info[:node_info]['id'],
|
538
|
-
'workspace_id' => app_info[:
|
539
|
-
'data' => created_data
|
526
|
+
'workspace_id' => app_info[:workspace_id],
|
527
|
+
'data' => created_data
|
540
528
|
}
|
541
|
-
# (optional). The name of the folder to be displayed to the destination user.
|
529
|
+
# (optional). The name of the folder to be displayed to the destination user.
|
530
|
+
# Use it if its value is different from the "share_as" field.
|
542
531
|
event_creation['link_name'] = app_info[:opt_link_name] unless app_info[:opt_link_name].nil?
|
543
532
|
create('events', event_creation)
|
544
533
|
end
|
data/lib/aspera/ascmd.rb
CHANGED
@@ -24,10 +24,11 @@ module Aspera
|
|
24
24
|
# concatenate arguments, enclose in double quotes, protect backslash and double quotes, add "as_" command and as_exit
|
25
25
|
stdin_input = (args || []).map{|v| '"' + v.gsub(/["\\]/n) {|s| '\\' + s } + '"'}.unshift('as_' + action_sym.to_s).join(' ') + "\nas_exit\n"
|
26
26
|
# execute, get binary output
|
27
|
-
|
27
|
+
byte_buffer = @command_executor.execute('ascmd', stdin_input).unpack('C*')
|
28
|
+
raise 'ERROR: empty answer from server' if byte_buffer.empty?
|
28
29
|
# get hash or table result
|
29
|
-
result = self.class.parse(
|
30
|
-
raise 'ERROR: unparsed bytes remaining' unless
|
30
|
+
result = self.class.parse(byte_buffer, :result)
|
31
|
+
raise 'ERROR: unparsed bytes remaining' unless byte_buffer.empty?
|
31
32
|
# get and delete info,always present in results
|
32
33
|
system_info = result[:info]
|
33
34
|
result.delete(:info)
|
@@ -126,6 +127,7 @@ module Aspera
|
|
126
127
|
byte_array = buffer.shift(num_bytes)
|
127
128
|
byte_array = [byte_array] unless byte_array.is_a?(Array)
|
128
129
|
result = byte_array.pack('C*').unpack1(type_descr[:unpack])
|
130
|
+
result.force_encoding('UTF-8') if type_name.eql?(:zstr)
|
129
131
|
Log.log.debug{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
|
130
132
|
result = Time.at(result) if type_name.eql?(:epoch)
|
131
133
|
when :buffer_list
|
@@ -9,9 +9,9 @@ module Aspera
|
|
9
9
|
class BasicAuthPlugin < Aspera::Cli::Plugin
|
10
10
|
class << self
|
11
11
|
def register_options(env)
|
12
|
-
env[:options].
|
13
|
-
env[:options].
|
14
|
-
env[:options].
|
12
|
+
env[:options].declare(:url, 'URL of application, e.g. https://org.asperafiles.com')
|
13
|
+
env[:options].declare(:username, 'Username to log in')
|
14
|
+
env[:options].declare(:password, "User's password")
|
15
15
|
env[:options].parse_options!
|
16
16
|
end
|
17
17
|
end
|
@@ -23,14 +23,14 @@ module Aspera
|
|
23
23
|
|
24
24
|
# returns a Rest object with basic auth
|
25
25
|
def basic_auth_params(subpath=nil)
|
26
|
-
api_url = options.get_option(:url,
|
26
|
+
api_url = options.get_option(:url, mandatory: true)
|
27
27
|
api_url = api_url + '/' + subpath unless subpath.nil?
|
28
28
|
return {
|
29
29
|
base_url: api_url,
|
30
30
|
auth: {
|
31
31
|
type: :basic,
|
32
|
-
username: options.get_option(:username,
|
33
|
-
password: options.get_option(:password,
|
32
|
+
username: options.get_option(:username, mandatory: true),
|
33
|
+
password: options.get_option(:password, mandatory: true)
|
34
34
|
}}
|
35
35
|
end
|
36
36
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'aspera/cli/plugins/config'
|
4
3
|
require 'aspera/uri_reader'
|
5
4
|
require 'aspera/environment'
|
6
5
|
require 'json'
|
@@ -39,38 +38,33 @@ module Aspera
|
|
39
38
|
|
40
39
|
def initialize
|
41
40
|
@handlers = {
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
},
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
uri: lambda{|v|UriReader.read(v)},
|
57
|
-
stdin: lambda{|v|raise 'no value allowed for stdin' unless v.empty?; $stdin.read} # rubocop:disable Style/Semicolon
|
58
|
-
}
|
41
|
+
base64: lambda{|v|Base64.decode64(v)},
|
42
|
+
csvt: lambda{|v|ExtendedValue.decode_csvt(v)},
|
43
|
+
env: lambda{|v|ENV[v]},
|
44
|
+
file: lambda{|v|File.read(File.expand_path(v))},
|
45
|
+
json: lambda{|v|JSON.parse(v)},
|
46
|
+
lines: lambda{|v|v.split("\n")},
|
47
|
+
list: lambda{|v|v[1..-1].split(v[0])},
|
48
|
+
path: lambda{|v|File.expand_path(v)},
|
49
|
+
ruby: lambda{|v|Environment.secure_eval(v)},
|
50
|
+
secret: lambda{|v|raise 'no value allowed for secret' unless v.empty?; $stdin.getpass('secret> ')}, # rubocop:disable Style/Semicolon
|
51
|
+
stdin: lambda{|v|raise 'no value allowed for stdin' unless v.empty?; $stdin.read}, # rubocop:disable Style/Semicolon
|
52
|
+
uri: lambda{|v|UriReader.read(v)},
|
53
|
+
val: lambda{|v|v},
|
54
|
+
zlib: lambda{|v|Zlib::Inflate.inflate(v)}
|
59
55
|
# other handlers can be set using set_handler, e.g. preset is reader in config plugin
|
60
56
|
}
|
61
57
|
end
|
62
58
|
|
63
59
|
public
|
64
60
|
|
65
|
-
def modifiers; @handlers.keys
|
61
|
+
def modifiers; @handlers.keys; end
|
66
62
|
|
67
|
-
# add a new
|
68
|
-
|
69
|
-
|
70
|
-
Log.log.debug{"setting #{type} handler for #{name}"}
|
63
|
+
# add a new handler
|
64
|
+
def set_handler(name, method)
|
65
|
+
Log.log.debug{"setting handler for #{name}"}
|
71
66
|
raise 'name must be Symbol' unless name.is_a?(Symbol)
|
72
|
-
|
73
|
-
@handlers[type][name] = method
|
67
|
+
@handlers[name] = method
|
74
68
|
end
|
75
69
|
|
76
70
|
# parse an option value if it is a String using supported extended value modifiers
|
@@ -78,20 +72,13 @@ module Aspera
|
|
78
72
|
def evaluate(value)
|
79
73
|
return value if !value.is_a?(String)
|
80
74
|
# first determine decoders, in reversed order
|
81
|
-
|
82
|
-
while (m = value.match(/^@([^:]+):(.*)/)) && @handlers
|
83
|
-
|
75
|
+
handlers_reversed = []
|
76
|
+
while (m = value.match(/^@([^:]+):(.*)/)) && @handlers.include?(m[1].to_sym)
|
77
|
+
handlers_reversed.unshift(m[1].to_sym)
|
84
78
|
value = m[2]
|
85
79
|
end
|
86
|
-
|
87
|
-
|
88
|
-
if (m = value.match(/^@#{reader}:(.*)/))
|
89
|
-
value = method.call(m[1])
|
90
|
-
break
|
91
|
-
end
|
92
|
-
end
|
93
|
-
decoders_reversed.each do |decoder|
|
94
|
-
value = @handlers[:decoder][decoder].call(value)
|
80
|
+
handlers_reversed.each do |handler|
|
81
|
+
value = @handlers[handler].call(value)
|
95
82
|
end
|
96
83
|
return value
|
97
84
|
end # parse
|
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -18,9 +18,10 @@ module Aspera
|
|
18
18
|
# user output levels
|
19
19
|
DISPLAY_LEVELS = %i[info data error].freeze
|
20
20
|
CONF_OVERVIEW_KEYS = %w[config parameter value].freeze
|
21
|
+
KEY_VALUE = %w[key value].freeze
|
21
22
|
|
22
23
|
private_constant :FIELDS_ALL, :FIELDS_DEFAULT, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR,
|
23
|
-
:CONF_OVERVIEW_KEYS
|
24
|
+
:CONF_OVERVIEW_KEYS, :KEY_VALUE
|
24
25
|
|
25
26
|
class << self
|
26
27
|
# special for Aspera on Cloud display node
|
@@ -81,22 +82,14 @@ module Aspera
|
|
81
82
|
@option_flat_hash = true
|
82
83
|
@option_transpose_single = true
|
83
84
|
@option_show_secrets = false
|
84
|
-
opt_mgr.
|
85
|
-
opt_mgr.
|
86
|
-
opt_mgr.
|
87
|
-
opt_mgr.
|
88
|
-
opt_mgr.
|
89
|
-
opt_mgr.
|
90
|
-
opt_mgr.
|
91
|
-
opt_mgr.
|
92
|
-
opt_mgr.add_opt_list(:format, DISPLAY_FORMATS, 'output format')
|
93
|
-
opt_mgr.add_opt_list(:display, DISPLAY_LEVELS, 'output only some information')
|
94
|
-
opt_mgr.add_opt_simple(:fields, "comma separated list of fields, or #{FIELDS_ALL}, or #{FIELDS_DEFAULT}")
|
95
|
-
opt_mgr.add_opt_simple(:select, 'select only some items in lists, extended value: hash (column, value)')
|
96
|
-
opt_mgr.add_opt_simple(:table_style, 'table display style')
|
97
|
-
opt_mgr.add_opt_boolean(:flat_hash, 'display hash values as additional keys')
|
98
|
-
opt_mgr.add_opt_boolean(:transpose_single, 'single object fields output vertically')
|
99
|
-
opt_mgr.add_opt_boolean(:show_secrets, 'show secrets on command output')
|
85
|
+
opt_mgr.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_format})
|
86
|
+
opt_mgr.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_display})
|
87
|
+
opt_mgr.declare(:fields, "Comma separated list of fields, or #{FIELDS_ALL}, or #{FIELDS_DEFAULT}", handler: {o: self, m: :option_fields})
|
88
|
+
opt_mgr.declare(:select, 'Select only some items in lists: column, value', types: Hash, handler: {o: self, m: :option_select})
|
89
|
+
opt_mgr.declare(:table_style, 'Table display style', handler: {o: self, m: :option_table_style})
|
90
|
+
opt_mgr.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_flat_hash})
|
91
|
+
opt_mgr.declare(:transpose_single, 'Single object fields output vertically', values: :bool, handler: {o: self, m: :option_transpose_single})
|
92
|
+
opt_mgr.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_show_secrets})
|
100
93
|
end
|
101
94
|
|
102
95
|
# main output method
|
@@ -116,6 +109,12 @@ module Aspera
|
|
116
109
|
display_message(:info, status)
|
117
110
|
end
|
118
111
|
|
112
|
+
def display_item_count(number, total)
|
113
|
+
count_msg = "Items: #{number}/#{total}"
|
114
|
+
count_msg = count_msg.bg_red unless number.to_i.eql?(total.to_i)
|
115
|
+
display_status(count_msg)
|
116
|
+
end
|
117
|
+
|
119
118
|
def result_default_fields(results, table_rows_hash_val)
|
120
119
|
unless results[:fields].nil?
|
121
120
|
raise "internal error: [fields] must be Array, not #{results[:fields].class}" unless results[:fields].is_a?(Array)
|
@@ -189,7 +188,7 @@ module Aspera
|
|
189
188
|
when :single_object # goes to table display
|
190
189
|
# :single_object is a simple hash table (can be nested)
|
191
190
|
raise "internal error: expecting Hash: got #{res_data.class}: #{res_data}" unless res_data.is_a?(Hash)
|
192
|
-
final_table_columns = results[:columns] ||
|
191
|
+
final_table_columns = results[:columns] || KEY_VALUE
|
193
192
|
if @option_flat_hash
|
194
193
|
res_data = self.class.flattened_object(res_data, expand_last: results[:option_expand_last])
|
195
194
|
self.class.flatten_name_value_list(res_data)
|
@@ -201,6 +200,11 @@ module Aspera
|
|
201
200
|
else user_asked_fields_list_str.split(',')
|
202
201
|
end
|
203
202
|
table_rows_hash_val = asked_fields.map { |i| { final_table_columns.first => i, final_table_columns.last => res_data[i] } }
|
203
|
+
# if only one row, and columns are key/value, then display the value only
|
204
|
+
if table_rows_hash_val.length == 1 && final_table_columns.eql?(KEY_VALUE)
|
205
|
+
display_message(:data, res_data.values.first)
|
206
|
+
return
|
207
|
+
end
|
204
208
|
when :value_list # goes to table display
|
205
209
|
# :value_list is a simple array of values, name of column provided in the :name
|
206
210
|
final_table_columns = [results[:name]]
|
@@ -229,7 +233,7 @@ module Aspera
|
|
229
233
|
# here we expect: table_rows_hash_val and final_table_columns
|
230
234
|
raise 'no field specified' if final_table_columns.nil?
|
231
235
|
if table_rows_hash_val.empty?
|
232
|
-
display_message(:info, 'empty'.gray)
|
236
|
+
display_message(:info, 'empty'.gray) if @option_format.eql?(:table)
|
233
237
|
return
|
234
238
|
end
|
235
239
|
# convert to string with special function. here table_rows_hash_val is an array of hash
|
@@ -246,12 +250,6 @@ module Aspera
|
|
246
250
|
when :table
|
247
251
|
style = @option_table_style.chars
|
248
252
|
# display the table !
|
249
|
-
# display_message(:data,Text::Table.new(
|
250
|
-
# head: final_table_columns,
|
251
|
-
# rows: final_table_rows,
|
252
|
-
# horizontal_boundary: style[0],
|
253
|
-
# vertical_boundary: style[1],
|
254
|
-
# boundary_intersection: style[2]))
|
255
253
|
display_message(:data, Terminal::Table.new(
|
256
254
|
headings: final_table_columns,
|
257
255
|
rows: final_table_rows,
|
data/lib/aspera/cli/info.rb
CHANGED
@@ -10,9 +10,7 @@ module Aspera
|
|
10
10
|
GEM_URL = "https://rubygems.org/gems/#{GEM_NAME}"
|
11
11
|
SRC_URL = 'https://github.com/IBM/aspera-cli'
|
12
12
|
# set this to warn in advance when minimum required ruby version will increase
|
13
|
-
#
|
14
|
-
|
15
|
-
# the actual current minimum required version is in gemspec at required_ruby_version
|
16
|
-
RUBY_FUTURE_MINIMUM_VERSION = '2.7'
|
13
|
+
# see also required_ruby_version in gemspec file
|
14
|
+
RUBY_FUTURE_MINIMUM_VERSION = '3.0'
|
17
15
|
end
|
18
16
|
end
|