aspera-cli 4.26.0 → 4.26.2
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 +36 -4
- data/CONTRIBUTING.md +2 -5
- data/bin/ascli +2 -2
- data/lib/aspera/agent/direct.rb +1 -1
- data/lib/aspera/agent/factory.rb +4 -0
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/agent/node.rb +2 -2
- data/lib/aspera/api/aoc.rb +45 -37
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +7 -6
- data/lib/aspera/api/node.rb +9 -9
- data/lib/aspera/ascmd.rb +4 -4
- data/lib/aspera/ascp/installation.rb +13 -6
- data/lib/aspera/assert.rb +17 -13
- data/lib/aspera/cli/context.rb +58 -0
- data/lib/aspera/cli/extended_value.rb +9 -4
- data/lib/aspera/cli/formatter.rb +91 -187
- data/lib/aspera/cli/http.rb +167 -0
- data/lib/aspera/cli/manager.rb +132 -77
- data/lib/aspera/cli/options.schema.yaml +82 -0
- data/lib/aspera/cli/plugins/alee.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +113 -77
- data/lib/aspera/cli/plugins/ats.rb +16 -16
- data/lib/aspera/cli/plugins/base.rb +64 -49
- data/lib/aspera/cli/plugins/config.rb +172 -444
- data/lib/aspera/cli/plugins/console.rb +7 -7
- data/lib/aspera/cli/plugins/cos.rb +2 -2
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex.rb +22 -22
- data/lib/aspera/cli/plugins/faspex5.rb +68 -49
- data/lib/aspera/cli/plugins/faspio.rb +1 -1
- data/lib/aspera/cli/plugins/httpgw.rb +2 -2
- data/lib/aspera/cli/plugins/node.rb +80 -80
- data/lib/aspera/cli/plugins/orchestrator.rb +21 -26
- data/lib/aspera/cli/plugins/preview.rb +9 -9
- data/lib/aspera/cli/plugins/server.rb +7 -7
- data/lib/aspera/cli/plugins/shares.rb +2 -2
- data/lib/aspera/cli/preset_manager.rb +235 -0
- data/lib/aspera/cli/result.rb +310 -0
- data/lib/aspera/cli/{main.rb → runner.rb} +48 -130
- data/lib/aspera/cli/sync_actions.rb +15 -11
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- data/lib/aspera/cli/transfer_agent.rb +17 -15
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -6
- data/lib/aspera/command_line_builder.rb +22 -18
- data/lib/aspera/data_repository.rb +4 -0
- data/lib/aspera/dot_container.rb +1 -1
- data/lib/aspera/environment.rb +25 -6
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/formatter_interface.rb +14 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc.rb +6 -5
- data/lib/aspera/keychain/base.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +10 -5
- data/lib/aspera/markdown.rb +4 -1
- data/lib/aspera/nagios.rb +2 -2
- data/lib/aspera/oauth/base.rb +5 -5
- data/lib/aspera/oauth/boot.rb +43 -0
- data/lib/aspera/oauth/factory.rb +38 -14
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_action_once.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -0
- data/lib/aspera/products/connect.rb +3 -0
- data/lib/aspera/products/transferd.rb +2 -2
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +9 -5
- data/lib/aspera/rest_error_analyzer.rb +4 -0
- data/lib/aspera/rest_list.rb +10 -3
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62811 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +9637 -0
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/documentation.rb +107 -0
- data/lib/aspera/schema/reader.rb +75 -0
- data/lib/aspera/schema/registry.rb +70 -0
- data/lib/aspera/secret_hider.rb +4 -0
- data/lib/aspera/sync/conf.schema.yaml +0 -26
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +14 -10
- data/lib/aspera/temp_file_manager.rb +4 -0
- data/lib/aspera/transfer/faux_file.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +4 -4
- data/lib/aspera/transfer/resumer.rb +2 -2
- data/lib/aspera/transfer/spec.rb +3 -3
- data/lib/aspera/transfer/spec.schema.yaml +18 -9
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/yaml.rb +4 -2
- data.tar.gz.sig +0 -0
- metadata +28 -15
- metadata.gz.sig +1 -1
- data/lib/aspera/transfer/spec_doc.rb +0 -76
|
@@ -1,63 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'aspera/cli/context'
|
|
3
4
|
require 'aspera/cli/manager'
|
|
4
5
|
require 'aspera/cli/formatter'
|
|
6
|
+
require 'aspera/cli/plugins/factory'
|
|
5
7
|
require 'aspera/cli/plugins/config'
|
|
6
8
|
require 'aspera/cli/extended_value'
|
|
7
|
-
require 'aspera/cli/plugins/factory'
|
|
8
9
|
require 'aspera/cli/transfer_agent'
|
|
9
10
|
require 'aspera/cli/version'
|
|
10
11
|
require 'aspera/cli/info'
|
|
11
12
|
require 'aspera/cli/hints'
|
|
13
|
+
require 'aspera/cli/result'
|
|
12
14
|
require 'aspera/secret_hider'
|
|
13
15
|
require 'aspera/log'
|
|
14
16
|
require 'aspera/assert'
|
|
17
|
+
require 'aspera/schema/documentation'
|
|
18
|
+
require 'aspera/schema/registry'
|
|
15
19
|
require 'net/ssh/errors'
|
|
16
20
|
require 'openssl'
|
|
17
21
|
|
|
18
22
|
module Aspera
|
|
19
23
|
module Cli
|
|
20
|
-
# Global objects shared with plugins
|
|
21
|
-
class Context
|
|
22
|
-
# @!attribute [rw] options
|
|
23
|
-
# @return [Manager] the command line options manager
|
|
24
|
-
# @!attribute [rw] transfer
|
|
25
|
-
# @return [TransferAgent] the transfer agent, used by transfer plugins
|
|
26
|
-
# @!attribute [rw] config
|
|
27
|
-
# @return [Plugins::Config] the configuration plugin, used by plugins to get configuration values and presets
|
|
28
|
-
# @!attribute [rw] formatter
|
|
29
|
-
# @return [Formatter] the formatter, used by plugins to display results and messages
|
|
30
|
-
# @!attribute [rw] persistency
|
|
31
|
-
# @return [Object] # whatever the type is
|
|
32
|
-
# @!attribute [rw] man_header
|
|
33
|
-
# @return [Boolean] whether to display the manual header in plugin help
|
|
34
|
-
MEMBERS = %i[options transfer config formatter persistency man_header].freeze
|
|
35
|
-
attr_accessor(*MEMBERS)
|
|
36
|
-
|
|
37
|
-
# Initialize all members to nil, so that they are defined and can be validated later
|
|
38
|
-
def initialize
|
|
39
|
-
MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Validate that all members are set, raise exception if not
|
|
43
|
-
def validate
|
|
44
|
-
MEMBERS.each do |i|
|
|
45
|
-
Aspera.assert(instance_variable_defined?(:"@#{i}"))
|
|
46
|
-
Aspera.assert(!instance_variable_get(:"@#{i}").nil?)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def only_manual?
|
|
51
|
-
transfer.eql?(:only_manual)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def only_manual!
|
|
55
|
-
@transfer = :only_manual
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
24
|
# The main CLI class
|
|
60
|
-
class
|
|
25
|
+
class Runner
|
|
61
26
|
# Plugins store transfer result using this key and use result_transfer_multiple()
|
|
62
27
|
STATUS_FIELD = 'status'
|
|
63
28
|
COMMAND_CONFIG = :config
|
|
@@ -69,35 +34,19 @@ module Aspera
|
|
|
69
34
|
private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES, :USER_INTERFACES
|
|
70
35
|
|
|
71
36
|
class << self
|
|
72
|
-
def result_special(how); {type: :special, data: how}; end
|
|
73
|
-
|
|
74
|
-
# Expect some list, but nothing to display
|
|
75
|
-
def result_empty; result_special(:empty); end
|
|
76
|
-
|
|
77
|
-
# Nothing expected
|
|
78
|
-
def result_nothing; result_special(:nothing); end
|
|
79
|
-
|
|
80
|
-
# Result is some status, such as "complete", "deleted"...
|
|
81
|
-
# @param status [String] The status
|
|
82
|
-
def result_status(status); return {type: :status, data: status}; end
|
|
83
|
-
|
|
84
|
-
# Text result coming from command result
|
|
85
|
-
def result_text(data); return {type: :text, data: data}; end
|
|
86
|
-
|
|
87
|
-
def result_success; return result_status('complete'); end
|
|
88
|
-
|
|
89
37
|
# Process statuses of finished transfer sessions
|
|
90
|
-
# @
|
|
91
|
-
#
|
|
38
|
+
# @param statuses [Array] array of transfer session statuses
|
|
39
|
+
# @raise [Symbol] exception if there is one error
|
|
40
|
+
# @return [Result] empty status result if all transfers succeeded
|
|
92
41
|
def result_transfer(statuses)
|
|
93
42
|
worst = TransferAgent.session_status(statuses)
|
|
94
43
|
raise worst unless worst.eql?(:success)
|
|
95
|
-
return
|
|
44
|
+
return Result::Nothing.new
|
|
96
45
|
end
|
|
97
46
|
|
|
98
47
|
# Used when one command executes several transfer jobs (each job being possibly multi session)
|
|
99
48
|
# @param status_table [Array] [{STATUS_FIELD=>[status array],...},...]
|
|
100
|
-
# @return a status object suitable as command result
|
|
49
|
+
# @return [Result] a status object suitable as command result
|
|
101
50
|
# Each element has a key STATUS_FIELD which contains the result of possibly multiple sessions
|
|
102
51
|
def result_transfer_multiple(status_table)
|
|
103
52
|
global_status = :success
|
|
@@ -108,68 +57,25 @@ module Aspera
|
|
|
108
57
|
item[STATUS_FIELD] = item[STATUS_FIELD].join(',')
|
|
109
58
|
end
|
|
110
59
|
raise global_status unless global_status.eql?(:success)
|
|
111
|
-
return
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Display image for that URL or directly blob
|
|
115
|
-
#
|
|
116
|
-
# @param url_or_blob [String] URL or blob to display as image
|
|
117
|
-
def result_image(url_or_blob)
|
|
118
|
-
return {type: :image, data: url_or_blob}
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# A single object, must be Hash
|
|
122
|
-
def result_single_object(data, fields: nil)
|
|
123
|
-
return {type: :single_object, data: data, fields: fields}
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# An Array of Hash
|
|
127
|
-
def result_object_list(data, fields: nil, total: nil)
|
|
128
|
-
return {type: :object_list, data: data, fields: fields, total: total}
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# A list of values
|
|
132
|
-
#
|
|
133
|
-
# @param data [Array] The list of values
|
|
134
|
-
# @param name [String] The name of the list (used for display)
|
|
135
|
-
def result_value_list(data, name: 'id')
|
|
136
|
-
Aspera.assert_type(data, Array)
|
|
137
|
-
Aspera.assert_type(name, String)
|
|
138
|
-
return {type: :value_list, data: data, name: name}
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Determines type of result based on data
|
|
142
|
-
def result_auto(data)
|
|
143
|
-
case data
|
|
144
|
-
when NilClass
|
|
145
|
-
return result_special(:null)
|
|
146
|
-
when Hash
|
|
147
|
-
return result_single_object(data)
|
|
148
|
-
when Array
|
|
149
|
-
all_types = data.map(&:class).uniq
|
|
150
|
-
return result_object_list(data) if all_types.eql?([Hash])
|
|
151
|
-
unsupported_types = all_types - SCALAR_TYPES
|
|
152
|
-
return result_value_list(data, name: 'list') if unsupported_types.empty?
|
|
153
|
-
Aspera.error_unexpected_value(unsupported_types){'list item types'}
|
|
154
|
-
when *SCALAR_TYPES
|
|
155
|
-
return result_text(data)
|
|
156
|
-
else Aspera.error_unexpected_value(data.class.name){'result type'}
|
|
157
|
-
end
|
|
60
|
+
return Result::ObjectList.new(status_table)
|
|
158
61
|
end
|
|
159
62
|
end
|
|
160
63
|
|
|
161
64
|
# Minimum initialization, no exception raised
|
|
65
|
+
# @param argv [Array<String>] command line arguments
|
|
66
|
+
# @return [nil]
|
|
162
67
|
def initialize(argv)
|
|
163
68
|
@argv = argv
|
|
164
69
|
Log.dump(:argv, @argv, level: :trace2)
|
|
165
70
|
@option_help = false
|
|
166
71
|
@option_show_config = false
|
|
167
|
-
@bash_completion = false
|
|
168
72
|
@context = Context.new
|
|
169
73
|
end
|
|
170
74
|
|
|
171
75
|
# This is the main function called by initial script just after constructor
|
|
172
|
-
|
|
76
|
+
# Processes command line arguments, executes commands, and handles exceptions
|
|
77
|
+
# @return [nil]
|
|
78
|
+
def run
|
|
173
79
|
# Catch exception information , if any
|
|
174
80
|
exception_info = nil
|
|
175
81
|
# False if command shall not be executed (e.g. --show-config)
|
|
@@ -181,7 +87,6 @@ module Aspera
|
|
|
181
87
|
Plugins::Factory.instance.add_plugins_from_lookup_folders
|
|
182
88
|
# Help requested without command ? (plugins must be known here)
|
|
183
89
|
show_usage if @option_help && @context.options.command_or_arg_empty?
|
|
184
|
-
generate_bash_completion if @bash_completion
|
|
185
90
|
@context.config.periodic_check_newer_gem_version
|
|
186
91
|
command_sym =
|
|
187
92
|
if @option_show_config && @context.options.command_or_arg_empty?
|
|
@@ -206,7 +111,7 @@ module Aspera
|
|
|
206
111
|
# Help requested for current plugin
|
|
207
112
|
show_usage(all: false) if @option_help
|
|
208
113
|
if @option_show_config
|
|
209
|
-
@context.formatter.display_results(
|
|
114
|
+
@context.formatter.display_results(Result::SingleObject.new(@context.options.known_options(only_defined: true).stringify_keys))
|
|
210
115
|
execute_command = false
|
|
211
116
|
end
|
|
212
117
|
# Locking for single execution (only after "per plugin" option, in case lock port is there)
|
|
@@ -229,7 +134,7 @@ module Aspera
|
|
|
229
134
|
at_exit{File.delete(pid_file)}
|
|
230
135
|
end
|
|
231
136
|
# Execute and display (if not exclusive execution)
|
|
232
|
-
@context.formatter.display_results(
|
|
137
|
+
@context.formatter.display_results(command_plugin.execute_action) if execute_command
|
|
233
138
|
# Save config file if command modified it
|
|
234
139
|
@context.config.save_config_file_if_needed
|
|
235
140
|
# Finish
|
|
@@ -237,8 +142,9 @@ module Aspera
|
|
|
237
142
|
rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
|
|
238
143
|
rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
|
|
239
144
|
rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
|
|
240
|
-
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing'
|
|
145
|
+
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing'}
|
|
241
146
|
rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
|
|
147
|
+
rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
|
|
242
148
|
rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
|
|
243
149
|
rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
|
|
244
150
|
rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
|
|
@@ -251,11 +157,22 @@ module Aspera
|
|
|
251
157
|
# 1- processing of error condition
|
|
252
158
|
unless exception_info.nil?
|
|
253
159
|
Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
|
|
254
|
-
Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"}
|
|
160
|
+
Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"} unless exception_info[:e].is_a?(Cli::SchemaRequest)
|
|
255
161
|
Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
|
|
256
162
|
@context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
|
|
257
163
|
# Is that a known error condition with proposal for remediation ?
|
|
258
164
|
Hints.hint_for(exception_info[:e], @context.formatter)
|
|
165
|
+
# Requested help for a Hash parameter/option ?
|
|
166
|
+
if exception_info[:e].is_a?(Cli::SchemaRequest)
|
|
167
|
+
Log.log.info{"#{exception_info[:t]}: #{exception_info[:e].message}"}
|
|
168
|
+
schema_path = exception_info[:e].path
|
|
169
|
+
if schema_path.nil?
|
|
170
|
+
Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
|
|
171
|
+
else
|
|
172
|
+
builder = Schema::Documentation.new(TerminalFormatter, Schema::Registry.instance.reader(schema_path)).build
|
|
173
|
+
@context.formatter.display_results(Result::ObjectList.new(builder.rows, fields: builder.columns))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
259
176
|
end
|
|
260
177
|
# 2- processing of command not processed (due to exception or bad command line)
|
|
261
178
|
if execute_command || @option_show_config
|
|
@@ -276,6 +193,10 @@ module Aspera
|
|
|
276
193
|
return
|
|
277
194
|
end
|
|
278
195
|
|
|
196
|
+
# Display usage information and help
|
|
197
|
+
# @param all [Boolean] if true, show help for all plugins; if false, show only current plugin
|
|
198
|
+
# @param exit [Boolean] if true, exit the process after displaying help
|
|
199
|
+
# @return [nil]
|
|
279
200
|
def show_usage(all: true, exit: true)
|
|
280
201
|
# Display main plugin options (+config)
|
|
281
202
|
@context.formatter.display_message(:error, @context.options.parser)
|
|
@@ -298,7 +219,10 @@ module Aspera
|
|
|
298
219
|
|
|
299
220
|
private
|
|
300
221
|
|
|
222
|
+
# Initialize agents and options
|
|
301
223
|
# This can throw exception if there is a problem with the environment, needs to be caught by execute method
|
|
224
|
+
# @raise [StandardError] if there is a problem with the environment
|
|
225
|
+
# @return [nil]
|
|
302
226
|
def init_agents_and_options
|
|
303
227
|
@context.man_header = true
|
|
304
228
|
# Create formatter, in case there is an exception, it is used to display.
|
|
@@ -318,7 +242,7 @@ module Aspera
|
|
|
318
242
|
@context.config = Plugins::Config.new(context: @context)
|
|
319
243
|
@context.man_header = true
|
|
320
244
|
# Data persistency is set in config
|
|
321
|
-
Aspera.assert(@context.persistency
|
|
245
|
+
Aspera.assert(@context.persistency, 'missing persistency object')
|
|
322
246
|
# The TransferAgent plugin may use the @preset parser
|
|
323
247
|
@context.transfer = TransferAgent.new(@context.options, @context.config)
|
|
324
248
|
# Add commands for config plugin after all options have been added
|
|
@@ -328,6 +252,8 @@ module Aspera
|
|
|
328
252
|
@context.options.parser.banner = app_banner
|
|
329
253
|
end
|
|
330
254
|
|
|
255
|
+
# Generate the application banner for help display
|
|
256
|
+
# @return [String] formatted banner text
|
|
331
257
|
def app_banner
|
|
332
258
|
t = ' ' * 8
|
|
333
259
|
return <<~END_OF_BANNER
|
|
@@ -362,11 +288,11 @@ module Aspera
|
|
|
362
288
|
END_OF_BANNER
|
|
363
289
|
end
|
|
364
290
|
|
|
365
|
-
# Define header for manual
|
|
291
|
+
# Define header for manual and declare all global options
|
|
292
|
+
# @return [nil]
|
|
366
293
|
def declare_global_options
|
|
367
294
|
Log.log.debug('declare_global_options')
|
|
368
295
|
@context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
|
|
369
|
-
@context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
|
|
370
296
|
@context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
|
|
371
297
|
@context.options.declare(:version, 'Display version', allowed: Allowed::TYPES_NONE, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
|
|
372
298
|
@context.options.declare(
|
|
@@ -397,9 +323,10 @@ module Aspera
|
|
|
397
323
|
@context.options.parse_options!
|
|
398
324
|
end
|
|
399
325
|
|
|
400
|
-
#
|
|
326
|
+
# Get the plugin instance based on name
|
|
401
327
|
# Also loads the plugin options, and default values from conf file
|
|
402
|
-
# @param plugin_name_sym
|
|
328
|
+
# @param plugin_name_sym [Symbol] symbol for plugin name
|
|
329
|
+
# @return [Plugins::Base] the plugin instance
|
|
403
330
|
def get_plugin_instance_with_options(plugin_name_sym)
|
|
404
331
|
Log.log.debug{"get_plugin_instance_with_options(#{plugin_name_sym})"}
|
|
405
332
|
# Load default params only if no param already loaded before plugin instantiation
|
|
@@ -407,15 +334,6 @@ module Aspera
|
|
|
407
334
|
command_plugin = Plugins::Factory.instance.create(plugin_name_sym, context: @context)
|
|
408
335
|
return command_plugin
|
|
409
336
|
end
|
|
410
|
-
|
|
411
|
-
def generate_bash_completion
|
|
412
|
-
if @context.options.get_next_argument('', multiple: true, mandatory: false).nil?
|
|
413
|
-
Plugins::Factory.instance.plugin_list.each{ |p| puts p}
|
|
414
|
-
else
|
|
415
|
-
Log.log.warn('only first level completion so far')
|
|
416
|
-
end
|
|
417
|
-
Process.exit(0)
|
|
418
|
-
end
|
|
419
337
|
end
|
|
420
338
|
end
|
|
421
339
|
end
|
|
@@ -15,7 +15,8 @@ module Aspera
|
|
|
15
15
|
%w[Syncd Error Confl Pconf] +
|
|
16
16
|
(23..24).map{ |i| "P(#{i})"}).freeze
|
|
17
17
|
class << self
|
|
18
|
-
def declare_options(
|
|
18
|
+
def declare_options(options)
|
|
19
|
+
options.declare(:sql, 'SQL suffix appended to sqlite3 queries for admin subcommands (e.g. WHERE clause)')
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -27,13 +28,13 @@ module Aspera
|
|
|
27
28
|
# @return [Hash] sync info
|
|
28
29
|
def async_info_from_args(direction: nil)
|
|
29
30
|
path = options.get_next_argument('path')
|
|
30
|
-
sync_info = options.get_next_argument('sync info', mandatory: false, validation: Hash, default: {})
|
|
31
|
+
sync_info = options.get_next_argument('sync info', mandatory: false, validation: Hash, default: {}, schema: Schema::Registry::SYNC_CONF)
|
|
31
32
|
# is the positional path a remote path ?
|
|
32
33
|
path_is_remote = direction.eql?(:pull)
|
|
33
34
|
if sync_info.key?('sessions') || sync_info.key?('instance')
|
|
34
35
|
# `args`
|
|
35
36
|
sync_info['sessions'] ||= [{}]
|
|
36
|
-
Aspera.assert(sync_info['sessions'].length == 1
|
|
37
|
+
Aspera.assert(sync_info['sessions'].length == 1, 'Only one session is supported')
|
|
37
38
|
session = sync_info['sessions'].first
|
|
38
39
|
dir_key = path_is_remote ? 'remote_dir' : 'local_dir'
|
|
39
40
|
raise "Parameter #{dir_key} shall not be in sync_info" if session.key?(dir_key)
|
|
@@ -94,31 +95,34 @@ module Aspera
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def execute_sync_admin
|
|
97
|
-
command2 = options.get_next_command(%i[status find meta counters file_info overview])
|
|
98
|
+
command2 = options.get_next_command(%i[status find meta counters file_info overview query])
|
|
98
99
|
require 'aspera/sync/database' unless command2.eql?(:status)
|
|
100
|
+
sql_suffix = options.get_option(:sql)
|
|
99
101
|
case command2
|
|
100
102
|
when :status
|
|
101
|
-
return
|
|
103
|
+
return Result::SingleObject.new(Sync::Operations.admin_status(async_info_from_args))
|
|
102
104
|
when :find
|
|
103
105
|
folder = options.get_next_argument('path')
|
|
104
106
|
dbs = Sync::Operations.list_db_files(folder)
|
|
105
|
-
return
|
|
107
|
+
return Result::ObjectList.new(dbs.keys.map{ |n| {name: n, path: dbs[n]}})
|
|
106
108
|
when :meta, :counters
|
|
107
|
-
return
|
|
109
|
+
return Result::SingleObject.new(db_from_args.send(command2, sql_suffix))
|
|
108
110
|
when :file_info
|
|
109
|
-
result = db_from_args.
|
|
111
|
+
result = db_from_args.file_info(sql_suffix)
|
|
110
112
|
result.each do |r|
|
|
111
113
|
r['sstate'] = SyncActions::STATE_STR[r['state']] if r['state']
|
|
112
114
|
end
|
|
113
|
-
return
|
|
115
|
+
return Result::ObjectList.new(
|
|
114
116
|
result,
|
|
115
117
|
fields: %w[sstate record_id f_meta_path message]
|
|
116
118
|
)
|
|
117
119
|
when :overview
|
|
118
|
-
return
|
|
120
|
+
return Result::ObjectList.new(
|
|
119
121
|
db_from_args.overview,
|
|
120
122
|
fields: %w[table name type]
|
|
121
123
|
)
|
|
124
|
+
when :query
|
|
125
|
+
return Result.auto(db_from_args.execute(options.get_option(:sql, mandatory: true)))
|
|
122
126
|
else Aspera.error_unexpected_value(command2)
|
|
123
127
|
end
|
|
124
128
|
end
|
|
@@ -131,7 +135,7 @@ module Aspera
|
|
|
131
135
|
case command
|
|
132
136
|
when *Sync::Operations::DIRECTIONS
|
|
133
137
|
Sync::Operations.start(async_info_from_args(direction: command), transfer.user_transfer_spec, &block)
|
|
134
|
-
return
|
|
138
|
+
return Result::Success.new
|
|
135
139
|
when :admin
|
|
136
140
|
return execute_sync_admin
|
|
137
141
|
else Aspera.error_unexpected_value(command)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/environment'
|
|
4
|
+
require 'aspera/formatter_interface'
|
|
5
|
+
require 'aspera/markdown'
|
|
6
|
+
require 'aspera/assert'
|
|
7
|
+
|
|
8
|
+
module Aspera
|
|
9
|
+
module Cli
|
|
10
|
+
# Terminal formatter with ANSI colors and Unicode support
|
|
11
|
+
# @see FormatterInterface
|
|
12
|
+
# @see MarkdownFormatter (in build/lib/doc_helper.rb)
|
|
13
|
+
module TerminalFormatter
|
|
14
|
+
HINT = 'HINT:'.bg_green.gray.blink.freeze
|
|
15
|
+
include FormatterInterface
|
|
16
|
+
|
|
17
|
+
# Format boolean with colored symbol (✓/✗ or Y/ )
|
|
18
|
+
def tick(yes)
|
|
19
|
+
result =
|
|
20
|
+
if Environment.terminal_supports_unicode?
|
|
21
|
+
yes ? "\u2713" : "\u2717"
|
|
22
|
+
else
|
|
23
|
+
yes ? 'Y' : ' '
|
|
24
|
+
end
|
|
25
|
+
return result.green if yes
|
|
26
|
+
return result.red
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Format special values with colors (dim for empty, reverse for others)
|
|
30
|
+
def special_format(what)
|
|
31
|
+
result = "<#{what}>"
|
|
32
|
+
return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Prepare table row for terminal display (word wrap arrays)
|
|
36
|
+
def check_row(row)
|
|
37
|
+
row.each_key do |k|
|
|
38
|
+
row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Convert Markdown to terminal format (**bold** -> blue, `code` -> bold)
|
|
43
|
+
# @param match [MatchData, String]
|
|
44
|
+
def markdown_text(match)
|
|
45
|
+
if match.is_a?(String)
|
|
46
|
+
match = Markdown::FORMATS.match(match)
|
|
47
|
+
Aspera.assert(match, 'markdown text does not match any known format')
|
|
48
|
+
end
|
|
49
|
+
Aspera.assert_type(match, MatchData)
|
|
50
|
+
if match[:entity]
|
|
51
|
+
Aspera.assert_values(match[:entity], %w[bsol])
|
|
52
|
+
'\\'
|
|
53
|
+
elsif match[:bold]
|
|
54
|
+
match[:bold].to_s.blue
|
|
55
|
+
elsif match[:code]
|
|
56
|
+
match[:code].to_s.bold
|
|
57
|
+
else
|
|
58
|
+
Aspera.error_unexpected_value(match.to_s)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module_function :tick, :special_format, :check_row, :markdown_text
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -5,6 +5,7 @@ require 'aspera/transfer/spec'
|
|
|
5
5
|
require 'aspera/cli/info'
|
|
6
6
|
require 'aspera/log'
|
|
7
7
|
require 'aspera/assert'
|
|
8
|
+
require 'aspera/schema/registry'
|
|
8
9
|
|
|
9
10
|
module Aspera
|
|
10
11
|
module Cli
|
|
@@ -12,9 +13,9 @@ module Aspera
|
|
|
12
13
|
# one of the supported transfer agents.
|
|
13
14
|
# Provide CLI options to select one of the transfer agents (FASP/ascp client)
|
|
14
15
|
class TransferAgent
|
|
15
|
-
#
|
|
16
|
+
# `@args` special value for --sources : read file list from arguments
|
|
16
17
|
FILE_LIST_FROM_ARGS = '@args'
|
|
17
|
-
#
|
|
18
|
+
# `@ts` special value for --sources : read file list from transfer spec (--ts)
|
|
18
19
|
FILE_LIST_FROM_TRANSFER_SPEC = '@ts'
|
|
19
20
|
FILE_LIST_OPTIONS = [FILE_LIST_FROM_ARGS, FILE_LIST_FROM_TRANSFER_SPEC, 'Array'].freeze
|
|
20
21
|
DEFAULT_TRANSFER_NOTIFY_TEMPLATE = <<~END_OF_TEMPLATE
|
|
@@ -63,12 +64,12 @@ module Aspera
|
|
|
63
64
|
@transfer_paths = nil
|
|
64
65
|
# HTTPGW URL provided by webapp
|
|
65
66
|
@httpgw_url_lambda = nil
|
|
66
|
-
@opt_mgr.declare(:ts, 'Override transfer spec values', allowed: Hash, handler: {o: self, m: :user_transfer_spec})
|
|
67
|
+
@opt_mgr.declare(:ts, 'Override transfer spec values', allowed: Hash, handler: {o: self, m: :user_transfer_spec}, schema: Schema::Registry::TRANSFER_SPEC)
|
|
67
68
|
@opt_mgr.declare(:to_folder, 'Destination folder for transferred files')
|
|
68
69
|
@opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})", default: FILE_LIST_FROM_ARGS)
|
|
69
70
|
@opt_mgr.declare(:src_type, 'Type of file list', allowed: %i[list pair], default: :list)
|
|
70
71
|
@opt_mgr.declare(:transfer, 'Type of transfer agent', allowed: Agent::Factory::ALL.keys, default: :direct)
|
|
71
|
-
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', allowed: Hash, handler: {o: self, m: :transfer_info})
|
|
72
|
+
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', allowed: Hash, handler: {o: self, m: :transfer_info}, schema: Schema::Registry::TRANSFER_INFO)
|
|
72
73
|
@opt_mgr.parse_options!
|
|
73
74
|
@notification_cb = nil
|
|
74
75
|
if !@opt_mgr.get_option(:notify_to).nil?
|
|
@@ -154,24 +155,25 @@ module Aspera
|
|
|
154
155
|
end
|
|
155
156
|
|
|
156
157
|
# Transform the list of paths to a list of hash with source/dest
|
|
157
|
-
# @param file_list [Array]
|
|
158
|
+
# @param file_list [Array<Hash>]
|
|
158
159
|
def list_to_paths(file_list)
|
|
159
160
|
source_type = @opt_mgr.get_option(:src_type, mandatory: true)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
@transfer_paths =
|
|
162
|
+
case source_type
|
|
163
|
+
when :list
|
|
164
|
+
# when providing a list, just specify source
|
|
165
|
+
file_list.map{ |i| {'source' => i}}
|
|
166
|
+
when :pair
|
|
167
|
+
Aspera.assert(file_list.length.even?, type: Cli::BadArgument){"When using pair, provide an even number of paths: #{file_list.length}"}
|
|
168
|
+
file_list.each_slice(2).map{ |s, d| {'source' => s, 'destination' => d}}
|
|
169
|
+
else Aspera.error_unexpected_value(source_type)
|
|
170
|
+
end
|
|
169
171
|
end
|
|
170
172
|
|
|
171
173
|
# This is how the list of files to be transferred is specified
|
|
172
174
|
# get paths suitable for transfer spec from command line
|
|
173
175
|
# computation is done only once, cache is kept in @transfer_paths
|
|
174
|
-
# @param default [Array
|
|
176
|
+
# @param default [nil, Array<String>] If set, used as default file for --sources=@args
|
|
175
177
|
# @return [Array, nil] of Hash {source: (mandatory), destination: (optional)}
|
|
176
178
|
def ts_source_paths(default: nil)
|
|
177
179
|
# return cache if set
|
|
@@ -43,13 +43,13 @@ module Aspera
|
|
|
43
43
|
case type
|
|
44
44
|
when :sessions_init
|
|
45
45
|
# Give opportunity to show progress of initialization with multiple status
|
|
46
|
-
Aspera.assert(session_id.nil
|
|
46
|
+
Aspera.assert(session_id.nil?, 'session_id must be nil for :sessions_init event')
|
|
47
47
|
Aspera.assert_type(info, String)
|
|
48
48
|
# Initialization of progress bar
|
|
49
49
|
@title = info
|
|
50
50
|
when :session_start
|
|
51
51
|
Aspera.assert_type(session_id, String)
|
|
52
|
-
Aspera.assert(info.nil
|
|
52
|
+
Aspera.assert(info.nil?, 'info must be nil for :session_start event')
|
|
53
53
|
raise "Session #{session_id} already started" if @sessions[session_id]
|
|
54
54
|
@sessions[session_id] = {
|
|
55
55
|
job_size: 0, # Total size of transfer (pre-calc)
|
|
@@ -60,7 +60,7 @@ module Aspera
|
|
|
60
60
|
@title = nil
|
|
61
61
|
when :session_size
|
|
62
62
|
Aspera.assert_type(session_id, String)
|
|
63
|
-
Aspera.assert(!info.nil
|
|
63
|
+
Aspera.assert(!info.nil?, 'info must not be nil for :session_size event')
|
|
64
64
|
Aspera.assert_type(@sessions[session_id], Hash)
|
|
65
65
|
@sessions[session_id][:job_size] = info.to_i
|
|
66
66
|
sessions_total = total(:job_size)
|
|
@@ -76,12 +76,12 @@ module Aspera
|
|
|
76
76
|
end
|
|
77
77
|
when :session_end
|
|
78
78
|
Aspera.assert_type(session_id, String)
|
|
79
|
-
Aspera.assert(info.nil
|
|
79
|
+
Aspera.assert(info.nil?, 'info must be nil for :session_end event')
|
|
80
80
|
# A session may be too short and finish before it has been started
|
|
81
81
|
@sessions[session_id][:running] = false if @sessions[session_id].is_a?(Hash)
|
|
82
82
|
when :end
|
|
83
|
-
Aspera.assert(session_id.nil
|
|
84
|
-
Aspera.assert(info.nil
|
|
83
|
+
Aspera.assert(session_id.nil?, 'session_id must be nil for :end event')
|
|
84
|
+
Aspera.assert(info.nil?, 'info must be nil for :end event')
|
|
85
85
|
@progress_bar.finish
|
|
86
86
|
else Aspera.error_unexpected_value(type){'event type'}
|
|
87
87
|
end
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -80,7 +80,7 @@ module Aspera
|
|
|
80
80
|
end
|
|
81
81
|
RestParameters.instance.spinner_cb.call(action: :success)
|
|
82
82
|
raise "No known application found at #{app_url}" if found_apps.empty?
|
|
83
|
-
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
|
|
83
|
+
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)}, 'all app info keys must be symbols')
|
|
84
84
|
return found_apps
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -128,7 +128,8 @@ module Aspera
|
|
|
128
128
|
apps.first
|
|
129
129
|
else
|
|
130
130
|
formatter.display_status('Multiple applications detected, please select from:')
|
|
131
|
-
|
|
131
|
+
require 'aspera/cli/result'
|
|
132
|
+
formatter.display_results(Result::ObjectList.new(apps, fields: %w[product url version]))
|
|
132
133
|
answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
|
|
133
134
|
apps.find{ |a| a[:product].eql?(answer)}
|
|
134
135
|
end
|
|
@@ -140,9 +141,7 @@ module Aspera
|
|
|
140
141
|
options.add_option_preset({url: wiz_url}, 'wizard')
|
|
141
142
|
# Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
|
|
142
143
|
plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
|
|
143
|
-
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument)
|
|
144
|
-
"Detected: #{identification[:product]}, but this application has no wizard"
|
|
145
|
-
end
|
|
144
|
+
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
|
|
146
145
|
# Call the wizard
|
|
147
146
|
wizard_result = plugin_instance.wizard(self, wiz_url)
|
|
148
147
|
Log.log.debug{"wizard result: #{wizard_result}"}
|
|
@@ -166,7 +165,7 @@ module Aspera
|
|
|
166
165
|
test_args = "-P#{wiz_preset_name} #{test_args}" unless option_default
|
|
167
166
|
# TODO: actually test the command
|
|
168
167
|
test_cmd = "#{Info::CMD_NAME} #{identification[:product]} #{test_args}"
|
|
169
|
-
return
|
|
168
|
+
return Result::Status.new("You can test with:\n#{test_cmd.red}")
|
|
170
169
|
end
|
|
171
170
|
end
|
|
172
171
|
end
|