aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- 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 +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
@@ -1,33 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/cli/plugins/node'
|
4
|
-
|
4
|
+
require 'aspera/assert'
|
5
5
|
module Aspera
|
6
6
|
module Cli
|
7
7
|
module Plugins
|
8
8
|
# Plugin for Aspera Shares v1
|
9
9
|
class Shares < Aspera::Cli::BasicAuthPlugin
|
10
|
+
API_BASE = 'node_api'
|
10
11
|
class << self
|
11
|
-
def detect(
|
12
|
-
|
13
|
-
|
12
|
+
def detect(address_or_url)
|
13
|
+
address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
|
14
|
+
api = Rest.new(base_url: address_or_url, redirect_max: 1)
|
15
|
+
found = false
|
14
16
|
begin
|
15
17
|
# shall fail: shares requires auth, but we check error message
|
16
18
|
# TODO: use ping instead ?
|
17
|
-
api.read(
|
19
|
+
api.read("#{API_BASE}/app")
|
18
20
|
rescue RestCallError => e
|
19
21
|
if e.response.code.to_s.eql?('401') && e.response.body.eql?('{"error":{"user_message":"API user authentication failed"}}')
|
20
|
-
|
22
|
+
found = true
|
21
23
|
end
|
22
24
|
end
|
23
|
-
nil
|
25
|
+
return nil unless found
|
26
|
+
version = 'unknown'
|
27
|
+
test_page = api.call({ operation: 'GET', subpath: 'login' })
|
28
|
+
if (m = test_page[:http].body.match(/\(v(1\..*)\)/))
|
29
|
+
version = m[1]
|
30
|
+
end
|
31
|
+
return {
|
32
|
+
version: version,
|
33
|
+
url: address_or_url
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def wizard(object:, private_key_path: nil, pub_key_pem: nil)
|
38
|
+
options = object.options
|
39
|
+
return {
|
40
|
+
preset_value: {
|
41
|
+
url: options.get_option(:url, mandatory: true),
|
42
|
+
username: options.get_option(:username, mandatory: true),
|
43
|
+
password: options.get_option(:password, mandatory: true)
|
44
|
+
},
|
45
|
+
test_args: 'files br /'
|
46
|
+
}
|
24
47
|
end
|
25
48
|
end
|
26
49
|
|
27
50
|
def initialize(env)
|
28
51
|
super(env)
|
29
|
-
options.declare(:type, 'Type of user/group for operations', values: %i[any local ldap saml], default: :any)
|
30
|
-
options.parse_options!
|
31
52
|
end
|
32
53
|
|
33
54
|
SAML_IMPORT_MANDATORY = %w[id name_id].freeze
|
@@ -44,7 +65,7 @@ module Aspera
|
|
44
65
|
nagios = Nagios.new
|
45
66
|
begin
|
46
67
|
Rest
|
47
|
-
.new(base_url: options.get_option(:url, mandatory: true)
|
68
|
+
.new(base_url: "#{options.get_option(:url, mandatory: true)}/#{API_BASE}")
|
48
69
|
.call(
|
49
70
|
operation: 'GET',
|
50
71
|
subpath: 'ping',
|
@@ -56,69 +77,74 @@ module Aspera
|
|
56
77
|
end
|
57
78
|
return nagios.result
|
58
79
|
when :repository, :files
|
59
|
-
api_shares_node = basic_auth_api(
|
80
|
+
api_shares_node = basic_auth_api(API_BASE)
|
60
81
|
repo_command = options.get_next_command(Node::COMMANDS_SHARES)
|
61
|
-
return Node.new(@agents
|
82
|
+
return Node.new(@agents, api: api_shares_node).execute_action(repo_command)
|
62
83
|
when :admin
|
63
84
|
api_shares_admin = basic_auth_api('api/v1')
|
64
|
-
admin_command = options.get_next_command(%i[user group
|
85
|
+
admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
|
65
86
|
case admin_command
|
66
87
|
when :node
|
67
88
|
return entity_action(api_shares_admin, 'data/nodes')
|
89
|
+
when :share
|
90
|
+
share_command = options.get_next_command(%i[user_permissions group_permissions].concat(Plugin::ALL_OPS))
|
91
|
+
case share_command
|
92
|
+
when *Plugin::ALL_OPS
|
93
|
+
return entity_command(share_command, api_shares_admin, 'data/shares')
|
94
|
+
# return {type: :object_list, data: all_shares, fields: %w[id name status status_message]}
|
95
|
+
when :user_permissions, :group_permissions
|
96
|
+
share_id = instance_identifier
|
97
|
+
return entity_action(api_shares_admin, "data/shares/#{share_id}/#{share_command}")
|
98
|
+
end
|
99
|
+
when :transfer_settings
|
100
|
+
xfer_settings_command = options.get_next_command(%i[show modify])
|
101
|
+
return entity_command(xfer_settings_command, api_shares_admin, 'data/transfer_settings', is_singleton: true)
|
68
102
|
when :user, :group
|
69
103
|
entity_type = admin_command
|
70
|
-
entities_location = options.
|
104
|
+
entities_location = options.get_next_command(%i[all local ldap saml])
|
71
105
|
entities_path = "data/#{entities_location}_#{entity_type}s"
|
72
106
|
entity_action = nil
|
73
107
|
case entities_location
|
74
|
-
when :
|
108
|
+
when :all
|
75
109
|
entities_path = "data/#{entity_type}s"
|
76
110
|
entity_action = %i[list show delete]
|
77
111
|
entity_action.concat(USR_GRP_SETTINGS)
|
78
112
|
entity_action.push(:users) if entity_type.eql?(:group)
|
79
113
|
entity_action.freeze
|
80
114
|
when :local
|
81
|
-
entity_action = %i[list show create modify
|
115
|
+
entity_action = %i[list show delete create modify].freeze
|
82
116
|
when :ldap
|
83
117
|
entity_action = %i[add].freeze
|
84
118
|
when :saml
|
85
119
|
entity_action = %i[import].freeze
|
86
120
|
end
|
87
121
|
entity_verb = options.get_next_command(entity_action)
|
88
|
-
# entity_path = "#{entities_path}/#{instance_identifier}" if %i[app_authorizations share_permissions].include?(entity_verb)
|
89
122
|
case entity_verb
|
90
|
-
when *Plugin::ALL_OPS
|
123
|
+
when *Plugin::ALL_OPS # list, show, delete, create, modify
|
91
124
|
display_fields = entity_type.eql?(:user) ? %w[id username first_name last_name email] : nil
|
92
|
-
display_fields.push(:directory_user) if entity_type.eql?(:user) && entities_location.eql?(:
|
125
|
+
display_fields.push(:directory_user) if entity_type.eql?(:user) && entities_location.eql?(:all)
|
93
126
|
return entity_command(entity_verb, api_shares_admin, entities_path, display_fields: display_fields)
|
94
|
-
when
|
95
|
-
|
127
|
+
when *USR_GRP_SETTINGS # transfer_settings, app_authorizations, share_permissions
|
128
|
+
group_id = instance_identifier
|
129
|
+
entities_path = "#{entities_path}/#{group_id}/#{entity_verb}"
|
130
|
+
return entity_action(api_shares_admin, entities_path, is_singleton: !entity_verb.eql?(:share_permissions))
|
131
|
+
when :import # saml
|
132
|
+
return do_bulk_operation(command: entity_verb, descr: 'user information') do |entity_parameters|
|
96
133
|
entity_parameters = entity_parameters.transform_keys{|k|k.gsub(/\s+/, '_').downcase}
|
97
|
-
|
134
|
+
assert_type(entity_parameters, Hash)
|
98
135
|
SAML_IMPORT_MANDATORY.each{|p|raise "missing mandatory field: #{p}" if entity_parameters[p].nil?}
|
99
136
|
entity_parameters.each_key do |p|
|
100
137
|
raise "unsupported field: #{p}, use: #{SAML_IMPORT_ALLOWED.join(',')}" unless SAML_IMPORT_ALLOWED.include?(p)
|
101
138
|
end
|
102
139
|
api_shares_admin.create("#{entities_path}/import", entity_parameters)[:data]
|
103
140
|
end
|
104
|
-
when :add
|
105
|
-
return do_bulk_operation(
|
106
|
-
raise "expecting string (name), have #{entity_name.class}" unless entity_name.is_a?(String)
|
141
|
+
when :add # ldap
|
142
|
+
return do_bulk_operation(command: entity_verb, descr: "#{entity_type} name", values: String) do |entity_name|
|
107
143
|
api_shares_admin.create(entities_path, {entity_type=>entity_name})[:data]
|
108
144
|
end
|
109
|
-
when
|
110
|
-
|
111
|
-
|
112
|
-
return entity_action(api_shares_admin, entities_path, is_singleton: !entity_verb.eql?(:share_permissions))
|
113
|
-
end
|
114
|
-
when :share
|
115
|
-
share_command = options.get_next_command(%i[user_permissions group_permissions].concat(Plugin::ALL_OPS))
|
116
|
-
case share_command
|
117
|
-
when *Plugin::ALL_OPS
|
118
|
-
return entity_command(share_command, api_shares_admin, 'data/shares')
|
119
|
-
# return {type: :object_list, data: all_shares, fields: %w[id name status status_message]}
|
120
|
-
when :user_permissions, :group_permissions
|
121
|
-
return entity_action(api_shares_admin, "data/shares/#{instance_identifier}/#{share_command}")
|
145
|
+
when :users # group
|
146
|
+
raise "TODO, not implemented"
|
147
|
+
else error_unexpected_value(entity_verb)
|
122
148
|
end
|
123
149
|
end
|
124
150
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/fasp/sync'
|
4
|
+
require 'aspera/assert'
|
5
|
+
|
6
|
+
module Aspera
|
7
|
+
module Cli
|
8
|
+
# Module for sync actions
|
9
|
+
module SyncActions
|
10
|
+
SIMPLE_ARGUMENTS_SYNC = {
|
11
|
+
direction: Aspera::Fasp::Sync::DIRECTIONS,
|
12
|
+
local_dir: String,
|
13
|
+
remote_dir: String
|
14
|
+
}.stringify_keys.freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def declare_options(options)
|
18
|
+
options.declare(:sync_info, 'Information for sync instance and sessions', types: Hash)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute_sync_action(&block)
|
23
|
+
assert(block){'No block given'}
|
24
|
+
command = options.get_next_command(%i[start admin])
|
25
|
+
# try to get 3 arguments as simple arguments
|
26
|
+
case command
|
27
|
+
when :start
|
28
|
+
simple_session_args = {}
|
29
|
+
SIMPLE_ARGUMENTS_SYNC.each do |arg, check|
|
30
|
+
value = options.get_next_argument(
|
31
|
+
arg,
|
32
|
+
type: check.is_a?(Class) ? check : nil,
|
33
|
+
expected: check.is_a?(Class) ? :single : check,
|
34
|
+
mandatory: false)
|
35
|
+
break if value.nil?
|
36
|
+
simple_session_args[arg] = value.to_s
|
37
|
+
end
|
38
|
+
async_params = nil
|
39
|
+
if simple_session_args.empty?
|
40
|
+
async_params = options.get_option(:sync_info, mandatory: true)
|
41
|
+
else
|
42
|
+
raise Cli::BadArgument,
|
43
|
+
"Provide zero or 3 arguments: #{SIMPLE_ARGUMENTS_SYNC.keys.join(',')}" unless simple_session_args.keys.sort == SIMPLE_ARGUMENTS_SYNC.keys.sort
|
44
|
+
async_params = options.get_option(
|
45
|
+
:sync_info,
|
46
|
+
mandatory: false,
|
47
|
+
default: {'sessions' => [{'name' => File.basename(simple_session_args['local_dir'])}]})
|
48
|
+
assert_type(async_params, Hash){'sync_info'}
|
49
|
+
assert_type(async_params['sessions'], Array){'sync_info[sessions]'}
|
50
|
+
assert_type(async_params['sessions'].first, Hash){'sync_info[sessions][0]'}
|
51
|
+
async_params['sessions'].first.merge!(simple_session_args)
|
52
|
+
end
|
53
|
+
Log.log.debug{Log.dump('async_params', async_params)}
|
54
|
+
Aspera::Fasp::Sync.start(async_params, &block)
|
55
|
+
return Main.result_success
|
56
|
+
when :admin
|
57
|
+
command2 = options.get_next_command([:status])
|
58
|
+
case command2
|
59
|
+
when :status
|
60
|
+
sync_session_name = options.get_next_argument('name of sync session', mandatory: false, type: String)
|
61
|
+
async_params = options.get_option(:sync_info, mandatory: true)
|
62
|
+
return {type: :single_object, data: Aspera::Fasp::Sync.admin_status(async_params, sync_session_name)}
|
63
|
+
end # command2
|
64
|
+
end # command
|
65
|
+
end # execute_action
|
66
|
+
end # SyncActions
|
67
|
+
end # Cli
|
68
|
+
end # Aspera
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aspera/fasp/agent_base'
|
3
4
|
require 'aspera/fasp/transfer_spec'
|
4
|
-
require 'aspera/cli/listener/logger'
|
5
|
-
require 'aspera/cli/listener/progress_multi'
|
6
5
|
require 'aspera/cli/info'
|
6
|
+
require 'aspera/log'
|
7
|
+
require 'aspera/assert'
|
7
8
|
|
8
9
|
module Aspera
|
9
10
|
module Cli
|
@@ -16,7 +17,7 @@ module Aspera
|
|
16
17
|
# special value for --sources : read file list from transfer spec (--ts)
|
17
18
|
FILE_LIST_FROM_TRANSFER_SPEC = '@ts'
|
18
19
|
FILE_LIST_OPTIONS = [FILE_LIST_FROM_ARGS, FILE_LIST_FROM_TRANSFER_SPEC, 'Array'].freeze
|
19
|
-
|
20
|
+
DEFAULT_TRANSFER_NOTIFY_TEMPLATE = <<~END_OF_TEMPLATE
|
20
21
|
From: <%=from_name%> <<%=from_email%>>
|
21
22
|
To: <<%=to%>>
|
22
23
|
Subject: <%=subject%>
|
@@ -29,8 +30,8 @@ module Aspera
|
|
29
30
|
private_constant :FILE_LIST_FROM_ARGS,
|
30
31
|
:FILE_LIST_FROM_TRANSFER_SPEC,
|
31
32
|
:FILE_LIST_OPTIONS,
|
32
|
-
:
|
33
|
-
TRANSFER_AGENTS =
|
33
|
+
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
34
|
+
TRANSFER_AGENTS = Fasp::AgentBase.agent_list.freeze
|
34
35
|
|
35
36
|
class << self
|
36
37
|
# @return :success if all sessions statuses returned by "start" are success
|
@@ -43,15 +44,15 @@ module Aspera
|
|
43
44
|
end
|
44
45
|
|
45
46
|
# @param env external objects: option manager, config file manager
|
46
|
-
def initialize(opt_mgr,
|
47
|
+
def initialize(opt_mgr, config_plugin)
|
47
48
|
@opt_mgr = opt_mgr
|
48
|
-
@config =
|
49
|
+
@config = config_plugin
|
49
50
|
# command line can override transfer spec
|
50
|
-
@
|
51
|
+
@transfer_spec_command_line = {'create_dir' => true}
|
52
|
+
# options for transfer agent
|
51
53
|
@transfer_info = {}
|
52
54
|
# the currently selected transfer agent
|
53
55
|
@agent = nil
|
54
|
-
@progress_listener = Listener::ProgressMulti.new
|
55
56
|
# source/destination pair, like "paths" of transfer spec
|
56
57
|
@transfer_paths = nil
|
57
58
|
@opt_mgr.declare(:ts, 'Override transfer spec values', types: Hash, handler: {o: self, m: :option_transfer_spec})
|
@@ -59,73 +60,68 @@ module Aspera
|
|
59
60
|
@opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})")
|
60
61
|
@opt_mgr.declare(:src_type, 'Type of file list', values: %i[list pair], default: :list)
|
61
62
|
@opt_mgr.declare(:transfer, 'Type of transfer agent', values: TRANSFER_AGENTS, default: :direct)
|
62
|
-
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :
|
63
|
-
@opt_mgr.declare(:progress, 'Type of progress bar', values: %i[none native multi], default: :native)
|
63
|
+
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :transfer_info})
|
64
64
|
@opt_mgr.parse_options!
|
65
65
|
end
|
66
66
|
|
67
|
-
def option_transfer_spec; @
|
67
|
+
def option_transfer_spec; @transfer_spec_command_line; end
|
68
68
|
|
69
69
|
# multiple option are merged
|
70
70
|
def option_transfer_spec=(value)
|
71
|
-
|
72
|
-
@
|
71
|
+
assert_type(value, Hash){'ts'}
|
72
|
+
@transfer_spec_command_line.deep_merge!(value)
|
73
73
|
end
|
74
74
|
|
75
75
|
# add other transfer spec parameters
|
76
|
-
def option_transfer_spec_deep_merge(ts); @
|
76
|
+
def option_transfer_spec_deep_merge(ts); @transfer_spec_command_line.deep_merge!(ts); end
|
77
77
|
|
78
78
|
# @return [Hash] transfer spec with updated values from command line, including removed values
|
79
79
|
def updated_ts(transfer_spec={})
|
80
|
-
transfer_spec.deep_merge!(@
|
80
|
+
transfer_spec.deep_merge!(@transfer_spec_command_line)
|
81
81
|
# recursively remove values that are nil (user wants to delete)
|
82
82
|
transfer_spec.deep_do { |hash, key, value, _unused| hash.delete(key) if value.nil?}
|
83
83
|
return transfer_spec
|
84
84
|
end
|
85
85
|
|
86
|
-
|
86
|
+
attr_reader :transfer_info
|
87
87
|
|
88
88
|
# multiple option are merged
|
89
|
-
def
|
90
|
-
raise 'option transfer_info shall be a Hash' unless value.is_a?(Hash)
|
89
|
+
def transfer_info=(value)
|
91
90
|
@transfer_info.deep_merge!(value)
|
92
91
|
end
|
93
92
|
|
94
93
|
def agent_instance=(instance)
|
95
94
|
@agent = instance
|
96
|
-
@agent.add_listener(Listener::Logger.new)
|
97
|
-
# use local progress bar if asked so, or if native and non local ascp (because only local ascp has native progress bar)
|
98
|
-
if @opt_mgr.get_option(:progress, mandatory: true).eql?(:multi) ||
|
99
|
-
(@opt_mgr.get_option(:progress, mandatory: true).eql?(:native) && !instance.class.to_s.eql?('Aspera::Fasp::AgentDirect'))
|
100
|
-
@agent.add_listener(@progress_listener)
|
101
|
-
end
|
102
95
|
end
|
103
96
|
|
104
97
|
# analyze options and create new agent if not already created or set
|
105
|
-
def
|
106
|
-
return
|
98
|
+
def agent_instance
|
99
|
+
return @agent unless @agent.nil?
|
107
100
|
agent_type = @opt_mgr.get_option(:transfer, mandatory: true)
|
108
101
|
# agent plugin is loaded on demand to avoid loading unnecessary dependencies
|
109
102
|
require "aspera/fasp/agent_#{agent_type}"
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
103
|
+
# set keys as symbols
|
104
|
+
agent_options = @opt_mgr.get_option(:transfer_info).symbolize_keys
|
105
|
+
# special cases
|
106
|
+
case agent_type
|
107
|
+
when :node
|
108
|
+
if agent_options.empty?
|
109
|
+
param_set_name = @config.get_plugin_default_config_name(:node)
|
110
|
+
raise Cli::BadArgument, "No default node configured. Please specify #{Manager.option_name_to_line(:transfer_info)}" if param_set_name.nil?
|
111
|
+
agent_options = @config.preset_by_name(param_set_name).symbolize_keys
|
112
|
+
end
|
113
|
+
when :direct
|
114
|
+
# by default do not display ascp native progress bar
|
115
|
+
agent_options[:quiet] = true unless agent_options.key?(:quiet)
|
116
|
+
agent_options[:check_ignore] = ->(host, port){@config.ignore_cert?(host, port)}
|
117
|
+
agent_options[:trusted_certs] = @config.trusted_cert_locations(files_only: true) unless agent_options.key?(:trusted_certs)
|
122
118
|
end
|
123
|
-
|
124
|
-
agent_options = agent_options.symbolize_keys
|
119
|
+
agent_options[:progress] = @config.progress_bar
|
125
120
|
# get agent instance
|
126
121
|
new_agent = Kernel.const_get("Aspera::Fasp::Agent#{agent_type.capitalize}").new(agent_options)
|
127
122
|
self.agent_instance = new_agent
|
128
|
-
|
123
|
+
Log.log.debug{"transfer agent is a #{@agent.class}"}
|
124
|
+
return @agent
|
129
125
|
end
|
130
126
|
|
131
127
|
# return destination folder for transfers
|
@@ -135,13 +131,13 @@ module Aspera
|
|
135
131
|
dest_folder = @opt_mgr.get_option(:to_folder)
|
136
132
|
# do not expand path, if user wants to expand path: user @path:
|
137
133
|
return dest_folder unless dest_folder.nil?
|
138
|
-
dest_folder = @
|
134
|
+
dest_folder = @transfer_spec_command_line['destination_root']
|
139
135
|
return dest_folder unless dest_folder.nil?
|
140
136
|
# default: / on remote, . on local
|
141
137
|
case direction.to_s
|
142
138
|
when Fasp::TransferSpec::DIRECTION_SEND then dest_folder = '/'
|
143
139
|
when Fasp::TransferSpec::DIRECTION_RECEIVE then dest_folder = '.'
|
144
|
-
else
|
140
|
+
else error_unexpected_value(direction)
|
145
141
|
end
|
146
142
|
return dest_folder
|
147
143
|
end
|
@@ -161,7 +157,7 @@ module Aspera
|
|
161
157
|
# return cache if set
|
162
158
|
return @transfer_paths unless @transfer_paths.nil?
|
163
159
|
# start with lower priority : get paths from transfer spec on command line
|
164
|
-
@transfer_paths = @
|
160
|
+
@transfer_paths = @transfer_spec_command_line['paths'] if @transfer_spec_command_line.key?('paths')
|
165
161
|
# is there a source list option ?
|
166
162
|
file_list = @opt_mgr.get_option(:sources)
|
167
163
|
case file_list
|
@@ -169,34 +165,35 @@ module Aspera
|
|
169
165
|
Log.log.debug('getting file list as parameters')
|
170
166
|
# get remaining arguments
|
171
167
|
file_list = @opt_mgr.get_next_argument('source file list', expected: :multiple)
|
172
|
-
raise
|
168
|
+
raise Cli::BadArgument, 'specify at least one file on command line or use ' \
|
173
169
|
"--sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !file_list.is_a?(Array) || file_list.empty?
|
174
170
|
when FILE_LIST_FROM_TRANSFER_SPEC
|
175
171
|
Log.log.debug('assume list provided in transfer spec')
|
176
172
|
special_case_direct_with_list =
|
177
173
|
@opt_mgr.get_option(:transfer, mandatory: true).eql?(:direct) &&
|
178
|
-
Fasp::Parameters.ts_has_ascp_file_list(@
|
179
|
-
raise
|
174
|
+
Fasp::Parameters.ts_has_ascp_file_list(@transfer_spec_command_line, @opt_mgr.get_option(:transfer_info))
|
175
|
+
raise Cli::BadArgument, 'transfer spec on command line must have sources' if @transfer_paths.nil? && !special_case_direct_with_list
|
180
176
|
# here we assume check of sources is made in transfer agent
|
181
177
|
return @transfer_paths
|
182
178
|
when Array
|
183
179
|
Log.log.debug('getting file list as extended value')
|
184
|
-
raise
|
180
|
+
raise Cli::BadArgument, 'sources must be a Array of String' if !file_list.reject{|f|f.is_a?(String)}.empty?
|
185
181
|
else
|
186
|
-
raise
|
182
|
+
raise Cli::BadArgument, "sources must be a Array, not #{file_list.class}"
|
187
183
|
end
|
188
184
|
# here, file_list is an Array or String
|
189
185
|
if !@transfer_paths.nil?
|
190
186
|
Log.log.warn('--sources overrides paths from --ts')
|
191
187
|
end
|
192
|
-
|
188
|
+
source_type=@opt_mgr.get_option(:src_type, mandatory: true)
|
189
|
+
case source_type
|
193
190
|
when :list
|
194
191
|
# when providing a list, just specify source
|
195
192
|
@transfer_paths = file_list.map{|i|{'source' => i}}
|
196
193
|
when :pair
|
197
|
-
|
194
|
+
assert(file_list.length.even?, exception_class: Cli::BadArgument){"When using pair, provide an even number of paths: #{file_list.length}"}
|
198
195
|
@transfer_paths = file_list.each_slice(2).to_a.map{|s, d|{'source' => s, 'destination' => d}}
|
199
|
-
else
|
196
|
+
else error_unexpected_value(source_type)
|
200
197
|
end
|
201
198
|
Log.log.debug{"paths=#{@transfer_paths}"}
|
202
199
|
return @transfer_paths
|
@@ -207,44 +204,44 @@ module Aspera
|
|
207
204
|
# @param rest_token [Rest] if oauth token regeneration supported
|
208
205
|
def start(transfer_spec, rest_token: nil)
|
209
206
|
# check parameters
|
210
|
-
|
207
|
+
assert_type(transfer_spec, Hash){'transfer_spec'}
|
211
208
|
# process :src option
|
212
209
|
case transfer_spec['direction']
|
213
210
|
when Fasp::TransferSpec::DIRECTION_RECEIVE
|
214
211
|
# init default if required in any case
|
215
|
-
@
|
212
|
+
@transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
|
216
213
|
when Fasp::TransferSpec::DIRECTION_SEND
|
217
214
|
if transfer_spec.dig('tags', Fasp::TransferSpec::TAG_RESERVED, 'node', 'access_key')
|
218
215
|
# gen4
|
219
|
-
@
|
216
|
+
@transfer_spec_command_line.delete('destination_root') if @transfer_spec_command_line.key?('destination_root_id')
|
220
217
|
elsif transfer_spec.key?('token')
|
221
218
|
# gen3
|
222
219
|
# in that case, destination is set in return by application (API/upload_setup)
|
223
220
|
# but to_folder was used in initial API call
|
224
|
-
@
|
221
|
+
@transfer_spec_command_line.delete('destination_root')
|
225
222
|
else
|
226
223
|
# init default if required
|
227
|
-
@
|
224
|
+
@transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
|
228
225
|
end
|
229
226
|
end
|
230
227
|
# update command line paths, unless destination already has one
|
231
|
-
@
|
228
|
+
@transfer_spec_command_line['paths'] = transfer_spec['paths'] || ts_source_paths
|
232
229
|
# updated transfer spec with command line
|
233
230
|
updated_ts(transfer_spec)
|
231
|
+
# if TS from app has content_protection (e.g. F5), that means content is protected: ask password if not provided
|
232
|
+
if transfer_spec['content_protection'].eql?('decrypt') && !transfer_spec.key?('content_protection_password')
|
233
|
+
transfer_spec['content_protection_password'] = @opt_mgr.prompt_user_input('content protection password', true)
|
234
|
+
end
|
234
235
|
# create transfer agent
|
235
|
-
|
236
|
-
Log.log.debug{"transfer agent is a #{@agent.class}"}
|
237
|
-
@agent.start_transfer(transfer_spec, token_regenerator: rest_token)
|
236
|
+
agent_instance.start_transfer(transfer_spec, token_regenerator: rest_token)
|
238
237
|
# list of: :success or "error message string"
|
239
|
-
result =
|
240
|
-
@progress_listener.reset
|
241
|
-
Fasp::AgentBase.validate_status_list(result)
|
238
|
+
result = agent_instance.wait_for_completion
|
242
239
|
send_email_transfer_notification(transfer_spec, result)
|
243
240
|
return result
|
244
241
|
end
|
245
242
|
|
246
243
|
def send_email_transfer_notification(transfer_spec, statuses)
|
247
|
-
return if @opt_mgr.get_option(:
|
244
|
+
return if @opt_mgr.get_option(:notify_to).nil?
|
248
245
|
global_status = self.class.session_status(statuses)
|
249
246
|
email_vars = {
|
250
247
|
global_transfer_status: global_status,
|
@@ -252,7 +249,7 @@ module Aspera
|
|
252
249
|
body: "Transfer is: #{global_status}",
|
253
250
|
ts: transfer_spec
|
254
251
|
}
|
255
|
-
@config.send_email_template(email_template_default:
|
252
|
+
@config.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIFY_TEMPLATE, values: email_vars)
|
256
253
|
end
|
257
254
|
|
258
255
|
# shut down if agent requires it
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/log'
|
4
|
+
require 'aspera/assert'
|
5
|
+
require 'ruby-progressbar'
|
6
|
+
|
7
|
+
module Aspera
|
8
|
+
module Cli
|
9
|
+
# progress bar for transfers, supports multi-session
|
10
|
+
class TransferProgress
|
11
|
+
def initialize
|
12
|
+
reset
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@progress_bar = nil
|
17
|
+
# key is session id
|
18
|
+
@sessions = {}
|
19
|
+
@completed = false
|
20
|
+
@title = ''
|
21
|
+
end
|
22
|
+
|
23
|
+
def total(key)
|
24
|
+
@sessions.values.inject(0){|m, s|m + s[key]}
|
25
|
+
end
|
26
|
+
|
27
|
+
def event(session_id:, type:, info: nil)
|
28
|
+
Log.log.debug{"progress: #{type} #{session_id} #{info}"}
|
29
|
+
assert(!session_id.nil? || type.eql?(:pre_start)){'session_id is nil'}
|
30
|
+
return if @completed
|
31
|
+
if @progress_bar.nil?
|
32
|
+
@progress_bar = ProgressBar.create(
|
33
|
+
format: '%t %a %B %p%% %r Mbps %E',
|
34
|
+
rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
|
35
|
+
title: '',
|
36
|
+
total: nil)
|
37
|
+
end
|
38
|
+
need_increment = true
|
39
|
+
case type
|
40
|
+
when :pre_start
|
41
|
+
@title = info
|
42
|
+
when :session_start
|
43
|
+
raise "Session #{session_id} already started" if @sessions[session_id]
|
44
|
+
@sessions[session_id] = {
|
45
|
+
job_size: 0, # total size of transfer (pre-calc)
|
46
|
+
current: 0
|
47
|
+
}
|
48
|
+
@title = ''
|
49
|
+
when :session_size
|
50
|
+
@sessions[session_id][:job_size] = info.to_i
|
51
|
+
current_total = total(:job_size)
|
52
|
+
@progress_bar.total = current_total unless current_total.eql?(@progress_bar.total) || current_total < @progress_bar.progress
|
53
|
+
when :transfer
|
54
|
+
if !@progress_bar.total.nil?
|
55
|
+
need_increment = false
|
56
|
+
@sessions[session_id][:current] = info.to_i
|
57
|
+
current_total = total(:current)
|
58
|
+
@progress_bar.progress = current_total unless @progress_bar.progress.eql?(current_total)
|
59
|
+
end
|
60
|
+
when :end
|
61
|
+
@title = ''
|
62
|
+
@completed = true
|
63
|
+
@progress_bar.finish
|
64
|
+
else
|
65
|
+
raise "Unknown event type #{type}"
|
66
|
+
end
|
67
|
+
new_title = @sessions.length < 2 ? @title : "[#{@sessions.length}] #{@title}"
|
68
|
+
@progress_bar.title = new_title unless @progress_bar.title.eql?(new_title)
|
69
|
+
@progress_bar.increment if need_increment && !@completed
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/colors.rb
CHANGED
@@ -14,10 +14,12 @@ class String
|
|
14
14
|
# it adds control chars to set color (and reset at the end).
|
15
15
|
VT_STYLES = {
|
16
16
|
bold: 1,
|
17
|
+
dim: 2,
|
17
18
|
italic: 3,
|
18
19
|
underline: 4,
|
19
20
|
blink: 5,
|
20
21
|
reverse_color: 7,
|
22
|
+
invisible: 8,
|
21
23
|
black: 30,
|
22
24
|
red: 31,
|
23
25
|
green: 32,
|
@@ -38,7 +40,7 @@ class String
|
|
38
40
|
private_constant :VT_STYLES
|
39
41
|
# defines methods to String, one per entry in VT_STYLES
|
40
42
|
VT_STYLES.each do |name, code|
|
41
|
-
if $
|
43
|
+
if $stdout.tty?
|
42
44
|
begin_seq = vt_cmd(code)
|
43
45
|
end_code = 0 # by default reset all
|
44
46
|
if code <= 7 then code + 20
|